mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-08-25 10:09:20 +03:00
Merge branch 'master' into plugin-hooks
# Conflicts: # dev/View/User/Login.js
This commit is contained in:
commit
2ab42b1f03
547 changed files with 29045 additions and 52993 deletions
33
README.md
33
README.md
|
|
@ -116,23 +116,23 @@ RainLoop 1.15 vs SnappyMail
|
||||||
|
|
||||||
|js/* |RainLoop |Snappy |
|
|js/* |RainLoop |Snappy |
|
||||||
|--------------- |--------: |--------: |
|
|--------------- |--------: |--------: |
|
||||||
|admin.js |2.158.025 | 100.608 |
|
|admin.js |2.158.025 | 94.549 |
|
||||||
|app.js |4.215.733 | 495.934 |
|
|app.js |4.215.733 | 458.762 |
|
||||||
|boot.js | 672.433 | 4.726 |
|
|boot.js | 672.433 | 4.554 |
|
||||||
|libs.js | 647.679 | 227.974 |
|
|libs.js | 647.679 | 218.511 |
|
||||||
|polyfills.js | 325.908 | 0 |
|
|polyfills.js | 325.908 | 0 |
|
||||||
|serviceworker.js | 0 | 285 |
|
|serviceworker.js | 0 | 285 |
|
||||||
|TOTAL |8.019.778 | 829.355 |
|
|TOTAL |8.019.778 | 776.661 |
|
||||||
|
|
||||||
|js/min/* |RainLoop |Snappy |RL gzip |SM gzip |RL brotli |SM brotli |
|
|js/min/* |RainLoop |Snappy |RL gzip |SM gzip |RL brotli |SM brotli |
|
||||||
|--------------- |--------: |--------: |------: |------: |--------: |--------: |
|
|--------------- |--------: |--------: |------: |------: |--------: |--------: |
|
||||||
|admin.min.js | 255.514 | 50.607 | 73.899 | 14.944 | 60.674 | 13.364 |
|
|admin.min.js | 255.514 | 49.871 | 73.899 | 14.771 | 60.674 | 13.213 |
|
||||||
|app.min.js | 516.000 | 240.553 |140.430 | 70.291 |110.657 | 59.309 |
|
|app.min.js | 516.000 | 235.474 |140.430 | 69.223 |110.657 | 58.398 |
|
||||||
|boot.min.js | 66.456 | 2.442 | 22.553 | 1.371 | 20.043 | 1.178 |
|
|boot.min.js | 66.456 | 2.442 | 22.553 | 1.371 | 20.043 | 1.178 |
|
||||||
|libs.min.js | 574.626 | 115.877 |177.280 | 42.809 |151.855 | 38.099 |
|
|libs.min.js | 574.626 | 106.574 |177.280 | 38.600 |151.855 | 34.532 |
|
||||||
|polyfills.min.js | 32.608 | 0 | 11.315 | 0 | 10.072 | 0 |
|
|polyfills.min.js | 32.608 | 0 | 11.315 | 0 | 10.072 | 0 |
|
||||||
|TOTAL |1.445.204 | 409.479 |425.477 |129.415 |353.301 |111.950 |
|
|TOTAL |1.445.204 | 394.361 |425.477 |123.965 |353.301 |107.321 |
|
||||||
|TOTAL (no admin) |1.189.690 | 358.872 |351.061 |114.471 |292.627 | 98.586 |
|
|TOTAL (no admin) |1.189.690 | 344.490 |351.061 |109.194 |292.627 | 94.108 |
|
||||||
|
|
||||||
For a user its around 66% smaller and faster than traditional RainLoop.
|
For a user its around 66% smaller and faster than traditional RainLoop.
|
||||||
|
|
||||||
|
|
@ -166,12 +166,12 @@ For a user its around 66% smaller and faster than traditional RainLoop.
|
||||||
|
|
||||||
|css/* |RainLoop |Snappy |RL gzip |SM gzip |SM brotli |
|
|css/* |RainLoop |Snappy |RL gzip |SM gzip |SM brotli |
|
||||||
|------------ |-------: |-------: |------: |------: |--------: |
|
|------------ |-------: |-------: |------: |------: |--------: |
|
||||||
|app.css | 340.334 | 107.279 | 46,959 | 18.816 | 16.160 |
|
|app.css | 340.334 | 106.864 | 46,959 | 18.751 | 16.110 |
|
||||||
|app.min.css | 274.791 | 88.402 | 39.618 | 16.831 | 14.827 |
|
|app.min.css | 274.791 | 88.077 | 39.618 | 16.788 | 14.787 |
|
||||||
|boot.css | | 2.066 | | 913 | 742 |
|
|boot.css | | 2.066 | | 913 | 742 |
|
||||||
|boot.min.css | | 1.696 | | 818 | 664 |
|
|boot.min.css | | 1.696 | | 818 | 664 |
|
||||||
|admin.css | | 46.393 | | 9.237 | 8.073 |
|
|admin.css | | 46.337 | | 9.221 | 8.058 |
|
||||||
|admin.min.css | | 37.306 | | 8.191 | 7.282 |
|
|admin.min.css | | 37.251 | | 8.178 | 7.269 |
|
||||||
|
|
||||||
|
|
||||||
### Squire vs CKEditor
|
### Squire vs CKEditor
|
||||||
|
|
@ -190,8 +190,3 @@ Still TODO:
|
||||||
CKEditor including the 7 asset requests (css,language,plugins,icons) is 633.46 KB / 180.47 KB (gzip).
|
CKEditor including the 7 asset requests (css,language,plugins,icons) is 633.46 KB / 180.47 KB (gzip).
|
||||||
|
|
||||||
To use the old CKEditor, you must install the plugin.
|
To use the old CKEditor, you must install the plugin.
|
||||||
|
|
||||||
### PHP73 branch
|
|
||||||
|
|
||||||
There's a RainLoop 1.14.0 branch with only the PHP 7.3 changes at
|
|
||||||
https://github.com/the-djmaze/snappymail/tree/php73
|
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,6 @@ import { AbstractApp } from 'App/Abstract';
|
||||||
import { ComposePopupView } from 'View/Popup/Compose';
|
import { ComposePopupView } from 'View/Popup/Compose';
|
||||||
import { FolderSystemPopupView } from 'View/Popup/FolderSystem';
|
import { FolderSystemPopupView } from 'View/Popup/FolderSystem';
|
||||||
import { AskPopupView } from 'View/Popup/Ask';
|
import { AskPopupView } from 'View/Popup/Ask';
|
||||||
import { TwoFactorConfigurationPopupView } from 'View/Popup/TwoFactorConfiguration';
|
|
||||||
|
|
||||||
// Every 5 minutes
|
// Every 5 minutes
|
||||||
const refreshFolders = 300000;
|
const refreshFolders = 300000;
|
||||||
|
|
@ -187,16 +186,18 @@ class AppUser extends AbstractApp {
|
||||||
}
|
}
|
||||||
MessageUserStore.listLoading(false);
|
MessageUserStore.listLoading(false);
|
||||||
},
|
},
|
||||||
FolderUserStore.currentFolderFullNameRaw(),
|
{
|
||||||
iOffset,
|
Folder: FolderUserStore.currentFolderFullNameRaw(),
|
||||||
SettingsUserStore.messagesPerPage(),
|
Offset: iOffset,
|
||||||
MessageUserStore.listSearch(),
|
Limit: SettingsUserStore.messagesPerPage(),
|
||||||
MessageUserStore.listThreadUid()
|
Search: MessageUserStore.listSearch(),
|
||||||
|
ThreadUid: MessageUserStore.listThreadUid()
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
recacheInboxMessageList() {
|
recacheInboxMessageList() {
|
||||||
Remote.messageList(()=>{}, getFolderInboxName(), 0, SettingsUserStore.messagesPerPage(), '', '', true);
|
Remote.messageList(null, {Folder: getFolderInboxName()}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -910,18 +911,8 @@ class AppUser extends AbstractApp {
|
||||||
|
|
||||||
rl.setWindowTitle();
|
rl.setWindowTitle();
|
||||||
if (SettingsGet('Auth')) {
|
if (SettingsGet('Auth')) {
|
||||||
if (
|
|
||||||
Settings.capa(Capa.TwoFactor) &&
|
|
||||||
Settings.capa(Capa.TwoFactorForce) &&
|
|
||||||
SettingsGet('RequireTwoFactor')
|
|
||||||
) {
|
|
||||||
this.bootend();
|
|
||||||
showScreenPopup(TwoFactorConfigurationPopupView, [true]);
|
|
||||||
} else {
|
|
||||||
rl.setWindowTitle(i18n('GLOBAL/LOADING'));
|
rl.setWindowTitle(i18n('GLOBAL/LOADING'));
|
||||||
|
|
||||||
// require.ensure([], function() { // require code splitting
|
|
||||||
|
|
||||||
this.foldersReload(value => {
|
this.foldersReload(value => {
|
||||||
try {
|
try {
|
||||||
this.bootend();
|
this.bootend();
|
||||||
|
|
@ -1038,8 +1029,6 @@ class AppUser extends AbstractApp {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// }); // require code splitting
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
this.bootend();
|
this.bootend();
|
||||||
startScreens([LoginUserScreen]);
|
startScreens([LoginUserScreen]);
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,6 @@
|
||||||
* @enum {string}
|
* @enum {string}
|
||||||
*/
|
*/
|
||||||
export const Capa = {
|
export const Capa = {
|
||||||
TwoFactor: 'TWO_FACTOR',
|
|
||||||
TwoFactorForce: 'TWO_FACTOR_FORCE',
|
|
||||||
OpenPGP: 'OPEN_PGP',
|
OpenPGP: 'OPEN_PGP',
|
||||||
Prefetch: 'PREFETCH',
|
Prefetch: 'PREFETCH',
|
||||||
Composer: 'COMPOSER',
|
Composer: 'COMPOSER',
|
||||||
|
|
@ -86,9 +84,6 @@ export const Notification = {
|
||||||
DomainNotAllowed: 109,
|
DomainNotAllowed: 109,
|
||||||
AccountNotAllowed: 110,
|
AccountNotAllowed: 110,
|
||||||
|
|
||||||
AccountTwoFactorAuthRequired: 120,
|
|
||||||
AccountTwoFactorAuthError: 121,
|
|
||||||
|
|
||||||
ContactsSyncError: 140,
|
ContactsSyncError: 140,
|
||||||
|
|
||||||
CantGetMessageList: 201,
|
CantGetMessageList: 201,
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,22 @@ export const FolderType = {
|
||||||
User: 99
|
User: 99
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
export const FolderSortMode = {
|
||||||
|
DateDesc: '', // default 'REVERSE DATE'
|
||||||
|
DateAsc: 'DATE',
|
||||||
|
FromDesc: 'REVERSE FROM',
|
||||||
|
FromAsc: 'FROM',
|
||||||
|
SizeDesc: 'REVERSE SIZE',
|
||||||
|
SizeAsc: 'SIZE',
|
||||||
|
SubjectDesc: 'REVERSE SUBJECT',
|
||||||
|
SubjectAsc: 'SUBJECT'
|
||||||
|
// ToDesc: 'REVERSE TO',
|
||||||
|
// ToAsc: 'TO',
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @enum {string}
|
* @enum {string}
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -13,16 +13,15 @@ const
|
||||||
exts = {
|
exts = {
|
||||||
eml: 'message/rfc822',
|
eml: 'message/rfc822',
|
||||||
mime: 'message/rfc822',
|
mime: 'message/rfc822',
|
||||||
rtx: 'text/richtext',
|
|
||||||
vcard: 'text/vcard',
|
vcard: 'text/vcard',
|
||||||
vcf: 'text/vcard',
|
vcf: 'text/vcard',
|
||||||
htm: 'text/html',
|
htm: 'text/html',
|
||||||
html: 'text/html',
|
html: 'text/html',
|
||||||
csv: 'text/csv',
|
csv: 'text/csv',
|
||||||
ics: 'text/calendar',
|
ics: 'text/calendar',
|
||||||
ifb: 'text/calendar',
|
|
||||||
xml: 'text/xml',
|
xml: 'text/xml',
|
||||||
json: app+'json',
|
json: app+'json',
|
||||||
|
asc: app+'pgp-signature',
|
||||||
p10: app+'pkcs10',
|
p10: app+'pkcs10',
|
||||||
p7c: app+'pkcs7-mime',
|
p7c: app+'pkcs7-mime',
|
||||||
p7m: app+'pkcs7-mime',
|
p7m: app+'pkcs7-mime',
|
||||||
|
|
@ -34,8 +33,7 @@ const
|
||||||
pl: 'text/perl',
|
pl: 'text/perl',
|
||||||
css: 'text/css',
|
css: 'text/css',
|
||||||
asp: 'text/asp',
|
asp: 'text/asp',
|
||||||
php: app+'x-httpd-php',
|
php: app+'x-php',
|
||||||
phtml: app+'x-httpd-php',
|
|
||||||
|
|
||||||
// images
|
// images
|
||||||
jpg: 'image/jpeg',
|
jpg: 'image/jpeg',
|
||||||
|
|
@ -106,18 +104,16 @@ const
|
||||||
export const FileType = {
|
export const FileType = {
|
||||||
Unknown: 'unknown',
|
Unknown: 'unknown',
|
||||||
Text: 'text',
|
Text: 'text',
|
||||||
Html: 'html',
|
|
||||||
Code: 'code',
|
Code: 'code',
|
||||||
Eml: 'eml',
|
Eml: 'eml',
|
||||||
WordText: 'word-text',
|
Word: 'word',
|
||||||
Pdf: 'pdf',
|
Pdf: 'pdf',
|
||||||
Image: 'image',
|
Image: 'image',
|
||||||
Audio: 'audio',
|
Audio: 'audio',
|
||||||
Video: 'video',
|
Video: 'video',
|
||||||
Sheet: 'sheet',
|
Spreadsheet: 'spreadsheet',
|
||||||
Presentation: 'presentation',
|
Presentation: 'presentation',
|
||||||
Certificate: 'certificate',
|
Certificate: 'certificate',
|
||||||
CertificateBin: 'certificate-bin',
|
|
||||||
Archive: 'archive'
|
Archive: 'archive'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -138,7 +134,7 @@ export const FileInfo = {
|
||||||
return app + 'ms-tnef';
|
return app + 'ms-tnef';
|
||||||
}
|
}
|
||||||
let ext = fileName.split('.').pop();
|
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';
|
return 'text/plain';
|
||||||
if (/^(mpe?g|mpe|m1v|m2v)$/.test(ext))
|
if (/^(mpe?g|mpe|m1v|m2v)$/.test(ext))
|
||||||
return 'video/mpeg';
|
return 'video/mpeg';
|
||||||
|
|
@ -186,15 +182,12 @@ export const FileInfo = {
|
||||||
case 'video' == mimeTypeParts[0] || 'mkv' == ext || 'avi' == ext:
|
case 'video' == mimeTypeParts[0] || 'mkv' == ext || 'avi' == ext:
|
||||||
result = FileType.Video;
|
result = FileType.Video;
|
||||||
break;
|
break;
|
||||||
case ['php', 'js', 'css'].includes(ext):
|
case ['php', 'js', 'css', 'xml', 'html'].includes(ext) || 'text/html' == mimeType:
|
||||||
result = FileType.Code;
|
result = FileType.Code;
|
||||||
break;
|
break;
|
||||||
case 'eml' == ext || ['message/delivery-status', 'message/rfc822'].includes(mimeType):
|
case 'eml' == ext || ['message/delivery-status', 'message/rfc822'].includes(mimeType):
|
||||||
result = FileType.Eml;
|
result = FileType.Eml;
|
||||||
break;
|
break;
|
||||||
case 'text/html' == mimeType || 'html' == ext:
|
|
||||||
result = FileType.Html;
|
|
||||||
break;
|
|
||||||
case 'text' == mimeTypeParts[0] || 'txt' == ext || 'log' == ext:
|
case 'text' == mimeTypeParts[0] || 'txt' == ext || 'log' == ext:
|
||||||
result = FileType.Text;
|
result = FileType.Text;
|
||||||
break;
|
break;
|
||||||
|
|
@ -204,19 +197,17 @@ export const FileInfo = {
|
||||||
case 'pdf' == type || 'pdf' == ext:
|
case 'pdf' == type || 'pdf' == ext:
|
||||||
result = FileType.Pdf;
|
result = FileType.Pdf;
|
||||||
break;
|
break;
|
||||||
case [app+'pgp-signature', app+'pgp-keys'].includes(mimeType) ||
|
case [app+'pgp-signature', app+'pgp-keys'].includes(mimeType)
|
||||||
['asc', 'pem', 'ppk'].includes(ext):
|
|| ['asc', 'pem', 'ppk'].includes(ext)
|
||||||
|
|| [app+'pkcs7-signature'].includes(mimeType) || 'p7s' == ext:
|
||||||
result = FileType.Certificate;
|
result = FileType.Certificate;
|
||||||
break;
|
break;
|
||||||
case [app+'pkcs7-signature'].includes(mimeType) || 'p7s' == ext:
|
|
||||||
result = FileType.CertificateBin;
|
|
||||||
break;
|
|
||||||
case match(msOffice+'.wordprocessingml') || match(openDoc+'.text') || match('vnd.ms-word')
|
case match(msOffice+'.wordprocessingml') || match(openDoc+'.text') || match('vnd.ms-word')
|
||||||
|| ['rtf', 'msword', 'vnd.msword'].includes(type):
|
|| ['rtf', 'msword', 'vnd.msword'].includes(type):
|
||||||
result = FileType.WordText;
|
result = FileType.Word;
|
||||||
break;
|
break;
|
||||||
case match(msOffice+'.spreadsheetml') || match(openDoc+'.spreadsheet') || match('ms-excel'):
|
case match(msOffice+'.spreadsheetml') || match(openDoc+'.spreadsheet') || match('ms-excel'):
|
||||||
result = FileType.Sheet;
|
result = FileType.Spreadsheet;
|
||||||
break;
|
break;
|
||||||
case match(msOffice+'.presentationml') || match(openDoc+'.presentation') || match('ms-powerpoint'):
|
case match(msOffice+'.presentationml') || match(openDoc+'.presentation') || match('ms-powerpoint'):
|
||||||
result = FileType.Presentation;
|
result = FileType.Presentation;
|
||||||
|
|
@ -232,45 +223,23 @@ export const FileInfo = {
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
getTypeIconClass: fileType => {
|
getTypeIconClass: fileType => {
|
||||||
let result = ['icon-file', ''];
|
let result = 'icon-file';
|
||||||
switch (fileType) {
|
switch (fileType) {
|
||||||
case FileType.Text:
|
case FileType.Text:
|
||||||
case FileType.Eml:
|
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:
|
case FileType.Pdf:
|
||||||
result = ['icon-none', 'pdf'];
|
case FileType.Word:
|
||||||
break;
|
return result + '-text';
|
||||||
// no default
|
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;
|
return result;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -283,7 +252,7 @@ export const FileInfo = {
|
||||||
getCombinedIconClass: data => {
|
getCombinedIconClass: data => {
|
||||||
if (isNonEmptyArray(data)) {
|
if (isNonEmptyArray(data)) {
|
||||||
let icons = 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();
|
.validUnique();
|
||||||
|
|
||||||
return (icons && 1 === icons.length && 'icon-file' !== icons[0])
|
return (icons && 1 === icons.length && 'icon-file' !== icons[0])
|
||||||
|
|
@ -297,7 +266,7 @@ export const FileInfo = {
|
||||||
friendlySize: bytes => {
|
friendlySize: bytes => {
|
||||||
bytes = parseInt(bytes, 10) || 0;
|
bytes = parseInt(bytes, 10) || 0;
|
||||||
let i = Math.floor(Math.log(bytes) / Math.log(1024));
|
let i = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||||
return (bytes / Math.pow(1024, i)).toFixed(2>i ? 0 : 1) + sizes[i];
|
return (bytes / Math.pow(1024, i)).toFixed(2>i ? 0 : 1) + ' ' + sizes[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -179,12 +179,13 @@ export function mailBox(folder, page = 1, search = '', threadUid = '') {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (1 < page) {
|
if (1 < page) {
|
||||||
result = result.replace(/[/]+$/, '') + '/p' + page;
|
result = result.replace(/\/+$/, '') + '/p' + page;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (search) {
|
if (search) {
|
||||||
result = result.replace(/[/]+$/, '') + '/' + encodeURI(search);
|
result = result.replace(/\/+$/, '') + '/' + encodeURI(search);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { Notification, UploadErrorCode } from 'Common/Enums';
|
||||||
import { langLink } from 'Common/Links';
|
import { langLink } from 'Common/Links';
|
||||||
import { doc, createElement } from 'Common/Globals';
|
import { doc, createElement } from 'Common/Globals';
|
||||||
|
|
||||||
let I18N_DATA = window.rainloopI18N || {};
|
let I18N_DATA = window.snappymailI18N || {};
|
||||||
|
|
||||||
export const trigger = ko.observable(false);
|
export const trigger = ko.observable(false);
|
||||||
|
|
||||||
|
|
@ -14,14 +14,16 @@ export const trigger = ko.observable(false);
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function i18n(key, valueList, defaulValue) {
|
export function i18n(key, valueList, defaulValue) {
|
||||||
let result = I18N_DATA[key] || defaulValue || key;
|
let path = key.split('/');
|
||||||
|
if (!I18N_DATA[path[0]] || !path[1]) {
|
||||||
|
return defaulValue || key;
|
||||||
|
}
|
||||||
|
let result = I18N_DATA[path[0]][path[1]] || defaulValue || key;
|
||||||
if (valueList) {
|
if (valueList) {
|
||||||
Object.entries(valueList).forEach(([key, value]) => {
|
Object.entries(valueList).forEach(([key, value]) => {
|
||||||
result = result.replace('%' + key + '%', value);
|
result = result.replace('%' + key + '%', value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,7 +77,7 @@ function getNotificationMessage(code) {
|
||||||
let key = getKeyByValue(Notification, code);
|
let key = getKeyByValue(Notification, code);
|
||||||
if (key) {
|
if (key) {
|
||||||
key = i18nKey(key).replace('_NOTIFICATION', '_ERROR');
|
key = i18nKey(key).replace('_NOTIFICATION', '_ERROR');
|
||||||
return I18N_DATA['NOTIFICATIONS/' + key];
|
return I18N_DATA.NOTIFICATIONS[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,13 +127,13 @@ export function reload(admin, language) {
|
||||||
const script = createElement('script');
|
const script = createElement('script');
|
||||||
script.onload = () => {
|
script.onload = () => {
|
||||||
// reload the data
|
// reload the data
|
||||||
if (window.rainloopI18N) {
|
if (window.snappymailI18N) {
|
||||||
I18N_DATA = window.rainloopI18N || {};
|
I18N_DATA = window.snappymailI18N;
|
||||||
i18nToNodes(doc);
|
i18nToNodes(doc);
|
||||||
dispatchEvent(new CustomEvent('reload-time'));
|
dispatchEvent(new CustomEvent('reload-time'));
|
||||||
trigger(!trigger());
|
trigger(!trigger());
|
||||||
}
|
}
|
||||||
window.rainloopI18N = null;
|
window.snappymailI18N = null;
|
||||||
script.remove();
|
script.remove();
|
||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
|
|
@ -150,7 +152,7 @@ export function reload(admin, language) {
|
||||||
*/
|
*/
|
||||||
export function convertLangName(language, isEng = false) {
|
export function convertLangName(language, isEng = false) {
|
||||||
return i18n(
|
return i18n(
|
||||||
'LANGS_NAMES' + (true === isEng ? '_EN' : '') + '/LANG_' + language.toUpperCase().replace(/[^a-zA-Z0-9]+/g, '_'),
|
'LANGS_NAMES' + (true === isEng ? '_EN' : '') + '/' + language,
|
||||||
null,
|
null,
|
||||||
language
|
language
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ class AbstractCheckbox extends AbstractComponent {
|
||||||
this.inline = !!params.inline;
|
this.inline = !!params.inline;
|
||||||
|
|
||||||
this.labeled = undefined !== params.label;
|
this.labeled = undefined !== params.label;
|
||||||
this.labelAnimated = !!params.labelAnimated;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
click() {
|
click() {
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,6 @@ import { AbstractModel } from 'Knoin/AbstractModel';
|
||||||
|
|
||||||
import { SMAudio } from 'Common/Audio';
|
import { SMAudio } from 'Common/Audio';
|
||||||
|
|
||||||
const bAllowPdfPreview = undefined !== navigator.mimeTypes['application/pdf'];
|
|
||||||
|
|
||||||
export class AttachmentModel extends AbstractModel {
|
export class AttachmentModel extends AbstractModel {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
@ -92,27 +90,21 @@ export class AttachmentModel extends AbstractModel {
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
isText() {
|
isText() {
|
||||||
return (
|
return FileType.Text === this.fileType || FileType.Eml === this.fileType;
|
||||||
FileType.Text === this.fileType ||
|
|
||||||
FileType.Eml === this.fileType ||
|
|
||||||
FileType.Certificate === this.fileType ||
|
|
||||||
FileType.Html === this.fileType ||
|
|
||||||
FileType.Code === this.fileType
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
isPdf() {
|
pdfPreview() {
|
||||||
return FileType.Pdf === this.fileType;
|
return null != navigator.mimeTypes['application/pdf'] && FileType.Pdf === this.fileType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
hasPreview() {
|
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 + ')' : '';
|
return link ? 'background:url(' + link + ')' : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
linkPreviewAsPlain() {
|
|
||||||
return serverRequestRaw('ViewAsPlain', this.download);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
|
|
@ -169,11 +154,11 @@ export class AttachmentModel extends AbstractModel {
|
||||||
let result = '';
|
let result = '';
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case this.isImage():
|
case this.isImage():
|
||||||
case this.isPdf() && bAllowPdfPreview:
|
case this.pdfPreview():
|
||||||
result = this.linkPreview();
|
result = this.linkPreview();
|
||||||
break;
|
break;
|
||||||
case this.isText():
|
case this.isText():
|
||||||
result = this.linkPreviewAsPlain();
|
result = serverRequestRaw('ViewAsPlain', this.download);
|
||||||
break;
|
break;
|
||||||
// no default
|
// no default
|
||||||
}
|
}
|
||||||
|
|
@ -181,18 +166,6 @@ export class AttachmentModel extends AbstractModel {
|
||||||
return result;
|
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 {AttachmentModel} attachment
|
||||||
* @param {*} event
|
* @param {*} event
|
||||||
|
|
@ -201,7 +174,11 @@ export class AttachmentModel extends AbstractModel {
|
||||||
eventDragStart(attachment, event) {
|
eventDragStart(attachment, event) {
|
||||||
const localEvent = event.originalEvent || event;
|
const localEvent = event.originalEvent || event;
|
||||||
if (attachment && localEvent && localEvent.dataTransfer && localEvent.dataTransfer.setData) {
|
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;
|
return true;
|
||||||
|
|
@ -211,13 +188,6 @@ export class AttachmentModel extends AbstractModel {
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
iconClass() {
|
iconClass() {
|
||||||
return FileInfo.getTypeIconClass(this.fileType)[0];
|
return FileInfo.getTypeIconClass(this.fileType);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
iconText() {
|
|
||||||
return FileInfo.getTypeIconClass(this.fileType)[1];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,13 +96,6 @@ export class ComposeAttachmentModel extends AbstractModel {
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
iconClass() {
|
iconClass() {
|
||||||
return FileInfo.getIconClass(this.fileExt(), this.mimeType())[0];
|
return FileInfo.getIconClass(this.fileExt(), this.mimeType());
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
iconText() {
|
|
||||||
return FileInfo.getIconClass(this.fileExt(), this.mimeType())[1];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import * as Local from 'Storage/Client';
|
||||||
|
|
||||||
import { AppUserStore } from 'Stores/User/App';
|
import { AppUserStore } from 'Stores/User/App';
|
||||||
import { FolderUserStore } from 'Stores/User/Folder';
|
import { FolderUserStore } from 'Stores/User/Folder';
|
||||||
|
import { SettingsUserStore } from 'Stores/User/Settings';
|
||||||
|
|
||||||
import ko from 'ko';
|
import ko from 'ko';
|
||||||
|
|
||||||
|
|
@ -113,6 +114,7 @@ export class FolderCollectionModel extends AbstractCollectionModel
|
||||||
AppUserStore.threadsAllowed(!!(Settings.app('useImapThread') && this.IsThreadsSupported));
|
AppUserStore.threadsAllowed(!!(Settings.app('useImapThread') && this.IsThreadsSupported));
|
||||||
|
|
||||||
FolderUserStore.folderListOptimized(!!this.Optimized);
|
FolderUserStore.folderListOptimized(!!this.Optimized);
|
||||||
|
FolderUserStore.sortSupported(!!this.IsSortSupported);
|
||||||
|
|
||||||
let update = false;
|
let update = false;
|
||||||
|
|
||||||
|
|
@ -123,8 +125,7 @@ export class FolderCollectionModel extends AbstractCollectionModel
|
||||||
SettingsGet('DraftFolder') +
|
SettingsGet('DraftFolder') +
|
||||||
SettingsGet('SpamFolder') +
|
SettingsGet('SpamFolder') +
|
||||||
SettingsGet('TrashFolder') +
|
SettingsGet('TrashFolder') +
|
||||||
SettingsGet('ArchiveFolder') +
|
SettingsGet('ArchiveFolder'))
|
||||||
SettingsGet('NullFolder'))
|
|
||||||
) {
|
) {
|
||||||
Settings.set('SentFolder', this.SystemFolders[ServerFolderType.SENT] || null);
|
Settings.set('SentFolder', this.SystemFolders[ServerFolderType.SENT] || null);
|
||||||
Settings.set('DraftFolder', this.SystemFolders[ServerFolderType.DRAFTS] || null);
|
Settings.set('DraftFolder', this.SystemFolders[ServerFolderType.DRAFTS] || null);
|
||||||
|
|
@ -147,8 +148,7 @@ export class FolderCollectionModel extends AbstractCollectionModel
|
||||||
DraftFolder: FolderUserStore.draftFolder(),
|
DraftFolder: FolderUserStore.draftFolder(),
|
||||||
SpamFolder: FolderUserStore.spamFolder(),
|
SpamFolder: FolderUserStore.spamFolder(),
|
||||||
TrashFolder: FolderUserStore.trashFolder(),
|
TrashFolder: FolderUserStore.trashFolder(),
|
||||||
ArchiveFolder: FolderUserStore.archiveFolder(),
|
ArchiveFolder: FolderUserStore.archiveFolder()
|
||||||
NullFolder: 'NullFolder'
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -301,7 +301,10 @@ export class FolderModel extends AbstractModel {
|
||||||
|
|
||||||
canBeDeleted: () => !folder.isSystemFolder() && !folder.subFolders.length,
|
canBeDeleted: () => !folder.isSystemFolder() && !folder.subFolders.length,
|
||||||
|
|
||||||
canBeSubscribed: () => !folder.isSystemFolder() && folder.selectable && Settings.app('useImapSubscribe'),
|
canBeSubscribed: () => !folder.isSystemFolder()
|
||||||
|
&& SettingsUserStore.hideUnsubscribed()
|
||||||
|
&& folder.selectable
|
||||||
|
&& Settings.app('useImapSubscribe'),
|
||||||
|
|
||||||
canBeSelected: () => !folder.isSystemFolder() && folder.selectable,
|
canBeSelected: () => !folder.isSystemFolder() && folder.selectable,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,9 @@ export class MessageModel extends AbstractModel {
|
||||||
this.addObservables({
|
this.addObservables({
|
||||||
subject: '',
|
subject: '',
|
||||||
size: 0,
|
size: 0,
|
||||||
|
spamScore: 0,
|
||||||
|
spamResult: '',
|
||||||
|
isSpam: false,
|
||||||
dateTimeStampInUTC: 0,
|
dateTimeStampInUTC: 0,
|
||||||
priority: MessagePriority.Normal,
|
priority: MessagePriority.Normal,
|
||||||
|
|
||||||
|
|
@ -116,6 +119,9 @@ export class MessageModel extends AbstractModel {
|
||||||
this._reset();
|
this._reset();
|
||||||
this.subject('');
|
this.subject('');
|
||||||
this.size(0);
|
this.size(0);
|
||||||
|
this.spamScore(0);
|
||||||
|
this.spamResult('');
|
||||||
|
this.isSpam(false);
|
||||||
this.dateTimeStampInUTC(0);
|
this.dateTimeStampInUTC(0);
|
||||||
this.priority(MessagePriority.Normal);
|
this.priority(MessagePriority.Normal);
|
||||||
|
|
||||||
|
|
@ -211,10 +217,10 @@ export class MessageModel extends AbstractModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {boolean} friendlyView
|
* @param {boolean} friendlyView
|
||||||
* @param {boolean=} wrapWithLink = false
|
* @param {boolean=} wrapWithLink
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
fromToLine(friendlyView, wrapWithLink = false) {
|
fromToLine(friendlyView, wrapWithLink) {
|
||||||
return this.from.toString(friendlyView, wrapWithLink);
|
return this.from.toString(friendlyView, wrapWithLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -232,37 +238,37 @@ export class MessageModel extends AbstractModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {boolean} friendlyView
|
* @param {boolean} friendlyView
|
||||||
* @param {boolean=} wrapWithLink = false
|
* @param {boolean=} wrapWithLink
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
toToLine(friendlyView, wrapWithLink = false) {
|
toToLine(friendlyView, wrapWithLink) {
|
||||||
return this.to.toString(friendlyView, wrapWithLink);
|
return this.to.toString(friendlyView, wrapWithLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {boolean} friendlyView
|
* @param {boolean} friendlyView
|
||||||
* @param {boolean=} wrapWithLink = false
|
* @param {boolean=} wrapWithLink
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
ccToLine(friendlyView, wrapWithLink = false) {
|
ccToLine(friendlyView, wrapWithLink) {
|
||||||
return this.cc.toString(friendlyView, wrapWithLink);
|
return this.cc.toString(friendlyView, wrapWithLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {boolean} friendlyView
|
* @param {boolean} friendlyView
|
||||||
* @param {boolean=} wrapWithLink = false
|
* @param {boolean=} wrapWithLink
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
bccToLine(friendlyView, wrapWithLink = false) {
|
bccToLine(friendlyView, wrapWithLink) {
|
||||||
return this.bcc.toString(friendlyView, wrapWithLink);
|
return this.bcc.toString(friendlyView, wrapWithLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {boolean} friendlyView
|
* @param {boolean} friendlyView
|
||||||
* @param {boolean=} wrapWithLink = false
|
* @param {boolean=} wrapWithLink
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
replyToToLine(friendlyView, wrapWithLink = false) {
|
replyToToLine(friendlyView, wrapWithLink) {
|
||||||
return this.replyTo.toString(friendlyView, wrapWithLink);
|
return this.replyTo.toString(friendlyView, wrapWithLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -318,7 +324,7 @@ export class MessageModel extends AbstractModel {
|
||||||
* @param {boolean=} last = false
|
* @param {boolean=} last = false
|
||||||
* @returns {Array}
|
* @returns {Array}
|
||||||
*/
|
*/
|
||||||
replyEmails(excludeEmails, last = false) {
|
replyEmails(excludeEmails, last) {
|
||||||
const result = [],
|
const result = [],
|
||||||
unic = undefined === excludeEmails ? {} : excludeEmails;
|
unic = undefined === excludeEmails ? {} : excludeEmails;
|
||||||
|
|
||||||
|
|
@ -339,7 +345,7 @@ export class MessageModel extends AbstractModel {
|
||||||
* @param {boolean=} last = false
|
* @param {boolean=} last = false
|
||||||
* @returns {Array.<Array>}
|
* @returns {Array.<Array>}
|
||||||
*/
|
*/
|
||||||
replyAllEmails(excludeEmails, last = false) {
|
replyAllEmails(excludeEmails, last) {
|
||||||
let data = [];
|
let data = [];
|
||||||
const toResult = [],
|
const toResult = [],
|
||||||
ccResult = [],
|
ccResult = [],
|
||||||
|
|
@ -364,7 +370,7 @@ export class MessageModel extends AbstractModel {
|
||||||
/**
|
/**
|
||||||
* @param {boolean=} print = false
|
* @param {boolean=} print = false
|
||||||
*/
|
*/
|
||||||
viewPopupMessage(print = false) {
|
viewPopupMessage(print) {
|
||||||
const timeStampInUTC = this.dateTimeStampInUTC() || 0,
|
const timeStampInUTC = this.dateTimeStampInUTC() || 0,
|
||||||
ccLine = this.ccToLine(false),
|
ccLine = this.ccToLine(false),
|
||||||
m = 0 < timeStampInUTC ? new Date(timeStampInUTC * 1000) : null,
|
m = 0 < timeStampInUTC ? new Date(timeStampInUTC * 1000) : null,
|
||||||
|
|
@ -414,6 +420,9 @@ export class MessageModel extends AbstractModel {
|
||||||
this.subject(message.subject());
|
this.subject(message.subject());
|
||||||
|
|
||||||
this.size(message.size());
|
this.size(message.size());
|
||||||
|
this.spamScore(message.spamScore());
|
||||||
|
this.spamResult(message.spamResult());
|
||||||
|
this.isSpam(message.isSpam());
|
||||||
this.dateTimeStampInUTC(message.dateTimeStampInUTC());
|
this.dateTimeStampInUTC(message.dateTimeStampInUTC());
|
||||||
this.priority(message.priority());
|
this.priority(message.priority());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,76 +55,17 @@ class RemoteUserFetch extends AbstractFetchRemote {
|
||||||
* @param {string} sPassword
|
* @param {string} sPassword
|
||||||
* @param {boolean} bSignMe
|
* @param {boolean} bSignMe
|
||||||
* @param {string=} sLanguage
|
* @param {string=} sLanguage
|
||||||
* @param {string=} sAdditionalCode
|
|
||||||
* @param {boolean=} bAdditionalCodeSignMe
|
|
||||||
*/
|
*/
|
||||||
login(fCallback, sEmail, sPassword, bSignMe, sLanguage, sAdditionalCode, bAdditionalCodeSignMe) {
|
login(fCallback, sEmail, sPassword, bSignMe, sLanguage) {
|
||||||
this.defaultRequest(fCallback, 'Login', {
|
this.defaultRequest(fCallback, 'Login', {
|
||||||
Email: sEmail,
|
Email: sEmail,
|
||||||
Login: '',
|
Login: '',
|
||||||
Password: sPassword,
|
Password: sPassword,
|
||||||
Language: sLanguage || '',
|
Language: sLanguage || '',
|
||||||
AdditionalCode: sAdditionalCode || '',
|
|
||||||
AdditionalCodeSignMe: bAdditionalCodeSignMe ? 1 : 0,
|
|
||||||
SignMe: bSignMe ? 1 : 0
|
SignMe: bSignMe ? 1 : 0
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {?Function} fCallback
|
|
||||||
*/
|
|
||||||
getTwoFactor(fCallback) {
|
|
||||||
this.defaultRequest(fCallback, 'GetTwoFactorInfo');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {?Function} fCallback
|
|
||||||
*/
|
|
||||||
createTwoFactor(fCallback) {
|
|
||||||
this.defaultRequest(fCallback, 'CreateTwoFactorSecret');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {?Function} fCallback
|
|
||||||
*/
|
|
||||||
clearTwoFactor(fCallback) {
|
|
||||||
this.defaultRequest(fCallback, 'ClearTwoFactorInfo');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {?Function} fCallback
|
|
||||||
*/
|
|
||||||
showTwoFactorSecret(fCallback) {
|
|
||||||
this.defaultRequest(fCallback, 'ShowTwoFactorSecret');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {?Function} fCallback
|
|
||||||
* @param {string} sCode
|
|
||||||
*/
|
|
||||||
testTwoFactor(fCallback, sCode) {
|
|
||||||
this.defaultRequest(fCallback, 'TestTwoFactorInfo', {
|
|
||||||
Code: sCode
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {?Function} fCallback
|
|
||||||
* @param {boolean} bEnable
|
|
||||||
*/
|
|
||||||
enableTwoFactor(fCallback, bEnable) {
|
|
||||||
this.defaultRequest(fCallback, 'EnableTwoFactor', {
|
|
||||||
Enable: bEnable ? 1 : 0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {?Function} fCallback
|
|
||||||
*/
|
|
||||||
clearTwoFactorInfo(fCallback) {
|
|
||||||
this.defaultRequest(fCallback, 'ClearTwoFactorInfo');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {?Function} fCallback
|
* @param {?Function} fCallback
|
||||||
*/
|
*/
|
||||||
|
|
@ -297,54 +238,44 @@ class RemoteUserFetch extends AbstractFetchRemote {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Function} fCallback
|
* @param {Function} fCallback
|
||||||
* @param {string} sFolderFullNameRaw
|
* @param {object} params
|
||||||
* @param {number=} iOffset = 0
|
|
||||||
* @param {number=} iLimit = 20
|
|
||||||
* @param {string=} sSearch = ''
|
|
||||||
* @param {string=} sThreadUid = ''
|
|
||||||
* @param {boolean=} bSilent = false
|
* @param {boolean=} bSilent = false
|
||||||
*/
|
*/
|
||||||
messageList(fCallback, sFolderFullNameRaw, iOffset = 0, iLimit = 20, sSearch = '', sThreadUid = '', bSilent = false) {
|
messageList(fCallback, params, bSilent = false) {
|
||||||
sFolderFullNameRaw = pString(sFolderFullNameRaw);
|
const
|
||||||
|
sFolderFullNameRaw = pString(params.Folder),
|
||||||
const folderHash = getFolderHash(sFolderFullNameRaw),
|
folderHash = getFolderHash(sFolderFullNameRaw),
|
||||||
useThreads = AppUserStore.threadsAllowed() && SettingsUserStore.useThreads(),
|
useThreads = AppUserStore.threadsAllowed() && SettingsUserStore.useThreads() ? 1 : 0,
|
||||||
inboxUidNext = getFolderInboxName() === sFolderFullNameRaw ? getFolderUidNext(sFolderFullNameRaw) : '';
|
inboxUidNext = getFolderInboxName() === sFolderFullNameRaw ? getFolderUidNext(sFolderFullNameRaw) : '';
|
||||||
|
|
||||||
let params = {}, sGetAdd = '';
|
params.Folder = sFolderFullNameRaw;
|
||||||
|
params.ThreadUid = useThreads ? params.ThreadUid : '';
|
||||||
|
params = Object.assign({
|
||||||
|
Folder: '',
|
||||||
|
Offset: 0,
|
||||||
|
Limit: SettingsUserStore.messagesPerPage(),
|
||||||
|
Search: '',
|
||||||
|
UidNext: inboxUidNext,
|
||||||
|
UseThreads: useThreads,
|
||||||
|
ThreadUid: '',
|
||||||
|
Sort: FolderUserStore.sortMode()
|
||||||
|
}, params);
|
||||||
|
|
||||||
if (folderHash && (!sSearch || !sSearch.includes('is:'))) {
|
let sGetAdd = '';
|
||||||
|
|
||||||
|
if (folderHash && (!params.Search || !params.Search.includes('is:'))) {
|
||||||
sGetAdd = 'MessageList/' +
|
sGetAdd = 'MessageList/' +
|
||||||
SUB_QUERY_PREFIX +
|
SUB_QUERY_PREFIX +
|
||||||
'/' +
|
'/' +
|
||||||
urlsafeArray([
|
urlsafeArray([SettingsGet('ProjectHash'),folderHash].concat(Object.values(params)));
|
||||||
sFolderFullNameRaw,
|
params = {};
|
||||||
iOffset,
|
|
||||||
iLimit,
|
|
||||||
sSearch,
|
|
||||||
SettingsGet('ProjectHash'),
|
|
||||||
folderHash,
|
|
||||||
inboxUidNext,
|
|
||||||
useThreads ? 1 : 0,
|
|
||||||
useThreads ? sThreadUid : ''
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
params = {
|
|
||||||
Folder: sFolderFullNameRaw,
|
|
||||||
Offset: iOffset,
|
|
||||||
Limit: iLimit,
|
|
||||||
Search: sSearch,
|
|
||||||
UidNext: inboxUidNext,
|
|
||||||
UseThreads: useThreads ? 1 : 0,
|
|
||||||
ThreadUid: useThreads ? sThreadUid : ''
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.defaultRequest(
|
this.defaultRequest(
|
||||||
fCallback,
|
fCallback,
|
||||||
'MessageList',
|
'MessageList',
|
||||||
params,
|
params,
|
||||||
sSearch ? 300000 : 30000,
|
30000,
|
||||||
sGetAdd,
|
sGetAdd,
|
||||||
bSilent ? [] : ['MessageList']
|
bSilent ? [] : ['MessageList']
|
||||||
);
|
);
|
||||||
|
|
@ -562,7 +493,7 @@ class RemoteUserFetch extends AbstractFetchRemote {
|
||||||
* @param {Object} oData
|
* @param {Object} oData
|
||||||
*/
|
*/
|
||||||
sendMessage(fCallback, oData) {
|
sendMessage(fCallback, oData) {
|
||||||
this.defaultRequest(fCallback, 'SendMessage', oData, 300000);
|
this.defaultRequest(fCallback, 'SendMessage', oData, 30000);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ export class MailBoxUserScreen extends AbstractScreen {
|
||||||
|
|
||||||
FolderUserStore.currentFolder(folder);
|
FolderUserStore.currentFolder(folder);
|
||||||
|
|
||||||
MessageUserStore.listPage(page);
|
MessageUserStore.listPage(1 > page ? 1 : page);
|
||||||
MessageUserStore.listSearch(search);
|
MessageUserStore.listSearch(search);
|
||||||
MessageUserStore.listThreadUid(threadUid);
|
MessageUserStore.listThreadUid(threadUid);
|
||||||
|
|
||||||
|
|
@ -119,23 +119,15 @@ export class MailBoxUserScreen extends AbstractScreen {
|
||||||
*/
|
*/
|
||||||
routes() {
|
routes() {
|
||||||
const
|
const
|
||||||
fNormS = (request, vals) => {
|
folder = (request, vals) => request ? decodeURI(pString(vals[0])) : getFolderInboxName(),
|
||||||
if (request) {
|
fNormS = (request, vals) => [folder(request, vals), request ? pInt(vals[1]) : 1, decodeURI(pString(vals[2]))];
|
||||||
vals[0] = decodeURI(pString(vals[0]));
|
|
||||||
vals[1] = pInt(vals[1]);
|
|
||||||
} else {
|
|
||||||
vals[0] = getFolderInboxName();
|
|
||||||
vals[1] = 1;
|
|
||||||
}
|
|
||||||
return [vals[0], 1 > vals[1] ? 1 : vals[1], decodeURI(pString(vals[2]))];
|
|
||||||
},
|
|
||||||
fNormD = (request, vals) =>
|
|
||||||
[decodeURI(request ? pString(vals[0]) : getFolderInboxName()), 1, decodeURI(pString(vals[1]))];
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
[/^([^/]*)$/, { 'normalize_': fNormS }],
|
[/^([^/]*)$/, { normalize_: fNormS }],
|
||||||
[/^([a-zA-Z0-9~]+)\/(.+)\/?$/, { 'normalize_': fNormD }],
|
[/^([a-zA-Z0-9~]+)\/(.+)\/?$/, { normalize_: (request, vals) =>
|
||||||
[/^([a-zA-Z0-9~]+)\/p([1-9][0-9]*)(?:\/(.+)\/?)?$/, { 'normalize_': fNormS }]
|
[folder(request, vals), 1, decodeURI(pString(vals[1]))]
|
||||||
|
}],
|
||||||
|
[/^([a-zA-Z0-9~]+)\/p([1-9][0-9]*)(?:\/(.+)\/?)?$/, { normalize_: fNormS }]
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ export class SettingsUserScreen extends AbstractSettingsScreen {
|
||||||
settingsAddViewModel(FiltersUserSettings, 'SettingsFilters', 'SETTINGS_LABELS/LABEL_FILTERS_NAME', 'filters');
|
settingsAddViewModel(FiltersUserSettings, 'SettingsFilters', 'SETTINGS_LABELS/LABEL_FILTERS_NAME', 'filters');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Settings.capa(Capa.AutoLogout) || Settings.capa(Capa.TwoFactor)) {
|
if (Settings.capa(Capa.AutoLogout)) {
|
||||||
settingsAddViewModel(SecurityUserSettings, 'SettingsSecurity', 'SETTINGS_LABELS/LABEL_SECURITY_NAME', 'security');
|
settingsAddViewModel(SecurityUserSettings, 'SettingsSecurity', 'SETTINGS_LABELS/LABEL_SECURITY_NAME', 'security');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ export class ContactsAdminSettings {
|
||||||
addObservablesTo(this, {
|
addObservablesTo(this, {
|
||||||
enableContacts: !!SettingsGet('ContactsEnable'),
|
enableContacts: !!SettingsGet('ContactsEnable'),
|
||||||
contactsSync: !!SettingsGet('ContactsSync'),
|
contactsSync: !!SettingsGet('ContactsSync'),
|
||||||
contactsType: '',
|
contactsType: SettingsGet('ContactsPdoType'),
|
||||||
|
|
||||||
pdoDsn: SettingsGet('ContactsPdoDsn'),
|
pdoDsn: SettingsGet('ContactsPdoDsn'),
|
||||||
pdoUser: SettingsGet('ContactsPdoUser'),
|
pdoUser: SettingsGet('ContactsPdoUser'),
|
||||||
|
|
@ -69,8 +69,6 @@ export class ContactsAdminSettings {
|
||||||
})
|
})
|
||||||
.extend({ notify: 'always' });
|
.extend({ notify: 'always' });
|
||||||
|
|
||||||
this.contactsType(SettingsGet('ContactsPdoType'));
|
|
||||||
|
|
||||||
addSubscribablesTo(this, {
|
addSubscribablesTo(this, {
|
||||||
enableContacts: value =>
|
enableContacts: value =>
|
||||||
Remote.saveAdminConfig(null, {
|
Remote.saveAdminConfig(null, {
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,11 @@ export class GeneralAdminSettings {
|
||||||
|
|
||||||
this.weakPassword = rl.app.weakPassword;
|
this.weakPassword = rl.app.weakPassword;
|
||||||
|
|
||||||
// fetch('./data/VERSION?' + Math.random()).then(response => this.dataFolderAccess(response.ok));
|
/** https://github.com/RainLoop/rainloop-webmail/issues/1924
|
||||||
|
if (this.weakPassword) {
|
||||||
|
fetch('./data/VERSION?' + Math.random()).then(response => this.dataFolderAccess(response.ok));
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
this.mainAttachmentLimit = ko
|
this.mainAttachmentLimit = ko
|
||||||
.observable(pInt(SettingsGet('AttachmentLimit')) / (1024 * 1024))
|
.observable(pInt(SettingsGet('AttachmentLimit')) / (1024 * 1024))
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,6 @@ export class SecurityAdminSettings {
|
||||||
verifySslCertificate: !!SettingsGet('VerifySslCertificate'),
|
verifySslCertificate: !!SettingsGet('VerifySslCertificate'),
|
||||||
allowSelfSigned: !!SettingsGet('AllowSelfSigned'),
|
allowSelfSigned: !!SettingsGet('AllowSelfSigned'),
|
||||||
|
|
||||||
isTwoFactorDropperShown: false,
|
|
||||||
twoFactorDropperUser: '',
|
|
||||||
twoFactorDropperUserFocused: false,
|
|
||||||
|
|
||||||
adminLogin: SettingsGet('AdminLogin'),
|
adminLogin: SettingsGet('AdminLogin'),
|
||||||
adminLoginError: false,
|
adminLoginError: false,
|
||||||
adminPassword: '',
|
adminPassword: '',
|
||||||
|
|
@ -30,9 +26,7 @@ export class SecurityAdminSettings {
|
||||||
adminPasswordUpdateError: false,
|
adminPasswordUpdateError: false,
|
||||||
adminPasswordUpdateSuccess: false,
|
adminPasswordUpdateSuccess: false,
|
||||||
|
|
||||||
capaOpenPGP: Settings.capa(Capa.OpenPGP),
|
capaOpenPGP: Settings.capa(Capa.OpenPGP)
|
||||||
capaTwoFactorAuth: Settings.capa(Capa.TwoFactor),
|
|
||||||
capaTwoFactorAuthForce: Settings.capa(Capa.TwoFactorForce)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
addSubscribablesTo(this, {
|
addSubscribablesTo(this, {
|
||||||
|
|
@ -60,18 +54,6 @@ export class SecurityAdminSettings {
|
||||||
CapaOpenPGP: value ? 1 : 0
|
CapaOpenPGP: value ? 1 : 0
|
||||||
}),
|
}),
|
||||||
|
|
||||||
capaTwoFactorAuth: value => {
|
|
||||||
value || this.capaTwoFactorAuthForce(false);
|
|
||||||
Remote.saveAdminConfig(null, {
|
|
||||||
CapaTwoFactorAuth: value ? 1 : 0
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
capaTwoFactorAuthForce: value =>
|
|
||||||
Remote.saveAdminConfig(null, {
|
|
||||||
CapaTwoFactorAuthForce: value ? 1 : 0
|
|
||||||
}),
|
|
||||||
|
|
||||||
useLocalProxyForExternalImages: value =>
|
useLocalProxyForExternalImages: value =>
|
||||||
Remote.saveAdminConfig(null, {
|
Remote.saveAdminConfig(null, {
|
||||||
UseLocalProxyForExternalImages: value ? 1 : 0
|
UseLocalProxyForExternalImages: value ? 1 : 0
|
||||||
|
|
@ -120,15 +102,6 @@ export class SecurityAdminSettings {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
showTwoFactorDropper() {
|
|
||||||
this.twoFactorDropperUser('');
|
|
||||||
this.isTwoFactorDropperShown(true);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
this.twoFactorDropperUserFocused(true);
|
|
||||||
}, 50);
|
|
||||||
}
|
|
||||||
|
|
||||||
onNewAdminPasswordResponse(iError, data) {
|
onNewAdminPasswordResponse(iError, data) {
|
||||||
if (iError) {
|
if (iError) {
|
||||||
this.adminPasswordUpdateError(true);
|
this.adminPasswordUpdateError(true);
|
||||||
|
|
@ -147,9 +120,5 @@ export class SecurityAdminSettings {
|
||||||
this.adminPassword('');
|
this.adminPassword('');
|
||||||
this.adminPasswordNew('');
|
this.adminPasswordNew('');
|
||||||
this.adminPasswordNew2('');
|
this.adminPasswordNew2('');
|
||||||
|
|
||||||
this.isTwoFactorDropperShown(false);
|
|
||||||
this.twoFactorDropperUser('');
|
|
||||||
this.twoFactorDropperUserFocused(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import ko from 'ko';
|
||||||
|
|
||||||
import { Notification } from 'Common/Enums';
|
import { Notification } from 'Common/Enums';
|
||||||
import { ClientSideKeyName } from 'Common/EnumsUser';
|
import { ClientSideKeyName } from 'Common/EnumsUser';
|
||||||
|
import { Settings } from 'Common/Globals';
|
||||||
import { getNotification } from 'Common/Translator';
|
import { getNotification } from 'Common/Translator';
|
||||||
|
|
||||||
import { removeFolderFromCacheList } from 'Common/Cache';
|
import { removeFolderFromCacheList } from 'Common/Cache';
|
||||||
|
|
@ -9,6 +10,7 @@ import { removeFolderFromCacheList } from 'Common/Cache';
|
||||||
import * as Local from 'Storage/Client';
|
import * as Local from 'Storage/Client';
|
||||||
|
|
||||||
import { FolderUserStore } from 'Stores/User/Folder';
|
import { FolderUserStore } from 'Stores/User/Folder';
|
||||||
|
import { SettingsUserStore } from 'Stores/User/Settings';
|
||||||
|
|
||||||
import Remote from 'Remote/User/Fetch';
|
import Remote from 'Remote/User/Fetch';
|
||||||
|
|
||||||
|
|
@ -23,6 +25,7 @@ export class FoldersUserSettings {
|
||||||
this.folderList = FolderUserStore.folderList;
|
this.folderList = FolderUserStore.folderList;
|
||||||
this.folderListOptimized = FolderUserStore.folderListOptimized;
|
this.folderListOptimized = FolderUserStore.folderListOptimized;
|
||||||
this.folderListError = FolderUserStore.folderListError;
|
this.folderListError = FolderUserStore.folderListError;
|
||||||
|
this.hideUnsubscribed = SettingsUserStore.hideUnsubscribed;
|
||||||
|
|
||||||
this.loading = ko.computed(() => {
|
this.loading = ko.computed(() => {
|
||||||
const loading = FolderUserStore.foldersLoading(),
|
const loading = FolderUserStore.foldersLoading(),
|
||||||
|
|
@ -36,6 +39,9 @@ export class FoldersUserSettings {
|
||||||
this.folderForDeletion = ko.observable(null).deleteAccessHelper();
|
this.folderForDeletion = ko.observable(null).deleteAccessHelper();
|
||||||
|
|
||||||
this.folderForEdit = ko.observable(null).extend({ toggleSubscribeProperty: [this, 'edited'] });
|
this.folderForEdit = ko.observable(null).extend({ toggleSubscribeProperty: [this, 'edited'] });
|
||||||
|
|
||||||
|
this.useImapSubscribe = Settings.app('useImapSubscribe');
|
||||||
|
this.hideUnsubscribed.subscribe(value => Remote.saveSetting('HideUnsubscribed', value ? 1 : 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
folderEditOnEnter(folder) {
|
folderEditOnEnter(folder) {
|
||||||
|
|
|
||||||
|
|
@ -5,18 +5,13 @@ import { Capa, SaveSettingsStep } from 'Common/Enums';
|
||||||
import { Settings } from 'Common/Globals';
|
import { Settings } from 'Common/Globals';
|
||||||
import { i18n, trigger as translatorTrigger } from 'Common/Translator';
|
import { i18n, trigger as translatorTrigger } from 'Common/Translator';
|
||||||
|
|
||||||
import { showScreenPopup } from 'Knoin/Knoin';
|
|
||||||
|
|
||||||
import { SettingsUserStore } from 'Stores/User/Settings';
|
import { SettingsUserStore } from 'Stores/User/Settings';
|
||||||
|
|
||||||
import Remote from 'Remote/User/Fetch';
|
import Remote from 'Remote/User/Fetch';
|
||||||
|
|
||||||
import { TwoFactorConfigurationPopupView } from 'View/Popup/TwoFactorConfiguration';
|
|
||||||
|
|
||||||
export class SecurityUserSettings {
|
export class SecurityUserSettings {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.capaAutoLogout = Settings.capa(Capa.AutoLogout);
|
this.capaAutoLogout = Settings.capa(Capa.AutoLogout);
|
||||||
this.capaTwoFactor = Settings.capa(Capa.TwoFactor);
|
|
||||||
|
|
||||||
this.autoLogout = SettingsUserStore.autoLogout;
|
this.autoLogout = SettingsUserStore.autoLogout;
|
||||||
this.autoLogoutTrigger = ko.observable(SaveSettingsStep.Idle);
|
this.autoLogoutTrigger = ko.observable(SaveSettingsStep.Idle);
|
||||||
|
|
@ -43,8 +38,4 @@ export class SecurityUserSettings {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
configureTwoFactor() {
|
|
||||||
showScreenPopup(TwoFactorConfigurationPopupView);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import ko from 'ko';
|
import ko from 'ko';
|
||||||
|
|
||||||
import { FolderType } from 'Common/EnumsUser';
|
import { FolderType, FolderSortMode } from 'Common/EnumsUser';
|
||||||
import { UNUSED_OPTION_VALUE } from 'Common/Consts';
|
import { UNUSED_OPTION_VALUE } from 'Common/Consts';
|
||||||
import { addObservablesTo, addSubscribablesTo } from 'Common/Utils';
|
import { addObservablesTo, addSubscribablesTo } from 'Common/Utils';
|
||||||
import { folderListOptionsBuilder } from 'Common/UtilsUser';
|
import { folderListOptionsBuilder } from 'Common/UtilsUser';
|
||||||
|
|
@ -19,6 +19,12 @@ export const FolderUserStore = new class {
|
||||||
*/
|
*/
|
||||||
displaySpecSetting: false,
|
displaySpecSetting: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the IMAP server supports SORT
|
||||||
|
*/
|
||||||
|
sortSupported: false,
|
||||||
|
// sortMode: '',
|
||||||
|
|
||||||
sentFolder: '',
|
sentFolder: '',
|
||||||
draftFolder: '',
|
draftFolder: '',
|
||||||
spamFolder: '',
|
spamFolder: '',
|
||||||
|
|
@ -36,6 +42,8 @@ export const FolderUserStore = new class {
|
||||||
foldersInboxUnreadCount: 0
|
foldersInboxUnreadCount: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.sortMode = ko.observable('').extend({ limitedList: Object.values(FolderSortMode) });
|
||||||
|
|
||||||
this.namespace = '';
|
this.namespace = '';
|
||||||
|
|
||||||
this.folderList = ko.observableArray();
|
this.folderList = ko.observableArray();
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ export const SettingsUserStore = new class {
|
||||||
allowDraftAutosave: !!SettingsGet('AllowDraftAutosave'),
|
allowDraftAutosave: !!SettingsGet('AllowDraftAutosave'),
|
||||||
useThreads: !!SettingsGet('UseThreads'),
|
useThreads: !!SettingsGet('UseThreads'),
|
||||||
replySameFolder: !!SettingsGet('ReplySameFolder'),
|
replySameFolder: !!SettingsGet('ReplySameFolder'),
|
||||||
|
hideUnsubscribed: !!SettingsGet('HideUnsubscribed'),
|
||||||
|
|
||||||
autoLogout: pInt(SettingsGet('AutoLogout'))
|
autoLogout: pInt(SettingsGet('AutoLogout'))
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,6 @@
|
||||||
@import "Filter.less";
|
@import "Filter.less";
|
||||||
@import "Template.less";
|
@import "Template.less";
|
||||||
@import "OpenPgpKey.less";
|
@import "OpenPgpKey.less";
|
||||||
@import "TwoFactor.less";
|
|
||||||
@import "Identity.less";
|
@import "Identity.less";
|
||||||
@import "AdvancedSearch.less";
|
@import "AdvancedSearch.less";
|
||||||
@import "Attachmnets.less";
|
@import "Attachmnets.less";
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkboxAttachment {
|
.checkboxAttachment {
|
||||||
position: absolute;
|
bottom: 6px;
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
padding: 1px 4px 0 5px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
right: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.attachmentSize {
|
.attachmentSize {
|
||||||
|
|
@ -104,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 {
|
.attachmentIconParent.hasPreview.isImage {
|
||||||
.iconMain {
|
.iconMain {
|
||||||
|
|
|
||||||
|
|
@ -18,14 +18,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.attachmentAreaParent {
|
.attachmentAreaParent {
|
||||||
padding: 10px 10px 6px 10px;
|
|
||||||
background: #ddd;
|
|
||||||
border-top: 1px solid #ccc;
|
border-top: 1px solid #ccc;
|
||||||
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
padding: 10px 10px 6px 10px;
|
||||||
|
|
||||||
.attachmentAreaParent {
|
|
||||||
|
|
||||||
.no-attachments-desc {
|
.no-attachments-desc {
|
||||||
padding: 50px 10px;
|
padding: 50px 10px;
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.controls {
|
.controls {
|
||||||
.inputLoginForm, .inputEmail, .inputLogin, .inputPassword, .inputAdditionalCode {
|
.inputLoginForm, .inputEmail, .inputLogin, .inputPassword {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,11 @@ html.rl-no-preview-pane {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#sort-list-dropdown-id {
|
||||||
|
padding-left: 6px;
|
||||||
|
padding-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
.messageList {
|
.messageList {
|
||||||
|
|
||||||
.toolbar {
|
.toolbar {
|
||||||
|
|
@ -254,9 +259,9 @@ html.rl-no-preview-pane {
|
||||||
padding: 0 10px 0 5px;
|
padding: 0 10px 0 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
time {
|
time, .sizeParent {
|
||||||
margin: 0 5px;
|
margin: 0 5px;
|
||||||
color: #999;
|
opacity: 0.6;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
@ -472,7 +477,7 @@ html .messageList .line-loading {
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
margin-left: 30px;
|
margin-left: 30px;
|
||||||
order: 1;
|
order: 1;
|
||||||
width: calc(100% - 60px);
|
width: calc(100% - 120px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.senderParent {
|
.senderParent {
|
||||||
|
|
@ -483,9 +488,13 @@ html .messageList .line-loading {
|
||||||
order: 1;
|
order: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.attachmentParent {
|
.sizeParent {
|
||||||
order: 2;
|
order: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.attachmentParent {
|
||||||
|
order: 3;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -136,15 +136,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.informationFull {
|
.informationFull {
|
||||||
margin-top: 10px;
|
margin-top: 30px;
|
||||||
border: 1px solid #999;
|
|
||||||
border-radius: 5px;
|
|
||||||
padding: 8px 0;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
.size {
|
|
||||||
margin-left: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
@ -155,7 +148,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
padding: 0 10px;
|
padding: 4px;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
min-width: 43px;
|
min-width: 43px;
|
||||||
}
|
}
|
||||||
|
|
@ -163,17 +156,15 @@
|
||||||
td:first-child {
|
td:first-child {
|
||||||
border-right: 1px solid #999;
|
border-right: 1px solid #999;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
white-space: nowrap;
|
||||||
width: 1%;
|
width: 1%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.uiLine {
|
.labelTo {
|
||||||
}
|
|
||||||
|
|
||||||
.uiLabel {
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.uiLabelValue {
|
td + td {
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -273,14 +264,10 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:not(.selection-mode) {
|
||||||
.checkboxAttachment {
|
.checkboxAttachment {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.selection-mode {
|
|
||||||
.checkboxAttachment {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.unselectedAttachmentsError {
|
&.unselectedAttachmentsError {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-table {
|
.list-table {
|
||||||
margin-top: 40px;
|
margin-top: 1em;
|
||||||
|
|
||||||
.folder-padding {
|
.folder-padding {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
.b-two-factor-content {
|
|
||||||
|
|
||||||
width: 750px;
|
|
||||||
|
|
||||||
.modal-body {
|
|
||||||
min-height: 100px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -64,8 +64,7 @@ class FolderSystemPopupView extends AbstractViewPopup {
|
||||||
DraftFolder: FolderUserStore.draftFolder(),
|
DraftFolder: FolderUserStore.draftFolder(),
|
||||||
SpamFolder: FolderUserStore.spamFolder(),
|
SpamFolder: FolderUserStore.spamFolder(),
|
||||||
TrashFolder: FolderUserStore.trashFolder(),
|
TrashFolder: FolderUserStore.trashFolder(),
|
||||||
ArchiveFolder: FolderUserStore.archiveFolder(),
|
ArchiveFolder: FolderUserStore.archiveFolder()
|
||||||
NullFolder: 'NullFolder'
|
|
||||||
});
|
});
|
||||||
}).debounce(1000),
|
}).debounce(1000),
|
||||||
fCallback = () => {
|
fCallback = () => {
|
||||||
|
|
|
||||||
|
|
@ -1,193 +0,0 @@
|
||||||
import { Capa } from 'Common/Enums';
|
|
||||||
import { Settings } from 'Common/Globals';
|
|
||||||
import { pString } from 'Common/Utils';
|
|
||||||
import { i18n, trigger as translatorTrigger } from 'Common/Translator';
|
|
||||||
|
|
||||||
import Remote from 'Remote/User/Fetch';
|
|
||||||
|
|
||||||
import { showScreenPopup } from 'Knoin/Knoin';
|
|
||||||
import { AbstractViewPopup } from 'Knoin/AbstractViews';
|
|
||||||
|
|
||||||
import { TwoFactorTestPopupView } from 'View/Popup/TwoFactorTest';
|
|
||||||
|
|
||||||
class TwoFactorConfigurationPopupView extends AbstractViewPopup {
|
|
||||||
constructor() {
|
|
||||||
super('TwoFactorConfiguration');
|
|
||||||
|
|
||||||
this.addObservables({
|
|
||||||
lock: false,
|
|
||||||
|
|
||||||
processing: false,
|
|
||||||
clearing: false,
|
|
||||||
secreting: false,
|
|
||||||
|
|
||||||
viewUser: '',
|
|
||||||
twoFactorStatus: false,
|
|
||||||
|
|
||||||
twoFactorTested: false,
|
|
||||||
|
|
||||||
viewSecret: '',
|
|
||||||
viewBackupCodes: '',
|
|
||||||
viewUrlTitle: '',
|
|
||||||
viewUrl: '',
|
|
||||||
|
|
||||||
viewEnable_: false
|
|
||||||
});
|
|
||||||
|
|
||||||
this.capaTwoFactor = Settings.capa(Capa.TwoFactor);
|
|
||||||
|
|
||||||
const fn = iError => iError && this.viewEnable_(false);
|
|
||||||
this.addComputables({
|
|
||||||
viewEnable: {
|
|
||||||
read: this.viewEnable_,
|
|
||||||
write: (value) => {
|
|
||||||
value = !!value;
|
|
||||||
if (value && this.twoFactorTested()) {
|
|
||||||
this.viewEnable_(value);
|
|
||||||
Remote.enableTwoFactor(fn, value);
|
|
||||||
} else {
|
|
||||||
if (!value) {
|
|
||||||
this.viewEnable_(value);
|
|
||||||
}
|
|
||||||
Remote.enableTwoFactor(fn, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
viewTwoFactorEnableTooltip: () => {
|
|
||||||
translatorTrigger();
|
|
||||||
return this.twoFactorTested() || this.viewEnable_()
|
|
||||||
? ''
|
|
||||||
: i18n('POPUPS_TWO_FACTOR_CFG/TWO_FACTOR_SECRET_TEST_BEFORE_DESC');
|
|
||||||
},
|
|
||||||
|
|
||||||
viewTwoFactorStatus: () => {
|
|
||||||
translatorTrigger();
|
|
||||||
return i18n(
|
|
||||||
this.twoFactorStatus()
|
|
||||||
? 'POPUPS_TWO_FACTOR_CFG/TWO_FACTOR_SECRET_CONFIGURED_DESC'
|
|
||||||
: 'POPUPS_TWO_FACTOR_CFG/TWO_FACTOR_SECRET_NOT_CONFIGURED_DESC'
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
twoFactorAllowedEnable: () => this.viewEnable() || this.twoFactorTested()
|
|
||||||
});
|
|
||||||
|
|
||||||
this.onResult = this.onResult.bind(this);
|
|
||||||
this.onShowSecretResult = this.onShowSecretResult.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
showSecret() {
|
|
||||||
this.secreting(true);
|
|
||||||
Remote.showTwoFactorSecret(this.onShowSecretResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
hideSecret() {
|
|
||||||
this.viewSecret('');
|
|
||||||
this.viewBackupCodes('');
|
|
||||||
this.viewUrlTitle('');
|
|
||||||
this.viewUrl('');
|
|
||||||
}
|
|
||||||
|
|
||||||
createTwoFactor() {
|
|
||||||
this.processing(true);
|
|
||||||
Remote.createTwoFactor(this.onResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
logout() {
|
|
||||||
rl.app.logout();
|
|
||||||
}
|
|
||||||
|
|
||||||
testTwoFactor() {
|
|
||||||
showScreenPopup(TwoFactorTestPopupView, [this.twoFactorTested]);
|
|
||||||
}
|
|
||||||
|
|
||||||
clearTwoFactor() {
|
|
||||||
this.viewSecret('');
|
|
||||||
this.viewBackupCodes('');
|
|
||||||
this.viewUrlTitle('');
|
|
||||||
this.viewUrl('');
|
|
||||||
|
|
||||||
this.twoFactorTested(false);
|
|
||||||
|
|
||||||
this.clearing(true);
|
|
||||||
Remote.clearTwoFactor(this.onResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
onShow(bLock) {
|
|
||||||
this.lock(!!bLock);
|
|
||||||
|
|
||||||
this.viewSecret('');
|
|
||||||
this.viewBackupCodes('');
|
|
||||||
this.viewUrlTitle('');
|
|
||||||
this.viewUrl('');
|
|
||||||
}
|
|
||||||
|
|
||||||
onHide() {
|
|
||||||
if (this.lock()) {
|
|
||||||
location.reload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getQr() {
|
|
||||||
return (
|
|
||||||
'otpauth://totp/' +
|
|
||||||
encodeURIComponent(this.viewUser()) +
|
|
||||||
'?secret=' +
|
|
||||||
encodeURIComponent(this.viewSecret()) +
|
|
||||||
'&issuer=' +
|
|
||||||
encodeURIComponent('')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
onResult(iError, oData) {
|
|
||||||
this.processing(false);
|
|
||||||
this.clearing(false);
|
|
||||||
|
|
||||||
if (iError) {
|
|
||||||
this.viewUser('');
|
|
||||||
this.viewEnable_(false);
|
|
||||||
this.twoFactorStatus(false);
|
|
||||||
this.twoFactorTested(false);
|
|
||||||
|
|
||||||
this.viewSecret('');
|
|
||||||
this.viewBackupCodes('');
|
|
||||||
this.viewUrlTitle('');
|
|
||||||
this.viewUrl('');
|
|
||||||
} else {
|
|
||||||
this.viewUser(pString(oData.Result.User));
|
|
||||||
this.viewEnable_(!!oData.Result.Enable);
|
|
||||||
this.twoFactorStatus(!!oData.Result.IsSet);
|
|
||||||
this.twoFactorTested(!!oData.Result.Tested);
|
|
||||||
|
|
||||||
this.viewSecret(pString(oData.Result.Secret));
|
|
||||||
this.viewBackupCodes(pString(oData.Result.BackupCodes).replace(/[\s]+/g, ' '));
|
|
||||||
|
|
||||||
this.viewUrlTitle(pString(oData.Result.UrlTitle));
|
|
||||||
this.viewUrl(qr.toDataURL({ level: 'M', size: 8, value: this.getQr() }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onShowSecretResult(iError, data) {
|
|
||||||
this.secreting(false);
|
|
||||||
|
|
||||||
if (iError) {
|
|
||||||
this.viewSecret('');
|
|
||||||
this.viewUrlTitle('');
|
|
||||||
this.viewUrl('');
|
|
||||||
} else {
|
|
||||||
this.viewSecret(pString(data.Result.Secret));
|
|
||||||
this.viewUrlTitle(pString(data.Result.UrlTitle));
|
|
||||||
this.viewUrl(qr.toDataURL({ level: 'M', size: 6, value: this.getQr() }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onBuild() {
|
|
||||||
if (this.capaTwoFactor) {
|
|
||||||
this.processing(true);
|
|
||||||
Remote.getTwoFactor(this.onResult);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { TwoFactorConfigurationPopupView, TwoFactorConfigurationPopupView as default };
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
import Remote from 'Remote/User/Fetch';
|
|
||||||
|
|
||||||
import { decorateKoCommands } from 'Knoin/Knoin';
|
|
||||||
import { AbstractViewPopup } from 'Knoin/AbstractViews';
|
|
||||||
|
|
||||||
class TwoFactorTestPopupView extends AbstractViewPopup {
|
|
||||||
constructor() {
|
|
||||||
super('TwoFactorTest');
|
|
||||||
|
|
||||||
this.addObservables({
|
|
||||||
code: '',
|
|
||||||
codeStatus: null,
|
|
||||||
|
|
||||||
testing: false
|
|
||||||
});
|
|
||||||
|
|
||||||
this.koTestedTrigger = null;
|
|
||||||
|
|
||||||
decorateKoCommands(this, {
|
|
||||||
testCodeCommand: self => self.code() && !self.testing()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
testCodeCommand() {
|
|
||||||
this.testing(true);
|
|
||||||
Remote.testTwoFactor(iError => {
|
|
||||||
this.testing(false);
|
|
||||||
this.codeStatus(!iError);
|
|
||||||
|
|
||||||
if (this.koTestedTrigger && this.codeStatus()) {
|
|
||||||
this.koTestedTrigger(true);
|
|
||||||
}
|
|
||||||
}, this.code());
|
|
||||||
}
|
|
||||||
|
|
||||||
clearPopup() {
|
|
||||||
this.code('');
|
|
||||||
this.codeStatus(null);
|
|
||||||
this.testing(false);
|
|
||||||
|
|
||||||
this.koTestedTrigger = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
onShow(koTestedTrigger) {
|
|
||||||
this.clearPopup();
|
|
||||||
|
|
||||||
this.koTestedTrigger = koTestedTrigger;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { TwoFactorTestPopupView, TwoFactorTestPopupView as default };
|
|
||||||
|
|
@ -43,7 +43,6 @@ class LoginUserView extends AbstractViewCenter {
|
||||||
email: SettingsGet('DevEmail'),
|
email: SettingsGet('DevEmail'),
|
||||||
password: SettingsGet('DevPassword'),
|
password: SettingsGet('DevPassword'),
|
||||||
signMe: false,
|
signMe: false,
|
||||||
additionalCode: '',
|
|
||||||
|
|
||||||
emailError: false,
|
emailError: false,
|
||||||
passwordError: false,
|
passwordError: false,
|
||||||
|
|
@ -56,9 +55,6 @@ class LoginUserView extends AbstractViewCenter {
|
||||||
|
|
||||||
langRequest: false,
|
langRequest: false,
|
||||||
|
|
||||||
additionalCodeError: false,
|
|
||||||
additionalCodeSignMe: false,
|
|
||||||
additionalCodeVisibility: false,
|
|
||||||
signMeType: LoginSignMeType.Unused
|
signMeType: LoginSignMeType.Unused
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -84,15 +80,10 @@ class LoginUserView extends AbstractViewCenter {
|
||||||
this.addSubscribables({
|
this.addSubscribables({
|
||||||
email: () => {
|
email: () => {
|
||||||
this.emailError(false);
|
this.emailError(false);
|
||||||
this.additionalCode('');
|
|
||||||
this.additionalCodeVisibility(false);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
password: () => this.passwordError(false),
|
password: () => this.passwordError(false),
|
||||||
|
|
||||||
additionalCode: () => this.additionalCodeError(false),
|
|
||||||
additionalCodeVisibility: () => this.additionalCodeError(false),
|
|
||||||
|
|
||||||
submitError: value => value || this.submitErrorAddidional(''),
|
submitError: value => value || this.submitErrorAddidional(''),
|
||||||
|
|
||||||
signMeType: iValue => this.signMe(LoginSignMeType.DefaultOn === iValue),
|
signMeType: iValue => this.signMe(LoginSignMeType.DefaultOn === iValue),
|
||||||
|
|
@ -121,16 +112,13 @@ class LoginUserView extends AbstractViewCenter {
|
||||||
submitCommand(self, event) {
|
submitCommand(self, event) {
|
||||||
let email = this.email().trim(),
|
let email = this.email().trim(),
|
||||||
valid = event.target.form.reportValidity() && email,
|
valid = event.target.form.reportValidity() && email,
|
||||||
pass = this.password(),
|
pass = this.password();
|
||||||
totp = this.additionalCodeVisibility(),
|
|
||||||
code = totp ? this.additionalCode() : '';
|
|
||||||
|
|
||||||
this.emailError(!email);
|
this.emailError(!email);
|
||||||
this.passwordError(!pass);
|
this.passwordError(!pass);
|
||||||
this.additionalCodeError(totp && !code);
|
|
||||||
|
|
||||||
let pluginResultCode = 0, pluginResultMessage = '';
|
let pluginResultCode = 0, pluginResultMessage = '';
|
||||||
Plugins.runHook('user-login-submit', [(iResultCode, sResultMessage) => {
|
runHook('user-login-submit', [(iResultCode, sResultMessage) => {
|
||||||
pluginResultCode = iResultCode;
|
pluginResultCode = iResultCode;
|
||||||
pluginResultMessage = sResultMessage;
|
pluginResultMessage = sResultMessage;
|
||||||
}]);
|
}]);
|
||||||
|
|
@ -156,13 +144,6 @@ class LoginUserView extends AbstractViewCenter {
|
||||||
}
|
}
|
||||||
this.submitError(getNotification(iError, oData.ErrorMessage, Notification.UnknownNotification));
|
this.submitError(getNotification(iError, oData.ErrorMessage, Notification.UnknownNotification));
|
||||||
this.submitErrorAddidional((oData && oData.ErrorMessageAdditional) || '');
|
this.submitErrorAddidional((oData && oData.ErrorMessageAdditional) || '');
|
||||||
} else if (oData.TwoFactorAuth) {
|
|
||||||
this.submitRequest(false);
|
|
||||||
this.additionalCode('');
|
|
||||||
this.additionalCodeVisibility(true);
|
|
||||||
let input = this.querySelector('.inputAdditionalCode');
|
|
||||||
input.required = true;
|
|
||||||
setTimeout(() => input.focus(), 100);
|
|
||||||
} else {
|
} else {
|
||||||
rl.route.reload();
|
rl.route.reload();
|
||||||
}
|
}
|
||||||
|
|
@ -170,9 +151,7 @@ class LoginUserView extends AbstractViewCenter {
|
||||||
email,
|
email,
|
||||||
pass,
|
pass,
|
||||||
!!this.signMe(),
|
!!this.signMe(),
|
||||||
this.bSendLanguage ? this.language() : '',
|
this.bSendLanguage ? this.language() : ''
|
||||||
code,
|
|
||||||
!!(totp && this.additionalCodeSignMe())
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Local.set(ClientSideKeyName.LastSignMe, this.signMe() ? '-1-' : '-0-');
|
Local.set(ClientSideKeyName.LastSignMe, this.signMe() ? '-1-' : '-0-');
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ export class FolderListMailBoxUserView extends AbstractViewLeft {
|
||||||
|
|
||||||
this.composeInEdit = AppUserStore.composeInEdit;
|
this.composeInEdit = AppUserStore.composeInEdit;
|
||||||
|
|
||||||
this.messageList = MessageUserStore.list;
|
|
||||||
this.folderList = FolderUserStore.folderList;
|
this.folderList = FolderUserStore.folderList;
|
||||||
this.folderListSystem = FolderUserStore.folderListSystem;
|
this.folderListSystem = FolderUserStore.folderListSystem;
|
||||||
this.foldersChanging = FolderUserStore.foldersChanging;
|
this.foldersChanging = FolderUserStore.foldersChanging;
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,6 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
||||||
constructor() {
|
constructor() {
|
||||||
super('User/MailBox/MessageList', 'MailMessageList');
|
super('User/MailBox/MessageList', 'MailMessageList');
|
||||||
|
|
||||||
this.sLastUid = null;
|
|
||||||
this.bPrefetch = false;
|
this.bPrefetch = false;
|
||||||
this.emptySubjectValue = '';
|
this.emptySubjectValue = '';
|
||||||
|
|
||||||
|
|
@ -69,43 +68,34 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
||||||
|
|
||||||
this.popupVisibility = ko.computed(() => 0 < popupVisibilityNames().length);
|
this.popupVisibility = ko.computed(() => 0 < popupVisibilityNames().length);
|
||||||
|
|
||||||
this.message = MessageUserStore.message;
|
|
||||||
this.messageList = MessageUserStore.list;
|
this.messageList = MessageUserStore.list;
|
||||||
this.messageListDisableAutoSelect = MessageUserStore.listDisableAutoSelect;
|
|
||||||
|
|
||||||
this.folderList = FolderUserStore.folderList;
|
this.sortSupported = FolderUserStore.sortSupported;
|
||||||
|
|
||||||
this.composeInEdit = AppUserStore.composeInEdit;
|
this.composeInEdit = AppUserStore.composeInEdit;
|
||||||
this.leftPanelDisabled = leftPanelDisabled;
|
this.leftPanelDisabled = leftPanelDisabled;
|
||||||
|
|
||||||
this.selectorMessageSelected = MessageUserStore.selectorMessageSelected;
|
|
||||||
this.selectorMessageFocused = MessageUserStore.selectorMessageFocused;
|
|
||||||
this.isMessageSelected = MessageUserStore.isMessageSelected;
|
this.isMessageSelected = MessageUserStore.isMessageSelected;
|
||||||
this.messageListSearch = MessageUserStore.listSearch;
|
this.messageListSearch = MessageUserStore.listSearch;
|
||||||
this.messageListThreadUid = MessageUserStore.listThreadUid;
|
|
||||||
this.messageListError = MessageUserStore.listError;
|
this.messageListError = MessageUserStore.listError;
|
||||||
this.folderMenuForMove = FolderUserStore.folderMenuForMove;
|
this.folderMenuForMove = FolderUserStore.folderMenuForMove;
|
||||||
|
|
||||||
this.useCheckboxesInList = SettingsUserStore.useCheckboxesInList;
|
this.useCheckboxesInList = SettingsUserStore.useCheckboxesInList;
|
||||||
|
|
||||||
this.mainMessageListSearch = MessageUserStore.mainMessageListSearch;
|
|
||||||
this.messageListEndFolder = MessageUserStore.listEndFolder;
|
|
||||||
this.messageListEndThreadUid = MessageUserStore.listEndThreadUid;
|
this.messageListEndThreadUid = MessageUserStore.listEndThreadUid;
|
||||||
|
|
||||||
this.messageListCheckedOrSelected = MessageUserStore.listCheckedOrSelected;
|
this.messageListCheckedOrSelected = MessageUserStore.listCheckedOrSelected;
|
||||||
this.messageListCheckedOrSelectedUidsWithSubMails = MessageUserStore.listCheckedOrSelectedUidsWithSubMails;
|
|
||||||
this.messageListCompleteLoadingThrottle = MessageUserStore.listCompleteLoading;
|
this.messageListCompleteLoadingThrottle = MessageUserStore.listCompleteLoading;
|
||||||
this.messageListCompleteLoadingThrottleForAnimation = MessageUserStore.listLoadingAnimation;
|
this.messageListCompleteLoadingThrottleForAnimation = MessageUserStore.listLoadingAnimation;
|
||||||
|
|
||||||
initOnStartOrLangChange(() => this.emptySubjectValue = i18n('MESSAGE_LIST/EMPTY_SUBJECT_TEXT'));
|
initOnStartOrLangChange(() => this.emptySubjectValue = i18n('MESSAGE_LIST/EMPTY_SUBJECT_TEXT'));
|
||||||
|
|
||||||
this.userQuota = QuotaUserStore.quota;
|
|
||||||
this.userUsageSize = QuotaUserStore.usage;
|
|
||||||
this.userUsageProc = QuotaUserStore.percentage;
|
this.userUsageProc = QuotaUserStore.percentage;
|
||||||
|
|
||||||
this.addObservables({
|
this.addObservables({
|
||||||
moveDropdownTrigger: false,
|
moveDropdownTrigger: false,
|
||||||
moreDropdownTrigger: false,
|
moreDropdownTrigger: false,
|
||||||
|
sortDropdownTrigger: false,
|
||||||
|
|
||||||
dragOverArea: null,
|
dragOverArea: null,
|
||||||
dragOverBodyArea: null,
|
dragOverBodyArea: null,
|
||||||
|
|
@ -137,7 +127,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
||||||
},
|
},
|
||||||
|
|
||||||
inputProxyMessageListSearch: {
|
inputProxyMessageListSearch: {
|
||||||
read: this.mainMessageListSearch,
|
read: MessageUserStore.mainMessageListSearch,
|
||||||
write: value => this.sLastSearchValue = value
|
write: value => this.sLastSearchValue = value
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -146,19 +136,19 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
||||||
return c && MessageUserStore.list.length > c;
|
return c && MessageUserStore.list.length > c;
|
||||||
},
|
},
|
||||||
|
|
||||||
hasMessages: () => 0 < this.messageList.length,
|
hasMessages: () => 0 < MessageUserStore.list.length,
|
||||||
|
|
||||||
isSpamFolder: () => (FolderUserStore.spamFolder() || 0) === this.messageListEndFolder(),
|
isSpamFolder: () => (FolderUserStore.spamFolder() || 0) === MessageUserStore.listEndFolder(),
|
||||||
|
|
||||||
isSpamDisabled: () => UNUSED_OPTION_VALUE === FolderUserStore.spamFolder(),
|
isSpamDisabled: () => UNUSED_OPTION_VALUE === FolderUserStore.spamFolder(),
|
||||||
|
|
||||||
isTrashFolder: () => (FolderUserStore.trashFolder() || 0) === this.messageListEndFolder(),
|
isTrashFolder: () => (FolderUserStore.trashFolder() || 0) === MessageUserStore.listEndFolder(),
|
||||||
|
|
||||||
isDraftFolder: () => (FolderUserStore.draftFolder() || 0) === this.messageListEndFolder(),
|
isDraftFolder: () => (FolderUserStore.draftFolder() || 0) === MessageUserStore.listEndFolder(),
|
||||||
|
|
||||||
isSentFolder: () => (FolderUserStore.sentFolder() || 0) === this.messageListEndFolder(),
|
isSentFolder: () => (FolderUserStore.sentFolder() || 0) === MessageUserStore.listEndFolder(),
|
||||||
|
|
||||||
isArchiveFolder: () => (FolderUserStore.archiveFolder() || 0) === this.messageListEndFolder(),
|
isArchiveFolder: () => (FolderUserStore.archiveFolder() || 0) === MessageUserStore.listEndFolder(),
|
||||||
|
|
||||||
isArchiveDisabled: () => UNUSED_OPTION_VALUE === FolderUserStore.archiveFolder(),
|
isArchiveDisabled: () => UNUSED_OPTION_VALUE === FolderUserStore.archiveFolder(),
|
||||||
|
|
||||||
|
|
@ -174,17 +164,28 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
||||||
|
|
||||||
mobileCheckedStateHide: () => ThemeStore.isMobile() ? !MessageUserStore.listChecked().length : true,
|
mobileCheckedStateHide: () => ThemeStore.isMobile() ? !MessageUserStore.listChecked().length : true,
|
||||||
|
|
||||||
messageListFocused: () => Scope.MessageList === AppUserStore.focusedState()
|
messageListFocused: () => Scope.MessageList === AppUserStore.focusedState(),
|
||||||
|
|
||||||
|
sortText: () => {
|
||||||
|
let mode = FolderUserStore.sortMode(),
|
||||||
|
desc = '' === mode || mode.includes('REVERSE');
|
||||||
|
mode = mode.split(/\s+/);
|
||||||
|
if (mode.includes('FROM')) {
|
||||||
|
return '@' + (desc ? '⬆' : '⬇');
|
||||||
|
}
|
||||||
|
if (mode.includes('SUBJECT')) {
|
||||||
|
return '𝐒' + (desc ? '⬆' : '⬇');
|
||||||
|
}
|
||||||
|
return (mode.includes('SIZE') ? '✉' : '📅') + (desc ? '⬇' : '⬆');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.hasCheckedOrSelectedLines = MessageUserStore.hasCheckedOrSelected,
|
this.hasCheckedOrSelectedLines = MessageUserStore.hasCheckedOrSelected,
|
||||||
|
|
||||||
this.quotaTooltip = this.quotaTooltip.bind(this);
|
|
||||||
|
|
||||||
this.selector = new Selector(
|
this.selector = new Selector(
|
||||||
this.messageList,
|
MessageUserStore.list,
|
||||||
this.selectorMessageSelected,
|
MessageUserStore.selectorMessageSelected,
|
||||||
this.selectorMessageFocused,
|
MessageUserStore.selectorMessageFocused,
|
||||||
'.messageListItem .actionHandle',
|
'.messageListItem .actionHandle',
|
||||||
'.messageListItem .checkboxMessage',
|
'.messageListItem .checkboxMessage',
|
||||||
'.messageListItem.focused'
|
'.messageListItem.focused'
|
||||||
|
|
@ -209,7 +210,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
||||||
addEventListener('mailbox.message.show', e => {
|
addEventListener('mailbox.message.show', e => {
|
||||||
const sFolder = e.detail.Folder, sUid = e.detail.Uid;
|
const sFolder = e.detail.Folder, sUid = e.detail.Uid;
|
||||||
|
|
||||||
const message = this.messageList.find(
|
const message = MessageUserStore.list.find(
|
||||||
item => item && sFolder === item.folder && sUid === item.uid
|
item => item && sFolder === item.folder && sUid === item.uid
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -246,6 +247,11 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
changeSort(self, event) {
|
||||||
|
FolderUserStore.sortMode(event.target.closest('li').dataset.sort);
|
||||||
|
this.reloadCommand();
|
||||||
|
}
|
||||||
|
|
||||||
clearCommand() {
|
clearCommand() {
|
||||||
if (Settings.capa(Capa.DangerousActions)) {
|
if (Settings.capa(Capa.DangerousActions)) {
|
||||||
showScreenPopup(FolderClearPopupView, [FolderUserStore.currentFolder()]);
|
showScreenPopup(FolderClearPopupView, [FolderUserStore.currentFolder()]);
|
||||||
|
|
@ -375,7 +381,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!SettingsUserStore.usePreviewPane() && !this.message()) {
|
if (!SettingsUserStore.usePreviewPane() && !MessageUserStore.message()) {
|
||||||
this.selector.iFocusedNextHelper = up ? -1 : 1;
|
this.selector.iFocusedNextHelper = up ? -1 : 1;
|
||||||
} else {
|
} else {
|
||||||
this.selector.iSelectNextHelper = up ? -1 : 1;
|
this.selector.iSelectNextHelper = up ? -1 : 1;
|
||||||
|
|
@ -391,18 +397,18 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
||||||
}
|
}
|
||||||
|
|
||||||
useAutoSelect() {
|
useAutoSelect() {
|
||||||
return !this.messageListDisableAutoSelect()
|
return !MessageUserStore.listDisableAutoSelect()
|
||||||
&& !/is:unseen/.test(this.mainMessageListSearch())
|
&& !/is:unseen/.test(MessageUserStore.mainMessageListSearch())
|
||||||
&& SettingsUserStore.usePreviewPane();
|
&& SettingsUserStore.usePreviewPane();
|
||||||
}
|
}
|
||||||
|
|
||||||
searchEnterAction() {
|
searchEnterAction() {
|
||||||
this.mainMessageListSearch(this.sLastSearchValue);
|
MessageUserStore.mainMessageListSearch(this.sLastSearchValue);
|
||||||
this.inputMessageListSearchFocus(false);
|
this.inputMessageListSearchFocus(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelSearch() {
|
cancelSearch() {
|
||||||
this.mainMessageListSearch('');
|
MessageUserStore.mainMessageListSearch('');
|
||||||
this.inputMessageListSearchFocus(false);
|
this.inputMessageListSearchFocus(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -571,7 +577,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
||||||
}
|
}
|
||||||
|
|
||||||
flagMessages(currentMessage) {
|
flagMessages(currentMessage) {
|
||||||
const checked = this.messageListCheckedOrSelected();
|
const checked = MessageUserStore.listCheckedOrSelected();
|
||||||
if (currentMessage) {
|
if (currentMessage) {
|
||||||
const checkedUids = checked.map(message => message.uid);
|
const checkedUids = checked.map(message => message.uid);
|
||||||
if (checkedUids.includes(currentMessage.uid)) {
|
if (checkedUids.includes(currentMessage.uid)) {
|
||||||
|
|
@ -591,7 +597,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
||||||
}
|
}
|
||||||
|
|
||||||
flagMessagesFast(bFlag) {
|
flagMessagesFast(bFlag) {
|
||||||
const checked = this.messageListCheckedOrSelected();
|
const checked = MessageUserStore.listCheckedOrSelected();
|
||||||
if (checked.length) {
|
if (checked.length) {
|
||||||
if (undefined === bFlag) {
|
if (undefined === bFlag) {
|
||||||
const flagged = checked.filter(message => message.isFlagged());
|
const flagged = checked.filter(message => message.isFlagged());
|
||||||
|
|
@ -611,7 +617,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
||||||
}
|
}
|
||||||
|
|
||||||
seenMessagesFast(seen) {
|
seenMessagesFast(seen) {
|
||||||
const checked = this.messageListCheckedOrSelected();
|
const checked = MessageUserStore.listCheckedOrSelected();
|
||||||
if (checked.length) {
|
if (checked.length) {
|
||||||
if (undefined === seen) {
|
if (undefined === seen) {
|
||||||
const unseen = checked.filter(message => message.isUnseen());
|
const unseen = checked.filter(message => message.isUnseen());
|
||||||
|
|
@ -656,7 +662,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
||||||
!this.messageListSearchDesc() &&
|
!this.messageListSearchDesc() &&
|
||||||
!this.messageListError() &&
|
!this.messageListError() &&
|
||||||
!this.messageListEndThreadUid() &&
|
!this.messageListEndThreadUid() &&
|
||||||
this.messageList.length &&
|
MessageUserStore.list.length &&
|
||||||
(this.isSpamFolder() || this.isTrashFolder())
|
(this.isSpamFolder() || this.isTrashFolder())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -700,7 +706,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
||||||
|
|
||||||
initShortcuts() {
|
initShortcuts() {
|
||||||
shortcuts.add('enter,open', '', Scope.MessageList, () => {
|
shortcuts.add('enter,open', '', Scope.MessageList, () => {
|
||||||
if (this.message() && this.useAutoSelect()) {
|
if (MessageUserStore.message() && this.useAutoSelect()) {
|
||||||
dispatchEvent(new CustomEvent('mailbox.message-view.toggle-full-screen'));
|
dispatchEvent(new CustomEvent('mailbox.message-view.toggle-full-screen'));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -756,9 +762,9 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
||||||
}
|
}
|
||||||
|
|
||||||
shortcuts.add('t', '', [Scope.MessageList], () => {
|
shortcuts.add('t', '', [Scope.MessageList], () => {
|
||||||
let message = this.selectorMessageSelected();
|
let message = MessageUserStore.selectorMessageSelected();
|
||||||
if (!message) {
|
if (!message) {
|
||||||
message = this.selectorMessageFocused();
|
message = MessageUserStore.selectorMessageFocused();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message && 0 < message.threadsLen()) {
|
if (message && 0 < message.threadsLen()) {
|
||||||
|
|
@ -831,7 +837,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
shortcuts.add('tab,arrowright', '', Scope.MessageList, () => {
|
shortcuts.add('tab,arrowright', '', Scope.MessageList, () => {
|
||||||
this.message() && AppUserStore.focusedState(Scope.MessageView);
|
MessageUserStore.message() && AppUserStore.focusedState(Scope.MessageView);
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -841,7 +847,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
||||||
|
|
||||||
prefetchNextTick() {
|
prefetchNextTick() {
|
||||||
if (!this.bPrefetch && !ifvisible.now() && this.viewModelVisible) {
|
if (!this.bPrefetch && !ifvisible.now() && this.viewModelVisible) {
|
||||||
const message = this.messageList.find(
|
const message = MessageUserStore.list.find(
|
||||||
item => item && !hasRequestedMessage(item.folder, item.uid)
|
item => item && !hasRequestedMessage(item.folder, item.uid)
|
||||||
);
|
);
|
||||||
if (message) {
|
if (message) {
|
||||||
|
|
@ -866,15 +872,15 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
||||||
|
|
||||||
advancedSearchClick() {
|
advancedSearchClick() {
|
||||||
Settings.capa(Capa.SearchAdv)
|
Settings.capa(Capa.SearchAdv)
|
||||||
&& showScreenPopup(AdvancedSearchPopupView, [this.mainMessageListSearch()]);
|
&& showScreenPopup(AdvancedSearchPopupView, [MessageUserStore.mainMessageListSearch()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
quotaTooltip() {
|
quotaTooltip() {
|
||||||
return i18n('MESSAGE_LIST/QUOTA_SIZE', {
|
return i18n('MESSAGE_LIST/QUOTA_SIZE', {
|
||||||
SIZE: FileInfo.friendlySize(this.userUsageSize()),
|
SIZE: FileInfo.friendlySize(QuotaUserStore.usage()),
|
||||||
PROC: this.userUsageProc(),
|
PROC: QuotaUserStore.percentage(),
|
||||||
LIMIT: FileInfo.friendlySize(this.userQuota())
|
LIMIT: FileInfo.friendlySize(QuotaUserStore.quota())
|
||||||
});
|
}).replace(/<[^>]+>/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
initUploaderForAppend() {
|
initUploaderForAppend() {
|
||||||
|
|
|
||||||
|
|
@ -71,8 +71,6 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
|
||||||
moreDropdownTrigger: false
|
moreDropdownTrigger: false
|
||||||
});
|
});
|
||||||
|
|
||||||
this.pswp = null;
|
|
||||||
|
|
||||||
this.moveAction = moveAction;
|
this.moveAction = moveAction;
|
||||||
|
|
||||||
this.allowComposer = Settings.capa(Capa.Composer);
|
this.allowComposer = Settings.capa(Capa.Composer);
|
||||||
|
|
@ -84,7 +82,6 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
|
||||||
|
|
||||||
this.message = MessageUserStore.message;
|
this.message = MessageUserStore.message;
|
||||||
this.hasCheckedMessages = MessageUserStore.hasCheckedMessages;
|
this.hasCheckedMessages = MessageUserStore.hasCheckedMessages;
|
||||||
this.messageListCheckedOrSelectedUidsWithSubMails = MessageUserStore.listCheckedOrSelectedUidsWithSubMails;
|
|
||||||
this.messageLoadingThrottle = MessageUserStore.messageLoading;
|
this.messageLoadingThrottle = MessageUserStore.messageLoading;
|
||||||
this.messagesBodiesDom = MessageUserStore.messagesBodiesDom;
|
this.messagesBodiesDom = MessageUserStore.messagesBodiesDom;
|
||||||
this.useThreads = SettingsUserStore.useThreads;
|
this.useThreads = SettingsUserStore.useThreads;
|
||||||
|
|
@ -136,6 +133,8 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
|
||||||
viewReplyTo: '',
|
viewReplyTo: '',
|
||||||
viewTimeStamp: 0,
|
viewTimeStamp: 0,
|
||||||
viewSize: '',
|
viewSize: '',
|
||||||
|
viewSpamScore: 0,
|
||||||
|
viewSpamStatus: '',
|
||||||
viewLineAsCss: '',
|
viewLineAsCss: '',
|
||||||
viewViewLink: '',
|
viewViewLink: '',
|
||||||
viewUnsubscribeLink: '',
|
viewUnsubscribeLink: '',
|
||||||
|
|
@ -220,13 +219,15 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
|
||||||
this.viewFromShort(message.fromToLine(true, true));
|
this.viewFromShort(message.fromToLine(true, true));
|
||||||
this.viewFromDkimData(message.fromDkimData());
|
this.viewFromDkimData(message.fromDkimData());
|
||||||
this.viewToShort(message.toToLine(true, true));
|
this.viewToShort(message.toToLine(true, true));
|
||||||
this.viewFrom(message.fromToLine(false));
|
this.viewFrom(message.fromToLine());
|
||||||
this.viewTo(message.toToLine(false));
|
this.viewTo(message.toToLine());
|
||||||
this.viewCc(message.ccToLine(false));
|
this.viewCc(message.ccToLine());
|
||||||
this.viewBcc(message.bccToLine(false));
|
this.viewBcc(message.bccToLine());
|
||||||
this.viewReplyTo(message.replyToToLine(false));
|
this.viewReplyTo(message.replyToToLine());
|
||||||
this.viewTimeStamp(message.dateTimeStampInUTC());
|
this.viewTimeStamp(message.dateTimeStampInUTC());
|
||||||
this.viewSize(message.friendlySize());
|
this.viewSize(message.friendlySize());
|
||||||
|
this.viewSpamScore(message.spamScore());
|
||||||
|
this.viewSpamStatus(i18n(message.isSpam() ? 'GLOBAL/SPAM' : 'GLOBAL/NOT_SPAM') + ': ' + message.spamResult());
|
||||||
this.viewLineAsCss(message.lineAsCss());
|
this.viewLineAsCss(message.lineAsCss());
|
||||||
this.viewViewLink(message.viewLink());
|
this.viewViewLink(message.viewLink());
|
||||||
this.viewUnsubscribeLink(message.getFirstUnsubsribeLink());
|
this.viewUnsubscribeLink(message.getFirstUnsubsribeLink());
|
||||||
|
|
@ -717,7 +718,7 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
printableCheckedMessageCount() {
|
printableCheckedMessageCount() {
|
||||||
const cnt = this.messageListCheckedOrSelectedUidsWithSubMails().length;
|
const cnt = MessageUserStore.listCheckedOrSelectedUidsWithSubMails().length;
|
||||||
return 0 < cnt ? (100 > cnt ? cnt : '99+') : '';
|
return 0 < cnt ? (100 > cnt ? cnt : '99+') : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
"title": "SnappyMail",
|
"title": "SnappyMail",
|
||||||
"description": "Simple, modern & fast web-based email client",
|
"description": "Simple, modern & fast web-based email client",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2.4.2",
|
"version": "2.5.0-rc.4",
|
||||||
"homepage": "https://snappymail.eu",
|
"homepage": "https://snappymail.eu",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "DJ Maze",
|
"name": "DJ Maze",
|
||||||
|
|
@ -42,11 +42,6 @@
|
||||||
],
|
],
|
||||||
"readmeFilename": "README.md",
|
"readmeFilename": "README.md",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.8.7",
|
|
||||||
"@babel/plugin-proposal-class-properties": "7.8.3",
|
|
||||||
"@babel/plugin-proposal-decorators": "7.8.3",
|
|
||||||
"@babel/preset-env": "7.8.7",
|
|
||||||
"@babel/runtime-corejs3": "^7.12.13",
|
|
||||||
"babel-eslint": "10.1.0",
|
"babel-eslint": "10.1.0",
|
||||||
"core-js": "^3.8.3",
|
"core-js": "^3.8.3",
|
||||||
"eslint": "6.8.0",
|
"eslint": "6.8.0",
|
||||||
|
|
@ -75,7 +70,6 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"gulp-rollup-2": "^1.2.1",
|
"gulp-rollup-2": "^1.2.1",
|
||||||
"rollup": "^2.38.0",
|
"rollup": "^2.38.0",
|
||||||
"rollup-plugin-babel": "^4.4.0",
|
|
||||||
"rollup-plugin-external-globals": "^0.6.1",
|
"rollup-plugin-external-globals": "^0.6.1",
|
||||||
"rollup-plugin-html": "^0.2.1",
|
"rollup-plugin-html": "^0.2.1",
|
||||||
"rollup-plugin-includepaths": "^0.2.4",
|
"rollup-plugin-includepaths": "^0.2.4",
|
||||||
|
|
|
||||||
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
|
public function Init() : void
|
||||||
{
|
{
|
||||||
$this->addHook('filter.smtp-credentials', 'FilterSmtpCredentials');
|
$this->addHook('smtp.credentials', 'FilterSmtpCredentials');
|
||||||
$this->addHook('filter.imap-credentials', 'FilterImapCredentials');
|
$this->addHook('imap.credentials', 'FilterImapCredentials');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ class BlackListPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||||
|
|
||||||
public function Init() : void
|
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
|
public function Init() : void
|
||||||
{
|
{
|
||||||
$this->addHook('filter.smtp-credentials', 'FilterSmtpCredentials');
|
$this->addHook('smtp.credentials', 'FilterSmtpCredentials');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
213
plugins/ckeditor/ckeditor.less
Normal file
213
plugins/ckeditor/ckeditor.less
Normal file
|
|
@ -0,0 +1,213 @@
|
||||||
|
/*.cke_button__sourcedialog_label {
|
||||||
|
display: none !important;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
.cke_chrome {
|
||||||
|
border: 1px solid #ccc !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cke_toolgroup {
|
||||||
|
padding-right: 0 !important;
|
||||||
|
background: #FBFBFB !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cke_toolgroup, .cke_combo_button {
|
||||||
|
border: 1px solid #A6A6A6 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cke_top {
|
||||||
|
padding: 2px;
|
||||||
|
box-shadow: none !important;
|
||||||
|
border-bottom: 1px solid #b6b6b6 !important;
|
||||||
|
background: #F0F0F0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cke_combo_on a.cke_combo_button,
|
||||||
|
.cke_combo_off a.cke_combo_button:hover,
|
||||||
|
.cke_combo_off a.cke_combo_button:focus,
|
||||||
|
.cke_combo_off a.cke_combo_button:active {
|
||||||
|
padding: 1px !important;
|
||||||
|
margin-left: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cke_toolgroup a.cke_button:last-child:after,
|
||||||
|
.cke_toolgroup a.cke_button.cke_button_disabled:hover:last-child:after {
|
||||||
|
border: none !important;
|
||||||
|
height: 0 !important;
|
||||||
|
width: 0 !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cke_button_on {
|
||||||
|
background: #ddd !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cke_combo {
|
||||||
|
margin-top: 1px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cke_combo__fontsize {
|
||||||
|
margin-left: 3px !important;
|
||||||
|
.cke_combo_text {
|
||||||
|
width: 40px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cke_combo_text {
|
||||||
|
line-height: 24px !important;
|
||||||
|
padding-left: 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cke_combo_button {
|
||||||
|
background: #FBFBFB !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cke_source {
|
||||||
|
font-family: var(--fontMono) !important;
|
||||||
|
padding: 10px !important;
|
||||||
|
padding-right: 0 !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cke_plain {
|
||||||
|
|
||||||
|
background-color: #fff;
|
||||||
|
outline: none;
|
||||||
|
overflow: auto;
|
||||||
|
z-index: 1;
|
||||||
|
margin: 0;
|
||||||
|
border: 0;
|
||||||
|
|
||||||
|
padding: 10px;
|
||||||
|
padding-right: 0;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
font-family: var(--fontMono), monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 16px;
|
||||||
|
color: #333;
|
||||||
|
resize: none;
|
||||||
|
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cke_plain:focus, .cke_source:focus {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-compose .cke_chrome {
|
||||||
|
border-right: 0;
|
||||||
|
border-left: 0;
|
||||||
|
border-bottom: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cke_wysiwyg_div {
|
||||||
|
|
||||||
|
padding: 10px !important;
|
||||||
|
font-family: var(--fontSans);
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 16px;
|
||||||
|
color: #333;
|
||||||
|
|
||||||
|
ul {
|
||||||
|
padding-left: 40px;
|
||||||
|
li {
|
||||||
|
list-style-type: disc !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ol {
|
||||||
|
padding-left: 40px;
|
||||||
|
li {
|
||||||
|
list-style-type: decimal !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pre, code {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
font-family: var(--fontMono);
|
||||||
|
display: block;
|
||||||
|
word-break: normal;
|
||||||
|
word-wrap: break-word;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
display: inline;
|
||||||
|
padding: 2px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre > code {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
border: 0;
|
||||||
|
border-left: solid 2px #444;
|
||||||
|
margin: 5px 0 5px 5px;
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote p {
|
||||||
|
margin: 0 0 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
vertical-align: bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: blue;
|
||||||
|
text-decoration: underline;
|
||||||
|
|
||||||
|
&:visited {
|
||||||
|
color: #609;
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cke_dialog {
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cke_dialog_ui_labeled_content {
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cke_dialog_ui_input_select, .cke_dialog_ui_input_text, .cke_dialog_ui_input_textarea {
|
||||||
|
|
||||||
|
box-shadow: none;
|
||||||
|
border-radius: 2px;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: 0;
|
||||||
|
border: 1px solid #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cke_dialog_ui_input_select, .cke_dialog_ui_input_text {
|
||||||
|
height: 25px;
|
||||||
|
line-height: 25px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,9 +4,9 @@ class CKEditorPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||||
{
|
{
|
||||||
const
|
const
|
||||||
NAME = 'CKEditor',
|
NAME = 'CKEditor',
|
||||||
VERSION = '2.0',
|
VERSION = '2.1',
|
||||||
RELEASE = '2021-03-25',
|
RELEASE = '2021-04-01',
|
||||||
REQUIRED = '2.4.2',
|
REQUIRED = '2.5.0',
|
||||||
DESCRIPTION = 'Use CKEditor instead of Squire as WYSIWYG';
|
DESCRIPTION = 'Use CKEditor instead of Squire as WYSIWYG';
|
||||||
|
|
||||||
public function Init() : void
|
public function Init() : void
|
||||||
|
|
@ -32,6 +32,7 @@ class CKEditorPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\is_file("{$path}/ckeditor.js")) {
|
if (\is_file("{$path}/ckeditor.js")) {
|
||||||
|
$this->addCss('ckeditor.less');
|
||||||
$this->addJs('ckeditor.js');
|
$this->addJs('ckeditor.js');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ class CustomAuthExamplePlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||||
|
|
||||||
public function Init() : void
|
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
|
* @throws \RainLoop\Exceptions\ClientException
|
||||||
*/
|
*/
|
||||||
public function FilterLoginСredentials(&$sEmail, &$sLogin, &$sPassword)
|
public function FilterLoginCredentials(&$sEmail, &$sLogin, &$sPassword)
|
||||||
{
|
{
|
||||||
// Your custom php logic
|
// Your custom php logic
|
||||||
// You may change login credentials
|
// You may change login credentials
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ class CustomLoginMappingPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||||
|
|
||||||
public function Init() : void
|
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
|
* @throws \RainLoop\Exceptions\ClientException
|
||||||
*/
|
*/
|
||||||
public function FilterLoginСredentials(&$sEmail, &$sLogin, &$sPassword)
|
public function FilterLoginCredentials(&$sEmail, &$sLogin, &$sPassword)
|
||||||
{
|
{
|
||||||
$sMapping = \trim($this->Config()->Get('plugin', 'mapping', ''));
|
$sMapping = \trim($this->Config()->Get('plugin', 'mapping', ''));
|
||||||
if (!empty($sMapping))
|
if (!empty($sMapping))
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ class OverrideSmtpCredentialsPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||||
|
|
||||||
public function Init() : void
|
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
|
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
|
public function Init() : void
|
||||||
{
|
{
|
||||||
$this->addHook('filter.login-credentials', 'FilterLoginCredentials');
|
$this->addHook('login.credentials', 'FilterLoginCredentials');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -30,16 +30,6 @@ if (\class_exists('RainLoop\Api'))
|
||||||
{
|
{
|
||||||
\MailSo\Base\Loader::Init();
|
\MailSo\Base\Loader::Init();
|
||||||
|
|
||||||
if (!\function_exists('yaml_parse')) {
|
|
||||||
function yaml_parse(string $input) {
|
|
||||||
require_once RAINLOOP_APP_LIBRARIES_PATH.'spyc/Spyc.php';
|
|
||||||
return \Spyc::YAMLLoadString(\str_replace(array(': >-', ': |-', ': |+'), array(': >', ': |', ': |'), $input));
|
|
||||||
}
|
|
||||||
function yaml_parse_file(string $filename) {
|
|
||||||
return yaml_parse(\file_get_contents($filename));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($_ENV['RAINLOOP_INCLUDE_AS_API']))
|
if (!empty($_ENV['RAINLOOP_INCLUDE_AS_API']))
|
||||||
{
|
{
|
||||||
if (!\defined('APP_API_STARTED'))
|
if (!\defined('APP_API_STARTED'))
|
||||||
|
|
|
||||||
|
|
@ -1,149 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Draw;
|
|
||||||
|
|
||||||
use Imagine\Image\AbstractFont;
|
|
||||||
use Imagine\Image\BoxInterface;
|
|
||||||
use Imagine\Image\Palette\Color\ColorInterface;
|
|
||||||
use Imagine\Image\PointInterface;
|
|
||||||
use Imagine\Exception\RuntimeException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for the drawer
|
|
||||||
*/
|
|
||||||
interface DrawerInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Draws an arc on a starting at a given x, y coordinates under a given
|
|
||||||
* start and end angles
|
|
||||||
*
|
|
||||||
* @param PointInterface $center
|
|
||||||
* @param BoxInterface $size
|
|
||||||
* @param integer $start
|
|
||||||
* @param integer $end
|
|
||||||
* @param ColorInterface $color
|
|
||||||
* @param integer $thickness
|
|
||||||
*
|
|
||||||
* @throws RuntimeException
|
|
||||||
*
|
|
||||||
* @return DrawerInterface
|
|
||||||
*/
|
|
||||||
public function arc(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $thickness = 1);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Same as arc, but also connects end points with a straight line
|
|
||||||
*
|
|
||||||
* @param PointInterface $center
|
|
||||||
* @param BoxInterface $size
|
|
||||||
* @param integer $start
|
|
||||||
* @param integer $end
|
|
||||||
* @param ColorInterface $color
|
|
||||||
* @param Boolean $fill
|
|
||||||
* @param integer $thickness
|
|
||||||
*
|
|
||||||
* @throws RuntimeException
|
|
||||||
*
|
|
||||||
* @return DrawerInterface
|
|
||||||
*/
|
|
||||||
public function chord(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws and ellipse with center at the given x, y coordinates, and given
|
|
||||||
* width and height
|
|
||||||
*
|
|
||||||
* @param PointInterface $center
|
|
||||||
* @param BoxInterface $size
|
|
||||||
* @param ColorInterface $color
|
|
||||||
* @param Boolean $fill
|
|
||||||
* @param integer $thickness
|
|
||||||
*
|
|
||||||
* @throws RuntimeException
|
|
||||||
*
|
|
||||||
* @return DrawerInterface
|
|
||||||
*/
|
|
||||||
public function ellipse(PointInterface $center, BoxInterface $size, ColorInterface $color, $fill = false, $thickness = 1);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws a line from start(x, y) to end(x, y) coordinates
|
|
||||||
*
|
|
||||||
* @param PointInterface $start
|
|
||||||
* @param PointInterface $end
|
|
||||||
* @param ColorInterface $outline
|
|
||||||
* @param integer $thickness
|
|
||||||
*
|
|
||||||
* @return DrawerInterface
|
|
||||||
*/
|
|
||||||
public function line(PointInterface $start, PointInterface $end, ColorInterface $outline, $thickness = 1);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Same as arc, but connects end points and the center
|
|
||||||
*
|
|
||||||
* @param PointInterface $center
|
|
||||||
* @param BoxInterface $size
|
|
||||||
* @param integer $start
|
|
||||||
* @param integer $end
|
|
||||||
* @param ColorInterface $color
|
|
||||||
* @param Boolean $fill
|
|
||||||
* @param integer $thickness
|
|
||||||
*
|
|
||||||
* @throws RuntimeException
|
|
||||||
*
|
|
||||||
* @return DrawerInterface
|
|
||||||
*/
|
|
||||||
public function pieSlice(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Places a one pixel point at specific coordinates and fills it with
|
|
||||||
* specified color
|
|
||||||
*
|
|
||||||
* @param PointInterface $position
|
|
||||||
* @param ColorInterface $color
|
|
||||||
*
|
|
||||||
* @throws RuntimeException
|
|
||||||
*
|
|
||||||
* @return DrawerInterface
|
|
||||||
*/
|
|
||||||
public function dot(PointInterface $position, ColorInterface $color);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws a polygon using array of x, y coordinates. Must contain at least
|
|
||||||
* three coordinates
|
|
||||||
*
|
|
||||||
* @param array $coordinates
|
|
||||||
* @param ColorInterface $color
|
|
||||||
* @param Boolean $fill
|
|
||||||
* @param integer $thickness
|
|
||||||
*
|
|
||||||
* @throws RuntimeException
|
|
||||||
*
|
|
||||||
* @return DrawerInterface
|
|
||||||
*/
|
|
||||||
public function polygon(array $coordinates, ColorInterface $color, $fill = false, $thickness = 1);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Annotates image with specified text at a given position starting on the
|
|
||||||
* top left of the final text box
|
|
||||||
*
|
|
||||||
* The rotation is done CW
|
|
||||||
*
|
|
||||||
* @param string $string
|
|
||||||
* @param AbstractFont $font
|
|
||||||
* @param PointInterface $position
|
|
||||||
* @param integer $angle
|
|
||||||
* @param integer $width
|
|
||||||
*
|
|
||||||
* @throws RuntimeException
|
|
||||||
*
|
|
||||||
* @return DrawerInterface
|
|
||||||
*/
|
|
||||||
public function text($string, AbstractFont $font, PointInterface $position, $angle = 0, $width = null);
|
|
||||||
}
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Effects;
|
|
||||||
|
|
||||||
use Imagine\Exception\RuntimeException;
|
|
||||||
use Imagine\Image\Palette\Color\ColorInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for the effects
|
|
||||||
*/
|
|
||||||
interface EffectsInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Apply gamma correction
|
|
||||||
*
|
|
||||||
* @param float $correction
|
|
||||||
* @return EffectsInterface
|
|
||||||
*
|
|
||||||
* @throws RuntimeException
|
|
||||||
*/
|
|
||||||
public function gamma($correction);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invert the colors of the image
|
|
||||||
*
|
|
||||||
* @return EffectsInterface
|
|
||||||
*
|
|
||||||
* @throws RuntimeException
|
|
||||||
*/
|
|
||||||
public function negative();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Grayscale the image
|
|
||||||
*
|
|
||||||
* @return EffectsInterface
|
|
||||||
*
|
|
||||||
* @throws RuntimeException
|
|
||||||
*/
|
|
||||||
public function grayscale();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Colorize the image
|
|
||||||
*
|
|
||||||
* @param ColorInterface $color
|
|
||||||
*
|
|
||||||
* @return EffectsInterface
|
|
||||||
*
|
|
||||||
* @throws RuntimeException
|
|
||||||
*/
|
|
||||||
public function colorize(ColorInterface $color);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sharpens the image
|
|
||||||
*
|
|
||||||
* @return EffectsInterface
|
|
||||||
*
|
|
||||||
* @throws RuntimeException
|
|
||||||
*/
|
|
||||||
public function sharpen();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Blur the image
|
|
||||||
*
|
|
||||||
* @param float|int $sigma
|
|
||||||
*
|
|
||||||
* @return EffectsInterface
|
|
||||||
*
|
|
||||||
* @throws RuntimeException
|
|
||||||
*/
|
|
||||||
public function blur($sigma);
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Exception;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Imagine-specific exception
|
|
||||||
*/
|
|
||||||
interface Exception
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Exception;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Imagine-specific invalid argument exception
|
|
||||||
*/
|
|
||||||
class InvalidArgumentException extends \InvalidArgumentException implements Exception
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Exception;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Should be used when a driver does not support an operation.
|
|
||||||
*/
|
|
||||||
class NotSupportedException extends RuntimeException implements Exception
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Exception;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Imagine-specific out of bounds exception
|
|
||||||
*/
|
|
||||||
class OutOfBoundsException extends \OutOfBoundsException implements Exception
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Exception;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Imagine-specific runtime exception
|
|
||||||
*/
|
|
||||||
class RuntimeException extends \RuntimeException implements Exception
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Filter\Advanced;
|
|
||||||
|
|
||||||
use Imagine\Filter\FilterInterface;
|
|
||||||
use Imagine\Image\ImageInterface;
|
|
||||||
use Imagine\Image\Palette\Color\ColorInterface;
|
|
||||||
use Imagine\Image\Point;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A border filter
|
|
||||||
*/
|
|
||||||
class Border implements FilterInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var ColorInterface
|
|
||||||
*/
|
|
||||||
private $color;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var integer
|
|
||||||
*/
|
|
||||||
private $width;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var integer
|
|
||||||
*/
|
|
||||||
private $height;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs Border filter with given color, width and height
|
|
||||||
*
|
|
||||||
* @param ColorInterface $color
|
|
||||||
* @param integer $width Width of the border on the left and right sides of the image
|
|
||||||
* @param integer $height Height of the border on the top and bottom sides of the image
|
|
||||||
*/
|
|
||||||
public function __construct(ColorInterface $color, $width = 1, $height = 1)
|
|
||||||
{
|
|
||||||
$this->color = $color;
|
|
||||||
$this->width = $width;
|
|
||||||
$this->height = $height;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function apply(ImageInterface $image)
|
|
||||||
{
|
|
||||||
$size = $image->getSize();
|
|
||||||
$width = $size->getWidth();
|
|
||||||
$height = $size->getHeight();
|
|
||||||
|
|
||||||
$draw = $image->draw();
|
|
||||||
|
|
||||||
// Draw top and bottom lines
|
|
||||||
$draw
|
|
||||||
->line(
|
|
||||||
new Point(0, 0),
|
|
||||||
new Point($width - 1, 0),
|
|
||||||
$this->color,
|
|
||||||
$this->height
|
|
||||||
)
|
|
||||||
->line(
|
|
||||||
new Point($width - 1, $height - 1),
|
|
||||||
new Point(0, $height - 1),
|
|
||||||
$this->color,
|
|
||||||
$this->height
|
|
||||||
)
|
|
||||||
;
|
|
||||||
|
|
||||||
// Draw sides
|
|
||||||
$draw
|
|
||||||
->line(
|
|
||||||
new Point(0, 0),
|
|
||||||
new Point(0, $height - 1),
|
|
||||||
$this->color,
|
|
||||||
$this->width
|
|
||||||
)
|
|
||||||
->line(
|
|
||||||
new Point($width - 1, 0),
|
|
||||||
new Point($width - 1, $height - 1),
|
|
||||||
$this->color,
|
|
||||||
$this->width
|
|
||||||
)
|
|
||||||
;
|
|
||||||
|
|
||||||
return $image;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Filter\Advanced;
|
|
||||||
|
|
||||||
use Imagine\Filter\FilterInterface;
|
|
||||||
use Imagine\Image\ImageInterface;
|
|
||||||
use Imagine\Image\BoxInterface;
|
|
||||||
use Imagine\Image\Point;
|
|
||||||
use Imagine\Image\PointInterface;
|
|
||||||
use Imagine\Image\Palette\Color\ColorInterface;
|
|
||||||
use Imagine\Image\ImagineInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A canvas filter
|
|
||||||
*/
|
|
||||||
class Canvas implements FilterInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var BoxInterface
|
|
||||||
*/
|
|
||||||
private $size;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var PointInterface
|
|
||||||
*/
|
|
||||||
private $placement;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var ColorInterface
|
|
||||||
*/
|
|
||||||
private $background;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var ImagineInterface
|
|
||||||
*/
|
|
||||||
private $imagine;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs Canvas filter with given width and height and the placement of the current image
|
|
||||||
* inside the new canvas
|
|
||||||
*
|
|
||||||
* @param ImagineInterface $imagine
|
|
||||||
* @param BoxInterface $size
|
|
||||||
* @param PointInterface $placement
|
|
||||||
* @param ColorInterface $background
|
|
||||||
*/
|
|
||||||
public function __construct(ImagineInterface $imagine, BoxInterface $size, PointInterface $placement = null, ColorInterface $background = null)
|
|
||||||
{
|
|
||||||
$this->imagine = $imagine;
|
|
||||||
$this->size = $size;
|
|
||||||
$this->placement = $placement ?: new Point(0, 0);
|
|
||||||
$this->background = $background;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function apply(ImageInterface $image)
|
|
||||||
{
|
|
||||||
$canvas = $this->imagine->create($this->size, $this->background);
|
|
||||||
$canvas->paste($image, $this->placement);
|
|
||||||
|
|
||||||
return $canvas;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Filter\Advanced;
|
|
||||||
|
|
||||||
use Imagine\Filter\FilterInterface;
|
|
||||||
use Imagine\Image\ImageInterface;
|
|
||||||
use Imagine\Image\Point;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Grayscale filter calculates the gray-value based on RGB.
|
|
||||||
*/
|
|
||||||
class Grayscale extends OnPixelBased implements FilterInterface
|
|
||||||
{
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
parent::__construct(function (ImageInterface $image, Point $point) {
|
|
||||||
$color = $image->getColorAt($point);
|
|
||||||
$image->draw()->dot($point, $color->grayscale());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Filter\Advanced;
|
|
||||||
|
|
||||||
use Imagine\Exception\InvalidArgumentException;
|
|
||||||
use Imagine\Filter\FilterInterface;
|
|
||||||
use Imagine\Image\ImageInterface;
|
|
||||||
use Imagine\Image\Point;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The OnPixelBased takes a callable, and for each pixel, this callable is called with the
|
|
||||||
* image (\Imagine\Image\ImageInterface) and the current point (\Imagine\Image\Point)
|
|
||||||
*/
|
|
||||||
class OnPixelBased implements FilterInterface
|
|
||||||
{
|
|
||||||
protected $callback;
|
|
||||||
|
|
||||||
public function __construct($callback)
|
|
||||||
{
|
|
||||||
if (!is_callable($callback)) {
|
|
||||||
throw new InvalidArgumentException('$callback has to be callable');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->callback = $callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies scheduled transformation to ImageInterface instance
|
|
||||||
* Returns processed ImageInterface instance
|
|
||||||
*
|
|
||||||
* @param ImageInterface $image
|
|
||||||
*
|
|
||||||
* @return ImageInterface
|
|
||||||
*/
|
|
||||||
public function apply(ImageInterface $image)
|
|
||||||
{
|
|
||||||
$w = $image->getSize()->getWidth();
|
|
||||||
$h = $image->getSize()->getHeight();
|
|
||||||
|
|
||||||
for ($x = 0; $x < $w; $x++) {
|
|
||||||
for ($y = 0; $y < $h; $y++) {
|
|
||||||
call_user_func($this->callback, $image, new Point($x, $y));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $image;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Filter\Advanced;
|
|
||||||
|
|
||||||
use Imagine\Exception\InvalidArgumentException;
|
|
||||||
use Imagine\Filter\FilterInterface;
|
|
||||||
use Imagine\Image\ImageInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The RelativeResize filter allows images to be resized relative to their
|
|
||||||
* existing dimensions.
|
|
||||||
*/
|
|
||||||
class RelativeResize implements FilterInterface
|
|
||||||
{
|
|
||||||
private $method;
|
|
||||||
private $parameter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a RelativeResize filter with the given method and argument.
|
|
||||||
*
|
|
||||||
* @param string $method BoxInterface method
|
|
||||||
* @param mixed $parameter Parameter for BoxInterface method
|
|
||||||
*/
|
|
||||||
public function __construct($method, $parameter)
|
|
||||||
{
|
|
||||||
if (!in_array($method, array('heighten', 'increase', 'scale', 'widen'))) {
|
|
||||||
throw new InvalidArgumentException(sprintf('Unsupported method: ', $method));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->method = $method;
|
|
||||||
$this->parameter = $parameter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function apply(ImageInterface $image)
|
|
||||||
{
|
|
||||||
return $image->resize(call_user_func(array($image->getSize(), $this->method), $this->parameter));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Filter\Basic;
|
|
||||||
|
|
||||||
use Imagine\Filter\FilterInterface;
|
|
||||||
use Imagine\Image\ImageInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An apply mask filter
|
|
||||||
*/
|
|
||||||
class ApplyMask implements FilterInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var ImageInterface
|
|
||||||
*/
|
|
||||||
private $mask;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param ImageInterface $mask
|
|
||||||
*/
|
|
||||||
public function __construct(ImageInterface $mask)
|
|
||||||
{
|
|
||||||
$this->mask = $mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function apply(ImageInterface $image)
|
|
||||||
{
|
|
||||||
return $image->applyMask($this->mask);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,87 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Filter\Basic;
|
|
||||||
|
|
||||||
use Imagine\Filter\FilterInterface;
|
|
||||||
use Imagine\Image\ImageInterface;
|
|
||||||
use Imagine\Image\Palette\Color\ColorInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rotates an image automatically based on exif information.
|
|
||||||
*
|
|
||||||
* Your attention please: This filter requires the use of the
|
|
||||||
* ExifMetadataReader to work.
|
|
||||||
*
|
|
||||||
* @see https://imagine.readthedocs.org/en/latest/usage/metadata.html
|
|
||||||
*/
|
|
||||||
class Autorotate implements FilterInterface
|
|
||||||
{
|
|
||||||
private $color;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string|array|ColorInterface $color A color
|
|
||||||
*/
|
|
||||||
public function __construct($color = '000000')
|
|
||||||
{
|
|
||||||
$this->color = $color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function apply(ImageInterface $image)
|
|
||||||
{
|
|
||||||
$metadata = $image->metadata();
|
|
||||||
|
|
||||||
switch (isset($metadata['ifd0.Orientation']) ? $metadata['ifd0.Orientation'] : null) {
|
|
||||||
case 1: // top-left
|
|
||||||
break;
|
|
||||||
case 2: // top-right
|
|
||||||
$image->flipHorizontally();
|
|
||||||
break;
|
|
||||||
case 3: // bottom-right
|
|
||||||
$image->rotate(180, $this->getColor($image));
|
|
||||||
break;
|
|
||||||
case 4: // bottom-left
|
|
||||||
$image->flipHorizontally();
|
|
||||||
$image->rotate(180, $this->getColor($image));
|
|
||||||
break;
|
|
||||||
case 5: // left-top
|
|
||||||
$image->flipHorizontally();
|
|
||||||
$image->rotate(-90, $this->getColor($image));
|
|
||||||
break;
|
|
||||||
case 6: // right-top
|
|
||||||
$image->rotate(90, $this->getColor($image));
|
|
||||||
break;
|
|
||||||
case 7: // right-bottom
|
|
||||||
$image->flipHorizontally();
|
|
||||||
$image->rotate(90, $this->getColor($image));
|
|
||||||
break;
|
|
||||||
case 8: // left-bottom
|
|
||||||
$image->rotate(-90, $this->getColor($image));
|
|
||||||
break;
|
|
||||||
default: // Invalid orientation
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $image;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getColor(ImageInterface $image)
|
|
||||||
{
|
|
||||||
if ($this->color instanceof ColorInterface) {
|
|
||||||
return $this->color;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $image->palette()->color($this->color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Filter\Basic;
|
|
||||||
|
|
||||||
use Imagine\Filter\FilterInterface;
|
|
||||||
use Imagine\Image\ImageInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A copy filter
|
|
||||||
*/
|
|
||||||
class Copy implements FilterInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function apply(ImageInterface $image)
|
|
||||||
{
|
|
||||||
return $image->copy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Filter\Basic;
|
|
||||||
|
|
||||||
use Imagine\Image\ImageInterface;
|
|
||||||
use Imagine\Image\BoxInterface;
|
|
||||||
use Imagine\Image\PointInterface;
|
|
||||||
use Imagine\Filter\FilterInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A crop filter
|
|
||||||
*/
|
|
||||||
class Crop implements FilterInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var PointInterface
|
|
||||||
*/
|
|
||||||
private $start;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var BoxInterface
|
|
||||||
*/
|
|
||||||
private $size;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a Crop filter with given x, y, coordinates and crop width and
|
|
||||||
* height values
|
|
||||||
*
|
|
||||||
* @param PointInterface $start
|
|
||||||
* @param BoxInterface $size
|
|
||||||
*/
|
|
||||||
public function __construct(PointInterface $start, BoxInterface $size)
|
|
||||||
{
|
|
||||||
$this->start = $start;
|
|
||||||
$this->size = $size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function apply(ImageInterface $image)
|
|
||||||
{
|
|
||||||
return $image->crop($this->start, $this->size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Filter\Basic;
|
|
||||||
|
|
||||||
use Imagine\Filter\FilterInterface;
|
|
||||||
use Imagine\Image\Fill\FillInterface;
|
|
||||||
use Imagine\Image\ImageInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A fill filter
|
|
||||||
*/
|
|
||||||
class Fill implements FilterInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var FillInterface
|
|
||||||
*/
|
|
||||||
private $fill;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param FillInterface $fill
|
|
||||||
*/
|
|
||||||
public function __construct(FillInterface $fill)
|
|
||||||
{
|
|
||||||
$this->fill = $fill;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function apply(ImageInterface $image)
|
|
||||||
{
|
|
||||||
return $image->fill($this->fill);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Filter\Basic;
|
|
||||||
|
|
||||||
use Imagine\Image\ImageInterface;
|
|
||||||
use Imagine\Filter\FilterInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A "flip horizontally" filter
|
|
||||||
*/
|
|
||||||
class FlipHorizontally implements FilterInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function apply(ImageInterface $image)
|
|
||||||
{
|
|
||||||
return $image->flipHorizontally();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Filter\Basic;
|
|
||||||
|
|
||||||
use Imagine\Image\ImageInterface;
|
|
||||||
use Imagine\Filter\FilterInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A "flip vertically" filter
|
|
||||||
*/
|
|
||||||
class FlipVertically implements FilterInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function apply(ImageInterface $image)
|
|
||||||
{
|
|
||||||
return $image->flipVertically();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Filter\Basic;
|
|
||||||
|
|
||||||
use Imagine\Image\ImageInterface;
|
|
||||||
use Imagine\Image\PointInterface;
|
|
||||||
use Imagine\Filter\FilterInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A paste filter
|
|
||||||
*/
|
|
||||||
class Paste implements FilterInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var ImageInterface
|
|
||||||
*/
|
|
||||||
private $image;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var PointInterface
|
|
||||||
*/
|
|
||||||
private $start;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a Paste filter with given ImageInterface to paste and x, y
|
|
||||||
* coordinates of target position
|
|
||||||
*
|
|
||||||
* @param ImageInterface $image
|
|
||||||
* @param PointInterface $start
|
|
||||||
*/
|
|
||||||
public function __construct(ImageInterface $image, PointInterface $start)
|
|
||||||
{
|
|
||||||
$this->image = $image;
|
|
||||||
$this->start = $start;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function apply(ImageInterface $image)
|
|
||||||
{
|
|
||||||
return $image->paste($this->image, $this->start);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Filter\Basic;
|
|
||||||
|
|
||||||
use Imagine\Filter\FilterInterface;
|
|
||||||
use Imagine\Image\ImageInterface;
|
|
||||||
use Imagine\Image\BoxInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A resize filter
|
|
||||||
*/
|
|
||||||
class Resize implements FilterInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var BoxInterface
|
|
||||||
*/
|
|
||||||
private $size;
|
|
||||||
private $filter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs Resize filter with given width and height
|
|
||||||
*
|
|
||||||
* @param BoxInterface $size
|
|
||||||
* @param string $filter
|
|
||||||
*/
|
|
||||||
public function __construct(BoxInterface $size, $filter = ImageInterface::FILTER_UNDEFINED)
|
|
||||||
{
|
|
||||||
$this->size = $size;
|
|
||||||
$this->filter = $filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function apply(ImageInterface $image)
|
|
||||||
{
|
|
||||||
return $image->resize($this->size, $this->filter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Filter\Basic;
|
|
||||||
|
|
||||||
use Imagine\Image\ImageInterface;
|
|
||||||
use Imagine\Image\Palette\Color\ColorInterface;
|
|
||||||
use Imagine\Filter\FilterInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A rotate filter
|
|
||||||
*/
|
|
||||||
class Rotate implements FilterInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var integer
|
|
||||||
*/
|
|
||||||
private $angle;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var ColorInterface
|
|
||||||
*/
|
|
||||||
private $background;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs Rotate filter with given angle and background color
|
|
||||||
*
|
|
||||||
* @param integer $angle
|
|
||||||
* @param ColorInterface $background
|
|
||||||
*/
|
|
||||||
public function __construct($angle, ColorInterface $background = null)
|
|
||||||
{
|
|
||||||
$this->angle = $angle;
|
|
||||||
$this->background = $background;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function apply(ImageInterface $image)
|
|
||||||
{
|
|
||||||
return $image->rotate($this->angle, $this->background);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Filter\Basic;
|
|
||||||
|
|
||||||
use Imagine\Image\ImageInterface;
|
|
||||||
use Imagine\Filter\FilterInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A save filter
|
|
||||||
*/
|
|
||||||
class Save implements FilterInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private $path;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private $options;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs Save filter with given path and options
|
|
||||||
*
|
|
||||||
* @param string $path
|
|
||||||
* @param array $options
|
|
||||||
*/
|
|
||||||
public function __construct($path = null, array $options = array())
|
|
||||||
{
|
|
||||||
$this->path = $path;
|
|
||||||
$this->options = $options;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function apply(ImageInterface $image)
|
|
||||||
{
|
|
||||||
return $image->save($this->path, $this->options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Filter\Basic;
|
|
||||||
|
|
||||||
use Imagine\Image\ImageInterface;
|
|
||||||
use Imagine\Filter\FilterInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A show filter
|
|
||||||
*/
|
|
||||||
class Show implements FilterInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private $format;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private $options;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs the Show filter with given format and options
|
|
||||||
*
|
|
||||||
* @param string $format
|
|
||||||
* @param array $options
|
|
||||||
*/
|
|
||||||
public function __construct($format, array $options = array())
|
|
||||||
{
|
|
||||||
$this->format = $format;
|
|
||||||
$this->options = $options;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function apply(ImageInterface $image)
|
|
||||||
{
|
|
||||||
return $image->show($this->format, $this->options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Filter\Basic;
|
|
||||||
|
|
||||||
use Imagine\Image\ImageInterface;
|
|
||||||
use Imagine\Filter\FilterInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A strip filter
|
|
||||||
*/
|
|
||||||
class Strip implements FilterInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function apply(ImageInterface $image)
|
|
||||||
{
|
|
||||||
return $image->strip();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Filter\Basic;
|
|
||||||
|
|
||||||
use Imagine\Image\ImageInterface;
|
|
||||||
use Imagine\Image\BoxInterface;
|
|
||||||
use Imagine\Filter\FilterInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A thumbnail filter
|
|
||||||
*/
|
|
||||||
class Thumbnail implements FilterInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var BoxInterface
|
|
||||||
*/
|
|
||||||
private $size;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private $mode;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private $filter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs the Thumbnail filter with given width, height and mode
|
|
||||||
*
|
|
||||||
* @param BoxInterface $size
|
|
||||||
* @param string $mode
|
|
||||||
* @param string $filter
|
|
||||||
*/
|
|
||||||
public function __construct(BoxInterface $size, $mode = ImageInterface::THUMBNAIL_INSET, $filter = ImageInterface::FILTER_UNDEFINED)
|
|
||||||
{
|
|
||||||
$this->size = $size;
|
|
||||||
$this->mode = $mode;
|
|
||||||
$this->filter = $filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function apply(ImageInterface $image)
|
|
||||||
{
|
|
||||||
return $image->thumbnail($this->size, $this->mode, $this->filter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Filter\Basic;
|
|
||||||
|
|
||||||
use Imagine\Image\ImageInterface;
|
|
||||||
use Imagine\Image\Palette\RGB;
|
|
||||||
use Imagine\Filter\FilterInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A filter to render web-optimized images
|
|
||||||
*/
|
|
||||||
class WebOptimization implements FilterInterface
|
|
||||||
{
|
|
||||||
private $palette;
|
|
||||||
private $path;
|
|
||||||
private $options;
|
|
||||||
|
|
||||||
public function __construct($path = null, array $options = array())
|
|
||||||
{
|
|
||||||
$this->path = $path;
|
|
||||||
$this->options = array_replace(array(
|
|
||||||
'resolution-units' => ImageInterface::RESOLUTION_PIXELSPERINCH,
|
|
||||||
'resolution-y' => 72,
|
|
||||||
'resolution-x' => 72,
|
|
||||||
), $options);
|
|
||||||
$this->palette = new RGB();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function apply(ImageInterface $image)
|
|
||||||
{
|
|
||||||
$image
|
|
||||||
->usePalette($this->palette)
|
|
||||||
->strip();
|
|
||||||
|
|
||||||
if (is_callable($this->path)) {
|
|
||||||
$path = call_user_func($this->path, $image);
|
|
||||||
} elseif (null !== $this->path) {
|
|
||||||
$path = $this->path;
|
|
||||||
} else {
|
|
||||||
return $image;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $image->save($path, $this->options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Filter;
|
|
||||||
|
|
||||||
use Imagine\Image\ImageInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for imagine filters
|
|
||||||
*/
|
|
||||||
interface FilterInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Applies scheduled transformation to ImageInterface instance
|
|
||||||
* Returns processed ImageInterface instance
|
|
||||||
*
|
|
||||||
* @param ImageInterface $image
|
|
||||||
*
|
|
||||||
* @return ImageInterface
|
|
||||||
*/
|
|
||||||
public function apply(ImageInterface $image);
|
|
||||||
}
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Filter;
|
|
||||||
|
|
||||||
use Imagine\Exception\InvalidArgumentException;
|
|
||||||
use Imagine\Image\ImagineInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ImagineAware base class
|
|
||||||
*/
|
|
||||||
abstract class ImagineAware implements FilterInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* An ImagineInterface instance.
|
|
||||||
*
|
|
||||||
* @var ImagineInterface
|
|
||||||
*/
|
|
||||||
private $imagine;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set ImagineInterface instance.
|
|
||||||
*
|
|
||||||
* @param ImagineInterface $imagine An ImagineInterface instance
|
|
||||||
*/
|
|
||||||
public function setImagine(ImagineInterface $imagine)
|
|
||||||
{
|
|
||||||
$this->imagine = $imagine;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get ImagineInterface instance.
|
|
||||||
*
|
|
||||||
* @return ImagineInterface
|
|
||||||
*
|
|
||||||
* @throws InvalidArgumentException
|
|
||||||
*/
|
|
||||||
public function getImagine()
|
|
||||||
{
|
|
||||||
if (!$this->imagine instanceof ImagineInterface) {
|
|
||||||
throw new InvalidArgumentException(sprintf('In order to use %s pass an Imagine\Image\ImagineInterface instance to filter constructor', get_class($this)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->imagine;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,240 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Filter;
|
|
||||||
|
|
||||||
use Imagine\Exception\InvalidArgumentException;
|
|
||||||
use Imagine\Filter\Basic\ApplyMask;
|
|
||||||
use Imagine\Filter\Basic\Copy;
|
|
||||||
use Imagine\Filter\Basic\Crop;
|
|
||||||
use Imagine\Filter\Basic\Fill;
|
|
||||||
use Imagine\Filter\Basic\FlipVertically;
|
|
||||||
use Imagine\Filter\Basic\FlipHorizontally;
|
|
||||||
use Imagine\Filter\Basic\Paste;
|
|
||||||
use Imagine\Filter\Basic\Resize;
|
|
||||||
use Imagine\Filter\Basic\Rotate;
|
|
||||||
use Imagine\Filter\Basic\Save;
|
|
||||||
use Imagine\Filter\Basic\Show;
|
|
||||||
use Imagine\Filter\Basic\Strip;
|
|
||||||
use Imagine\Filter\Basic\Thumbnail;
|
|
||||||
use Imagine\Image\ImageInterface;
|
|
||||||
use Imagine\Image\ImagineInterface;
|
|
||||||
use Imagine\Image\BoxInterface;
|
|
||||||
use Imagine\Image\Palette\Color\ColorInterface;
|
|
||||||
use Imagine\Image\Fill\FillInterface;
|
|
||||||
use Imagine\Image\ManipulatorInterface;
|
|
||||||
use Imagine\Image\PointInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A transformation filter
|
|
||||||
*/
|
|
||||||
final class Transformation implements FilterInterface, ManipulatorInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private $filters = array();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private $sorted;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An ImagineInterface instance.
|
|
||||||
*
|
|
||||||
* @var ImagineInterface
|
|
||||||
*/
|
|
||||||
private $imagine;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class constructor.
|
|
||||||
*
|
|
||||||
* @param ImagineInterface $imagine An ImagineInterface instance
|
|
||||||
*/
|
|
||||||
public function __construct(ImagineInterface $imagine = null)
|
|
||||||
{
|
|
||||||
$this->imagine = $imagine;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies a given FilterInterface onto given ImageInterface and returns
|
|
||||||
* modified ImageInterface
|
|
||||||
*
|
|
||||||
* @param ImageInterface $image
|
|
||||||
* @param FilterInterface $filter
|
|
||||||
*
|
|
||||||
* @return ImageInterface
|
|
||||||
* @throws InvalidArgumentException
|
|
||||||
*/
|
|
||||||
public function applyFilter(ImageInterface $image, FilterInterface $filter)
|
|
||||||
{
|
|
||||||
if ($filter instanceof ImagineAware) {
|
|
||||||
if ($this->imagine === null) {
|
|
||||||
throw new InvalidArgumentException(sprintf('In order to use %s pass an Imagine\Image\ImagineInterface instance to Transformation constructor', get_class($filter)));
|
|
||||||
}
|
|
||||||
$filter->setImagine($this->imagine);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $filter->apply($image);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list of filters sorted by their priority. Filters with same priority will be returned in the order they were added.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function getFilters()
|
|
||||||
{
|
|
||||||
if (null === $this->sorted) {
|
|
||||||
if (!empty($this->filters)) {
|
|
||||||
ksort($this->filters);
|
|
||||||
$this->sorted = call_user_func_array('array_merge', $this->filters);
|
|
||||||
} else {
|
|
||||||
$this->sorted = array();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->sorted;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function apply(ImageInterface $image)
|
|
||||||
{
|
|
||||||
return array_reduce(
|
|
||||||
$this->getFilters(),
|
|
||||||
array($this, 'applyFilter'),
|
|
||||||
$image
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function copy()
|
|
||||||
{
|
|
||||||
return $this->add(new Copy());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function crop(PointInterface $start, BoxInterface $size)
|
|
||||||
{
|
|
||||||
return $this->add(new Crop($start, $size));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function flipHorizontally()
|
|
||||||
{
|
|
||||||
return $this->add(new FlipHorizontally());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function flipVertically()
|
|
||||||
{
|
|
||||||
return $this->add(new FlipVertically());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function strip()
|
|
||||||
{
|
|
||||||
return $this->add(new Strip());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function paste(ImageInterface $image, PointInterface $start)
|
|
||||||
{
|
|
||||||
return $this->add(new Paste($image, $start));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function applyMask(ImageInterface $mask)
|
|
||||||
{
|
|
||||||
return $this->add(new ApplyMask($mask));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function fill(FillInterface $fill)
|
|
||||||
{
|
|
||||||
return $this->add(new Fill($fill));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function resize(BoxInterface $size, $filter = ImageInterface::FILTER_UNDEFINED)
|
|
||||||
{
|
|
||||||
return $this->add(new Resize($size, $filter));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function rotate($angle, ColorInterface $background = null)
|
|
||||||
{
|
|
||||||
return $this->add(new Rotate($angle, $background));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function save($path = null, array $options = array())
|
|
||||||
{
|
|
||||||
return $this->add(new Save($path, $options));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function show($format, array $options = array())
|
|
||||||
{
|
|
||||||
return $this->add(new Show($format, $options));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function thumbnail(BoxInterface $size, $mode = ImageInterface::THUMBNAIL_INSET, $filter = ImageInterface::FILTER_UNDEFINED)
|
|
||||||
{
|
|
||||||
return $this->add(new Thumbnail($size, $mode, $filter));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers a given FilterInterface in an internal array of filters for
|
|
||||||
* later application to an instance of ImageInterface
|
|
||||||
*
|
|
||||||
* @param FilterInterface $filter
|
|
||||||
* @param int $priority
|
|
||||||
* @return Transformation
|
|
||||||
*/
|
|
||||||
public function add(FilterInterface $filter, $priority = 0)
|
|
||||||
{
|
|
||||||
$this->filters[$priority][] = $filter;
|
|
||||||
$this->sorted = null;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,333 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Gd;
|
|
||||||
|
|
||||||
use Imagine\Draw\DrawerInterface;
|
|
||||||
use Imagine\Exception\InvalidArgumentException;
|
|
||||||
use Imagine\Exception\RuntimeException;
|
|
||||||
use Imagine\Image\AbstractFont;
|
|
||||||
use Imagine\Image\BoxInterface;
|
|
||||||
use Imagine\Image\Palette\Color\ColorInterface;
|
|
||||||
use Imagine\Image\Palette\Color\RGB as RGBColor;
|
|
||||||
use Imagine\Image\PointInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Drawer implementation using the GD library
|
|
||||||
*/
|
|
||||||
final class Drawer implements DrawerInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var resource
|
|
||||||
*/
|
|
||||||
private $resource;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private $info;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs Drawer with a given gd image resource
|
|
||||||
*
|
|
||||||
* @param resource $resource
|
|
||||||
*/
|
|
||||||
public function __construct($resource)
|
|
||||||
{
|
|
||||||
$this->loadGdInfo();
|
|
||||||
$this->resource = $resource;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function arc(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $thickness = 1)
|
|
||||||
{
|
|
||||||
imagesetthickness($this->resource, max(1, (int) $thickness));
|
|
||||||
|
|
||||||
if (false === imagealphablending($this->resource, true)) {
|
|
||||||
throw new RuntimeException('Draw arc operation failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false === imagearc($this->resource, $center->getX(), $center->getY(), $size->getWidth(), $size->getHeight(), $start, $end, $this->getColor($color))) {
|
|
||||||
imagealphablending($this->resource, false);
|
|
||||||
throw new RuntimeException('Draw arc operation failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false === imagealphablending($this->resource, false)) {
|
|
||||||
throw new RuntimeException('Draw arc operation failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function does not work properly because of a bug in GD
|
|
||||||
*
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function chord(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1)
|
|
||||||
{
|
|
||||||
imagesetthickness($this->resource, max(1, (int) $thickness));
|
|
||||||
|
|
||||||
if ($fill) {
|
|
||||||
$style = IMG_ARC_CHORD;
|
|
||||||
} else {
|
|
||||||
$style = IMG_ARC_CHORD | IMG_ARC_NOFILL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false === imagealphablending($this->resource, true)) {
|
|
||||||
throw new RuntimeException('Draw chord operation failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false === imagefilledarc($this->resource, $center->getX(), $center->getY(), $size->getWidth(), $size->getHeight(), $start, $end, $this->getColor($color), $style)) {
|
|
||||||
imagealphablending($this->resource, false);
|
|
||||||
throw new RuntimeException('Draw chord operation failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false === imagealphablending($this->resource, false)) {
|
|
||||||
throw new RuntimeException('Draw chord operation failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function ellipse(PointInterface $center, BoxInterface $size, ColorInterface $color, $fill = false, $thickness = 1)
|
|
||||||
{
|
|
||||||
imagesetthickness($this->resource, max(1, (int) $thickness));
|
|
||||||
|
|
||||||
if ($fill) {
|
|
||||||
$callback = 'imagefilledellipse';
|
|
||||||
} else {
|
|
||||||
$callback = 'imageellipse';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false === imagealphablending($this->resource, true)) {
|
|
||||||
throw new RuntimeException('Draw ellipse operation failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false === $callback($this->resource, $center->getX(), $center->getY(), $size->getWidth(), $size->getHeight(), $this->getColor($color))) {
|
|
||||||
imagealphablending($this->resource, false);
|
|
||||||
throw new RuntimeException('Draw ellipse operation failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false === imagealphablending($this->resource, false)) {
|
|
||||||
throw new RuntimeException('Draw ellipse operation failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function line(PointInterface $start, PointInterface $end, ColorInterface $color, $thickness = 1)
|
|
||||||
{
|
|
||||||
imagesetthickness($this->resource, max(1, (int) $thickness));
|
|
||||||
|
|
||||||
if (false === imagealphablending($this->resource, true)) {
|
|
||||||
throw new RuntimeException('Draw line operation failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false === imageline($this->resource, $start->getX(), $start->getY(), $end->getX(), $end->getY(), $this->getColor($color))) {
|
|
||||||
imagealphablending($this->resource, false);
|
|
||||||
throw new RuntimeException('Draw line operation failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false === imagealphablending($this->resource, false)) {
|
|
||||||
throw new RuntimeException('Draw line operation failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function pieSlice(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1)
|
|
||||||
{
|
|
||||||
imagesetthickness($this->resource, max(1, (int) $thickness));
|
|
||||||
|
|
||||||
if ($fill) {
|
|
||||||
$style = IMG_ARC_EDGED;
|
|
||||||
} else {
|
|
||||||
$style = IMG_ARC_EDGED | IMG_ARC_NOFILL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false === imagealphablending($this->resource, true)) {
|
|
||||||
throw new RuntimeException('Draw chord operation failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false === imagefilledarc($this->resource, $center->getX(), $center->getY(), $size->getWidth(), $size->getHeight(), $start, $end, $this->getColor($color), $style)) {
|
|
||||||
imagealphablending($this->resource, false);
|
|
||||||
throw new RuntimeException('Draw chord operation failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false === imagealphablending($this->resource, false)) {
|
|
||||||
throw new RuntimeException('Draw chord operation failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function dot(PointInterface $position, ColorInterface $color)
|
|
||||||
{
|
|
||||||
if (false === imagealphablending($this->resource, true)) {
|
|
||||||
throw new RuntimeException('Draw point operation failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false === imagesetpixel($this->resource, $position->getX(), $position->getY(), $this->getColor($color))) {
|
|
||||||
imagealphablending($this->resource, false);
|
|
||||||
throw new RuntimeException('Draw point operation failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false === imagealphablending($this->resource, false)) {
|
|
||||||
throw new RuntimeException('Draw point operation failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function polygon(array $coordinates, ColorInterface $color, $fill = false, $thickness = 1)
|
|
||||||
{
|
|
||||||
imagesetthickness($this->resource, max(1, (int) $thickness));
|
|
||||||
|
|
||||||
if (count($coordinates) < 3) {
|
|
||||||
throw new InvalidArgumentException(sprintf('A polygon must consist of at least 3 points, %d given', count($coordinates)));
|
|
||||||
}
|
|
||||||
|
|
||||||
$points = call_user_func_array('array_merge', array_map(function (PointInterface $p) {
|
|
||||||
return array($p->getX(), $p->getY());
|
|
||||||
}, $coordinates));
|
|
||||||
|
|
||||||
if ($fill) {
|
|
||||||
$callback = 'imagefilledpolygon';
|
|
||||||
} else {
|
|
||||||
$callback = 'imagepolygon';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false === imagealphablending($this->resource, true)) {
|
|
||||||
throw new RuntimeException('Draw polygon operation failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false === $callback($this->resource, $points, count($coordinates), $this->getColor($color))) {
|
|
||||||
imagealphablending($this->resource, false);
|
|
||||||
throw new RuntimeException('Draw polygon operation failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false === imagealphablending($this->resource, false)) {
|
|
||||||
throw new RuntimeException('Draw polygon operation failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function text($string, AbstractFont $font, PointInterface $position, $angle = 0, $width = null)
|
|
||||||
{
|
|
||||||
if (!$this->info['FreeType Support']) {
|
|
||||||
throw new RuntimeException('GD is not compiled with FreeType support');
|
|
||||||
}
|
|
||||||
|
|
||||||
$angle = -1 * $angle;
|
|
||||||
$fontsize = $font->getSize();
|
|
||||||
$fontfile = $font->getFile();
|
|
||||||
$x = $position->getX();
|
|
||||||
$y = $position->getY() + $fontsize;
|
|
||||||
|
|
||||||
if ($width !== null) {
|
|
||||||
$string = $this->wrapText($string, $font, $angle, $width);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false === imagealphablending($this->resource, true)) {
|
|
||||||
throw new RuntimeException('Font mask operation failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false === imagefttext($this->resource, $fontsize, $angle, $x, $y, $this->getColor($font->getColor()), $fontfile, $string)) {
|
|
||||||
imagealphablending($this->resource, false);
|
|
||||||
throw new RuntimeException('Font mask operation failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false === imagealphablending($this->resource, false)) {
|
|
||||||
throw new RuntimeException('Font mask operation failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal
|
|
||||||
*
|
|
||||||
* Generates a GD color from Color instance
|
|
||||||
*
|
|
||||||
* @param ColorInterface $color
|
|
||||||
*
|
|
||||||
* @return resource
|
|
||||||
*
|
|
||||||
* @throws RuntimeException
|
|
||||||
* @throws InvalidArgumentException
|
|
||||||
*/
|
|
||||||
private function getColor(ColorInterface $color)
|
|
||||||
{
|
|
||||||
if (!$color instanceof RGBColor) {
|
|
||||||
throw new InvalidArgumentException('GD driver only supports RGB colors');
|
|
||||||
}
|
|
||||||
|
|
||||||
$gdColor = imagecolorallocatealpha($this->resource, $color->getRed(), $color->getGreen(), $color->getBlue(), (100 - $color->getAlpha()) * 127 / 100);
|
|
||||||
if (false === $gdColor) {
|
|
||||||
throw new RuntimeException(sprintf('Unable to allocate color "RGB(%s, %s, %s)" with transparency of %d percent', $color->getRed(), $color->getGreen(), $color->getBlue(), $color->getAlpha()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $gdColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function loadGdInfo()
|
|
||||||
{
|
|
||||||
if (!function_exists('gd_info')) {
|
|
||||||
throw new RuntimeException('Gd not installed');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->info = gd_info();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal
|
|
||||||
*
|
|
||||||
* Fits a string into box with given width
|
|
||||||
*/
|
|
||||||
private function wrapText($string, AbstractFont $font, $angle, $width)
|
|
||||||
{
|
|
||||||
$result = '';
|
|
||||||
$words = explode(' ', $string);
|
|
||||||
foreach ($words as $word) {
|
|
||||||
$teststring = $result . ' ' . $word;
|
|
||||||
$testbox = imagettfbbox($font->getSize(), $angle, $font->getFile(), $teststring);
|
|
||||||
if ($testbox[2] > $width) {
|
|
||||||
$result .= ($result == '' ? '' : "\n") . $word;
|
|
||||||
} else {
|
|
||||||
$result .= ($result == '' ? '' : ' ') . $word;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,109 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Gd;
|
|
||||||
|
|
||||||
use Imagine\Effects\EffectsInterface;
|
|
||||||
use Imagine\Exception\RuntimeException;
|
|
||||||
use Imagine\Image\Palette\Color\ColorInterface;
|
|
||||||
use Imagine\Image\Palette\Color\RGB as RGBColor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Effects implementation using the GD library
|
|
||||||
*/
|
|
||||||
class Effects implements EffectsInterface
|
|
||||||
{
|
|
||||||
private $resource;
|
|
||||||
|
|
||||||
public function __construct($resource)
|
|
||||||
{
|
|
||||||
$this->resource = $resource;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function gamma($correction)
|
|
||||||
{
|
|
||||||
if (false === imagegammacorrect($this->resource, 1.0, $correction)) {
|
|
||||||
throw new RuntimeException('Failed to apply gamma correction to the image');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function negative()
|
|
||||||
{
|
|
||||||
if (false === imagefilter($this->resource, IMG_FILTER_NEGATE)) {
|
|
||||||
throw new RuntimeException('Failed to negate the image');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function grayscale()
|
|
||||||
{
|
|
||||||
if (false === imagefilter($this->resource, IMG_FILTER_GRAYSCALE)) {
|
|
||||||
throw new RuntimeException('Failed to grayscale the image');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function colorize(ColorInterface $color)
|
|
||||||
{
|
|
||||||
if (!$color instanceof RGBColor) {
|
|
||||||
throw new RuntimeException('Colorize effects only accepts RGB color in GD context');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false === imagefilter($this->resource, IMG_FILTER_COLORIZE, $color->getRed(), $color->getGreen(), $color->getBlue())) {
|
|
||||||
throw new RuntimeException('Failed to colorize the image');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function sharpen()
|
|
||||||
{
|
|
||||||
$sharpenMatrix = array(array(-1,-1,-1), array(-1,16,-1), array(-1,-1,-1));
|
|
||||||
$divisor = array_sum(array_map('array_sum', $sharpenMatrix));
|
|
||||||
|
|
||||||
if (false === imageconvolution($this->resource, $sharpenMatrix, $divisor, 0)) {
|
|
||||||
throw new RuntimeException('Failed to sharpen the image');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function blur($sigma = 1)
|
|
||||||
{
|
|
||||||
if (false === imagefilter($this->resource, IMG_FILTER_GAUSSIAN_BLUR)) {
|
|
||||||
throw new RuntimeException('Failed to blur the image');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Gd;
|
|
||||||
|
|
||||||
use Imagine\Exception\RuntimeException;
|
|
||||||
use Imagine\Image\AbstractFont;
|
|
||||||
use Imagine\Image\Box;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Font implementation using the GD library
|
|
||||||
*/
|
|
||||||
final class Font extends AbstractFont
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function box($string, $angle = 0)
|
|
||||||
{
|
|
||||||
if (!function_exists('imageftbbox')) {
|
|
||||||
throw new RuntimeException('GD must have been compiled with `--with-freetype-dir` option to use the Font feature.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$angle = -1 * $angle;
|
|
||||||
$info = imageftbbox($this->size, $angle, $this->file, $string);
|
|
||||||
$xs = array($info[0], $info[2], $info[4], $info[6]);
|
|
||||||
$ys = array($info[1], $info[3], $info[5], $info[7]);
|
|
||||||
$width = abs(max($xs) - min($xs));
|
|
||||||
$height = abs(max($ys) - min($ys));
|
|
||||||
|
|
||||||
return new Box($width, $height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,735 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Gd;
|
|
||||||
|
|
||||||
use Imagine\Image\AbstractImage;
|
|
||||||
use Imagine\Image\ImageInterface;
|
|
||||||
use Imagine\Image\Box;
|
|
||||||
use Imagine\Image\BoxInterface;
|
|
||||||
use Imagine\Image\Metadata\MetadataBag;
|
|
||||||
use Imagine\Image\Palette\Color\ColorInterface;
|
|
||||||
use Imagine\Image\Fill\FillInterface;
|
|
||||||
use Imagine\Image\Point;
|
|
||||||
use Imagine\Image\PointInterface;
|
|
||||||
use Imagine\Image\Palette\PaletteInterface;
|
|
||||||
use Imagine\Image\Palette\Color\RGB as RGBColor;
|
|
||||||
use Imagine\Image\ProfileInterface;
|
|
||||||
use Imagine\Image\Palette\RGB;
|
|
||||||
use Imagine\Exception\InvalidArgumentException;
|
|
||||||
use Imagine\Exception\OutOfBoundsException;
|
|
||||||
use Imagine\Exception\RuntimeException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Image implementation using the GD library
|
|
||||||
*/
|
|
||||||
final class Image extends AbstractImage
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var resource
|
|
||||||
*/
|
|
||||||
private $resource;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Layers|null
|
|
||||||
*/
|
|
||||||
private $layers;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var PaletteInterface
|
|
||||||
*/
|
|
||||||
private $palette;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new Image instance
|
|
||||||
*
|
|
||||||
* @param resource $resource
|
|
||||||
* @param PaletteInterface $palette
|
|
||||||
* @param MetadataBag $metadata
|
|
||||||
*/
|
|
||||||
public function __construct($resource, PaletteInterface $palette, MetadataBag $metadata)
|
|
||||||
{
|
|
||||||
$this->metadata = $metadata;
|
|
||||||
$this->palette = $palette;
|
|
||||||
$this->resource = $resource;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Makes sure the current image resource is destroyed
|
|
||||||
*/
|
|
||||||
public function __destruct()
|
|
||||||
{
|
|
||||||
if (is_resource($this->resource) && 'gd' === get_resource_type($this->resource)) {
|
|
||||||
imagedestroy($this->resource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns Gd resource
|
|
||||||
*
|
|
||||||
* @return resource
|
|
||||||
*/
|
|
||||||
public function getGdResource()
|
|
||||||
{
|
|
||||||
return $this->resource;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*
|
|
||||||
* @return ImageInterface
|
|
||||||
*/
|
|
||||||
final public function copy()
|
|
||||||
{
|
|
||||||
$size = $this->getSize();
|
|
||||||
$copy = $this->createImage($size, 'copy');
|
|
||||||
|
|
||||||
if (false === imagecopy($copy, $this->resource, 0, 0, 0, 0, $size->getWidth(), $size->getHeight())) {
|
|
||||||
throw new RuntimeException('Image copy operation failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Image($copy, $this->palette, $this->metadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*
|
|
||||||
* @return ImageInterface
|
|
||||||
*/
|
|
||||||
final public function crop(PointInterface $start, BoxInterface $size)
|
|
||||||
{
|
|
||||||
if (!$start->in($this->getSize())) {
|
|
||||||
throw new OutOfBoundsException('Crop coordinates must start at minimum 0, 0 position from top left corner, crop height and width must be positive integers and must not exceed the current image borders');
|
|
||||||
}
|
|
||||||
|
|
||||||
$width = $size->getWidth();
|
|
||||||
$height = $size->getHeight();
|
|
||||||
|
|
||||||
$dest = $this->createImage($size, 'crop');
|
|
||||||
|
|
||||||
if (false === imagecopy($dest, $this->resource, 0, 0, $start->getX(), $start->getY(), $width, $height)) {
|
|
||||||
throw new RuntimeException('Image crop operation failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
imagedestroy($this->resource);
|
|
||||||
|
|
||||||
$this->resource = $dest;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*
|
|
||||||
* @return ImageInterface
|
|
||||||
*/
|
|
||||||
final public function paste(ImageInterface $image, PointInterface $start)
|
|
||||||
{
|
|
||||||
if (!$image instanceof self) {
|
|
||||||
throw new InvalidArgumentException(sprintf('Gd\Image can only paste() Gd\Image instances, %s given', get_class($image)));
|
|
||||||
}
|
|
||||||
|
|
||||||
$size = $image->getSize();
|
|
||||||
if (!$this->getSize()->contains($size, $start)) {
|
|
||||||
throw new OutOfBoundsException('Cannot paste image of the given size at the specified position, as it moves outside of the current image\'s box');
|
|
||||||
}
|
|
||||||
|
|
||||||
imagealphablending($this->resource, true);
|
|
||||||
imagealphablending($image->resource, true);
|
|
||||||
|
|
||||||
if (false === imagecopy($this->resource, $image->resource, $start->getX(), $start->getY(), 0, 0, $size->getWidth(), $size->getHeight())) {
|
|
||||||
throw new RuntimeException('Image paste operation failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
imagealphablending($this->resource, false);
|
|
||||||
imagealphablending($image->resource, false);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*
|
|
||||||
* @return ImageInterface
|
|
||||||
*/
|
|
||||||
final public function resize(BoxInterface $size, $filter = ImageInterface::FILTER_UNDEFINED)
|
|
||||||
{
|
|
||||||
if (ImageInterface::FILTER_UNDEFINED !== $filter) {
|
|
||||||
throw new InvalidArgumentException('Unsupported filter type, GD only supports ImageInterface::FILTER_UNDEFINED filter');
|
|
||||||
}
|
|
||||||
|
|
||||||
$width = $size->getWidth();
|
|
||||||
$height = $size->getHeight();
|
|
||||||
|
|
||||||
$dest = $this->createImage($size, 'resize');
|
|
||||||
|
|
||||||
imagealphablending($this->resource, true);
|
|
||||||
imagealphablending($dest, true);
|
|
||||||
|
|
||||||
if (false === imagecopyresampled($dest, $this->resource, 0, 0, 0, 0, $width, $height, imagesx($this->resource), imagesy($this->resource))) {
|
|
||||||
throw new RuntimeException('Image resize operation failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
imagealphablending($this->resource, false);
|
|
||||||
imagealphablending($dest, false);
|
|
||||||
|
|
||||||
imagedestroy($this->resource);
|
|
||||||
|
|
||||||
$this->resource = $dest;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*
|
|
||||||
* @return ImageInterface
|
|
||||||
*/
|
|
||||||
final public function rotate($angle, ColorInterface $background = null)
|
|
||||||
{
|
|
||||||
$color = $background ? $background : $this->palette->color('fff');
|
|
||||||
$resource = imagerotate($this->resource, -1 * $angle, $this->getColor($color));
|
|
||||||
|
|
||||||
if (false === $resource) {
|
|
||||||
throw new RuntimeException('Image rotate operation failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
imagedestroy($this->resource);
|
|
||||||
$this->resource = $resource;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*
|
|
||||||
* @return ImageInterface
|
|
||||||
*/
|
|
||||||
final public function save($path = null, array $options = array())
|
|
||||||
{
|
|
||||||
$path = null === $path ? (isset($this->metadata['filepath']) ? $this->metadata['filepath'] : $path) : $path;
|
|
||||||
|
|
||||||
if (null === $path) {
|
|
||||||
throw new RuntimeException('You can omit save path only if image has been open from a file');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($options['format'])) {
|
|
||||||
$format = $options['format'];
|
|
||||||
} elseif ('' !== $extension = pathinfo($path, \PATHINFO_EXTENSION)) {
|
|
||||||
$format = $extension;
|
|
||||||
} else {
|
|
||||||
$originalPath = isset($this->metadata['filepath']) ? $this->metadata['filepath'] : null;
|
|
||||||
$format = pathinfo($originalPath, \PATHINFO_EXTENSION);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->saveOrOutput($format, $options, $path);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*
|
|
||||||
* @return ImageInterface
|
|
||||||
*/
|
|
||||||
public function show($format, array $options = array())
|
|
||||||
{
|
|
||||||
header('Content-type: '.$this->getMimeType($format));
|
|
||||||
|
|
||||||
$this->saveOrOutput($format, $options);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function get($format, array $options = array())
|
|
||||||
{
|
|
||||||
ob_start();
|
|
||||||
$this->saveOrOutput($format, $options);
|
|
||||||
|
|
||||||
return ob_get_clean();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function __toString()
|
|
||||||
{
|
|
||||||
return $this->get('png');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*
|
|
||||||
* @return ImageInterface
|
|
||||||
*/
|
|
||||||
final public function flipHorizontally()
|
|
||||||
{
|
|
||||||
$size = $this->getSize();
|
|
||||||
$width = $size->getWidth();
|
|
||||||
$height = $size->getHeight();
|
|
||||||
$dest = $this->createImage($size, 'flip');
|
|
||||||
|
|
||||||
for ($i = 0; $i < $width; $i++) {
|
|
||||||
if (false === imagecopy($dest, $this->resource, $i, 0, ($width - 1) - $i, 0, 1, $height)) {
|
|
||||||
throw new RuntimeException('Horizontal flip operation failed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
imagedestroy($this->resource);
|
|
||||||
|
|
||||||
$this->resource = $dest;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*
|
|
||||||
* @return ImageInterface
|
|
||||||
*/
|
|
||||||
final public function flipVertically()
|
|
||||||
{
|
|
||||||
$size = $this->getSize();
|
|
||||||
$width = $size->getWidth();
|
|
||||||
$height = $size->getHeight();
|
|
||||||
$dest = $this->createImage($size, 'flip');
|
|
||||||
|
|
||||||
for ($i = 0; $i < $height; $i++) {
|
|
||||||
if (false === imagecopy($dest, $this->resource, 0, $i, 0, ($height - 1) - $i, $width, 1)) {
|
|
||||||
throw new RuntimeException('Vertical flip operation failed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
imagedestroy($this->resource);
|
|
||||||
|
|
||||||
$this->resource = $dest;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*
|
|
||||||
* @return ImageInterface
|
|
||||||
*/
|
|
||||||
final public function strip()
|
|
||||||
{
|
|
||||||
// GD strips profiles and comment, so there's nothing to do here
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function draw()
|
|
||||||
{
|
|
||||||
return new Drawer($this->resource);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function effects()
|
|
||||||
{
|
|
||||||
return new Effects($this->resource);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function getSize()
|
|
||||||
{
|
|
||||||
return new Box(imagesx($this->resource), imagesy($this->resource));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*
|
|
||||||
* @return ImageInterface
|
|
||||||
*/
|
|
||||||
public function applyMask(ImageInterface $mask)
|
|
||||||
{
|
|
||||||
if (!$mask instanceof self) {
|
|
||||||
throw new InvalidArgumentException('Cannot mask non-gd images');
|
|
||||||
}
|
|
||||||
|
|
||||||
$size = $this->getSize();
|
|
||||||
$maskSize = $mask->getSize();
|
|
||||||
|
|
||||||
if ($size != $maskSize) {
|
|
||||||
throw new InvalidArgumentException(sprintf('The given mask doesn\'t match current image\'s size, Current mask\'s dimensions are %s, while image\'s dimensions are %s', $maskSize, $size));
|
|
||||||
}
|
|
||||||
|
|
||||||
for ($x = 0, $width = $size->getWidth(); $x < $width; $x++) {
|
|
||||||
for ($y = 0, $height = $size->getHeight(); $y < $height; $y++) {
|
|
||||||
$position = new Point($x, $y);
|
|
||||||
$color = $this->getColorAt($position);
|
|
||||||
$maskColor = $mask->getColorAt($position);
|
|
||||||
$round = (int) round(max($color->getAlpha(), (100 - $color->getAlpha()) * $maskColor->getRed() / 255));
|
|
||||||
|
|
||||||
if (false === imagesetpixel($this->resource, $x, $y, $this->getColor($color->dissolve($round - $color->getAlpha())))) {
|
|
||||||
throw new RuntimeException('Apply mask operation failed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*
|
|
||||||
* @return ImageInterface
|
|
||||||
*/
|
|
||||||
public function fill(FillInterface $fill)
|
|
||||||
{
|
|
||||||
$size = $this->getSize();
|
|
||||||
|
|
||||||
for ($x = 0, $width = $size->getWidth(); $x < $width; $x++) {
|
|
||||||
for ($y = 0, $height = $size->getHeight(); $y < $height; $y++) {
|
|
||||||
if (false === imagesetpixel($this->resource, $x, $y, $this->getColor($fill->getColor(new Point($x, $y))))) {
|
|
||||||
throw new RuntimeException('Fill operation failed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function mask()
|
|
||||||
{
|
|
||||||
$mask = $this->copy();
|
|
||||||
|
|
||||||
if (false === imagefilter($mask->resource, IMG_FILTER_GRAYSCALE)) {
|
|
||||||
throw new RuntimeException('Mask operation failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function histogram()
|
|
||||||
{
|
|
||||||
$size = $this->getSize();
|
|
||||||
$colors = array();
|
|
||||||
|
|
||||||
for ($x = 0, $width = $size->getWidth(); $x < $width; $x++) {
|
|
||||||
for ($y = 0, $height = $size->getHeight(); $y < $height; $y++) {
|
|
||||||
$colors[] = $this->getColorAt(new Point($x, $y));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return array_unique($colors);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function getColorAt(PointInterface $point)
|
|
||||||
{
|
|
||||||
if (!$point->in($this->getSize())) {
|
|
||||||
throw new RuntimeException(sprintf('Error getting color at point [%s,%s]. The point must be inside the image of size [%s,%s]', $point->getX(), $point->getY(), $this->getSize()->getWidth(), $this->getSize()->getHeight()));
|
|
||||||
}
|
|
||||||
|
|
||||||
$index = imagecolorat($this->resource, $point->getX(), $point->getY());
|
|
||||||
$info = imagecolorsforindex($this->resource, $index);
|
|
||||||
|
|
||||||
return $this->palette->color(array($info['red'], $info['green'], $info['blue']), max(min(100 - (int) round($info['alpha'] / 127 * 100), 100), 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function layers()
|
|
||||||
{
|
|
||||||
if (null === $this->layers) {
|
|
||||||
$this->layers = new Layers($this, $this->palette, $this->resource);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->layers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
**/
|
|
||||||
public function interlace($scheme)
|
|
||||||
{
|
|
||||||
static $supportedInterlaceSchemes = array(
|
|
||||||
ImageInterface::INTERLACE_NONE => 0,
|
|
||||||
ImageInterface::INTERLACE_LINE => 1,
|
|
||||||
ImageInterface::INTERLACE_PLANE => 1,
|
|
||||||
ImageInterface::INTERLACE_PARTITION => 1,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!array_key_exists($scheme, $supportedInterlaceSchemes)) {
|
|
||||||
throw new InvalidArgumentException('Unsupported interlace type');
|
|
||||||
}
|
|
||||||
|
|
||||||
imageinterlace($this->resource, $supportedInterlaceSchemes[$scheme]);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function palette()
|
|
||||||
{
|
|
||||||
return $this->palette;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function profile(ProfileInterface $profile)
|
|
||||||
{
|
|
||||||
throw new RuntimeException('GD driver does not support color profiles');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function usePalette(PaletteInterface $palette)
|
|
||||||
{
|
|
||||||
if (!$palette instanceof RGB) {
|
|
||||||
throw new RuntimeException('GD driver only supports RGB palette');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->palette = $palette;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal
|
|
||||||
*
|
|
||||||
* Performs save or show operation using one of GD's image... functions
|
|
||||||
*
|
|
||||||
* @param string $format
|
|
||||||
* @param array $options
|
|
||||||
* @param string $filename
|
|
||||||
*
|
|
||||||
* @throws InvalidArgumentException
|
|
||||||
* @throws RuntimeException
|
|
||||||
*/
|
|
||||||
private function saveOrOutput($format, array $options, $filename = null)
|
|
||||||
{
|
|
||||||
$format = $this->normalizeFormat($format);
|
|
||||||
|
|
||||||
if (!$this->supported($format)) {
|
|
||||||
throw new InvalidArgumentException(sprintf('Saving image in "%s" format is not supported, please use one of the following extensions: "%s"', $format, implode('", "', $this->supported())));
|
|
||||||
}
|
|
||||||
|
|
||||||
$save = 'image'.$format;
|
|
||||||
$args = array(&$this->resource, $filename);
|
|
||||||
|
|
||||||
// Preserve BC until version 1.0
|
|
||||||
if (isset($options['quality']) && !isset($options['png_compression_level'])) {
|
|
||||||
$options['png_compression_level'] = round((100 - $options['quality']) * 9 / 100);
|
|
||||||
}
|
|
||||||
if (isset($options['filters']) && !isset($options['png_compression_filter'])) {
|
|
||||||
$options['png_compression_filter'] = $options['filters'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$options = $this->updateSaveOptions($options);
|
|
||||||
|
|
||||||
if ($format === 'jpeg' && isset($options['jpeg_quality'])) {
|
|
||||||
$args[] = $options['jpeg_quality'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($format === 'png') {
|
|
||||||
if (isset($options['png_compression_level'])) {
|
|
||||||
if ($options['png_compression_level'] < 0 || $options['png_compression_level'] > 9) {
|
|
||||||
throw new InvalidArgumentException('png_compression_level option should be an integer from 0 to 9');
|
|
||||||
}
|
|
||||||
$args[] = $options['png_compression_level'];
|
|
||||||
} else {
|
|
||||||
$args[] = -1; // use default level
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($options['png_compression_filter'])) {
|
|
||||||
if (~PNG_ALL_FILTERS & $options['png_compression_filter']) {
|
|
||||||
throw new InvalidArgumentException('png_compression_filter option should be a combination of the PNG_FILTER_XXX constants');
|
|
||||||
}
|
|
||||||
$args[] = $options['png_compression_filter'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (($format === 'wbmp' || $format === 'xbm') && isset($options['foreground'])) {
|
|
||||||
$args[] = $options['foreground'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->setExceptionHandler();
|
|
||||||
|
|
||||||
if (false === call_user_func_array($save, $args)) {
|
|
||||||
throw new RuntimeException('Save operation failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->resetExceptionHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal
|
|
||||||
*
|
|
||||||
* Generates a GD image
|
|
||||||
*
|
|
||||||
* @param BoxInterface $size
|
|
||||||
* @param string the operation initiating the creation
|
|
||||||
*
|
|
||||||
* @return resource
|
|
||||||
*
|
|
||||||
* @throws RuntimeException
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
private function createImage(BoxInterface $size, $operation)
|
|
||||||
{
|
|
||||||
$resource = imagecreatetruecolor($size->getWidth(), $size->getHeight());
|
|
||||||
|
|
||||||
if (false === $resource) {
|
|
||||||
throw new RuntimeException('Image '.$operation.' failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false === imagealphablending($resource, false) || false === imagesavealpha($resource, true)) {
|
|
||||||
throw new RuntimeException('Image '.$operation.' failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (function_exists('imageantialias')) {
|
|
||||||
imageantialias($resource, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
$transparent = imagecolorallocatealpha($resource, 255, 255, 255, 127);
|
|
||||||
imagefill($resource, 0, 0, $transparent);
|
|
||||||
imagecolortransparent($resource, $transparent);
|
|
||||||
|
|
||||||
return $resource;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal
|
|
||||||
*
|
|
||||||
* Generates a GD color from Color instance
|
|
||||||
*
|
|
||||||
* @param ColorInterface $color
|
|
||||||
*
|
|
||||||
* @return integer A color identifier
|
|
||||||
*
|
|
||||||
* @throws RuntimeException
|
|
||||||
* @throws InvalidArgumentException
|
|
||||||
*/
|
|
||||||
private function getColor(ColorInterface $color)
|
|
||||||
{
|
|
||||||
if (!$color instanceof RGBColor) {
|
|
||||||
throw new InvalidArgumentException('GD driver only supports RGB colors');
|
|
||||||
}
|
|
||||||
|
|
||||||
$index = imagecolorallocatealpha($this->resource, $color->getRed(), $color->getGreen(), $color->getBlue(), round(127 * (100 - $color->getAlpha()) / 100));
|
|
||||||
|
|
||||||
if (false === $index) {
|
|
||||||
throw new RuntimeException(sprintf('Unable to allocate color "RGB(%s, %s, %s)" with transparency of %d percent', $color->getRed(), $color->getGreen(), $color->getBlue(), $color->getAlpha()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $index;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal
|
|
||||||
*
|
|
||||||
* Normalizes a given format name
|
|
||||||
*
|
|
||||||
* @param string $format
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
private function normalizeFormat($format)
|
|
||||||
{
|
|
||||||
$format = strtolower($format);
|
|
||||||
|
|
||||||
if ('jpg' === $format || 'pjpeg' === $format) {
|
|
||||||
$format = 'jpeg';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $format;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal
|
|
||||||
*
|
|
||||||
* Checks whether a given format is supported by GD library
|
|
||||||
*
|
|
||||||
* @param string $format
|
|
||||||
*
|
|
||||||
* @return Boolean
|
|
||||||
*/
|
|
||||||
private function supported($format = null)
|
|
||||||
{
|
|
||||||
$formats = array('gif', 'jpeg', 'png', 'wbmp', 'xbm');
|
|
||||||
|
|
||||||
if (null === $format) {
|
|
||||||
return $formats;
|
|
||||||
}
|
|
||||||
|
|
||||||
return in_array($format, $formats);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function setExceptionHandler()
|
|
||||||
{
|
|
||||||
set_error_handler(function ($errno, $errstr, $errfile, $errline) {
|
|
||||||
if (0 === error_reporting()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new RuntimeException($errstr, $errno, new \ErrorException($errstr, 0, $errno, $errfile, $errline));
|
|
||||||
}, E_WARNING | E_NOTICE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function resetExceptionHandler()
|
|
||||||
{
|
|
||||||
restore_error_handler();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal
|
|
||||||
*
|
|
||||||
* Get the mime type based on format.
|
|
||||||
*
|
|
||||||
* @param string $format
|
|
||||||
*
|
|
||||||
* @return string mime-type
|
|
||||||
*
|
|
||||||
* @throws RuntimeException
|
|
||||||
*/
|
|
||||||
private function getMimeType($format)
|
|
||||||
{
|
|
||||||
$format = $this->normalizeFormat($format);
|
|
||||||
|
|
||||||
if (!$this->supported($format)) {
|
|
||||||
throw new RuntimeException('Invalid format');
|
|
||||||
}
|
|
||||||
|
|
||||||
static $mimeTypes = array(
|
|
||||||
'jpeg' => 'image/jpeg',
|
|
||||||
'gif' => 'image/gif',
|
|
||||||
'png' => 'image/png',
|
|
||||||
'wbmp' => 'image/vnd.wap.wbmp',
|
|
||||||
'xbm' => 'image/xbm',
|
|
||||||
);
|
|
||||||
|
|
||||||
return $mimeTypes[$format];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,195 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Gd;
|
|
||||||
|
|
||||||
use Imagine\Image\AbstractImagine;
|
|
||||||
use Imagine\Image\Metadata\MetadataBag;
|
|
||||||
use Imagine\Image\Palette\Color\ColorInterface;
|
|
||||||
use Imagine\Image\Palette\RGB;
|
|
||||||
use Imagine\Image\Palette\PaletteInterface;
|
|
||||||
use Imagine\Image\BoxInterface;
|
|
||||||
use Imagine\Image\Palette\Color\RGB as RGBColor;
|
|
||||||
use Imagine\Exception\InvalidArgumentException;
|
|
||||||
use Imagine\Exception\RuntimeException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Imagine implementation using the GD library
|
|
||||||
*/
|
|
||||||
final class Imagine extends AbstractImagine
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private $info;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws RuntimeException
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->loadGdInfo();
|
|
||||||
$this->requireGdVersion('2.0.1');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function create(BoxInterface $size, ColorInterface $color = null)
|
|
||||||
{
|
|
||||||
$width = $size->getWidth();
|
|
||||||
$height = $size->getHeight();
|
|
||||||
|
|
||||||
$resource = imagecreatetruecolor($width, $height);
|
|
||||||
|
|
||||||
if (false === $resource) {
|
|
||||||
throw new RuntimeException('Create operation failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
$palette = null !== $color ? $color->getPalette() : new RGB();
|
|
||||||
$color = $color ? $color : $palette->color('fff');
|
|
||||||
|
|
||||||
if (!$color instanceof RGBColor) {
|
|
||||||
throw new InvalidArgumentException('GD driver only supports RGB colors');
|
|
||||||
}
|
|
||||||
|
|
||||||
$index = imagecolorallocatealpha($resource, $color->getRed(), $color->getGreen(), $color->getBlue(), round(127 * (100 - $color->getAlpha()) / 100));
|
|
||||||
|
|
||||||
if (false === $index) {
|
|
||||||
throw new RuntimeException('Unable to allocate color');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false === imagefill($resource, 0, 0, $index)) {
|
|
||||||
throw new RuntimeException('Could not set background color fill');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($color->getAlpha() >= 95) {
|
|
||||||
imagecolortransparent($resource, $index);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->wrap($resource, $palette, new MetadataBag());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function open($path)
|
|
||||||
{
|
|
||||||
$path = $this->checkPath($path);
|
|
||||||
$data = @file_get_contents($path);
|
|
||||||
|
|
||||||
if (false === $data) {
|
|
||||||
throw new RuntimeException(sprintf('Failed to open file %s', $path));
|
|
||||||
}
|
|
||||||
|
|
||||||
$resource = @imagecreatefromstring($data);
|
|
||||||
|
|
||||||
if (!is_resource($resource)) {
|
|
||||||
throw new RuntimeException(sprintf('Unable to open image %s', $path));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->wrap($resource, new RGB(), $this->getMetadataReader()->readFile($path));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function load($string)
|
|
||||||
{
|
|
||||||
return $this->doLoad($string, $this->getMetadataReader()->readData($string));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function read($resource)
|
|
||||||
{
|
|
||||||
if (!is_resource($resource)) {
|
|
||||||
throw new InvalidArgumentException('Variable does not contain a stream resource');
|
|
||||||
}
|
|
||||||
|
|
||||||
$content = stream_get_contents($resource);
|
|
||||||
|
|
||||||
if (false === $content) {
|
|
||||||
throw new InvalidArgumentException('Cannot read resource content');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->doLoad($content, $this->getMetadataReader()->readStream($resource));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function font($file, $size, ColorInterface $color)
|
|
||||||
{
|
|
||||||
if (!$this->info['FreeType Support']) {
|
|
||||||
throw new RuntimeException('GD is not compiled with FreeType support');
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Font($file, $size, $color);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function wrap($resource, PaletteInterface $palette, MetadataBag $metadata)
|
|
||||||
{
|
|
||||||
if (!imageistruecolor($resource)) {
|
|
||||||
list($width, $height) = array(imagesx($resource), imagesy($resource));
|
|
||||||
|
|
||||||
// create transparent truecolor canvas
|
|
||||||
$truecolor = imagecreatetruecolor($width, $height);
|
|
||||||
$transparent = imagecolorallocatealpha($truecolor, 255, 255, 255, 127);
|
|
||||||
|
|
||||||
imagefill($truecolor, 0, 0, $transparent);
|
|
||||||
imagecolortransparent($truecolor, $transparent);
|
|
||||||
|
|
||||||
imagecopymerge($truecolor, $resource, 0, 0, 0, 0, $width, $height, 100);
|
|
||||||
|
|
||||||
imagedestroy($resource);
|
|
||||||
$resource = $truecolor;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false === imagealphablending($resource, false) || false === imagesavealpha($resource, true)) {
|
|
||||||
throw new RuntimeException('Could not set alphablending, savealpha and antialias values');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (function_exists('imageantialias')) {
|
|
||||||
imageantialias($resource, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Image($resource, $palette, $metadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function loadGdInfo()
|
|
||||||
{
|
|
||||||
if (!function_exists('gd_info')) {
|
|
||||||
throw new RuntimeException('Gd not installed');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->info = gd_info();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function requireGdVersion($version)
|
|
||||||
{
|
|
||||||
if (version_compare(GD_VERSION, $version, '<')) {
|
|
||||||
throw new RuntimeException(sprintf('GD2 version %s or higher is required', $version));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function doLoad($string, MetadataBag $metadata)
|
|
||||||
{
|
|
||||||
$resource = @imagecreatefromstring($string);
|
|
||||||
|
|
||||||
if (!is_resource($resource)) {
|
|
||||||
throw new RuntimeException('An image could not be created from the given input');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->wrap($resource, new RGB(), $metadata);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,144 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Gd;
|
|
||||||
|
|
||||||
use Imagine\Image\AbstractLayers;
|
|
||||||
use Imagine\Exception\RuntimeException;
|
|
||||||
use Imagine\Image\Metadata\MetadataBag;
|
|
||||||
use Imagine\Image\Palette\PaletteInterface;
|
|
||||||
use Imagine\Exception\NotSupportedException;
|
|
||||||
|
|
||||||
class Layers extends AbstractLayers
|
|
||||||
{
|
|
||||||
private $image;
|
|
||||||
private $offset;
|
|
||||||
private $resource;
|
|
||||||
private $palette;
|
|
||||||
|
|
||||||
public function __construct(Image $image, PaletteInterface $palette, $resource)
|
|
||||||
{
|
|
||||||
if (!is_resource($resource)) {
|
|
||||||
throw new RuntimeException('Invalid Gd resource provided');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->image = $image;
|
|
||||||
$this->resource = $resource;
|
|
||||||
$this->offset = 0;
|
|
||||||
$this->palette = $palette;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function merge()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function coalesce()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function animate($format, $delay, $loops)
|
|
||||||
{
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function current()
|
|
||||||
{
|
|
||||||
return new Image($this->resource, $this->palette, new MetadataBag());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function key()
|
|
||||||
{
|
|
||||||
return $this->offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function next()
|
|
||||||
{
|
|
||||||
++$this->offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function rewind()
|
|
||||||
{
|
|
||||||
$this->offset = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function valid()
|
|
||||||
{
|
|
||||||
return $this->offset < 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function count()
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function offsetExists($offset)
|
|
||||||
{
|
|
||||||
return 0 === $offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function offsetGet($offset)
|
|
||||||
{
|
|
||||||
if (0 === $offset) {
|
|
||||||
return new Image($this->resource, $this->palette, new MetadataBag());
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new RuntimeException('GD only supports one layer at offset 0');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function offsetSet($offset, $value)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException('GD does not support layer set');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function offsetUnset($offset)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException('GD does not support layer unset');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,356 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Gmagick;
|
|
||||||
|
|
||||||
use Imagine\Draw\DrawerInterface;
|
|
||||||
use Imagine\Exception\InvalidArgumentException;
|
|
||||||
use Imagine\Exception\NotSupportedException;
|
|
||||||
use Imagine\Exception\RuntimeException;
|
|
||||||
use Imagine\Image\AbstractFont;
|
|
||||||
use Imagine\Image\BoxInterface;
|
|
||||||
use Imagine\Image\Palette\Color\ColorInterface;
|
|
||||||
use Imagine\Image\Point;
|
|
||||||
use Imagine\Image\PointInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Drawer implementation using the Gmagick PHP extension
|
|
||||||
*/
|
|
||||||
final class Drawer implements DrawerInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var \Gmagick
|
|
||||||
*/
|
|
||||||
private $gmagick;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param \Gmagick $gmagick
|
|
||||||
*/
|
|
||||||
public function __construct(\Gmagick $gmagick)
|
|
||||||
{
|
|
||||||
$this->gmagick = $gmagick;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function arc(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $thickness = 1)
|
|
||||||
{
|
|
||||||
$x = $center->getX();
|
|
||||||
$y = $center->getY();
|
|
||||||
$width = $size->getWidth();
|
|
||||||
$height = $size->getHeight();
|
|
||||||
|
|
||||||
try {
|
|
||||||
$pixel = $this->getColor($color);
|
|
||||||
$arc = new \GmagickDraw();
|
|
||||||
|
|
||||||
$arc->setstrokecolor($pixel);
|
|
||||||
$arc->setstrokewidth(max(1, (int) $thickness));
|
|
||||||
$arc->setfillcolor('transparent');
|
|
||||||
$arc->arc(
|
|
||||||
$x - $width / 2,
|
|
||||||
$y - $height / 2,
|
|
||||||
$x + $width / 2,
|
|
||||||
$y + $height / 2,
|
|
||||||
$start,
|
|
||||||
$end
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->gmagick->drawImage($arc);
|
|
||||||
|
|
||||||
$pixel = null;
|
|
||||||
|
|
||||||
$arc = null;
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException('Draw arc operation failed', $e->getCode(), $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function chord(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1)
|
|
||||||
{
|
|
||||||
$x = $center->getX();
|
|
||||||
$y = $center->getY();
|
|
||||||
$width = $size->getWidth();
|
|
||||||
$height = $size->getHeight();
|
|
||||||
|
|
||||||
try {
|
|
||||||
$pixel = $this->getColor($color);
|
|
||||||
$chord = new \GmagickDraw();
|
|
||||||
|
|
||||||
$chord->setstrokecolor($pixel);
|
|
||||||
$chord->setstrokewidth(max(1, (int) $thickness));
|
|
||||||
|
|
||||||
if ($fill) {
|
|
||||||
$chord->setfillcolor($pixel);
|
|
||||||
} else {
|
|
||||||
$x1 = round($x + $width / 2 * cos(deg2rad($start)));
|
|
||||||
$y1 = round($y + $height / 2 * sin(deg2rad($start)));
|
|
||||||
$x2 = round($x + $width / 2 * cos(deg2rad($end)));
|
|
||||||
$y2 = round($y + $height / 2 * sin(deg2rad($end)));
|
|
||||||
|
|
||||||
$this->line(new Point($x1, $y1), new Point($x2, $y2), $color);
|
|
||||||
|
|
||||||
$chord->setfillcolor('transparent');
|
|
||||||
}
|
|
||||||
|
|
||||||
$chord->arc($x - $width / 2, $y - $height / 2, $x + $width / 2, $y + $height / 2, $start, $end);
|
|
||||||
|
|
||||||
$this->gmagick->drawImage($chord);
|
|
||||||
|
|
||||||
$pixel = null;
|
|
||||||
|
|
||||||
$chord = null;
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException('Draw chord operation failed', $e->getCode(), $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function ellipse(PointInterface $center, BoxInterface $size, ColorInterface $color, $fill = false, $thickness = 1)
|
|
||||||
{
|
|
||||||
$width = $size->getWidth();
|
|
||||||
$height = $size->getHeight();
|
|
||||||
|
|
||||||
try {
|
|
||||||
$pixel = $this->getColor($color);
|
|
||||||
$ellipse = new \GmagickDraw();
|
|
||||||
|
|
||||||
$ellipse->setstrokecolor($pixel);
|
|
||||||
$ellipse->setstrokewidth(max(1, (int) $thickness));
|
|
||||||
|
|
||||||
if ($fill) {
|
|
||||||
$ellipse->setfillcolor($pixel);
|
|
||||||
} else {
|
|
||||||
$ellipse->setfillcolor('transparent');
|
|
||||||
}
|
|
||||||
|
|
||||||
$ellipse->ellipse(
|
|
||||||
$center->getX(),
|
|
||||||
$center->getY(),
|
|
||||||
$width / 2,
|
|
||||||
$height / 2,
|
|
||||||
0, 360
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->gmagick->drawImage($ellipse);
|
|
||||||
|
|
||||||
$pixel = null;
|
|
||||||
|
|
||||||
$ellipse = null;
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException('Draw ellipse operation failed', $e->getCode(), $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function line(PointInterface $start, PointInterface $end, ColorInterface $color, $thickness = 1)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$pixel = $this->getColor($color);
|
|
||||||
$line = new \GmagickDraw();
|
|
||||||
|
|
||||||
$line->setstrokecolor($pixel);
|
|
||||||
$line->setstrokewidth(max(1, (int) $thickness));
|
|
||||||
$line->setfillcolor($pixel);
|
|
||||||
$line->line(
|
|
||||||
$start->getX(),
|
|
||||||
$start->getY(),
|
|
||||||
$end->getX(),
|
|
||||||
$end->getY()
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->gmagick->drawImage($line);
|
|
||||||
|
|
||||||
$pixel = null;
|
|
||||||
|
|
||||||
$line = null;
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException('Draw line operation failed', $e->getCode(), $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function pieSlice(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1)
|
|
||||||
{
|
|
||||||
$width = $size->getWidth();
|
|
||||||
$height = $size->getHeight();
|
|
||||||
|
|
||||||
$x1 = round($center->getX() + $width / 2 * cos(deg2rad($start)));
|
|
||||||
$y1 = round($center->getY() + $height / 2 * sin(deg2rad($start)));
|
|
||||||
$x2 = round($center->getX() + $width / 2 * cos(deg2rad($end)));
|
|
||||||
$y2 = round($center->getY() + $height / 2 * sin(deg2rad($end)));
|
|
||||||
|
|
||||||
if ($fill) {
|
|
||||||
$this->chord($center, $size, $start, $end, $color, true, $thickness);
|
|
||||||
$this->polygon(
|
|
||||||
array(
|
|
||||||
$center,
|
|
||||||
new Point($x1, $y1),
|
|
||||||
new Point($x2, $y2),
|
|
||||||
),
|
|
||||||
$color,
|
|
||||||
true,
|
|
||||||
$thickness
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$this->arc($center, $size, $start, $end, $color, $thickness);
|
|
||||||
$this->line($center, new Point($x1, $y1), $color, $thickness);
|
|
||||||
$this->line($center, new Point($x2, $y2), $color, $thickness);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function dot(PointInterface $position, ColorInterface $color)
|
|
||||||
{
|
|
||||||
$x = $position->getX();
|
|
||||||
$y = $position->getY();
|
|
||||||
|
|
||||||
try {
|
|
||||||
$pixel = $this->getColor($color);
|
|
||||||
$point = new \GmagickDraw();
|
|
||||||
|
|
||||||
$point->setfillcolor($pixel);
|
|
||||||
$point->point($x, $y);
|
|
||||||
|
|
||||||
$this->gmagick->drawimage($point);
|
|
||||||
|
|
||||||
$pixel = null;
|
|
||||||
$point = null;
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException('Draw point operation failed', $e->getCode(), $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function polygon(array $coordinates, ColorInterface $color, $fill = false, $thickness = 1)
|
|
||||||
{
|
|
||||||
if (count($coordinates) < 3) {
|
|
||||||
throw new InvalidArgumentException(sprintf('Polygon must consist of at least 3 coordinates, %d given', count($coordinates)));
|
|
||||||
}
|
|
||||||
|
|
||||||
$points = array_map(function (PointInterface $p) {
|
|
||||||
return array('x' => $p->getX(), 'y' => $p->getY());
|
|
||||||
}, $coordinates);
|
|
||||||
|
|
||||||
try {
|
|
||||||
$pixel = $this->getColor($color);
|
|
||||||
$polygon = new \GmagickDraw();
|
|
||||||
|
|
||||||
$polygon->setstrokecolor($pixel);
|
|
||||||
$polygon->setstrokewidth(max(1, (int) $thickness));
|
|
||||||
|
|
||||||
if ($fill) {
|
|
||||||
$polygon->setfillcolor($pixel);
|
|
||||||
} else {
|
|
||||||
$polygon->setfillcolor('transparent');
|
|
||||||
}
|
|
||||||
|
|
||||||
$polygon->polygon($points);
|
|
||||||
|
|
||||||
$this->gmagick->drawImage($polygon);
|
|
||||||
|
|
||||||
unset($pixel, $polygon);
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException('Draw polygon operation failed', $e->getCode(), $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function text($string, AbstractFont $font, PointInterface $position, $angle = 0, $width = null)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$pixel = $this->getColor($font->getColor());
|
|
||||||
$text = new \GmagickDraw();
|
|
||||||
|
|
||||||
$text->setfont($font->getFile());
|
|
||||||
/**
|
|
||||||
* @see http://www.php.net/manual/en/imagick.queryfontmetrics.php#101027
|
|
||||||
*
|
|
||||||
* ensure font resolution is the same as GD's hard-coded 96
|
|
||||||
*/
|
|
||||||
$text->setfontsize((int) ($font->getSize() * (96 / 72)));
|
|
||||||
$text->setfillcolor($pixel);
|
|
||||||
|
|
||||||
$info = $this->gmagick->queryfontmetrics($text, $string);
|
|
||||||
$rad = deg2rad($angle);
|
|
||||||
$cos = cos($rad);
|
|
||||||
$sin = sin($rad);
|
|
||||||
|
|
||||||
$x1 = round(0 * $cos - 0 * $sin);
|
|
||||||
$x2 = round($info['textWidth'] * $cos - $info['textHeight'] * $sin);
|
|
||||||
$y1 = round(0 * $sin + 0 * $cos);
|
|
||||||
$y2 = round($info['textWidth'] * $sin + $info['textHeight'] * $cos);
|
|
||||||
|
|
||||||
$xdiff = 0 - min($x1, $x2);
|
|
||||||
$ydiff = 0 - min($y1, $y2);
|
|
||||||
|
|
||||||
if ($width !== null) {
|
|
||||||
throw new NotSupportedException('Gmagick doesn\'t support queryfontmetrics function for multiline text', 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->gmagick->annotateimage($text, $position->getX() + $x1 + $xdiff, $position->getY() + $y2 + $ydiff, $angle, $string);
|
|
||||||
|
|
||||||
unset($pixel, $text);
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException('Draw text operation failed', $e->getCode(), $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets specifically formatted color string from Color instance
|
|
||||||
*
|
|
||||||
* @param ColorInterface $color
|
|
||||||
*
|
|
||||||
* @return \GmagickPixel
|
|
||||||
*
|
|
||||||
* @throws InvalidArgumentException In case a non-opaque color is passed
|
|
||||||
*/
|
|
||||||
private function getColor(ColorInterface $color)
|
|
||||||
{
|
|
||||||
if (!$color->isOpaque()) {
|
|
||||||
throw new InvalidArgumentException('Gmagick doesn\'t support transparency');
|
|
||||||
}
|
|
||||||
|
|
||||||
return new \GmagickPixel((string) $color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,106 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Gmagick;
|
|
||||||
|
|
||||||
use Imagine\Effects\EffectsInterface;
|
|
||||||
use Imagine\Exception\RuntimeException;
|
|
||||||
use Imagine\Image\Palette\Color\ColorInterface;
|
|
||||||
use Imagine\Exception\NotSupportedException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Effects implementation using the Gmagick PHP extension
|
|
||||||
*/
|
|
||||||
class Effects implements EffectsInterface
|
|
||||||
{
|
|
||||||
private $gmagick;
|
|
||||||
|
|
||||||
public function __construct(\Gmagick $gmagick)
|
|
||||||
{
|
|
||||||
$this->gmagick = $gmagick;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function gamma($correction)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->gmagick->gammaimage($correction);
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException('Failed to apply gamma correction to the image');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function negative()
|
|
||||||
{
|
|
||||||
if (!method_exists($this->gmagick, 'negateimage')) {
|
|
||||||
throw new NotSupportedException('Gmagick version 1.1.0 RC3 is required for negative effect');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$this->gmagick->negateimage(false, \Gmagick::CHANNEL_ALL);
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException('Failed to negate the image');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function grayscale()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->gmagick->setImageType(2);
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException('Failed to grayscale the image');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function colorize(ColorInterface $color)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException('Gmagick does not support colorize');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function sharpen()
|
|
||||||
{
|
|
||||||
throw new NotSupportedException('Gmagick does not support sharpen yet');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function blur($sigma = 1)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->gmagick->blurImage(0, $sigma);
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException('Failed to blur the image', $e->getCode(), $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Gmagick;
|
|
||||||
|
|
||||||
use Imagine\Image\AbstractFont;
|
|
||||||
use Imagine\Image\Box;
|
|
||||||
use Imagine\Image\Palette\Color\ColorInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Font implementation using the Gmagick PHP extension
|
|
||||||
*/
|
|
||||||
final class Font extends AbstractFont
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var \Gmagick
|
|
||||||
*/
|
|
||||||
private $gmagick;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param \Gmagick $gmagick
|
|
||||||
* @param string $file
|
|
||||||
* @param integer $size
|
|
||||||
* @param ColorInterface $color
|
|
||||||
*/
|
|
||||||
public function __construct(\Gmagick $gmagick, $file, $size, ColorInterface $color)
|
|
||||||
{
|
|
||||||
$this->gmagick = $gmagick;
|
|
||||||
|
|
||||||
parent::__construct($file, $size, $color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function box($string, $angle = 0)
|
|
||||||
{
|
|
||||||
$text = new \GmagickDraw();
|
|
||||||
|
|
||||||
$text->setfont($this->file);
|
|
||||||
/**
|
|
||||||
* @see http://www.php.net/manual/en/imagick.queryfontmetrics.php#101027
|
|
||||||
*
|
|
||||||
* ensure font resolution is the same as GD's hard-coded 96
|
|
||||||
*/
|
|
||||||
$text->setfontsize((int) ($this->size * (96 / 72)));
|
|
||||||
$text->setfontstyle(\Gmagick::STYLE_OBLIQUE);
|
|
||||||
|
|
||||||
$info = $this->gmagick->queryfontmetrics($text, $string);
|
|
||||||
|
|
||||||
$box = new Box($info['textWidth'], $info['textHeight']);
|
|
||||||
|
|
||||||
return $box;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,786 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Gmagick;
|
|
||||||
|
|
||||||
use Imagine\Exception\OutOfBoundsException;
|
|
||||||
use Imagine\Exception\InvalidArgumentException;
|
|
||||||
use Imagine\Exception\RuntimeException;
|
|
||||||
use Imagine\Image\AbstractImage;
|
|
||||||
use Imagine\Image\Metadata\MetadataBag;
|
|
||||||
use Imagine\Image\Palette\PaletteInterface;
|
|
||||||
use Imagine\Image\ImageInterface;
|
|
||||||
use Imagine\Image\Box;
|
|
||||||
use Imagine\Image\BoxInterface;
|
|
||||||
use Imagine\Image\Palette\Color\ColorInterface;
|
|
||||||
use Imagine\Image\Fill\FillInterface;
|
|
||||||
use Imagine\Image\Point;
|
|
||||||
use Imagine\Image\PointInterface;
|
|
||||||
use Imagine\Image\ProfileInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Image implementation using the Gmagick PHP extension
|
|
||||||
*/
|
|
||||||
final class Image extends AbstractImage
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var \Gmagick
|
|
||||||
*/
|
|
||||||
private $gmagick;
|
|
||||||
/**
|
|
||||||
* @var Layers
|
|
||||||
*/
|
|
||||||
private $layers;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var PaletteInterface
|
|
||||||
*/
|
|
||||||
private $palette;
|
|
||||||
|
|
||||||
private static $colorspaceMapping = array(
|
|
||||||
PaletteInterface::PALETTE_CMYK => \Gmagick::COLORSPACE_CMYK,
|
|
||||||
PaletteInterface::PALETTE_RGB => \Gmagick::COLORSPACE_RGB,
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new Image instance
|
|
||||||
*
|
|
||||||
* @param \Gmagick $gmagick
|
|
||||||
* @param PaletteInterface $palette
|
|
||||||
* @param MetadataBag $metadata
|
|
||||||
*/
|
|
||||||
public function __construct(\Gmagick $gmagick, PaletteInterface $palette, MetadataBag $metadata)
|
|
||||||
{
|
|
||||||
$this->metadata = $metadata;
|
|
||||||
$this->gmagick = $gmagick;
|
|
||||||
$this->setColorspace($palette);
|
|
||||||
$this->layers = new Layers($this, $this->palette, $this->gmagick);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destroys allocated gmagick resources
|
|
||||||
*/
|
|
||||||
public function __destruct()
|
|
||||||
{
|
|
||||||
if ($this->gmagick instanceof \Gmagick) {
|
|
||||||
$this->gmagick->clear();
|
|
||||||
$this->gmagick->destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns gmagick instance
|
|
||||||
*
|
|
||||||
* @return Gmagick
|
|
||||||
*/
|
|
||||||
public function getGmagick()
|
|
||||||
{
|
|
||||||
return $this->gmagick;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*
|
|
||||||
* @return ImageInterface
|
|
||||||
*/
|
|
||||||
public function copy()
|
|
||||||
{
|
|
||||||
return new self(clone $this->gmagick, $this->palette, clone $this->metadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*
|
|
||||||
* @return ImageInterface
|
|
||||||
*/
|
|
||||||
public function crop(PointInterface $start, BoxInterface $size)
|
|
||||||
{
|
|
||||||
if (!$start->in($this->getSize())) {
|
|
||||||
throw new OutOfBoundsException('Crop coordinates must start at minimum 0, 0 position from top left corner, crop height and width must be positive integers and must not exceed the current image borders');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$this->gmagick->cropimage($size->getWidth(), $size->getHeight(), $start->getX(), $start->getY());
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException('Crop operation failed', $e->getCode(), $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*
|
|
||||||
* @return ImageInterface
|
|
||||||
*/
|
|
||||||
public function flipHorizontally()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->gmagick->flopimage();
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException('Horizontal flip operation failed', $e->getCode(), $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*
|
|
||||||
* @return ImageInterface
|
|
||||||
*/
|
|
||||||
public function flipVertically()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->gmagick->flipimage();
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException('Vertical flip operation failed', $e->getCode(), $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*
|
|
||||||
* @return ImageInterface
|
|
||||||
*/
|
|
||||||
public function strip()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
try {
|
|
||||||
$this->profile($this->palette->profile());
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
// here we discard setting the profile as the previous incorporated profile
|
|
||||||
// is corrupted, let's now strip the image
|
|
||||||
}
|
|
||||||
$this->gmagick->stripimage();
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException('Strip operation failed', $e->getCode(), $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*
|
|
||||||
* @return ImageInterface
|
|
||||||
*/
|
|
||||||
public function paste(ImageInterface $image, PointInterface $start)
|
|
||||||
{
|
|
||||||
if (!$image instanceof self) {
|
|
||||||
throw new InvalidArgumentException(sprintf('Gmagick\Image can only paste() Gmagick\Image instances, %s given', get_class($image)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$this->getSize()->contains($image->getSize(), $start)) {
|
|
||||||
throw new OutOfBoundsException('Cannot paste image of the given size at the specified position, as it moves outside of the current image\'s box');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$this->gmagick->compositeimage($image->gmagick, \Gmagick::COMPOSITE_DEFAULT, $start->getX(), $start->getY());
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException('Paste operation failed', $e->getCode(), $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*
|
|
||||||
* @return ImageInterface
|
|
||||||
*/
|
|
||||||
public function resize(BoxInterface $size, $filter = ImageInterface::FILTER_UNDEFINED)
|
|
||||||
{
|
|
||||||
static $supportedFilters = array(
|
|
||||||
ImageInterface::FILTER_UNDEFINED => \Gmagick::FILTER_UNDEFINED,
|
|
||||||
ImageInterface::FILTER_BESSEL => \Gmagick::FILTER_BESSEL,
|
|
||||||
ImageInterface::FILTER_BLACKMAN => \Gmagick::FILTER_BLACKMAN,
|
|
||||||
ImageInterface::FILTER_BOX => \Gmagick::FILTER_BOX,
|
|
||||||
ImageInterface::FILTER_CATROM => \Gmagick::FILTER_CATROM,
|
|
||||||
ImageInterface::FILTER_CUBIC => \Gmagick::FILTER_CUBIC,
|
|
||||||
ImageInterface::FILTER_GAUSSIAN => \Gmagick::FILTER_GAUSSIAN,
|
|
||||||
ImageInterface::FILTER_HANNING => \Gmagick::FILTER_HANNING,
|
|
||||||
ImageInterface::FILTER_HAMMING => \Gmagick::FILTER_HAMMING,
|
|
||||||
ImageInterface::FILTER_HERMITE => \Gmagick::FILTER_HERMITE,
|
|
||||||
ImageInterface::FILTER_LANCZOS => \Gmagick::FILTER_LANCZOS,
|
|
||||||
ImageInterface::FILTER_MITCHELL => \Gmagick::FILTER_MITCHELL,
|
|
||||||
ImageInterface::FILTER_POINT => \Gmagick::FILTER_POINT,
|
|
||||||
ImageInterface::FILTER_QUADRATIC => \Gmagick::FILTER_QUADRATIC,
|
|
||||||
ImageInterface::FILTER_SINC => \Gmagick::FILTER_SINC,
|
|
||||||
ImageInterface::FILTER_TRIANGLE => \Gmagick::FILTER_TRIANGLE
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!array_key_exists($filter, $supportedFilters)) {
|
|
||||||
throw new InvalidArgumentException('Unsupported filter type');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$this->gmagick->resizeimage($size->getWidth(), $size->getHeight(), $supportedFilters[$filter], 1);
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException('Resize operation failed', $e->getCode(), $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*
|
|
||||||
* @return ImageInterface
|
|
||||||
*/
|
|
||||||
public function rotate($angle, ColorInterface $background = null)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$background = $background ?: $this->palette->color('fff');
|
|
||||||
$pixel = $this->getColor($background);
|
|
||||||
|
|
||||||
$this->gmagick->rotateimage($pixel, $angle);
|
|
||||||
|
|
||||||
unset($pixel);
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException('Rotate operation failed', $e->getCode(), $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal
|
|
||||||
*
|
|
||||||
* Applies options before save or output
|
|
||||||
*
|
|
||||||
* @param \Gmagick $image
|
|
||||||
* @param array $options
|
|
||||||
* @param string $path
|
|
||||||
*
|
|
||||||
* @throws InvalidArgumentException
|
|
||||||
*/
|
|
||||||
private function applyImageOptions(\Gmagick $image, array $options, $path)
|
|
||||||
{
|
|
||||||
if (isset($options['format'])) {
|
|
||||||
$format = $options['format'];
|
|
||||||
} elseif ('' !== $extension = pathinfo($path, \PATHINFO_EXTENSION)) {
|
|
||||||
$format = $extension;
|
|
||||||
} else {
|
|
||||||
$format = pathinfo($image->getImageFilename(), \PATHINFO_EXTENSION);
|
|
||||||
}
|
|
||||||
|
|
||||||
$format = strtolower($format);
|
|
||||||
|
|
||||||
$options = $this->updateSaveOptions($options);
|
|
||||||
|
|
||||||
if (isset($options['jpeg_quality']) && in_array($format, array('jpeg', 'jpg', 'pjpeg'))) {
|
|
||||||
$image->setCompressionQuality($options['jpeg_quality']);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((isset($options['png_compression_level']) || isset($options['png_compression_filter'])) && $format === 'png') {
|
|
||||||
// first digit: compression level (default: 7)
|
|
||||||
if (isset($options['png_compression_level'])) {
|
|
||||||
if ($options['png_compression_level'] < 0 || $options['png_compression_level'] > 9) {
|
|
||||||
throw new InvalidArgumentException('png_compression_level option should be an integer from 0 to 9');
|
|
||||||
}
|
|
||||||
$compression = $options['png_compression_level'] * 10;
|
|
||||||
} else {
|
|
||||||
$compression = 70;
|
|
||||||
}
|
|
||||||
|
|
||||||
// second digit: compression filter (default: 5)
|
|
||||||
if (isset($options['png_compression_filter'])) {
|
|
||||||
if ($options['png_compression_filter'] < 0 || $options['png_compression_filter'] > 9) {
|
|
||||||
throw new InvalidArgumentException('png_compression_filter option should be an integer from 0 to 9');
|
|
||||||
}
|
|
||||||
$compression += $options['png_compression_filter'];
|
|
||||||
} else {
|
|
||||||
$compression += 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
$image->setCompressionQuality($compression);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($options['resolution-units']) && isset($options['resolution-x']) && isset($options['resolution-y'])) {
|
|
||||||
if ($options['resolution-units'] == ImageInterface::RESOLUTION_PIXELSPERCENTIMETER) {
|
|
||||||
$image->setimageunits(\Gmagick::RESOLUTION_PIXELSPERCENTIMETER);
|
|
||||||
} elseif ($options['resolution-units'] == ImageInterface::RESOLUTION_PIXELSPERINCH) {
|
|
||||||
$image->setimageunits(\Gmagick::RESOLUTION_PIXELSPERINCH);
|
|
||||||
} else {
|
|
||||||
throw new InvalidArgumentException('Unsupported image unit format');
|
|
||||||
}
|
|
||||||
|
|
||||||
$image->setimageresolution($options['resolution-x'], $options['resolution-y']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*
|
|
||||||
* @return ImageInterface
|
|
||||||
*/
|
|
||||||
public function save($path = null, array $options = array())
|
|
||||||
{
|
|
||||||
$path = null === $path ? $this->gmagick->getImageFilename() : $path;
|
|
||||||
|
|
||||||
if ('' === trim($path)) {
|
|
||||||
throw new RuntimeException('You can omit save path only if image has been open from a file');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$this->prepareOutput($options, $path);
|
|
||||||
$allFrames = !isset($options['animated']) || false === $options['animated'];
|
|
||||||
$this->gmagick->writeimage($path, $allFrames);
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException('Save operation failed', $e->getCode(), $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*
|
|
||||||
* @return ImageInterface
|
|
||||||
*/
|
|
||||||
public function show($format, array $options = array())
|
|
||||||
{
|
|
||||||
header('Content-type: '.$this->getMimeType($format));
|
|
||||||
echo $this->get($format, $options);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function get($format, array $options = array())
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$options['format'] = $format;
|
|
||||||
$this->prepareOutput($options);
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException('Get operation failed', $e->getCode(), $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->gmagick->getimagesblob();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array $options
|
|
||||||
* @param string $path
|
|
||||||
*/
|
|
||||||
private function prepareOutput(array $options, $path = null)
|
|
||||||
{
|
|
||||||
if (isset($options['format'])) {
|
|
||||||
$this->gmagick->setimageformat($options['format']);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($options['animated']) && true === $options['animated']) {
|
|
||||||
$format = isset($options['format']) ? $options['format'] : 'gif';
|
|
||||||
$delay = isset($options['animated.delay']) ? $options['animated.delay'] : null;
|
|
||||||
$loops = isset($options['animated.loops']) ? $options['animated.loops'] : 0;
|
|
||||||
|
|
||||||
$options['flatten'] = false;
|
|
||||||
|
|
||||||
$this->layers->animate($format, $delay, $loops);
|
|
||||||
} else {
|
|
||||||
$this->layers->merge();
|
|
||||||
}
|
|
||||||
$this->applyImageOptions($this->gmagick, $options, $path);
|
|
||||||
|
|
||||||
// flatten only if image has multiple layers
|
|
||||||
if ((!isset($options['flatten']) || $options['flatten'] === true) && count($this->layers) > 1) {
|
|
||||||
$this->flatten();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function __toString()
|
|
||||||
{
|
|
||||||
return $this->get('png');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function draw()
|
|
||||||
{
|
|
||||||
return new Drawer($this->gmagick);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function effects()
|
|
||||||
{
|
|
||||||
return new Effects($this->gmagick);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function getSize()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$i = $this->gmagick->getimageindex();
|
|
||||||
$this->gmagick->setimageindex(0); //rewind
|
|
||||||
$width = $this->gmagick->getimagewidth();
|
|
||||||
$height = $this->gmagick->getimageheight();
|
|
||||||
$this->gmagick->setimageindex($i);
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException('Get size operation failed', $e->getCode(), $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Box($width, $height);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*
|
|
||||||
* @return ImageInterface
|
|
||||||
*/
|
|
||||||
public function applyMask(ImageInterface $mask)
|
|
||||||
{
|
|
||||||
if (!$mask instanceof self) {
|
|
||||||
throw new InvalidArgumentException('Can only apply instances of Imagine\Gmagick\Image as masks');
|
|
||||||
}
|
|
||||||
|
|
||||||
$size = $this->getSize();
|
|
||||||
$maskSize = $mask->getSize();
|
|
||||||
|
|
||||||
if ($size != $maskSize) {
|
|
||||||
throw new InvalidArgumentException(sprintf('The given mask doesn\'t match current image\'s size, current mask\'s dimensions are %s, while image\'s dimensions are %s', $maskSize, $size));
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$mask = $mask->copy();
|
|
||||||
$this->gmagick->compositeimage($mask->gmagick, \Gmagick::COMPOSITE_DEFAULT, 0, 0);
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException('Apply mask operation failed', $e->getCode(), $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function mask()
|
|
||||||
{
|
|
||||||
$mask = $this->copy();
|
|
||||||
|
|
||||||
try {
|
|
||||||
$mask->gmagick->modulateimage(100, 0, 100);
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException('Mask operation failed', $e->getCode(), $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*
|
|
||||||
* @return ImageInterface
|
|
||||||
*/
|
|
||||||
public function fill(FillInterface $fill)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$draw = new \GmagickDraw();
|
|
||||||
$size = $this->getSize();
|
|
||||||
|
|
||||||
$w = $size->getWidth();
|
|
||||||
$h = $size->getHeight();
|
|
||||||
|
|
||||||
for ($x = 0; $x <= $w; $x++) {
|
|
||||||
for ($y = 0; $y <= $h; $y++) {
|
|
||||||
$pixel = $this->getColor($fill->getColor(new Point($x, $y)));
|
|
||||||
|
|
||||||
$draw->setfillcolor($pixel);
|
|
||||||
$draw->point($x, $y);
|
|
||||||
|
|
||||||
$pixel = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->gmagick->drawimage($draw);
|
|
||||||
|
|
||||||
$draw = null;
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException('Fill operation failed', $e->getCode(), $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function histogram()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$pixels = $this->gmagick->getimagehistogram();
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException('Error while fetching histogram', $e->getCode(), $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
$image = $this;
|
|
||||||
|
|
||||||
return array_map(function (\GmagickPixel $pixel) use ($image) {
|
|
||||||
return $image->pixelToColor($pixel);
|
|
||||||
}, $pixels);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function getColorAt(PointInterface $point)
|
|
||||||
{
|
|
||||||
if (!$point->in($this->getSize())) {
|
|
||||||
throw new InvalidArgumentException(sprintf('Error getting color at point [%s,%s]. The point must be inside the image of size [%s,%s]', $point->getX(), $point->getY(), $this->getSize()->getWidth(), $this->getSize()->getHeight()));
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$cropped = clone $this->gmagick;
|
|
||||||
$histogram = $cropped
|
|
||||||
->cropImage(1, 1, $point->getX(), $point->getY())
|
|
||||||
->getImageHistogram();
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException('Unable to get the pixel');
|
|
||||||
}
|
|
||||||
|
|
||||||
$pixel = array_shift($histogram);
|
|
||||||
|
|
||||||
unset($histogram, $cropped);
|
|
||||||
|
|
||||||
return $this->pixelToColor($pixel);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a color given a pixel, depending the Palette context
|
|
||||||
*
|
|
||||||
* Note : this method is public for PHP 5.3 compatibility
|
|
||||||
*
|
|
||||||
* @param \GmagickPixel $pixel
|
|
||||||
*
|
|
||||||
* @return ColorInterface
|
|
||||||
*
|
|
||||||
* @throws InvalidArgumentException In case a unknown color is requested
|
|
||||||
*/
|
|
||||||
public function pixelToColor(\GmagickPixel $pixel)
|
|
||||||
{
|
|
||||||
static $colorMapping = array(
|
|
||||||
ColorInterface::COLOR_RED => \Gmagick::COLOR_RED,
|
|
||||||
ColorInterface::COLOR_GREEN => \Gmagick::COLOR_GREEN,
|
|
||||||
ColorInterface::COLOR_BLUE => \Gmagick::COLOR_BLUE,
|
|
||||||
ColorInterface::COLOR_CYAN => \Gmagick::COLOR_CYAN,
|
|
||||||
ColorInterface::COLOR_MAGENTA => \Gmagick::COLOR_MAGENTA,
|
|
||||||
ColorInterface::COLOR_YELLOW => \Gmagick::COLOR_YELLOW,
|
|
||||||
ColorInterface::COLOR_KEYLINE => \Gmagick::COLOR_BLACK,
|
|
||||||
// There is no gray component in \Gmagick, let's use one of the RGB comp
|
|
||||||
ColorInterface::COLOR_GRAY => \Gmagick::COLOR_RED,
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($this->palette->supportsAlpha()) {
|
|
||||||
try {
|
|
||||||
$alpha = (int) round($pixel->getcolorvalue(\Gmagick::COLOR_ALPHA) * 100);
|
|
||||||
} catch (\GmagickPixelException $e) {
|
|
||||||
$alpha = null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$alpha = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$palette = $this->palette();
|
|
||||||
|
|
||||||
return $this->palette->color(array_map(function ($color) use ($palette, $pixel, $colorMapping) {
|
|
||||||
if (!isset($colorMapping[$color])) {
|
|
||||||
throw new InvalidArgumentException(sprintf('Color %s is not mapped in Gmagick', $color));
|
|
||||||
}
|
|
||||||
$multiplier = 255;
|
|
||||||
if ($palette->name() === PaletteInterface::PALETTE_CMYK) {
|
|
||||||
$multiplier = 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $pixel->getcolorvalue($colorMapping[$color]) * $multiplier;
|
|
||||||
}, $this->palette->pixelDefinition()), $alpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function layers()
|
|
||||||
{
|
|
||||||
return $this->layers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function interlace($scheme)
|
|
||||||
{
|
|
||||||
static $supportedInterlaceSchemes = array(
|
|
||||||
ImageInterface::INTERLACE_NONE => \Gmagick::INTERLACE_NO,
|
|
||||||
ImageInterface::INTERLACE_LINE => \Gmagick::INTERLACE_LINE,
|
|
||||||
ImageInterface::INTERLACE_PLANE => \Gmagick::INTERLACE_PLANE,
|
|
||||||
ImageInterface::INTERLACE_PARTITION => \Gmagick::INTERLACE_PARTITION,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!array_key_exists($scheme, $supportedInterlaceSchemes)) {
|
|
||||||
throw new InvalidArgumentException('Unsupported interlace type');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->gmagick->setInterlaceScheme($supportedInterlaceSchemes[$scheme]);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function usePalette(PaletteInterface $palette)
|
|
||||||
{
|
|
||||||
if (!isset(static::$colorspaceMapping[$palette->name()])) {
|
|
||||||
throw new InvalidArgumentException(sprintf('The palette %s is not supported by Gmagick driver',$palette->name()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->palette->name() === $palette->name()) {
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
try {
|
|
||||||
$hasICCProfile = (Boolean) $this->gmagick->getimageprofile('ICM');
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
$hasICCProfile = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$hasICCProfile) {
|
|
||||||
$this->profile($this->palette->profile());
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->profile($palette->profile());
|
|
||||||
|
|
||||||
$this->setColorspace($palette);
|
|
||||||
$this->palette = $palette;
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException('Failed to set colorspace', $e->getCode(), $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function palette()
|
|
||||||
{
|
|
||||||
return $this->palette;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function profile(ProfileInterface $profile)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->gmagick->profileimage('ICM', $profile->data());
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException(sprintf('Unable to add profile %s to image', $profile->name()), $e->getCode(), $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal
|
|
||||||
*
|
|
||||||
* Flatten the image.
|
|
||||||
*/
|
|
||||||
private function flatten()
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @see http://pecl.php.net/bugs/bug.php?id=22435
|
|
||||||
*/
|
|
||||||
if (method_exists($this->gmagick, 'flattenImages')) {
|
|
||||||
try {
|
|
||||||
$this->gmagick = $this->gmagick->flattenImages();
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException('Flatten operation failed', $e->getCode(), $e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets specifically formatted color string from Color instance
|
|
||||||
*
|
|
||||||
* @param ColorInterface $color
|
|
||||||
*
|
|
||||||
* @return \GmagickPixel
|
|
||||||
*
|
|
||||||
* @throws InvalidArgumentException
|
|
||||||
*/
|
|
||||||
private function getColor(ColorInterface $color)
|
|
||||||
{
|
|
||||||
if (!$color->isOpaque()) {
|
|
||||||
throw new InvalidArgumentException('Gmagick doesn\'t support transparency');
|
|
||||||
}
|
|
||||||
|
|
||||||
return new \GmagickPixel((string) $color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal
|
|
||||||
*
|
|
||||||
* Get the mime type based on format.
|
|
||||||
*
|
|
||||||
* @param string $format
|
|
||||||
*
|
|
||||||
* @return string mime-type
|
|
||||||
*
|
|
||||||
* @throws InvalidArgumentException
|
|
||||||
*/
|
|
||||||
private function getMimeType($format)
|
|
||||||
{
|
|
||||||
static $mimeTypes = array(
|
|
||||||
'jpeg' => 'image/jpeg',
|
|
||||||
'jpg' => 'image/jpeg',
|
|
||||||
'gif' => 'image/gif',
|
|
||||||
'png' => 'image/png',
|
|
||||||
'wbmp' => 'image/vnd.wap.wbmp',
|
|
||||||
'xbm' => 'image/xbm',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!isset($mimeTypes[$format])) {
|
|
||||||
throw new InvalidArgumentException(sprintf('Unsupported format given. Only %s are supported, %s given', implode(", ", array_keys($mimeTypes)), $format));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $mimeTypes[$format];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets colorspace and image type, assigns the palette.
|
|
||||||
*
|
|
||||||
* @param PaletteInterface $palette
|
|
||||||
*
|
|
||||||
* @throws InvalidArgumentException
|
|
||||||
*/
|
|
||||||
private function setColorspace(PaletteInterface $palette)
|
|
||||||
{
|
|
||||||
if (!isset(static::$colorspaceMapping[$palette->name()])) {
|
|
||||||
throw new InvalidArgumentException(sprintf('The palette %s is not supported by Gmagick driver', $palette->name()));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->gmagick->setimagecolorspace(static::$colorspaceMapping[$palette->name()]);
|
|
||||||
$this->palette = $palette;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,167 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Gmagick;
|
|
||||||
|
|
||||||
use Imagine\Image\AbstractImagine;
|
|
||||||
use Imagine\Exception\NotSupportedException;
|
|
||||||
use Imagine\Image\BoxInterface;
|
|
||||||
use Imagine\Image\Metadata\MetadataBag;
|
|
||||||
use Imagine\Image\Palette\Color\ColorInterface;
|
|
||||||
use Imagine\Image\Palette\Grayscale;
|
|
||||||
use Imagine\Image\Palette\CMYK;
|
|
||||||
use Imagine\Image\Palette\RGB;
|
|
||||||
use Imagine\Image\Palette\Color\CMYK as CMYKColor;
|
|
||||||
use Imagine\Exception\InvalidArgumentException;
|
|
||||||
use Imagine\Exception\RuntimeException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Imagine implementation using the Gmagick PHP extension
|
|
||||||
*/
|
|
||||||
class Imagine extends AbstractImagine
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @throws RuntimeException
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
if (!class_exists('Gmagick')) {
|
|
||||||
throw new RuntimeException('Gmagick not installed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function open($path)
|
|
||||||
{
|
|
||||||
$path = $this->checkPath($path);
|
|
||||||
|
|
||||||
try {
|
|
||||||
$gmagick = new \Gmagick($path);
|
|
||||||
$image = new Image($gmagick, $this->createPalette($gmagick), $this->getMetadataReader()->readFile($path));
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException(sprintf('Unable to open image %s', $path), $e->getCode(), $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $image;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function create(BoxInterface $size, ColorInterface $color = null)
|
|
||||||
{
|
|
||||||
$width = $size->getWidth();
|
|
||||||
$height = $size->getHeight();
|
|
||||||
|
|
||||||
$palette = null !== $color ? $color->getPalette() : new RGB();
|
|
||||||
$color = null !== $color ? $color : $palette->color('fff');
|
|
||||||
|
|
||||||
try {
|
|
||||||
$gmagick = new \Gmagick();
|
|
||||||
// Gmagick does not support creation of CMYK GmagickPixel
|
|
||||||
// see https://bugs.php.net/bug.php?id=64466
|
|
||||||
if ($color instanceof CMYKColor) {
|
|
||||||
$switchPalette = $palette;
|
|
||||||
$palette = new RGB();
|
|
||||||
$pixel = new \GmagickPixel($palette->color((string) $color));
|
|
||||||
} else {
|
|
||||||
$switchPalette = null;
|
|
||||||
$pixel = new \GmagickPixel((string) $color);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($color->getPalette()->supportsAlpha() && $color->getAlpha() < 100) {
|
|
||||||
throw new NotSupportedException('alpha transparency is not supported');
|
|
||||||
}
|
|
||||||
|
|
||||||
$gmagick->newimage($width, $height, $pixel->getcolor(false));
|
|
||||||
$gmagick->setimagecolorspace(\Gmagick::COLORSPACE_TRANSPARENT);
|
|
||||||
$gmagick->setimagebackgroundcolor($pixel);
|
|
||||||
|
|
||||||
$image = new Image($gmagick, $palette, new MetadataBag());
|
|
||||||
|
|
||||||
if ($switchPalette) {
|
|
||||||
$image->usePalette($switchPalette);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $image;
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException('Could not create empty image', $e->getCode(), $e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function load($string)
|
|
||||||
{
|
|
||||||
return $this->doLoad($string, $this->getMetadataReader()->readData($string));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function read($resource)
|
|
||||||
{
|
|
||||||
if (!is_resource($resource)) {
|
|
||||||
throw new InvalidArgumentException('Variable does not contain a stream resource');
|
|
||||||
}
|
|
||||||
|
|
||||||
$content = stream_get_contents($resource);
|
|
||||||
|
|
||||||
if (false === $content) {
|
|
||||||
throw new InvalidArgumentException('Couldn\'t read given resource');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->doLoad($content, $this->getMetadataReader()->readStream($resource));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function font($file, $size, ColorInterface $color)
|
|
||||||
{
|
|
||||||
$gmagick = new \Gmagick();
|
|
||||||
$gmagick->newimage(1, 1, 'transparent');
|
|
||||||
|
|
||||||
return new Font($gmagick, $file, $size, $color);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function createPalette(\Gmagick $gmagick)
|
|
||||||
{
|
|
||||||
switch ($gmagick->getimagecolorspace()) {
|
|
||||||
case \Gmagick::COLORSPACE_SRGB:
|
|
||||||
case \Gmagick::COLORSPACE_RGB:
|
|
||||||
return new RGB();
|
|
||||||
case \Gmagick::COLORSPACE_CMYK:
|
|
||||||
return new CMYK();
|
|
||||||
case \Gmagick::COLORSPACE_GRAY:
|
|
||||||
return new Grayscale();
|
|
||||||
default:
|
|
||||||
throw new NotSupportedException('Only RGB and CMYK colorspace are currently supported');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function doLoad($content, MetadataBag $metadata)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$gmagick = new \Gmagick();
|
|
||||||
$gmagick->readimageblob($content);
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException(
|
|
||||||
'Could not load image from string', $e->getCode(), $e
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Image($gmagick, $this->createPalette($gmagick), $metadata);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,272 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Gmagick;
|
|
||||||
|
|
||||||
use Imagine\Image\AbstractLayers;
|
|
||||||
use Imagine\Exception\RuntimeException;
|
|
||||||
use Imagine\Exception\NotSupportedException;
|
|
||||||
use Imagine\Exception\OutOfBoundsException;
|
|
||||||
use Imagine\Exception\InvalidArgumentException;
|
|
||||||
use Imagine\Image\Metadata\MetadataBag;
|
|
||||||
use Imagine\Image\Palette\PaletteInterface;
|
|
||||||
|
|
||||||
class Layers extends AbstractLayers
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var Image
|
|
||||||
*/
|
|
||||||
private $image;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var \Gmagick
|
|
||||||
*/
|
|
||||||
private $resource;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var integer
|
|
||||||
*/
|
|
||||||
private $offset = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private $layers = array();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var PaletteInterface
|
|
||||||
*/
|
|
||||||
private $palette;
|
|
||||||
|
|
||||||
public function __construct(Image $image, PaletteInterface $palette, \Gmagick $resource)
|
|
||||||
{
|
|
||||||
$this->image = $image;
|
|
||||||
$this->resource = $resource;
|
|
||||||
$this->palette = $palette;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function merge()
|
|
||||||
{
|
|
||||||
foreach ($this->layers as $offset => $image) {
|
|
||||||
try {
|
|
||||||
$this->resource->setimageindex($offset);
|
|
||||||
$this->resource->setimage($image->getGmagick());
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException('Failed to substitute layer', $e->getCode(), $e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function coalesce()
|
|
||||||
{
|
|
||||||
throw new NotSupportedException('Gmagick does not support coalescing');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function animate($format, $delay, $loops)
|
|
||||||
{
|
|
||||||
if ('gif' !== strtolower($format)) {
|
|
||||||
throw new NotSupportedException('Animated picture is currently only supported on gif');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is_int($loops) || $loops < 0) {
|
|
||||||
throw new InvalidArgumentException('Loops must be a positive integer.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null !== $delay && (!is_int($delay) || $delay < 0)) {
|
|
||||||
throw new InvalidArgumentException('Delay must be either null or a positive integer.');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
foreach ($this as $offset => $layer) {
|
|
||||||
$this->resource->setimageindex($offset);
|
|
||||||
$this->resource->setimageformat($format);
|
|
||||||
|
|
||||||
if (null !== $delay) {
|
|
||||||
$this->resource->setimagedelay($delay / 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->resource->setimageiterations($loops);
|
|
||||||
}
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException('Failed to animate layers', $e->getCode(), $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function current()
|
|
||||||
{
|
|
||||||
return $this->extractAt($this->offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tries to extract layer at given offset
|
|
||||||
*
|
|
||||||
* @param integer $offset
|
|
||||||
* @return Image
|
|
||||||
* @throws RuntimeException
|
|
||||||
*/
|
|
||||||
private function extractAt($offset)
|
|
||||||
{
|
|
||||||
if (!isset($this->layers[$offset])) {
|
|
||||||
try {
|
|
||||||
$this->resource->setimageindex($offset);
|
|
||||||
$this->layers[$offset] = new Image($this->resource->getimage(), $this->palette, new MetadataBag());
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException(sprintf('Failed to extract layer %d', $offset), $e->getCode(), $e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->layers[$offset];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function key()
|
|
||||||
{
|
|
||||||
return $this->offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function next()
|
|
||||||
{
|
|
||||||
++$this->offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function rewind()
|
|
||||||
{
|
|
||||||
$this->offset = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function valid()
|
|
||||||
{
|
|
||||||
return $this->offset < count($this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function count()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
return $this->resource->getnumberimages();
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException('Failed to count the number of layers', $e->getCode(), $e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function offsetExists($offset)
|
|
||||||
{
|
|
||||||
return is_int($offset) && $offset >= 0 && $offset < count($this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function offsetGet($offset)
|
|
||||||
{
|
|
||||||
return $this->extractAt($offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function offsetSet($offset, $image)
|
|
||||||
{
|
|
||||||
if (!$image instanceof Image) {
|
|
||||||
throw new InvalidArgumentException('Only a Gmagick Image can be used as layer');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null === $offset) {
|
|
||||||
$offset = count($this) - 1;
|
|
||||||
} else {
|
|
||||||
if (!is_int($offset)) {
|
|
||||||
throw new InvalidArgumentException('Invalid offset for layer, it must be an integer');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count($this) < $offset || 0 > $offset) {
|
|
||||||
throw new OutOfBoundsException(sprintf('Invalid offset for layer, it must be a value between 0 and %d, %d given', count($this), $offset));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($this[$offset])) {
|
|
||||||
unset($this[$offset]);
|
|
||||||
$offset = $offset - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$frame = $image->getGmagick();
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (count($this) > 0) {
|
|
||||||
$this->resource->setimageindex($offset);
|
|
||||||
$this->resource->nextimage();
|
|
||||||
}
|
|
||||||
$this->resource->addimage($frame);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ugly hack to bypass issue https://bugs.php.net/bug.php?id=64623
|
|
||||||
*/
|
|
||||||
if (count($this) == 2) {
|
|
||||||
$this->resource->setimageindex($offset+1);
|
|
||||||
$this->resource->nextimage();
|
|
||||||
$this->resource->addimage($frame);
|
|
||||||
unset($this[0]);
|
|
||||||
}
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException('Unable to set the layer', $e->getCode(), $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->layers = array();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function offsetUnset($offset)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->extractAt($offset);
|
|
||||||
} catch (RuntimeException $e) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$this->resource->setimageindex($offset);
|
|
||||||
$this->resource->removeimage();
|
|
||||||
} catch (\GmagickException $e) {
|
|
||||||
throw new RuntimeException('Unable to remove layer', $e->getCode(), $e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Image;
|
|
||||||
|
|
||||||
use Imagine\Image\Palette\Color\ColorInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abstract font base class
|
|
||||||
*/
|
|
||||||
abstract class AbstractFont implements FontInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $file;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var integer
|
|
||||||
*/
|
|
||||||
protected $size;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var ColorInterface
|
|
||||||
*/
|
|
||||||
protected $color;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a font with specified $file, $size and $color
|
|
||||||
*
|
|
||||||
* The font size is to be specified in points (e.g. 10pt means 10)
|
|
||||||
*
|
|
||||||
* @param string $file
|
|
||||||
* @param integer $size
|
|
||||||
* @param ColorInterface $color
|
|
||||||
*/
|
|
||||||
public function __construct($file, $size, ColorInterface $color)
|
|
||||||
{
|
|
||||||
$this->file = $file;
|
|
||||||
$this->size = $size;
|
|
||||||
$this->color = $color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
final public function getFile()
|
|
||||||
{
|
|
||||||
return $this->file;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
final public function getSize()
|
|
||||||
{
|
|
||||||
return $this->size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
final public function getColor()
|
|
||||||
{
|
|
||||||
return $this->color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,120 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Image;
|
|
||||||
|
|
||||||
use Imagine\Exception\InvalidArgumentException;
|
|
||||||
use Imagine\Image\Metadata\MetadataBag;
|
|
||||||
|
|
||||||
abstract class AbstractImage implements ImageInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var MetadataBag
|
|
||||||
*/
|
|
||||||
protected $metadata;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*
|
|
||||||
* @return ImageInterface
|
|
||||||
*/
|
|
||||||
public function thumbnail(BoxInterface $size, $mode = ImageInterface::THUMBNAIL_INSET, $filter = ImageInterface::FILTER_UNDEFINED)
|
|
||||||
{
|
|
||||||
if ($mode !== ImageInterface::THUMBNAIL_INSET &&
|
|
||||||
$mode !== ImageInterface::THUMBNAIL_OUTBOUND) {
|
|
||||||
throw new InvalidArgumentException('Invalid mode specified');
|
|
||||||
}
|
|
||||||
|
|
||||||
$imageSize = $this->getSize();
|
|
||||||
$ratios = array(
|
|
||||||
$size->getWidth() / $imageSize->getWidth(),
|
|
||||||
$size->getHeight() / $imageSize->getHeight()
|
|
||||||
);
|
|
||||||
|
|
||||||
$thumbnail = $this->copy();
|
|
||||||
|
|
||||||
$thumbnail->usePalette($this->palette());
|
|
||||||
$thumbnail->strip();
|
|
||||||
// if target width is larger than image width
|
|
||||||
// AND target height is longer than image height
|
|
||||||
if ($size->contains($imageSize)) {
|
|
||||||
return $thumbnail;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($mode === ImageInterface::THUMBNAIL_INSET) {
|
|
||||||
$ratio = min($ratios);
|
|
||||||
} else {
|
|
||||||
$ratio = max($ratios);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($mode === ImageInterface::THUMBNAIL_OUTBOUND) {
|
|
||||||
if (!$imageSize->contains($size)) {
|
|
||||||
$size = new Box(
|
|
||||||
min($imageSize->getWidth(), $size->getWidth()),
|
|
||||||
min($imageSize->getHeight(), $size->getHeight())
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$imageSize = $thumbnail->getSize()->scale($ratio);
|
|
||||||
$thumbnail->resize($imageSize, $filter);
|
|
||||||
}
|
|
||||||
$thumbnail->crop(new Point(
|
|
||||||
max(0, round(($imageSize->getWidth() - $size->getWidth()) / 2)),
|
|
||||||
max(0, round(($imageSize->getHeight() - $size->getHeight()) / 2))
|
|
||||||
), $size);
|
|
||||||
} else {
|
|
||||||
if (!$imageSize->contains($size)) {
|
|
||||||
$imageSize = $imageSize->scale($ratio);
|
|
||||||
$thumbnail->resize($imageSize, $filter);
|
|
||||||
} else {
|
|
||||||
$imageSize = $thumbnail->getSize()->scale($ratio);
|
|
||||||
$thumbnail->resize($imageSize, $filter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $thumbnail;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates a given array of save options for backward compatibility with legacy names
|
|
||||||
*
|
|
||||||
* @param array $options
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
protected function updateSaveOptions(array $options)
|
|
||||||
{
|
|
||||||
// Preserve BC until version 1.0
|
|
||||||
if (isset($options['quality']) && !isset($options['jpeg_quality'])) {
|
|
||||||
$options['jpeg_quality'] = $options['quality'];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $options;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function metadata()
|
|
||||||
{
|
|
||||||
return $this->metadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assures the metadata instance will be cloned, too
|
|
||||||
*/
|
|
||||||
public function __clone()
|
|
||||||
{
|
|
||||||
if ($this->metadata !== null) {
|
|
||||||
$this->metadata = clone $this->metadata;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Image;
|
|
||||||
|
|
||||||
use Imagine\Image\Metadata\DefaultMetadataReader;
|
|
||||||
use Imagine\Image\Metadata\ExifMetadataReader;
|
|
||||||
use Imagine\Image\Metadata\MetadataReaderInterface;
|
|
||||||
use Imagine\Exception\InvalidArgumentException;
|
|
||||||
|
|
||||||
abstract class AbstractImagine implements ImagineInterface
|
|
||||||
{
|
|
||||||
/** @var MetadataReaderInterface */
|
|
||||||
private $metadataReader;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param MetadataReaderInterface $metadataReader
|
|
||||||
*
|
|
||||||
* @return ImagineInterface
|
|
||||||
*/
|
|
||||||
public function setMetadataReader(MetadataReaderInterface $metadataReader)
|
|
||||||
{
|
|
||||||
$this->metadataReader = $metadataReader;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return MetadataReaderInterface
|
|
||||||
*/
|
|
||||||
public function getMetadataReader()
|
|
||||||
{
|
|
||||||
if (null === $this->metadataReader) {
|
|
||||||
if (ExifMetadataReader::isSupported()) {
|
|
||||||
$this->metadataReader = new ExifMetadataReader();
|
|
||||||
} else {
|
|
||||||
$this->metadataReader = new DefaultMetadataReader();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->metadataReader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks a path that could be used with ImagineInterface::open and returns
|
|
||||||
* a proper string.
|
|
||||||
*
|
|
||||||
* @param string|object $path
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*
|
|
||||||
* @throws InvalidArgumentException In case the given path is invalid.
|
|
||||||
*/
|
|
||||||
protected function checkPath($path)
|
|
||||||
{
|
|
||||||
// provide compatibility with objects such as \SplFileInfo
|
|
||||||
if (is_object($path) && method_exists($path, '__toString')) {
|
|
||||||
$path = (string) $path;
|
|
||||||
}
|
|
||||||
|
|
||||||
$handle = @fopen($path, 'r');
|
|
||||||
|
|
||||||
if (false === $handle) {
|
|
||||||
throw new InvalidArgumentException(sprintf('File %s does not exist.', $path));
|
|
||||||
}
|
|
||||||
|
|
||||||
fclose($handle);
|
|
||||||
|
|
||||||
return $path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Image;
|
|
||||||
|
|
||||||
abstract class AbstractLayers implements LayersInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function add(ImageInterface $image)
|
|
||||||
{
|
|
||||||
$this[] = $image;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function set($offset, ImageInterface $image)
|
|
||||||
{
|
|
||||||
$this[$offset] = $image;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function remove($offset)
|
|
||||||
{
|
|
||||||
unset($this[$offset]);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function get($offset)
|
|
||||||
{
|
|
||||||
return $this[$offset];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function has($offset)
|
|
||||||
{
|
|
||||||
return isset($this[$offset]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,122 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Image;
|
|
||||||
|
|
||||||
use Imagine\Exception\InvalidArgumentException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A box implementation
|
|
||||||
*/
|
|
||||||
final class Box implements BoxInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var integer
|
|
||||||
*/
|
|
||||||
private $width;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var integer
|
|
||||||
*/
|
|
||||||
private $height;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs the Size with given width and height
|
|
||||||
*
|
|
||||||
* @param integer $width
|
|
||||||
* @param integer $height
|
|
||||||
*
|
|
||||||
* @throws InvalidArgumentException
|
|
||||||
*/
|
|
||||||
public function __construct($width, $height)
|
|
||||||
{
|
|
||||||
if ($height < 1 || $width < 1) {
|
|
||||||
throw new InvalidArgumentException(sprintf('Length of either side cannot be 0 or negative, current size is %sx%s', $width, $height));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->width = (int) $width;
|
|
||||||
$this->height = (int) $height;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function getWidth()
|
|
||||||
{
|
|
||||||
return $this->width;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function getHeight()
|
|
||||||
{
|
|
||||||
return $this->height;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function scale($ratio)
|
|
||||||
{
|
|
||||||
return new Box(round($ratio * $this->width), round($ratio * $this->height));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function increase($size)
|
|
||||||
{
|
|
||||||
return new Box((int) $size + $this->width, (int) $size + $this->height);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function contains(BoxInterface $box, PointInterface $start = null)
|
|
||||||
{
|
|
||||||
$start = $start ? $start : new Point(0, 0);
|
|
||||||
|
|
||||||
return $start->in($this) && $this->width >= $box->getWidth() + $start->getX() && $this->height >= $box->getHeight() + $start->getY();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function square()
|
|
||||||
{
|
|
||||||
return $this->width * $this->height;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function __toString()
|
|
||||||
{
|
|
||||||
return sprintf('%dx%d px', $this->width, $this->height);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function widen($width)
|
|
||||||
{
|
|
||||||
return $this->scale($width / $this->width);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function heighten($height)
|
|
||||||
{
|
|
||||||
return $this->scale($height / $this->height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Image;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for a box
|
|
||||||
*/
|
|
||||||
interface BoxInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Gets current image height
|
|
||||||
*
|
|
||||||
* @return integer
|
|
||||||
*/
|
|
||||||
public function getHeight();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets current image width
|
|
||||||
*
|
|
||||||
* @return integer
|
|
||||||
*/
|
|
||||||
public function getWidth();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates new BoxInterface instance with ratios applied to both sides
|
|
||||||
*
|
|
||||||
* @param float $ratio
|
|
||||||
*
|
|
||||||
* @return BoxInterface
|
|
||||||
*/
|
|
||||||
public function scale($ratio);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates new BoxInterface, adding given size to both sides
|
|
||||||
*
|
|
||||||
* @param integer $size
|
|
||||||
*
|
|
||||||
* @return BoxInterface
|
|
||||||
*/
|
|
||||||
public function increase($size);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether current box can fit given box at a given start position,
|
|
||||||
* start position defaults to top left corner xy(0,0)
|
|
||||||
*
|
|
||||||
* @param BoxInterface $box
|
|
||||||
* @param PointInterface $start
|
|
||||||
*
|
|
||||||
* @return Boolean
|
|
||||||
*/
|
|
||||||
public function contains(BoxInterface $box, PointInterface $start = null);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets current box square, useful for getting total number of pixels in a
|
|
||||||
* given box
|
|
||||||
*
|
|
||||||
* @return integer
|
|
||||||
*/
|
|
||||||
public function square();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a string representation of the current box
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function __toString();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resizes box to given width, constraining proportions and returns the new box
|
|
||||||
*
|
|
||||||
* @param integer $width
|
|
||||||
*
|
|
||||||
* @return BoxInterface
|
|
||||||
*/
|
|
||||||
public function widen($width);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resizes box to given height, constraining proportions and returns the new box
|
|
||||||
*
|
|
||||||
* @param integer $height
|
|
||||||
*
|
|
||||||
* @return BoxInterface
|
|
||||||
*/
|
|
||||||
public function heighten($height);
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Image\Fill;
|
|
||||||
|
|
||||||
use Imagine\Image\Palette\Color\ColorInterface;
|
|
||||||
use Imagine\Image\PointInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for the fill
|
|
||||||
*/
|
|
||||||
interface FillInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Gets color of the fill for the given position
|
|
||||||
*
|
|
||||||
* @param PointInterface $position
|
|
||||||
*
|
|
||||||
* @return ColorInterface
|
|
||||||
*/
|
|
||||||
public function getColor(PointInterface $position);
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Imagine package.
|
|
||||||
*
|
|
||||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Imagine\Image\Fill\Gradient;
|
|
||||||
|
|
||||||
use Imagine\Image\PointInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Horizontal gradient fill
|
|
||||||
*/
|
|
||||||
final class Horizontal extends Linear
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function getDistance(PointInterface $position)
|
|
||||||
{
|
|
||||||
return $position->getX();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue