Merge branch 'master' into plugin-hooks

# Conflicts:
#	dev/View/User/Login.js
This commit is contained in:
djmaze 2021-04-15 16:58:59 +02:00
commit 2ab42b1f03
547 changed files with 29045 additions and 52993 deletions

View file

@ -116,23 +116,23 @@ RainLoop 1.15 vs SnappyMail
|js/* |RainLoop |Snappy |
|--------------- |--------: |--------: |
|admin.js |2.158.025 | 100.608 |
|app.js |4.215.733 | 495.934 |
|boot.js | 672.433 | 4.726 |
|libs.js | 647.679 | 227.974 |
|admin.js |2.158.025 | 94.549 |
|app.js |4.215.733 | 458.762 |
|boot.js | 672.433 | 4.554 |
|libs.js | 647.679 | 218.511 |
|polyfills.js | 325.908 | 0 |
|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 |
|--------------- |--------: |--------: |------: |------: |--------: |--------: |
|admin.min.js | 255.514 | 50.607 | 73.899 | 14.944 | 60.674 | 13.364 |
|app.min.js | 516.000 | 240.553 |140.430 | 70.291 |110.657 | 59.309 |
|admin.min.js | 255.514 | 49.871 | 73.899 | 14.771 | 60.674 | 13.213 |
|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 |
|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 |
|TOTAL |1.445.204 | 409.479 |425.477 |129.415 |353.301 |111.950 |
|TOTAL (no admin) |1.189.690 | 358.872 |351.061 |114.471 |292.627 | 98.586 |
|TOTAL |1.445.204 | 394.361 |425.477 |123.965 |353.301 |107.321 |
|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.
@ -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 |
|------------ |-------: |-------: |------: |------: |--------: |
|app.css | 340.334 | 107.279 | 46,959 | 18.816 | 16.160 |
|app.min.css | 274.791 | 88.402 | 39.618 | 16.831 | 14.827 |
|app.css | 340.334 | 106.864 | 46,959 | 18.751 | 16.110 |
|app.min.css | 274.791 | 88.077 | 39.618 | 16.788 | 14.787 |
|boot.css | | 2.066 | | 913 | 742 |
|boot.min.css | | 1.696 | | 818 | 664 |
|admin.css | | 46.393 | | 9.237 | 8.073 |
|admin.min.css | | 37.306 | | 8.191 | 7.282 |
|admin.css | | 46.337 | | 9.221 | 8.058 |
|admin.min.css | | 37.251 | | 8.178 | 7.269 |
### 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).
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

View file

@ -79,7 +79,6 @@ import { AbstractApp } from 'App/Abstract';
import { ComposePopupView } from 'View/Popup/Compose';
import { FolderSystemPopupView } from 'View/Popup/FolderSystem';
import { AskPopupView } from 'View/Popup/Ask';
import { TwoFactorConfigurationPopupView } from 'View/Popup/TwoFactorConfiguration';
// Every 5 minutes
const refreshFolders = 300000;
@ -187,16 +186,18 @@ class AppUser extends AbstractApp {
}
MessageUserStore.listLoading(false);
},
FolderUserStore.currentFolderFullNameRaw(),
iOffset,
SettingsUserStore.messagesPerPage(),
MessageUserStore.listSearch(),
MessageUserStore.listThreadUid()
{
Folder: FolderUserStore.currentFolderFullNameRaw(),
Offset: iOffset,
Limit: SettingsUserStore.messagesPerPage(),
Search: MessageUserStore.listSearch(),
ThreadUid: MessageUserStore.listThreadUid()
}
);
}
recacheInboxMessageList() {
Remote.messageList(()=>{}, getFolderInboxName(), 0, SettingsUserStore.messagesPerPage(), '', '', true);
Remote.messageList(null, {Folder: getFolderInboxName()}, true);
}
/**
@ -910,18 +911,8 @@ class AppUser extends AbstractApp {
rl.setWindowTitle();
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'));
// require.ensure([], function() { // require code splitting
this.foldersReload(value => {
try {
this.bootend();
@ -1038,8 +1029,6 @@ class AppUser extends AbstractApp {
}
});
// }); // require code splitting
}
} else {
this.bootend();
startScreens([LoginUserScreen]);

View file

@ -4,8 +4,6 @@
* @enum {string}
*/
export const Capa = {
TwoFactor: 'TWO_FACTOR',
TwoFactorForce: 'TWO_FACTOR_FORCE',
OpenPGP: 'OPEN_PGP',
Prefetch: 'PREFETCH',
Composer: 'COMPOSER',
@ -86,9 +84,6 @@ export const Notification = {
DomainNotAllowed: 109,
AccountNotAllowed: 110,
AccountTwoFactorAuthRequired: 120,
AccountTwoFactorAuthError: 121,
ContactsSyncError: 140,
CantGetMessageList: 201,

View file

@ -14,6 +14,22 @@ export const FolderType = {
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}
*/

View file

@ -13,16 +13,15 @@ const
exts = {
eml: 'message/rfc822',
mime: 'message/rfc822',
rtx: 'text/richtext',
vcard: 'text/vcard',
vcf: 'text/vcard',
htm: 'text/html',
html: 'text/html',
csv: 'text/csv',
ics: 'text/calendar',
ifb: 'text/calendar',
xml: 'text/xml',
json: app+'json',
asc: app+'pgp-signature',
p10: app+'pkcs10',
p7c: app+'pkcs7-mime',
p7m: app+'pkcs7-mime',
@ -34,8 +33,7 @@ const
pl: 'text/perl',
css: 'text/css',
asp: 'text/asp',
php: app+'x-httpd-php',
phtml: app+'x-httpd-php',
php: app+'x-php',
// images
jpg: 'image/jpeg',
@ -106,18 +104,16 @@ const
export const FileType = {
Unknown: 'unknown',
Text: 'text',
Html: 'html',
Code: 'code',
Eml: 'eml',
WordText: 'word-text',
Word: 'word',
Pdf: 'pdf',
Image: 'image',
Audio: 'audio',
Video: 'video',
Sheet: 'sheet',
Spreadsheet: 'spreadsheet',
Presentation: 'presentation',
Certificate: 'certificate',
CertificateBin: 'certificate-bin',
Archive: 'archive'
};
@ -138,7 +134,7 @@ export const FileInfo = {
return app + 'ms-tnef';
}
let ext = fileName.split('.').pop();
if (/^(txt|text|def|list|in|ini|log|sql|cfg|conf|asc)$/.test(ext))
if (/^(txt|text|def|list|in|ini|log|sql|cfg|conf)$/.test(ext))
return 'text/plain';
if (/^(mpe?g|mpe|m1v|m2v)$/.test(ext))
return 'video/mpeg';
@ -186,15 +182,12 @@ export const FileInfo = {
case 'video' == mimeTypeParts[0] || 'mkv' == ext || 'avi' == ext:
result = FileType.Video;
break;
case ['php', 'js', 'css'].includes(ext):
case ['php', 'js', 'css', 'xml', 'html'].includes(ext) || 'text/html' == mimeType:
result = FileType.Code;
break;
case 'eml' == ext || ['message/delivery-status', 'message/rfc822'].includes(mimeType):
result = FileType.Eml;
break;
case 'text/html' == mimeType || 'html' == ext:
result = FileType.Html;
break;
case 'text' == mimeTypeParts[0] || 'txt' == ext || 'log' == ext:
result = FileType.Text;
break;
@ -204,19 +197,17 @@ export const FileInfo = {
case 'pdf' == type || 'pdf' == ext:
result = FileType.Pdf;
break;
case [app+'pgp-signature', app+'pgp-keys'].includes(mimeType) ||
['asc', 'pem', 'ppk'].includes(ext):
case [app+'pgp-signature', app+'pgp-keys'].includes(mimeType)
|| ['asc', 'pem', 'ppk'].includes(ext)
|| [app+'pkcs7-signature'].includes(mimeType) || 'p7s' == ext:
result = FileType.Certificate;
break;
case [app+'pkcs7-signature'].includes(mimeType) || 'p7s' == ext:
result = FileType.CertificateBin;
break;
case match(msOffice+'.wordprocessingml') || match(openDoc+'.text') || match('vnd.ms-word')
|| ['rtf', 'msword', 'vnd.msword'].includes(type):
result = FileType.WordText;
result = FileType.Word;
break;
case match(msOffice+'.spreadsheetml') || match(openDoc+'.spreadsheet') || match('ms-excel'):
result = FileType.Sheet;
result = FileType.Spreadsheet;
break;
case match(msOffice+'.presentationml') || match(openDoc+'.presentation') || match('ms-powerpoint'):
result = FileType.Presentation;
@ -232,45 +223,23 @@ export const FileInfo = {
* @returns {string}
*/
getTypeIconClass: fileType => {
let result = ['icon-file', ''];
let result = 'icon-file';
switch (fileType) {
case FileType.Text:
case FileType.Eml:
case FileType.WordText:
result[0] += '-text';
break;
case FileType.Html:
case FileType.Code:
result[0] += '-code';
break;
case FileType.Image:
result[0] += '-image';
break;
case FileType.Audio:
result[0] += '-music';
break;
case FileType.Video:
result[0] += '-movie';
break;
case FileType.Archive:
result[0] += '-zip';
break;
case FileType.Certificate:
case FileType.CertificateBin:
result[0] += '-certificate';
break;
case FileType.Sheet:
result[0] += '-excel';
break;
case FileType.Presentation:
result[0] += '-chart-graph';
break;
case FileType.Pdf:
result = ['icon-none', 'pdf'];
break;
// no default
case FileType.Word:
return result + '-text';
case FileType.Code:
case FileType.Image:
case FileType.Audio:
case FileType.Video:
case FileType.Archive:
case FileType.Certificate:
case FileType.Spreadsheet:
case FileType.Presentation:
return result + '-' + fileType;
}
return result;
},
@ -283,7 +252,7 @@ export const FileInfo = {
getCombinedIconClass: data => {
if (isNonEmptyArray(data)) {
let icons = data
.map(item => item ? FileInfo.getIconClass(FileInfo.getExtension(item[0]), item[1])[0] : '')
.map(item => item ? FileInfo.getIconClass(FileInfo.getExtension(item[0]), item[1]) : '')
.validUnique();
return (icons && 1 === icons.length && 'icon-file' !== icons[0])
@ -297,7 +266,7 @@ export const FileInfo = {
friendlySize: bytes => {
bytes = parseInt(bytes, 10) || 0;
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];
}
};

View file

@ -179,12 +179,13 @@ export function mailBox(folder, page = 1, search = '', threadUid = '') {
}
if (1 < page) {
result = result.replace(/[/]+$/, '') + '/p' + page;
result = result.replace(/\/+$/, '') + '/p' + page;
}
if (search) {
result = result.replace(/[/]+$/, '') + '/' + encodeURI(search);
result = result.replace(/\/+$/, '') + '/' + encodeURI(search);
}
return result;
}

View file

@ -3,7 +3,7 @@ import { Notification, UploadErrorCode } from 'Common/Enums';
import { langLink } from 'Common/Links';
import { doc, createElement } from 'Common/Globals';
let I18N_DATA = window.rainloopI18N || {};
let I18N_DATA = window.snappymailI18N || {};
export const trigger = ko.observable(false);
@ -14,14 +14,16 @@ export const trigger = ko.observable(false);
* @returns {string}
*/
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) {
Object.entries(valueList).forEach(([key, value]) => {
result = result.replace('%' + key + '%', value);
});
}
return result;
}
@ -75,7 +77,7 @@ function getNotificationMessage(code) {
let key = getKeyByValue(Notification, code);
if (key) {
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');
script.onload = () => {
// reload the data
if (window.rainloopI18N) {
I18N_DATA = window.rainloopI18N || {};
if (window.snappymailI18N) {
I18N_DATA = window.snappymailI18N;
i18nToNodes(doc);
dispatchEvent(new CustomEvent('reload-time'));
trigger(!trigger());
}
window.rainloopI18N = null;
window.snappymailI18N = null;
script.remove();
resolve();
};
@ -150,7 +152,7 @@ export function reload(admin, language) {
*/
export function convertLangName(language, isEng = false) {
return i18n(
'LANGS_NAMES' + (true === isEng ? '_EN' : '') + '/LANG_' + language.toUpperCase().replace(/[^a-zA-Z0-9]+/g, '_'),
'LANGS_NAMES' + (true === isEng ? '_EN' : '') + '/' + language,
null,
language
);

View file

@ -27,7 +27,6 @@ class AbstractCheckbox extends AbstractComponent {
this.inline = !!params.inline;
this.labeled = undefined !== params.label;
this.labelAnimated = !!params.labelAnimated;
}
click() {

View file

@ -10,8 +10,6 @@ import { AbstractModel } from 'Knoin/AbstractModel';
import { SMAudio } from 'Common/Audio';
const bAllowPdfPreview = undefined !== navigator.mimeTypes['application/pdf'];
export class AttachmentModel extends AbstractModel {
constructor() {
super();
@ -92,27 +90,21 @@ export class AttachmentModel extends AbstractModel {
* @returns {boolean}
*/
isText() {
return (
FileType.Text === this.fileType ||
FileType.Eml === this.fileType ||
FileType.Certificate === this.fileType ||
FileType.Html === this.fileType ||
FileType.Code === this.fileType
);
return FileType.Text === this.fileType || FileType.Eml === this.fileType;
}
/**
* @returns {boolean}
*/
isPdf() {
return FileType.Pdf === this.fileType;
pdfPreview() {
return null != navigator.mimeTypes['application/pdf'] && FileType.Pdf === this.fileType;
}
/**
* @returns {boolean}
*/
hasPreview() {
return this.isImage() || (this.isPdf() && bAllowPdfPreview) || this.isText();
return this.isImage() || this.pdfPreview() || this.isText();
}
/**
@ -155,13 +147,6 @@ export class AttachmentModel extends AbstractModel {
return link ? 'background:url(' + link + ')' : '';
}
/**
* @returns {string}
*/
linkPreviewAsPlain() {
return serverRequestRaw('ViewAsPlain', this.download);
}
/**
* @returns {string}
*/
@ -169,11 +154,11 @@ export class AttachmentModel extends AbstractModel {
let result = '';
switch (true) {
case this.isImage():
case this.isPdf() && bAllowPdfPreview:
case this.pdfPreview():
result = this.linkPreview();
break;
case this.isText():
result = this.linkPreviewAsPlain();
result = serverRequestRaw('ViewAsPlain', this.download);
break;
// no default
}
@ -181,18 +166,6 @@ export class AttachmentModel extends AbstractModel {
return result;
}
/**
* @returns {string}
*/
generateTransferDownloadUrl() {
let link = this.linkDownload();
if ('http' !== link.substr(0, 4)) {
link = location.protocol + '//' + location.host + location.pathname + link;
}
return this.mimeType + ':' + this.fileName + ':' + link;
}
/**
* @param {AttachmentModel} attachment
* @param {*} event
@ -201,7 +174,11 @@ export class AttachmentModel extends AbstractModel {
eventDragStart(attachment, event) {
const localEvent = event.originalEvent || event;
if (attachment && localEvent && localEvent.dataTransfer && localEvent.dataTransfer.setData) {
localEvent.dataTransfer.setData('DownloadURL', this.generateTransferDownloadUrl());
let link = this.linkDownload();
if ('http' !== link.substr(0, 4)) {
link = location.protocol + '//' + location.host + location.pathname + link;
}
localEvent.dataTransfer.setData('DownloadURL', this.mimeType + ':' + this.fileName + ':' + link);
}
return true;
@ -211,13 +188,6 @@ export class AttachmentModel extends AbstractModel {
* @returns {string}
*/
iconClass() {
return FileInfo.getTypeIconClass(this.fileType)[0];
}
/**
* @returns {string}
*/
iconText() {
return FileInfo.getTypeIconClass(this.fileType)[1];
return FileInfo.getTypeIconClass(this.fileType);
}
}

View file

@ -96,13 +96,6 @@ export class ComposeAttachmentModel extends AbstractModel {
* @returns {string}
*/
iconClass() {
return FileInfo.getIconClass(this.fileExt(), this.mimeType())[0];
}
/**
* @returns {string}
*/
iconText() {
return FileInfo.getIconClass(this.fileExt(), this.mimeType())[1];
return FileInfo.getIconClass(this.fileExt(), this.mimeType());
}
}

View file

@ -10,6 +10,7 @@ import * as Local from 'Storage/Client';
import { AppUserStore } from 'Stores/User/App';
import { FolderUserStore } from 'Stores/User/Folder';
import { SettingsUserStore } from 'Stores/User/Settings';
import ko from 'ko';
@ -113,6 +114,7 @@ export class FolderCollectionModel extends AbstractCollectionModel
AppUserStore.threadsAllowed(!!(Settings.app('useImapThread') && this.IsThreadsSupported));
FolderUserStore.folderListOptimized(!!this.Optimized);
FolderUserStore.sortSupported(!!this.IsSortSupported);
let update = false;
@ -123,8 +125,7 @@ export class FolderCollectionModel extends AbstractCollectionModel
SettingsGet('DraftFolder') +
SettingsGet('SpamFolder') +
SettingsGet('TrashFolder') +
SettingsGet('ArchiveFolder') +
SettingsGet('NullFolder'))
SettingsGet('ArchiveFolder'))
) {
Settings.set('SentFolder', this.SystemFolders[ServerFolderType.SENT] || null);
Settings.set('DraftFolder', this.SystemFolders[ServerFolderType.DRAFTS] || null);
@ -147,8 +148,7 @@ export class FolderCollectionModel extends AbstractCollectionModel
DraftFolder: FolderUserStore.draftFolder(),
SpamFolder: FolderUserStore.spamFolder(),
TrashFolder: FolderUserStore.trashFolder(),
ArchiveFolder: FolderUserStore.archiveFolder(),
NullFolder: 'NullFolder'
ArchiveFolder: FolderUserStore.archiveFolder()
});
}
@ -301,7 +301,10 @@ export class FolderModel extends AbstractModel {
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,

View file

@ -45,6 +45,9 @@ export class MessageModel extends AbstractModel {
this.addObservables({
subject: '',
size: 0,
spamScore: 0,
spamResult: '',
isSpam: false,
dateTimeStampInUTC: 0,
priority: MessagePriority.Normal,
@ -116,6 +119,9 @@ export class MessageModel extends AbstractModel {
this._reset();
this.subject('');
this.size(0);
this.spamScore(0);
this.spamResult('');
this.isSpam(false);
this.dateTimeStampInUTC(0);
this.priority(MessagePriority.Normal);
@ -211,10 +217,10 @@ export class MessageModel extends AbstractModel {
/**
* @param {boolean} friendlyView
* @param {boolean=} wrapWithLink = false
* @param {boolean=} wrapWithLink
* @returns {string}
*/
fromToLine(friendlyView, wrapWithLink = false) {
fromToLine(friendlyView, wrapWithLink) {
return this.from.toString(friendlyView, wrapWithLink);
}
@ -232,37 +238,37 @@ export class MessageModel extends AbstractModel {
/**
* @param {boolean} friendlyView
* @param {boolean=} wrapWithLink = false
* @param {boolean=} wrapWithLink
* @returns {string}
*/
toToLine(friendlyView, wrapWithLink = false) {
toToLine(friendlyView, wrapWithLink) {
return this.to.toString(friendlyView, wrapWithLink);
}
/**
* @param {boolean} friendlyView
* @param {boolean=} wrapWithLink = false
* @param {boolean=} wrapWithLink
* @returns {string}
*/
ccToLine(friendlyView, wrapWithLink = false) {
ccToLine(friendlyView, wrapWithLink) {
return this.cc.toString(friendlyView, wrapWithLink);
}
/**
* @param {boolean} friendlyView
* @param {boolean=} wrapWithLink = false
* @param {boolean=} wrapWithLink
* @returns {string}
*/
bccToLine(friendlyView, wrapWithLink = false) {
bccToLine(friendlyView, wrapWithLink) {
return this.bcc.toString(friendlyView, wrapWithLink);
}
/**
* @param {boolean} friendlyView
* @param {boolean=} wrapWithLink = false
* @param {boolean=} wrapWithLink
* @returns {string}
*/
replyToToLine(friendlyView, wrapWithLink = false) {
replyToToLine(friendlyView, wrapWithLink) {
return this.replyTo.toString(friendlyView, wrapWithLink);
}
@ -318,7 +324,7 @@ export class MessageModel extends AbstractModel {
* @param {boolean=} last = false
* @returns {Array}
*/
replyEmails(excludeEmails, last = false) {
replyEmails(excludeEmails, last) {
const result = [],
unic = undefined === excludeEmails ? {} : excludeEmails;
@ -339,7 +345,7 @@ export class MessageModel extends AbstractModel {
* @param {boolean=} last = false
* @returns {Array.<Array>}
*/
replyAllEmails(excludeEmails, last = false) {
replyAllEmails(excludeEmails, last) {
let data = [];
const toResult = [],
ccResult = [],
@ -364,7 +370,7 @@ export class MessageModel extends AbstractModel {
/**
* @param {boolean=} print = false
*/
viewPopupMessage(print = false) {
viewPopupMessage(print) {
const timeStampInUTC = this.dateTimeStampInUTC() || 0,
ccLine = this.ccToLine(false),
m = 0 < timeStampInUTC ? new Date(timeStampInUTC * 1000) : null,
@ -414,6 +420,9 @@ export class MessageModel extends AbstractModel {
this.subject(message.subject());
this.size(message.size());
this.spamScore(message.spamScore());
this.spamResult(message.spamResult());
this.isSpam(message.isSpam());
this.dateTimeStampInUTC(message.dateTimeStampInUTC());
this.priority(message.priority());

View file

@ -55,76 +55,17 @@ class RemoteUserFetch extends AbstractFetchRemote {
* @param {string} sPassword
* @param {boolean} bSignMe
* @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', {
Email: sEmail,
Login: '',
Password: sPassword,
Language: sLanguage || '',
AdditionalCode: sAdditionalCode || '',
AdditionalCodeSignMe: bAdditionalCodeSignMe ? 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
*/
@ -297,54 +238,44 @@ class RemoteUserFetch extends AbstractFetchRemote {
/**
* @param {Function} fCallback
* @param {string} sFolderFullNameRaw
* @param {number=} iOffset = 0
* @param {number=} iLimit = 20
* @param {string=} sSearch = ''
* @param {string=} sThreadUid = ''
* @param {object} params
* @param {boolean=} bSilent = false
*/
messageList(fCallback, sFolderFullNameRaw, iOffset = 0, iLimit = 20, sSearch = '', sThreadUid = '', bSilent = false) {
sFolderFullNameRaw = pString(sFolderFullNameRaw);
const folderHash = getFolderHash(sFolderFullNameRaw),
useThreads = AppUserStore.threadsAllowed() && SettingsUserStore.useThreads(),
messageList(fCallback, params, bSilent = false) {
const
sFolderFullNameRaw = pString(params.Folder),
folderHash = getFolderHash(sFolderFullNameRaw),
useThreads = AppUserStore.threadsAllowed() && SettingsUserStore.useThreads() ? 1 : 0,
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/' +
SUB_QUERY_PREFIX +
'/' +
urlsafeArray([
sFolderFullNameRaw,
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 : ''
};
urlsafeArray([SettingsGet('ProjectHash'),folderHash].concat(Object.values(params)));
params = {};
}
this.defaultRequest(
fCallback,
'MessageList',
params,
sSearch ? 300000 : 30000,
30000,
sGetAdd,
bSilent ? [] : ['MessageList']
);
@ -562,7 +493,7 @@ class RemoteUserFetch extends AbstractFetchRemote {
* @param {Object} oData
*/
sendMessage(fCallback, oData) {
this.defaultRequest(fCallback, 'SendMessage', oData, 300000);
this.defaultRequest(fCallback, 'SendMessage', oData, 30000);
}
/**

View file

@ -74,7 +74,7 @@ export class MailBoxUserScreen extends AbstractScreen {
FolderUserStore.currentFolder(folder);
MessageUserStore.listPage(page);
MessageUserStore.listPage(1 > page ? 1 : page);
MessageUserStore.listSearch(search);
MessageUserStore.listThreadUid(threadUid);
@ -119,23 +119,15 @@ export class MailBoxUserScreen extends AbstractScreen {
*/
routes() {
const
fNormS = (request, vals) => {
if (request) {
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]))];
folder = (request, vals) => request ? decodeURI(pString(vals[0])) : getFolderInboxName(),
fNormS = (request, vals) => [folder(request, vals), request ? pInt(vals[1]) : 1, decodeURI(pString(vals[2]))];
return [
[/^([^/]*)$/, { 'normalize_': fNormS }],
[/^([a-zA-Z0-9~]+)\/(.+)\/?$/, { 'normalize_': fNormD }],
[/^([a-zA-Z0-9~]+)\/p([1-9][0-9]*)(?:\/(.+)\/?)?$/, { 'normalize_': fNormS }]
[/^([^/]*)$/, { normalize_: fNormS }],
[/^([a-zA-Z0-9~]+)\/(.+)\/?$/, { normalize_: (request, vals) =>
[folder(request, vals), 1, decodeURI(pString(vals[1]))]
}],
[/^([a-zA-Z0-9~]+)\/p([1-9][0-9]*)(?:\/(.+)\/?)?$/, { normalize_: fNormS }]
];
}
}

View file

@ -64,7 +64,7 @@ export class SettingsUserScreen extends AbstractSettingsScreen {
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');
}

View file

@ -19,7 +19,7 @@ export class ContactsAdminSettings {
addObservablesTo(this, {
enableContacts: !!SettingsGet('ContactsEnable'),
contactsSync: !!SettingsGet('ContactsSync'),
contactsType: '',
contactsType: SettingsGet('ContactsPdoType'),
pdoDsn: SettingsGet('ContactsPdoDsn'),
pdoUser: SettingsGet('ContactsPdoUser'),
@ -69,8 +69,6 @@ export class ContactsAdminSettings {
})
.extend({ notify: 'always' });
this.contactsType(SettingsGet('ContactsPdoType'));
addSubscribablesTo(this, {
enableContacts: value =>
Remote.saveAdminConfig(null, {

View file

@ -53,7 +53,11 @@ export class GeneralAdminSettings {
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
.observable(pInt(SettingsGet('AttachmentLimit')) / (1024 * 1024))

View file

@ -16,10 +16,6 @@ export class SecurityAdminSettings {
verifySslCertificate: !!SettingsGet('VerifySslCertificate'),
allowSelfSigned: !!SettingsGet('AllowSelfSigned'),
isTwoFactorDropperShown: false,
twoFactorDropperUser: '',
twoFactorDropperUserFocused: false,
adminLogin: SettingsGet('AdminLogin'),
adminLoginError: false,
adminPassword: '',
@ -30,9 +26,7 @@ export class SecurityAdminSettings {
adminPasswordUpdateError: false,
adminPasswordUpdateSuccess: false,
capaOpenPGP: Settings.capa(Capa.OpenPGP),
capaTwoFactorAuth: Settings.capa(Capa.TwoFactor),
capaTwoFactorAuthForce: Settings.capa(Capa.TwoFactorForce)
capaOpenPGP: Settings.capa(Capa.OpenPGP)
});
addSubscribablesTo(this, {
@ -60,18 +54,6 @@ export class SecurityAdminSettings {
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 =>
Remote.saveAdminConfig(null, {
UseLocalProxyForExternalImages: value ? 1 : 0
@ -120,15 +102,6 @@ export class SecurityAdminSettings {
return true;
}
showTwoFactorDropper() {
this.twoFactorDropperUser('');
this.isTwoFactorDropperShown(true);
setTimeout(() => {
this.twoFactorDropperUserFocused(true);
}, 50);
}
onNewAdminPasswordResponse(iError, data) {
if (iError) {
this.adminPasswordUpdateError(true);
@ -147,9 +120,5 @@ export class SecurityAdminSettings {
this.adminPassword('');
this.adminPasswordNew('');
this.adminPasswordNew2('');
this.isTwoFactorDropperShown(false);
this.twoFactorDropperUser('');
this.twoFactorDropperUserFocused(false);
}
}

View file

@ -2,6 +2,7 @@ import ko from 'ko';
import { Notification } from 'Common/Enums';
import { ClientSideKeyName } from 'Common/EnumsUser';
import { Settings } from 'Common/Globals';
import { getNotification } from 'Common/Translator';
import { removeFolderFromCacheList } from 'Common/Cache';
@ -9,6 +10,7 @@ import { removeFolderFromCacheList } from 'Common/Cache';
import * as Local from 'Storage/Client';
import { FolderUserStore } from 'Stores/User/Folder';
import { SettingsUserStore } from 'Stores/User/Settings';
import Remote from 'Remote/User/Fetch';
@ -23,6 +25,7 @@ export class FoldersUserSettings {
this.folderList = FolderUserStore.folderList;
this.folderListOptimized = FolderUserStore.folderListOptimized;
this.folderListError = FolderUserStore.folderListError;
this.hideUnsubscribed = SettingsUserStore.hideUnsubscribed;
this.loading = ko.computed(() => {
const loading = FolderUserStore.foldersLoading(),
@ -36,6 +39,9 @@ export class FoldersUserSettings {
this.folderForDeletion = ko.observable(null).deleteAccessHelper();
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) {

View file

@ -5,18 +5,13 @@ import { Capa, SaveSettingsStep } from 'Common/Enums';
import { Settings } from 'Common/Globals';
import { i18n, trigger as translatorTrigger } from 'Common/Translator';
import { showScreenPopup } from 'Knoin/Knoin';
import { SettingsUserStore } from 'Stores/User/Settings';
import Remote from 'Remote/User/Fetch';
import { TwoFactorConfigurationPopupView } from 'View/Popup/TwoFactorConfiguration';
export class SecurityUserSettings {
constructor() {
this.capaAutoLogout = Settings.capa(Capa.AutoLogout);
this.capaTwoFactor = Settings.capa(Capa.TwoFactor);
this.autoLogout = SettingsUserStore.autoLogout;
this.autoLogoutTrigger = ko.observable(SaveSettingsStep.Idle);
@ -43,8 +38,4 @@ export class SecurityUserSettings {
));
}
}
configureTwoFactor() {
showScreenPopup(TwoFactorConfigurationPopupView);
}
}

View file

@ -1,6 +1,6 @@
import ko from 'ko';
import { FolderType } from 'Common/EnumsUser';
import { FolderType, FolderSortMode } from 'Common/EnumsUser';
import { UNUSED_OPTION_VALUE } from 'Common/Consts';
import { addObservablesTo, addSubscribablesTo } from 'Common/Utils';
import { folderListOptionsBuilder } from 'Common/UtilsUser';
@ -19,6 +19,12 @@ export const FolderUserStore = new class {
*/
displaySpecSetting: false,
/**
* If the IMAP server supports SORT
*/
sortSupported: false,
// sortMode: '',
sentFolder: '',
draftFolder: '',
spamFolder: '',
@ -36,6 +42,8 @@ export const FolderUserStore = new class {
foldersInboxUnreadCount: 0
});
this.sortMode = ko.observable('').extend({ limitedList: Object.values(FolderSortMode) });
this.namespace = '';
this.folderList = ko.observableArray();

View file

@ -30,6 +30,7 @@ export const SettingsUserStore = new class {
allowDraftAutosave: !!SettingsGet('AllowDraftAutosave'),
useThreads: !!SettingsGet('UseThreads'),
replySameFolder: !!SettingsGet('ReplySameFolder'),
hideUnsubscribed: !!SettingsGet('HideUnsubscribed'),
autoLogout: pInt(SettingsGet('AutoLogout'))
});

View file

@ -42,7 +42,6 @@
@import "Filter.less";
@import "Template.less";
@import "OpenPgpKey.less";
@import "TwoFactor.less";
@import "Identity.less";
@import "AdvancedSearch.less";
@import "Attachmnets.less";

View file

@ -29,11 +29,10 @@
}
.checkboxAttachment {
position: absolute;
top: 0;
right: 0;
padding: 1px 4px 0 5px;
bottom: 6px;
cursor: pointer;
position: absolute;
right: 6px;
}
.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 {
.iconMain {

View file

@ -18,14 +18,9 @@
}
.attachmentAreaParent {
padding: 10px 10px 6px 10px;
background: #ddd;
border-top: 1px solid #ccc;
overflow-y: auto;
}
.attachmentAreaParent {
padding: 10px 10px 6px 10px;
.no-attachments-desc {
padding: 50px 10px;

View file

@ -54,7 +54,7 @@
}
.controls {
.inputLoginForm, .inputEmail, .inputLogin, .inputPassword, .inputAdditionalCode {
.inputLoginForm, .inputEmail, .inputLogin, .inputPassword {
font-size: 18px;
height: 40px;
line-height: 20px;

View file

@ -6,6 +6,11 @@ html.rl-no-preview-pane {
}
}
#sort-list-dropdown-id {
padding-left: 6px;
padding-right: 6px;
}
.messageList {
.toolbar {
@ -254,9 +259,9 @@ html.rl-no-preview-pane {
padding: 0 10px 0 5px;
}
time {
time, .sizeParent {
margin: 0 5px;
color: #999;
opacity: 0.6;
font-size: 11px;
white-space: nowrap;
}
@ -472,7 +477,7 @@ html .messageList .line-loading {
line-height: 16px;
margin-left: 30px;
order: 1;
width: calc(100% - 60px);
width: calc(100% - 120px);
}
.senderParent {
@ -483,9 +488,13 @@ html .messageList .line-loading {
order: 1;
}
.attachmentParent {
.sizeParent {
order: 2;
}
.attachmentParent {
order: 3;
}
}
}

View file

@ -136,15 +136,8 @@
}
.informationFull {
margin-top: 10px;
border: 1px solid #999;
border-radius: 5px;
padding: 8px 0;
overflow: hidden;
margin-top: 30px;
.size {
margin-left: 4px;
}
table {
width: 100%;
@ -155,7 +148,7 @@
}
td {
padding: 0 10px;
padding: 4px;
vertical-align: top;
min-width: 43px;
}
@ -163,17 +156,15 @@
td:first-child {
border-right: 1px solid #999;
text-align: right;
white-space: nowrap;
width: 1%;
}
.uiLine {
}
.uiLabel {
.labelTo {
white-space: nowrap;
}
.uiLabelValue {
td + td {
word-break: break-all;
}
}
@ -273,14 +264,10 @@
margin: 0;
}
&:not(.selection-mode) {
.checkboxAttachment {
display: none;
}
&.selection-mode {
.checkboxAttachment {
display: inline-block;
}
}
&.unselectedAttachmentsError {

View file

@ -6,7 +6,7 @@
}
.list-table {
margin-top: 40px;
margin-top: 1em;
.folder-padding {
display: inline-block;

View file

@ -1,8 +0,0 @@
.b-two-factor-content {
width: 750px;
.modal-body {
min-height: 100px;
}
}

View file

@ -64,8 +64,7 @@ class FolderSystemPopupView extends AbstractViewPopup {
DraftFolder: FolderUserStore.draftFolder(),
SpamFolder: FolderUserStore.spamFolder(),
TrashFolder: FolderUserStore.trashFolder(),
ArchiveFolder: FolderUserStore.archiveFolder(),
NullFolder: 'NullFolder'
ArchiveFolder: FolderUserStore.archiveFolder()
});
}).debounce(1000),
fCallback = () => {

View file

@ -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 };

View file

@ -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 };

View file

@ -43,7 +43,6 @@ class LoginUserView extends AbstractViewCenter {
email: SettingsGet('DevEmail'),
password: SettingsGet('DevPassword'),
signMe: false,
additionalCode: '',
emailError: false,
passwordError: false,
@ -56,9 +55,6 @@ class LoginUserView extends AbstractViewCenter {
langRequest: false,
additionalCodeError: false,
additionalCodeSignMe: false,
additionalCodeVisibility: false,
signMeType: LoginSignMeType.Unused
});
@ -84,15 +80,10 @@ class LoginUserView extends AbstractViewCenter {
this.addSubscribables({
email: () => {
this.emailError(false);
this.additionalCode('');
this.additionalCodeVisibility(false);
},
password: () => this.passwordError(false),
additionalCode: () => this.additionalCodeError(false),
additionalCodeVisibility: () => this.additionalCodeError(false),
submitError: value => value || this.submitErrorAddidional(''),
signMeType: iValue => this.signMe(LoginSignMeType.DefaultOn === iValue),
@ -121,16 +112,13 @@ class LoginUserView extends AbstractViewCenter {
submitCommand(self, event) {
let email = this.email().trim(),
valid = event.target.form.reportValidity() && email,
pass = this.password(),
totp = this.additionalCodeVisibility(),
code = totp ? this.additionalCode() : '';
pass = this.password();
this.emailError(!email);
this.passwordError(!pass);
this.additionalCodeError(totp && !code);
let pluginResultCode = 0, pluginResultMessage = '';
Plugins.runHook('user-login-submit', [(iResultCode, sResultMessage) => {
runHook('user-login-submit', [(iResultCode, sResultMessage) => {
pluginResultCode = iResultCode;
pluginResultMessage = sResultMessage;
}]);
@ -156,13 +144,6 @@ class LoginUserView extends AbstractViewCenter {
}
this.submitError(getNotification(iError, oData.ErrorMessage, Notification.UnknownNotification));
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 {
rl.route.reload();
}
@ -170,9 +151,7 @@ class LoginUserView extends AbstractViewCenter {
email,
pass,
!!this.signMe(),
this.bSendLanguage ? this.language() : '',
code,
!!(totp && this.additionalCodeSignMe())
this.bSendLanguage ? this.language() : ''
);
Local.set(ClientSideKeyName.LastSignMe, this.signMe() ? '-1-' : '-0-');

View file

@ -26,7 +26,6 @@ export class FolderListMailBoxUserView extends AbstractViewLeft {
this.composeInEdit = AppUserStore.composeInEdit;
this.messageList = MessageUserStore.list;
this.folderList = FolderUserStore.folderList;
this.folderListSystem = FolderUserStore.folderListSystem;
this.foldersChanging = FolderUserStore.foldersChanging;

View file

@ -52,7 +52,6 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
constructor() {
super('User/MailBox/MessageList', 'MailMessageList');
this.sLastUid = null;
this.bPrefetch = false;
this.emptySubjectValue = '';
@ -69,43 +68,34 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
this.popupVisibility = ko.computed(() => 0 < popupVisibilityNames().length);
this.message = MessageUserStore.message;
this.messageList = MessageUserStore.list;
this.messageListDisableAutoSelect = MessageUserStore.listDisableAutoSelect;
this.folderList = FolderUserStore.folderList;
this.sortSupported = FolderUserStore.sortSupported;
this.composeInEdit = AppUserStore.composeInEdit;
this.leftPanelDisabled = leftPanelDisabled;
this.selectorMessageSelected = MessageUserStore.selectorMessageSelected;
this.selectorMessageFocused = MessageUserStore.selectorMessageFocused;
this.isMessageSelected = MessageUserStore.isMessageSelected;
this.messageListSearch = MessageUserStore.listSearch;
this.messageListThreadUid = MessageUserStore.listThreadUid;
this.messageListError = MessageUserStore.listError;
this.folderMenuForMove = FolderUserStore.folderMenuForMove;
this.useCheckboxesInList = SettingsUserStore.useCheckboxesInList;
this.mainMessageListSearch = MessageUserStore.mainMessageListSearch;
this.messageListEndFolder = MessageUserStore.listEndFolder;
this.messageListEndThreadUid = MessageUserStore.listEndThreadUid;
this.messageListCheckedOrSelected = MessageUserStore.listCheckedOrSelected;
this.messageListCheckedOrSelectedUidsWithSubMails = MessageUserStore.listCheckedOrSelectedUidsWithSubMails;
this.messageListCompleteLoadingThrottle = MessageUserStore.listCompleteLoading;
this.messageListCompleteLoadingThrottleForAnimation = MessageUserStore.listLoadingAnimation;
initOnStartOrLangChange(() => this.emptySubjectValue = i18n('MESSAGE_LIST/EMPTY_SUBJECT_TEXT'));
this.userQuota = QuotaUserStore.quota;
this.userUsageSize = QuotaUserStore.usage;
this.userUsageProc = QuotaUserStore.percentage;
this.addObservables({
moveDropdownTrigger: false,
moreDropdownTrigger: false,
sortDropdownTrigger: false,
dragOverArea: null,
dragOverBodyArea: null,
@ -137,7 +127,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
},
inputProxyMessageListSearch: {
read: this.mainMessageListSearch,
read: MessageUserStore.mainMessageListSearch,
write: value => this.sLastSearchValue = value
},
@ -146,19 +136,19 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
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(),
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(),
@ -174,17 +164,28 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
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.quotaTooltip = this.quotaTooltip.bind(this);
this.selector = new Selector(
this.messageList,
this.selectorMessageSelected,
this.selectorMessageFocused,
MessageUserStore.list,
MessageUserStore.selectorMessageSelected,
MessageUserStore.selectorMessageFocused,
'.messageListItem .actionHandle',
'.messageListItem .checkboxMessage',
'.messageListItem.focused'
@ -209,7 +210,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
addEventListener('mailbox.message.show', e => {
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
);
@ -246,6 +247,11 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
});
}
changeSort(self, event) {
FolderUserStore.sortMode(event.target.closest('li').dataset.sort);
this.reloadCommand();
}
clearCommand() {
if (Settings.capa(Capa.DangerousActions)) {
showScreenPopup(FolderClearPopupView, [FolderUserStore.currentFolder()]);
@ -375,7 +381,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
return false;
});
if (!SettingsUserStore.usePreviewPane() && !this.message()) {
if (!SettingsUserStore.usePreviewPane() && !MessageUserStore.message()) {
this.selector.iFocusedNextHelper = up ? -1 : 1;
} else {
this.selector.iSelectNextHelper = up ? -1 : 1;
@ -391,18 +397,18 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
}
useAutoSelect() {
return !this.messageListDisableAutoSelect()
&& !/is:unseen/.test(this.mainMessageListSearch())
return !MessageUserStore.listDisableAutoSelect()
&& !/is:unseen/.test(MessageUserStore.mainMessageListSearch())
&& SettingsUserStore.usePreviewPane();
}
searchEnterAction() {
this.mainMessageListSearch(this.sLastSearchValue);
MessageUserStore.mainMessageListSearch(this.sLastSearchValue);
this.inputMessageListSearchFocus(false);
}
cancelSearch() {
this.mainMessageListSearch('');
MessageUserStore.mainMessageListSearch('');
this.inputMessageListSearchFocus(false);
}
@ -571,7 +577,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
}
flagMessages(currentMessage) {
const checked = this.messageListCheckedOrSelected();
const checked = MessageUserStore.listCheckedOrSelected();
if (currentMessage) {
const checkedUids = checked.map(message => message.uid);
if (checkedUids.includes(currentMessage.uid)) {
@ -591,7 +597,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
}
flagMessagesFast(bFlag) {
const checked = this.messageListCheckedOrSelected();
const checked = MessageUserStore.listCheckedOrSelected();
if (checked.length) {
if (undefined === bFlag) {
const flagged = checked.filter(message => message.isFlagged());
@ -611,7 +617,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
}
seenMessagesFast(seen) {
const checked = this.messageListCheckedOrSelected();
const checked = MessageUserStore.listCheckedOrSelected();
if (checked.length) {
if (undefined === seen) {
const unseen = checked.filter(message => message.isUnseen());
@ -656,7 +662,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
!this.messageListSearchDesc() &&
!this.messageListError() &&
!this.messageListEndThreadUid() &&
this.messageList.length &&
MessageUserStore.list.length &&
(this.isSpamFolder() || this.isTrashFolder())
);
}
@ -700,7 +706,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
initShortcuts() {
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'));
return false;
}
@ -756,9 +762,9 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
}
shortcuts.add('t', '', [Scope.MessageList], () => {
let message = this.selectorMessageSelected();
let message = MessageUserStore.selectorMessageSelected();
if (!message) {
message = this.selectorMessageFocused();
message = MessageUserStore.selectorMessageFocused();
}
if (message && 0 < message.threadsLen()) {
@ -831,7 +837,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
return false;
});
shortcuts.add('tab,arrowright', '', Scope.MessageList, () => {
this.message() && AppUserStore.focusedState(Scope.MessageView);
MessageUserStore.message() && AppUserStore.focusedState(Scope.MessageView);
return false;
});
@ -841,7 +847,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
prefetchNextTick() {
if (!this.bPrefetch && !ifvisible.now() && this.viewModelVisible) {
const message = this.messageList.find(
const message = MessageUserStore.list.find(
item => item && !hasRequestedMessage(item.folder, item.uid)
);
if (message) {
@ -866,15 +872,15 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
advancedSearchClick() {
Settings.capa(Capa.SearchAdv)
&& showScreenPopup(AdvancedSearchPopupView, [this.mainMessageListSearch()]);
&& showScreenPopup(AdvancedSearchPopupView, [MessageUserStore.mainMessageListSearch()]);
}
quotaTooltip() {
return i18n('MESSAGE_LIST/QUOTA_SIZE', {
SIZE: FileInfo.friendlySize(this.userUsageSize()),
PROC: this.userUsageProc(),
LIMIT: FileInfo.friendlySize(this.userQuota())
});
SIZE: FileInfo.friendlySize(QuotaUserStore.usage()),
PROC: QuotaUserStore.percentage(),
LIMIT: FileInfo.friendlySize(QuotaUserStore.quota())
}).replace(/<[^>]+>/g, '');
}
initUploaderForAppend() {

View file

@ -71,8 +71,6 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
moreDropdownTrigger: false
});
this.pswp = null;
this.moveAction = moveAction;
this.allowComposer = Settings.capa(Capa.Composer);
@ -84,7 +82,6 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
this.message = MessageUserStore.message;
this.hasCheckedMessages = MessageUserStore.hasCheckedMessages;
this.messageListCheckedOrSelectedUidsWithSubMails = MessageUserStore.listCheckedOrSelectedUidsWithSubMails;
this.messageLoadingThrottle = MessageUserStore.messageLoading;
this.messagesBodiesDom = MessageUserStore.messagesBodiesDom;
this.useThreads = SettingsUserStore.useThreads;
@ -136,6 +133,8 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
viewReplyTo: '',
viewTimeStamp: 0,
viewSize: '',
viewSpamScore: 0,
viewSpamStatus: '',
viewLineAsCss: '',
viewViewLink: '',
viewUnsubscribeLink: '',
@ -220,13 +219,15 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
this.viewFromShort(message.fromToLine(true, true));
this.viewFromDkimData(message.fromDkimData());
this.viewToShort(message.toToLine(true, true));
this.viewFrom(message.fromToLine(false));
this.viewTo(message.toToLine(false));
this.viewCc(message.ccToLine(false));
this.viewBcc(message.bccToLine(false));
this.viewReplyTo(message.replyToToLine(false));
this.viewFrom(message.fromToLine());
this.viewTo(message.toToLine());
this.viewCc(message.ccToLine());
this.viewBcc(message.bccToLine());
this.viewReplyTo(message.replyToToLine());
this.viewTimeStamp(message.dateTimeStampInUTC());
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.viewViewLink(message.viewLink());
this.viewUnsubscribeLink(message.getFirstUnsubsribeLink());
@ -717,7 +718,7 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
* @returns {string}
*/
printableCheckedMessageCount() {
const cnt = this.messageListCheckedOrSelectedUidsWithSubMails().length;
const cnt = MessageUserStore.listCheckedOrSelectedUidsWithSubMails().length;
return 0 < cnt ? (100 > cnt ? cnt : '99+') : '';
}

View file

@ -3,7 +3,7 @@
"title": "SnappyMail",
"description": "Simple, modern & fast web-based email client",
"private": true,
"version": "2.4.2",
"version": "2.5.0-rc.4",
"homepage": "https://snappymail.eu",
"author": {
"name": "DJ Maze",
@ -42,11 +42,6 @@
],
"readmeFilename": "README.md",
"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",
"core-js": "^3.8.3",
"eslint": "6.8.0",
@ -75,7 +70,6 @@
"dependencies": {
"gulp-rollup-2": "^1.2.1",
"rollup": "^2.38.0",
"rollup-plugin-babel": "^4.4.0",
"rollup-plugin-external-globals": "^0.6.1",
"rollup-plugin-html": "^0.2.1",
"rollup-plugin-includepaths": "^0.2.4",

345
plugins/README.md Normal file
View 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

View file

@ -22,8 +22,8 @@ class AutoDomainGrabPlugin extends \RainLoop\Plugins\AbstractPlugin
public function Init() : void
{
$this->addHook('filter.smtp-credentials', 'FilterSmtpCredentials');
$this->addHook('filter.imap-credentials', 'FilterImapCredentials');
$this->addHook('smtp.credentials', 'FilterSmtpCredentials');
$this->addHook('imap.credentials', 'FilterImapCredentials');
}
/**

View file

@ -10,7 +10,7 @@ class BlackListPlugin extends \RainLoop\Plugins\AbstractPlugin
public function Init() : void
{
$this->addHook('filter.login-credentials', 'FilterLoginCredentials');
$this->addHook('login.credentials', 'FilterLoginCredentials');
}
/**

View file

@ -9,7 +9,7 @@ class ChangeSmtpEhloMessagePlugin extends \RainLoop\Plugins\AbstractPlugin
public function Init() : void
{
$this->addHook('filter.smtp-credentials', 'FilterSmtpCredentials');
$this->addHook('smtp.credentials', 'FilterSmtpCredentials');
}
/**

View 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;
}
}

View file

@ -4,9 +4,9 @@ class CKEditorPlugin extends \RainLoop\Plugins\AbstractPlugin
{
const
NAME = 'CKEditor',
VERSION = '2.0',
RELEASE = '2021-03-25',
REQUIRED = '2.4.2',
VERSION = '2.1',
RELEASE = '2021-04-01',
REQUIRED = '2.5.0',
DESCRIPTION = 'Use CKEditor instead of Squire as WYSIWYG';
public function Init() : void
@ -32,6 +32,7 @@ class CKEditorPlugin extends \RainLoop\Plugins\AbstractPlugin
}
if (\is_file("{$path}/ckeditor.js")) {
$this->addCss('ckeditor.less');
$this->addJs('ckeditor.js');
}
}

View file

@ -9,7 +9,7 @@ class CustomAuthExamplePlugin extends \RainLoop\Plugins\AbstractPlugin
public function Init() : void
{
$this->addHook('filter.login-credentials', 'FilterLoginСredentials');
$this->addHook('login.credentials', 'FilterLoginCredentials');
}
/**
@ -19,7 +19,7 @@ class CustomAuthExamplePlugin extends \RainLoop\Plugins\AbstractPlugin
*
* @throws \RainLoop\Exceptions\ClientException
*/
public function FilterLoginСredentials(&$sEmail, &$sLogin, &$sPassword)
public function FilterLoginCredentials(&$sEmail, &$sLogin, &$sPassword)
{
// Your custom php logic
// You may change login credentials

View file

@ -10,7 +10,7 @@ class CustomLoginMappingPlugin extends \RainLoop\Plugins\AbstractPlugin
public function Init() : void
{
$this->addHook('filter.login-credentials', 'FilterLoginСredentials');
$this->addHook('login.credentials', 'FilterLoginCredentials');
}
/**
@ -20,7 +20,7 @@ class CustomLoginMappingPlugin extends \RainLoop\Plugins\AbstractPlugin
*
* @throws \RainLoop\Exceptions\ClientException
*/
public function FilterLoginСredentials(&$sEmail, &$sLogin, &$sPassword)
public function FilterLoginCredentials(&$sEmail, &$sLogin, &$sPassword)
{
$sMapping = \trim($this->Config()->Get('plugin', 'mapping', ''));
if (!empty($sMapping))

View file

@ -10,7 +10,7 @@ class OverrideSmtpCredentialsPlugin extends \RainLoop\Plugins\AbstractPlugin
public function Init() : void
{
$this->addHook('filter.smtp-credentials', 'FilterSmtpCredentials');
$this->addHook('smtp.credentials', 'FilterSmtpCredentials');
}
/**

View file

@ -9,7 +9,7 @@ class ProxyauthLoginExamplePlugin extends \RainLoop\Plugins\AbstractPlugin
public function Init() : void
{
$this->addHook('event.login-post-login-provide', 'EventLoginPostLoginProvide');
$this->addHook('login.success', 'EventLoginPostLoginProvide');
}
/**

View file

@ -10,7 +10,7 @@ class WhiteListPlugin extends \RainLoop\Plugins\AbstractPlugin
public function Init() : void
{
$this->addHook('filter.login-credentials', 'FilterLoginCredentials');
$this->addHook('login.credentials', 'FilterLoginCredentials');
}
/**

View file

@ -30,16 +30,6 @@ if (\class_exists('RainLoop\Api'))
{
\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 (!\defined('APP_API_STARTED'))

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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
{
}

View file

@ -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
{
}

View file

@ -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
{
}

View file

@ -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
{
}

View file

@ -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
{
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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());
});
}
}

View file

@ -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;
}
}

View file

@ -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));
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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];
}
}

View file

@ -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);
}
}

View file

@ -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');
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}

View file

@ -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]);
}
}

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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