mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-08-25 10:09:20 +03:00
Improvements of message displaying (internal styles)
This commit is contained in:
parent
0bba7146a6
commit
4457cdbc23
46 changed files with 1967 additions and 359 deletions
7
CONTRIBUTING.md
Normal file
7
CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
Thanks for contributing to RainLoop Webmail!
|
||||||
|
|
||||||
|
1. Sign the CLA (individual or corporate) at <http://www.rainloop.net/contribute/>.
|
||||||
|
2. Fork the repo, do work in a feature branch.
|
||||||
|
3. Issue a pull request.
|
||||||
|
|
||||||
|
If you have any questions, open an issue or email [support@rainloop.net](mailto:support@rainloop.net).
|
||||||
|
|
@ -5,9 +5,9 @@ Simple, modern & fast web-based email client.
|
||||||
|
|
||||||
Modest system requirements, decent performance, simple installation and upgrade, no database required - all these make RainLoop Webmail a perfect choice for your email solution.
|
Modest system requirements, decent performance, simple installation and upgrade, no database required - all these make RainLoop Webmail a perfect choice for your email solution.
|
||||||
|
|
||||||
For more information about the product, check [http://rainloop.net](http://rainloop.net).
|
For more information about the product, check [rainloop.net](http://www.rainloop.net/).
|
||||||
|
|
||||||
Information about installing the product, check the [documentation page](http://rainloop.net/docs/installation/).
|
Information about installing the product, check the [documentation page](http://www.rainloop.net/docs/installation/).
|
||||||
|
|
||||||
## Attention
|
## Attention
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1511,7 +1511,7 @@
|
||||||
self.initVerticalLayoutResizer(Enums.ClientSideKeyName.FolderListSize);
|
self.initVerticalLayoutResizer(Enums.ClientSideKeyName.FolderListSize);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (Tinycon)
|
if (Tinycon && !Settings.settingsGet('Filtered'))
|
||||||
{
|
{
|
||||||
Tinycon.setOptions({
|
Tinycon.setOptions({
|
||||||
fallback: false
|
fallback: false
|
||||||
|
|
|
||||||
|
|
@ -196,7 +196,7 @@
|
||||||
{
|
{
|
||||||
this.modeToggle(true);
|
this.modeToggle(true);
|
||||||
|
|
||||||
sHtml = sHtml.replace(/<[pP][^>]*><\/[pP]>/g, '');
|
sHtml = sHtml.replace(/<p[^>]*><\/p>/ig, '');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.editor.setData(sHtml);
|
this.editor.setData(sHtml);
|
||||||
|
|
|
||||||
|
|
@ -867,13 +867,16 @@
|
||||||
|
|
||||||
sText = sHtml
|
sText = sHtml
|
||||||
.replace(/\u0002([\s\S]*)\u0002/gm, '\u200C$1\u200C')
|
.replace(/\u0002([\s\S]*)\u0002/gm, '\u200C$1\u200C')
|
||||||
.replace(/<[pP][^>]*><\/[pP]>/g, '')
|
.replace(/<p[^>]*><\/p>/gi, '')
|
||||||
.replace(/<pre[^>]*>([\s\S\r\n]*)<\/pre>/gmi, convertPre)
|
.replace(/<pre[^>]*>([\s\S\r\n]*)<\/pre>/gmi, convertPre)
|
||||||
.replace(/[\s]+/gm, ' ')
|
.replace(/[\s]+/gm, ' ')
|
||||||
.replace(/((?:href|data)\s?=\s?)("[^"]+?"|'[^']+?')/gmi, fixAttibuteValue)
|
.replace(/((?:href|data)\s?=\s?)("[^"]+?"|'[^']+?')/gmi, fixAttibuteValue)
|
||||||
.replace(/<br[^>]*>/gmi, '\n')
|
.replace(/<br[^>]*>/gmi, '\n')
|
||||||
.replace(/<\/h[\d]>/gi, '\n')
|
.replace(/<\/h[\d]>/gi, '\n')
|
||||||
.replace(/<\/p>/gi, '\n\n')
|
.replace(/<\/p>/gi, '\n\n')
|
||||||
|
.replace(/<ul[^>]*>/gmi, '\n')
|
||||||
|
.replace(/<\/ul>/gi, '\n')
|
||||||
|
.replace(/<li[^>]*>/gmi, ' * ')
|
||||||
.replace(/<\/li>/gi, '\n')
|
.replace(/<\/li>/gi, '\n')
|
||||||
.replace(/<\/td>/gi, '\n')
|
.replace(/<\/td>/gi, '\n')
|
||||||
.replace(/<\/tr>/gi, '\n')
|
.replace(/<\/tr>/gi, '\n')
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,11 @@
|
||||||
nFoldersInboxUnreadCount = FolderStore.foldersInboxUnreadCount()
|
nFoldersInboxUnreadCount = FolderStore.foldersInboxUnreadCount()
|
||||||
;
|
;
|
||||||
|
|
||||||
|
if (Settings.settingsGet('Filtered'))
|
||||||
|
{
|
||||||
|
nFoldersInboxUnreadCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
require('App/User').setWindowTitle(('' === sEmail ? '' : '' +
|
require('App/User').setWindowTitle(('' === sEmail ? '' : '' +
|
||||||
(0 < nFoldersInboxUnreadCount ? '(' + nFoldersInboxUnreadCount + ') ' : ' ') +
|
(0 < nFoldersInboxUnreadCount ? '(' + nFoldersInboxUnreadCount + ') ' : ' ') +
|
||||||
sEmail + ' - ') + Translator.i18n('TITLES/MAILBOX'));
|
sEmail + ' - ') + Translator.i18n('TITLES/MAILBOX'));
|
||||||
|
|
@ -82,7 +87,6 @@
|
||||||
* @param {string} sFolderHash
|
* @param {string} sFolderHash
|
||||||
* @param {number} iPage
|
* @param {number} iPage
|
||||||
* @param {string} sSearch
|
* @param {string} sSearch
|
||||||
* @param {boolean=} bPreview = false
|
|
||||||
*/
|
*/
|
||||||
MailBoxUserScreen.prototype.onRoute = function (sFolderHash, iPage, sSearch)
|
MailBoxUserScreen.prototype.onRoute = function (sFolderHash, iPage, sSearch)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
|
|
||||||
this.googleEnable = SocialStore.google.enabled;
|
this.googleEnable = SocialStore.google.enabled;
|
||||||
this.googleEnableAuth = SocialStore.google.capa.auth;
|
this.googleEnableAuth = SocialStore.google.capa.auth;
|
||||||
|
this.googleEnableAuthFast = SocialStore.google.capa.authFast;
|
||||||
this.googleEnableDrive = SocialStore.google.capa.drive;
|
this.googleEnableDrive = SocialStore.google.capa.drive;
|
||||||
this.googleEnablePreview = SocialStore.google.capa.preview;
|
this.googleEnablePreview = SocialStore.google.capa.preview;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
this.googleEnable = SocialStore.google.enabled;
|
this.googleEnable = SocialStore.google.enabled;
|
||||||
this.googleEnableAuth = SocialStore.google.capa.auth;
|
this.googleEnableAuth = SocialStore.google.capa.auth;
|
||||||
|
this.googleEnableAuthFast = SocialStore.google.capa.authFast;
|
||||||
this.googleEnableDrive = SocialStore.google.capa.drive;
|
this.googleEnableDrive = SocialStore.google.capa.drive;
|
||||||
this.googleEnablePreview = SocialStore.google.capa.preview;
|
this.googleEnablePreview = SocialStore.google.capa.preview;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@
|
||||||
|
|
||||||
this.google.capa = {};
|
this.google.capa = {};
|
||||||
this.google.capa.auth = ko.observable(false);
|
this.google.capa.auth = ko.observable(false);
|
||||||
|
this.google.capa.authFast = ko.observable(false);
|
||||||
this.google.capa.drive = ko.observable(false);
|
this.google.capa.drive = ko.observable(false);
|
||||||
this.google.capa.preview = ko.observable(false);
|
this.google.capa.preview = ko.observable(false);
|
||||||
|
|
||||||
|
|
@ -88,6 +89,7 @@
|
||||||
this.google.apiKey(Settings.settingsGet('GoogleApiKey'));
|
this.google.apiKey(Settings.settingsGet('GoogleApiKey'));
|
||||||
|
|
||||||
this.google.capa.auth(!!Settings.settingsGet('AllowGoogleSocialAuth'));
|
this.google.capa.auth(!!Settings.settingsGet('AllowGoogleSocialAuth'));
|
||||||
|
this.google.capa.authFast(!!Settings.settingsGet('AllowGoogleSocialAuthFast'));
|
||||||
this.google.capa.drive(!!Settings.settingsGet('AllowGoogleSocialDrive'));
|
this.google.capa.drive(!!Settings.settingsGet('AllowGoogleSocialDrive'));
|
||||||
this.google.capa.preview(!!Settings.settingsGet('AllowGoogleSocialPreview'));
|
this.google.capa.preview(!!Settings.settingsGet('AllowGoogleSocialPreview'));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,9 @@
|
||||||
max-width: 450px;
|
max-width: 450px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wrapper-parent {
|
||||||
|
}
|
||||||
|
|
||||||
.loginForm {
|
.loginForm {
|
||||||
background-color: #efefef;
|
background-color: #efefef;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|
@ -34,6 +37,18 @@
|
||||||
width: 303px;
|
width: 303px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loginWelcomeForm {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-on {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-off {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* .control-label {
|
/* .control-label {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
|
|
|
||||||
|
|
@ -331,6 +331,10 @@ html.rl-no-preview-pane {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bodySubHeader {
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
.bodyText {
|
.bodyText {
|
||||||
|
|
||||||
color: #000;
|
color: #000;
|
||||||
|
|
@ -338,9 +342,11 @@ html.rl-no-preview-pane {
|
||||||
|
|
||||||
.b-text-part {
|
.b-text-part {
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
div[data-x-div-type=html] {
|
div[data-x-div-type=html] {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
div[data-x-div-type=html] {
|
div[data-x-div-type=body] {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -357,14 +363,10 @@ html.rl-no-preview-pane {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
table {
|
|
||||||
border-collapse: separate;
|
|
||||||
}
|
|
||||||
|
|
||||||
blockquote {
|
blockquote {
|
||||||
border-left: 2px solid #000;
|
border-left: 2px solid #000;
|
||||||
margin: 0;
|
|
||||||
padding: 0px 10px;
|
padding: 0px 10px;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rl-bq-switcher.hidden-bq {
|
.rl-bq-switcher.hidden-bq {
|
||||||
|
|
@ -372,8 +374,10 @@ html.rl-no-preview-pane {
|
||||||
}
|
}
|
||||||
|
|
||||||
&.html {
|
&.html {
|
||||||
|
|
||||||
div[data-x-div-type=body] {
|
div[data-x-div-type=body] {
|
||||||
margin: 15px;
|
padding: 15px;
|
||||||
|
// margin: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,10 @@
|
||||||
min-height: 300px;
|
min-height: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.g-ui-100-proc-height {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.g-ui-absolute-reset {
|
.g-ui-absolute-reset {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,19 @@
|
||||||
width: 17px;
|
width: 17px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.iconsize50 {
|
||||||
|
line-height: 50px;
|
||||||
|
font-size: 50px;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
.iconsize24 {
|
.iconsize24 {
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconsize24 {
|
.iconsize20 {
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,8 @@
|
||||||
{
|
{
|
||||||
AbstractView.call(this, 'Center', 'Login');
|
AbstractView.call(this, 'Center', 'Login');
|
||||||
|
|
||||||
|
this.welcome = ko.observable(!!Settings.settingsGet('UseLoginWelcomePage'));
|
||||||
|
|
||||||
this.email = ko.observable('');
|
this.email = ko.observable('');
|
||||||
this.password = ko.observable('');
|
this.password = ko.observable('');
|
||||||
this.signMe = ko.observable(false);
|
this.signMe = ko.observable(false);
|
||||||
|
|
@ -294,11 +296,12 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
this.googleLoginEnabled = ko.observable(false);
|
this.googleLoginEnabled = ko.observable(false);
|
||||||
|
this.googleFastLoginEnabled = ko.observable(false);
|
||||||
|
|
||||||
this.googleCommand = Utils.createCommand(this, function () {
|
this.googleCommand = Utils.createCommand(this, function () {
|
||||||
|
|
||||||
window.open(Links.socialGoogle(), 'Google',
|
window.open(Links.socialGoogle(), 'Google',
|
||||||
'left=200,top=100,width=650,height=450,menubar=no,status=no,resizable=yes,scrollbars=yes');
|
'left=200,top=100,width=650,height=500,menubar=no,status=no,resizable=yes,scrollbars=yes');
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
|
@ -306,15 +309,15 @@
|
||||||
return !this.submitRequest() && this.googleLoginEnabled();
|
return !this.submitRequest() && this.googleLoginEnabled();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.googleXAuthCommand = Utils.createCommand(this, function () {
|
this.googleFastCommand = Utils.createCommand(this, function () {
|
||||||
|
|
||||||
window.open(Links.socialGoogle(true), 'Google',
|
window.open(Links.socialGoogle(true), 'Google',
|
||||||
'left=200,top=100,width=650,height=450,menubar=no,status=no,resizable=yes,scrollbars=yes');
|
'left=200,top=100,width=650,height=500,menubar=no,status=no,resizable=yes,scrollbars=yes');
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
}, function () {
|
}, function () {
|
||||||
return !this.submitRequest() && this.googleLoginEnabled();
|
return !this.submitRequest() && this.googleFastLoginEnabled();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.twitterLoginEnabled = ko.observable(false);
|
this.twitterLoginEnabled = ko.observable(false);
|
||||||
|
|
@ -347,6 +350,11 @@
|
||||||
kn.extendAsViewModel(['View/User/Login', 'View/App/Login', 'LoginViewModel'], LoginUserView);
|
kn.extendAsViewModel(['View/User/Login', 'View/App/Login', 'LoginViewModel'], LoginUserView);
|
||||||
_.extend(LoginUserView.prototype, AbstractView.prototype);
|
_.extend(LoginUserView.prototype, AbstractView.prototype);
|
||||||
|
|
||||||
|
LoginUserView.prototype.displayMainForm = function ()
|
||||||
|
{
|
||||||
|
this.welcome(false);
|
||||||
|
};
|
||||||
|
|
||||||
LoginUserView.prototype.onShow = function ()
|
LoginUserView.prototype.onShow = function ()
|
||||||
{
|
{
|
||||||
kn.routeOff();
|
kn.routeOff();
|
||||||
|
|
@ -404,6 +412,8 @@
|
||||||
this.twitterLoginEnabled(!!Settings.settingsGet('AllowTwitterSocial'));
|
this.twitterLoginEnabled(!!Settings.settingsGet('AllowTwitterSocial'));
|
||||||
this.googleLoginEnabled(!!Settings.settingsGet('AllowGoogleSocial') &&
|
this.googleLoginEnabled(!!Settings.settingsGet('AllowGoogleSocial') &&
|
||||||
!!Settings.settingsGet('AllowGoogleSocialAuth'));
|
!!Settings.settingsGet('AllowGoogleSocialAuth'));
|
||||||
|
this.googleFastLoginEnabled(!!Settings.settingsGet('AllowGoogleSocial') &&
|
||||||
|
!!Settings.settingsGet('AllowGoogleSocialAuthFast'));
|
||||||
|
|
||||||
switch (sSignMe)
|
switch (sSignMe)
|
||||||
{
|
{
|
||||||
|
|
@ -433,7 +443,7 @@
|
||||||
this.email(AppStore.devEmail);
|
this.email(AppStore.devEmail);
|
||||||
this.password(AppStore.devPassword);
|
this.password(AppStore.devPassword);
|
||||||
|
|
||||||
if (this.googleLoginEnabled())
|
if (this.googleLoginEnabled() || this.googleFastLoginEnabled())
|
||||||
{
|
{
|
||||||
window['rl_' + sJsHash + '_google_login_service'] = fSocial;
|
window['rl_' + sJsHash + '_google_login_service'] = fSocial;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,6 @@
|
||||||
|
|
||||||
Cache = require('Common/Cache'),
|
Cache = require('Common/Cache'),
|
||||||
|
|
||||||
EmailModel = require('Model/Email'),
|
|
||||||
|
|
||||||
SocialStore = require('Stores/Social'),
|
SocialStore = require('Stores/Social'),
|
||||||
AppStore = require('Stores/User/App'),
|
AppStore = require('Stores/User/App'),
|
||||||
SettingsStore = require('Stores/User/Settings'),
|
SettingsStore = require('Stores/User/Settings'),
|
||||||
|
|
@ -66,6 +64,8 @@
|
||||||
this.oHeaderDom = null;
|
this.oHeaderDom = null;
|
||||||
this.oMessageScrollerDom = null;
|
this.oMessageScrollerDom = null;
|
||||||
|
|
||||||
|
this.bodyBackgroundColor = ko.observable('');
|
||||||
|
|
||||||
this.pswp = null;
|
this.pswp = null;
|
||||||
|
|
||||||
this.allowComposer = !!Settings.capa(Enums.Capa.Composer);
|
this.allowComposer = !!Settings.capa(Enums.Capa.Composer);
|
||||||
|
|
@ -353,6 +353,10 @@
|
||||||
|
|
||||||
}, this);
|
}, this);
|
||||||
|
|
||||||
|
this.messageActiveDom.subscribe(function (oDom) {
|
||||||
|
this.bodyBackgroundColor(oDom ? this.detectDomBackgroundColor(oDom): '');
|
||||||
|
}, this);
|
||||||
|
|
||||||
this.message.subscribe(function (oMessage) {
|
this.message.subscribe(function (oMessage) {
|
||||||
|
|
||||||
this.messageActiveDom(null);
|
this.messageActiveDom(null);
|
||||||
|
|
@ -466,6 +470,59 @@
|
||||||
kn.extendAsViewModel(['View/User/MailBox/MessageView', 'View/App/MailBox/MessageView', 'MailBoxMessageViewViewModel'], MessageViewMailBoxUserView);
|
kn.extendAsViewModel(['View/User/MailBox/MessageView', 'View/App/MailBox/MessageView', 'MailBoxMessageViewViewModel'], MessageViewMailBoxUserView);
|
||||||
_.extend(MessageViewMailBoxUserView.prototype, AbstractView.prototype);
|
_.extend(MessageViewMailBoxUserView.prototype, AbstractView.prototype);
|
||||||
|
|
||||||
|
MessageViewMailBoxUserView.prototype.detectDomBackgroundColor = function (oDom)
|
||||||
|
{
|
||||||
|
var
|
||||||
|
iLimit = 5,
|
||||||
|
sResult = '',
|
||||||
|
aC = null,
|
||||||
|
fFindDom = function (oDom) {
|
||||||
|
var aC = oDom ? oDom.children() : null;
|
||||||
|
return (aC && 1 === aC.length && aC.is('table,div,center')) ? aC : null;
|
||||||
|
},
|
||||||
|
fFindColor = function (oDom) {
|
||||||
|
var sResult = '';
|
||||||
|
if (oDom)
|
||||||
|
{
|
||||||
|
sResult = oDom.css('background-color') || '';
|
||||||
|
if (!oDom.is('table'))
|
||||||
|
{
|
||||||
|
sResult = 'rgba(0, 0, 0, 0)' === sResult || 'transparent' === sResult ? '' : sResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sResult;
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
if (oDom && 1 === oDom.length)
|
||||||
|
{
|
||||||
|
aC = oDom;
|
||||||
|
while ('' === sResult)
|
||||||
|
{
|
||||||
|
iLimit--;
|
||||||
|
if (0 >= iLimit)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
aC = fFindDom(aC);
|
||||||
|
if (aC)
|
||||||
|
{
|
||||||
|
sResult = fFindColor(aC);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sResult = 'rgba(0, 0, 0, 0)' === sResult || 'transparent' === sResult ? '' : sResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sResult;
|
||||||
|
};
|
||||||
|
|
||||||
MessageViewMailBoxUserView.prototype.isPgpActionVisible = function ()
|
MessageViewMailBoxUserView.prototype.isPgpActionVisible = function ()
|
||||||
{
|
{
|
||||||
return Enums.SignedVerifyStatus.Success !== this.viewPgpSignedVerifyStatus();
|
return Enums.SignedVerifyStatus.Success !== this.viewPgpSignedVerifyStatus();
|
||||||
|
|
@ -552,27 +609,28 @@
|
||||||
* @todo
|
* @todo
|
||||||
* @param {string} sEmail
|
* @param {string} sEmail
|
||||||
*/
|
*/
|
||||||
MessageViewMailBoxUserView.prototype.displayMailToPopup = function (sMailToUrl)
|
// MessageViewMailBoxUserView.prototype.displayMailToPopup = function (sMailToUrl)
|
||||||
{
|
// {
|
||||||
sMailToUrl = sMailToUrl.replace(/\?.+$/, '');
|
// sMailToUrl = sMailToUrl.replace(/\?.+$/, '');
|
||||||
|
//
|
||||||
var
|
// var
|
||||||
sResult = '',
|
// sResult = '',
|
||||||
aTo = [],
|
// aTo = [],
|
||||||
fParseEmailLine = function (sLine) {
|
// EmailModel = require('Model/Email'),
|
||||||
return sLine ? _.compact(_.map([window.decodeURIComponent(sLine)], function (sItem) {
|
// fParseEmailLine = function (sLine) {
|
||||||
var oEmailModel = new EmailModel();
|
// return sLine ? _.compact(_.map([window.decodeURIComponent(sLine)], function (sItem) {
|
||||||
oEmailModel.mailsoParse(sItem);
|
// var oEmailModel = new EmailModel();
|
||||||
return '' !== oEmailModel.email ? oEmailModel : null;
|
// oEmailModel.mailsoParse(sItem);
|
||||||
})) : null;
|
// return '' !== oEmailModel.email ? oEmailModel : null;
|
||||||
}
|
// })) : null;
|
||||||
;
|
// }
|
||||||
|
// ;
|
||||||
aTo = fParseEmailLine(sMailToUrl);
|
//
|
||||||
sResult = aTo && aTo[0] ? aTo[0].email : '';
|
// aTo = fParseEmailLine(sMailToUrl);
|
||||||
|
// sResult = aTo && aTo[0] ? aTo[0].email : '';
|
||||||
window.console.log(sResult);
|
//
|
||||||
};
|
// return sResult;
|
||||||
|
// };
|
||||||
|
|
||||||
MessageViewMailBoxUserView.prototype.onBuild = function (oDom)
|
MessageViewMailBoxUserView.prototype.onBuild = function (oDom)
|
||||||
{
|
{
|
||||||
|
|
@ -592,6 +650,7 @@
|
||||||
}
|
}
|
||||||
}, this);
|
}, this);
|
||||||
|
|
||||||
|
this.showAttachmnetControls.subscribe(fCheckHeaderHeight);
|
||||||
this.fullScreenMode.subscribe(fCheckHeaderHeight);
|
this.fullScreenMode.subscribe(fCheckHeaderHeight);
|
||||||
this.showFullInfo.subscribe(fCheckHeaderHeight);
|
this.showFullInfo.subscribe(fCheckHeaderHeight);
|
||||||
this.message.subscribe(fCheckHeaderHeight);
|
this.message.subscribe(fCheckHeaderHeight);
|
||||||
|
|
@ -806,18 +865,6 @@
|
||||||
this.initShortcuts();
|
this.initShortcuts();
|
||||||
};
|
};
|
||||||
|
|
||||||
MessageViewMailBoxUserView.prototype.selectSelectedThreadMessage = function ()
|
|
||||||
{
|
|
||||||
var self = this;
|
|
||||||
_.delay(function () {
|
|
||||||
var oLast = self.oDom ? $('.thread-list .e-item.thread-list-message.real-msg.selected a.e-link', self.oDom) : null;
|
|
||||||
if (oLast && oLast[0])
|
|
||||||
{
|
|
||||||
oLast.focus();
|
|
||||||
}
|
|
||||||
}, 30);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
*/
|
*/
|
||||||
|
|
@ -1196,6 +1243,8 @@
|
||||||
{
|
{
|
||||||
oMessage.showExternalImages(true);
|
oMessage.showExternalImages(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.checkHeaderHeight();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1217,6 +1266,8 @@
|
||||||
{
|
{
|
||||||
oMessage.verifyPgpSignedClearMessage();
|
oMessage.verifyPgpSignedClearMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.checkHeaderHeight();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1248,6 +1299,8 @@
|
||||||
|
|
||||||
require('App/User').reloadFlagsCurrentMessageListAndMessageFromCache();
|
require('App/User').reloadFlagsCurrentMessageListAndMessageFromCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.checkHeaderHeight();
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = MessageViewMailBoxUserView;
|
module.exports = MessageViewMailBoxUserView;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "RainLoop",
|
"name": "RainLoop",
|
||||||
"title": "RainLoop Webmail",
|
"title": "RainLoop Webmail",
|
||||||
"version": "1.9.1",
|
"version": "1.9.1",
|
||||||
"release": "333",
|
"release": "334",
|
||||||
"description": "Simple, modern & fast web-based email client",
|
"description": "Simple, modern & fast web-based email client",
|
||||||
"homepage": "http://rainloop.net",
|
"homepage": "http://rainloop.net",
|
||||||
"main": "gulpfile.js",
|
"main": "gulpfile.js",
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ if (!\defined('RAINLOOP_APP_LIBRARIES_PATH'))
|
||||||
|
|
||||||
if (\class_exists('RainLoop\Api'))
|
if (\class_exists('RainLoop\Api'))
|
||||||
{
|
{
|
||||||
if (!\class_exists('MailSo\Version'))
|
if (!\class_exists('MailSo\Version', false))
|
||||||
{
|
{
|
||||||
include APP_VERSION_ROOT_PATH.'app/libraries/MailSo/MailSo.php';
|
include APP_VERSION_ROOT_PATH.'app/libraries/MailSo/MailSo.php';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,23 +35,32 @@ class HtmlUtils
|
||||||
*/
|
*/
|
||||||
public static function GetDomFromText($sText, $sHtmlAttrs = '', $sBodyAttrs = '')
|
public static function GetDomFromText($sText, $sHtmlAttrs = '', $sBodyAttrs = '')
|
||||||
{
|
{
|
||||||
static $bOnce = true;
|
$bState = true;
|
||||||
if ($bOnce)
|
|
||||||
{
|
|
||||||
$bOnce = false;
|
|
||||||
if (\MailSo\Base\Utils::FunctionExistsAndEnabled('libxml_use_internal_errors'))
|
if (\MailSo\Base\Utils::FunctionExistsAndEnabled('libxml_use_internal_errors'))
|
||||||
{
|
{
|
||||||
@\libxml_use_internal_errors(true);
|
$bState = \libxml_use_internal_errors(true);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$oDom = new \DOMDocument('1.0', 'utf-8');
|
$oDom = new \DOMDocument('1.0', 'utf-8');
|
||||||
$oDom->encoding = 'UTF-8';
|
$oDom->encoding = 'UTF-8';
|
||||||
|
$oDom->strictErrorChecking = false;
|
||||||
$oDom->formatOutput = false;
|
$oDom->formatOutput = false;
|
||||||
|
|
||||||
@$oDom->loadHTML('<'.'?xml version="1.0" encoding="utf-8"?'.'>'.
|
@$oDom->loadHTML('<'.'?xml version="1.0" encoding="utf-8"?'.'>'.
|
||||||
'<html '.$sHtmlAttrs.'><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head><body '.$sBodyAttrs.'>'.$sText.'</body></html>');
|
'<html '.$sHtmlAttrs.'><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head><body '.$sBodyAttrs.'>'.$sText.'</body></html>');
|
||||||
|
|
||||||
|
@$oDom->normalizeDocument();
|
||||||
|
|
||||||
|
if (\MailSo\Base\Utils::FunctionExistsAndEnabled('libxml_use_internal_errors'))
|
||||||
|
{
|
||||||
|
@\libxml_clear_errors();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (\MailSo\Base\Utils::FunctionExistsAndEnabled('libxml_use_internal_errors'))
|
||||||
|
{
|
||||||
|
\libxml_use_internal_errors($bState);
|
||||||
|
}
|
||||||
|
|
||||||
return $oDom;
|
return $oDom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,13 +74,13 @@ class HtmlUtils
|
||||||
public static function ClearBodyAndHtmlTag($sHtml, &$sHtmlAttrs = '', &$sBodyAttrs = '')
|
public static function ClearBodyAndHtmlTag($sHtml, &$sHtmlAttrs = '', &$sBodyAttrs = '')
|
||||||
{
|
{
|
||||||
$aMatch = array();
|
$aMatch = array();
|
||||||
if (preg_match('/<html([^>]+)>/im', $sHtml, $aMatch) && !empty($aMatch[1]))
|
if (\preg_match('/<html([^>]+)>/im', $sHtml, $aMatch) && !empty($aMatch[1]))
|
||||||
{
|
{
|
||||||
$sHtmlAttrs = $aMatch[1];
|
$sHtmlAttrs = $aMatch[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
$aMatch = array();
|
$aMatch = array();
|
||||||
if (preg_match('/<body([^>]+)>/im', $sHtml, $aMatch) && !empty($aMatch[1]))
|
if (\preg_match('/<body([^>]+)>/im', $sHtml, $aMatch) && !empty($aMatch[1]))
|
||||||
{
|
{
|
||||||
$sBodyAttrs = $aMatch[1];
|
$sBodyAttrs = $aMatch[1];
|
||||||
}
|
}
|
||||||
|
|
@ -81,6 +90,29 @@ class HtmlUtils
|
||||||
$sHtml = \preg_replace('/<html([^>]*)>/im', '', $sHtml);
|
$sHtml = \preg_replace('/<html([^>]*)>/im', '', $sHtml);
|
||||||
$sHtml = \preg_replace('/<\/html>/im', '', $sHtml);
|
$sHtml = \preg_replace('/<\/html>/im', '', $sHtml);
|
||||||
|
|
||||||
|
$sHtmlAttrs = \preg_replace('/xmlns:[a-z]="[^"]*"/im', '', $sHtmlAttrs);
|
||||||
|
$sHtmlAttrs = \preg_replace('/xmlns="[^"]*"/im', '', $sHtmlAttrs);
|
||||||
|
$sBodyAttrs = \preg_replace('/xmlns:[a-z]="[^"]*"/im', '', $sBodyAttrs);
|
||||||
|
|
||||||
|
return $sHtml;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $sHtml
|
||||||
|
* @param bool $bClearEmpty = true
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function FixSchemas($sHtml, $bClearEmpty = true)
|
||||||
|
{
|
||||||
|
if ($bClearEmpty)
|
||||||
|
{
|
||||||
|
$sHtml = \str_replace('<o:p></o:p>', '', $sHtml);
|
||||||
|
}
|
||||||
|
|
||||||
|
$sHtml = \str_replace('<o:p>', '<span data-ms="o:p">', $sHtml);
|
||||||
|
$sHtml = \str_replace('</o:p>', '</span>', $sHtml);
|
||||||
|
|
||||||
return $sHtml;
|
return $sHtml;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,6 +136,7 @@ class HtmlUtils
|
||||||
}
|
}
|
||||||
|
|
||||||
$aToRemove = array(
|
$aToRemove = array(
|
||||||
|
'/<p[^>]*><\/p>/i',
|
||||||
'/<!doctype[^>]*>/msi',
|
'/<!doctype[^>]*>/msi',
|
||||||
'/<\?xml [^>]*\?>/msi'
|
'/<\?xml [^>]*\?>/msi'
|
||||||
);
|
);
|
||||||
|
|
@ -262,7 +295,7 @@ class HtmlUtils
|
||||||
// $mResult = false;
|
// $mResult = false;
|
||||||
// $oCss = null;
|
// $oCss = null;
|
||||||
//
|
//
|
||||||
// if (!\class_exists('\\Sabberworm\\CSS\\Parser'))
|
// if (!\class_exists('Sabberworm\CSS\Parser'))
|
||||||
// {
|
// {
|
||||||
// return $mResult;
|
// return $mResult;
|
||||||
// }
|
// }
|
||||||
|
|
@ -637,15 +670,17 @@ class HtmlUtils
|
||||||
* @param bool $bDoNotReplaceExternalUrl = false
|
* @param bool $bDoNotReplaceExternalUrl = false
|
||||||
* @param bool $bFindLinksInHtml = false
|
* @param bool $bFindLinksInHtml = false
|
||||||
* @param callback|null $fAdditionalExternalFilter = null
|
* @param callback|null $fAdditionalExternalFilter = null
|
||||||
|
* @param callback|null $fAdditionalDomReader = null
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public static function ClearHtml($sHtml, &$bHasExternals = false, &$aFoundCIDs = array(),
|
public static function ClearHtml($sHtml, &$bHasExternals = false, &$aFoundCIDs = array(),
|
||||||
$aContentLocationUrls = array(), &$aFoundedContentLocationUrls = array(),
|
$aContentLocationUrls = array(), &$aFoundedContentLocationUrls = array(),
|
||||||
$bDoNotReplaceExternalUrl = false, $bFindLinksInHtml = false, $fAdditionalExternalFilter = null)
|
$bDoNotReplaceExternalUrl = false, $bFindLinksInHtml = false,
|
||||||
|
$fAdditionalExternalFilter = null, $fAdditionalDomReader = false,
|
||||||
|
$bTryToDetectHiddenImages = false)
|
||||||
{
|
{
|
||||||
$sResult = '';
|
$sResult = '';
|
||||||
// $aBodyStyles = array();
|
|
||||||
|
|
||||||
$sHtml = null === $sHtml ? '' : (string) $sHtml;
|
$sHtml = null === $sHtml ? '' : (string) $sHtml;
|
||||||
$sHtml = \trim($sHtml);
|
$sHtml = \trim($sHtml);
|
||||||
|
|
@ -659,8 +694,15 @@ class HtmlUtils
|
||||||
$fAdditionalExternalFilter = null;
|
$fAdditionalExternalFilter = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($fAdditionalDomReader && !\is_callable($fAdditionalDomReader))
|
||||||
|
{
|
||||||
|
$fAdditionalDomReader = null;
|
||||||
|
}
|
||||||
|
|
||||||
$bHasExternals = false;
|
$bHasExternals = false;
|
||||||
|
|
||||||
|
$sHtml = \MailSo\Base\HtmlUtils::FixSchemas($sHtml);
|
||||||
|
|
||||||
$sHtml = \MailSo\Base\HtmlUtils::ClearTags($sHtml, false);
|
$sHtml = \MailSo\Base\HtmlUtils::ClearTags($sHtml, false);
|
||||||
$sHtml = \MailSo\Base\HtmlUtils::ClearOn($sHtml);
|
$sHtml = \MailSo\Base\HtmlUtils::ClearOn($sHtml);
|
||||||
|
|
||||||
|
|
@ -673,11 +715,37 @@ class HtmlUtils
|
||||||
|
|
||||||
if ($oDom)
|
if ($oDom)
|
||||||
{
|
{
|
||||||
|
if ($fAdditionalDomReader)
|
||||||
|
{
|
||||||
|
$oResDom = \call_user_func($fAdditionalDomReader, $oDom);
|
||||||
|
if ($oResDom)
|
||||||
|
{
|
||||||
|
$oDom = $oResDom;
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($oResDom);
|
||||||
|
}
|
||||||
|
|
||||||
if ($bFindLinksInHtml)
|
if ($bFindLinksInHtml)
|
||||||
{
|
{
|
||||||
\MailSo\Base\HtmlUtils::FindLinksInDOM($oDom);
|
\MailSo\Base\HtmlUtils::FindLinksInDOM($oDom);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$oXpath = new \DOMXpath($oDom);
|
||||||
|
$oComments = $oXpath->query('//comment()');
|
||||||
|
if ($oComments)
|
||||||
|
{
|
||||||
|
foreach ($oComments as $oComment)
|
||||||
|
{
|
||||||
|
if (isset($oComment->parentNode))
|
||||||
|
{
|
||||||
|
@$oComment->parentNode->removeChild($oComment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($oXpath, $oComments);
|
||||||
|
|
||||||
$aNodes = $oDom->getElementsByTagName('*');
|
$aNodes = $oDom->getElementsByTagName('*');
|
||||||
foreach ($aNodes as /* @var $oElement \DOMElement */ $oElement)
|
foreach ($aNodes as /* @var $oElement \DOMElement */ $oElement)
|
||||||
{
|
{
|
||||||
|
|
@ -685,19 +753,10 @@ class HtmlUtils
|
||||||
{
|
{
|
||||||
$sTagNameLower = \strtolower($oElement->tagName);
|
$sTagNameLower = \strtolower($oElement->tagName);
|
||||||
|
|
||||||
if (\in_array($sTagNameLower, array('svg', 'head', 'link',
|
if ('' !== $sTagNameLower && \in_array($sTagNameLower, array('svg', 'head', 'link',
|
||||||
'base', 'meta', 'title', 'style', 'x-script', 'script', 'bgsound', 'keygen', 'source',
|
'base', 'meta', 'title', 'style', 'x-script', 'script', 'bgsound', 'keygen', 'source',
|
||||||
'object', 'embed', 'applet', 'mocha', 'iframe', 'frame', 'frameset', 'video', 'audio')))
|
'object', 'embed', 'applet', 'mocha', 'iframe', 'frame', 'frameset', 'video', 'audio')))
|
||||||
{
|
{
|
||||||
// if ('style' === $sTagNameLower)
|
|
||||||
// {
|
|
||||||
// $sV = (string) $oElement->textContent;
|
|
||||||
// if (!empty($sV))
|
|
||||||
// {
|
|
||||||
// $aBodyStyles[] = $sV;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (isset($oElement->parentNode))
|
if (isset($oElement->parentNode))
|
||||||
{
|
{
|
||||||
@$oElement->parentNode->removeChild($oElement);
|
@$oElement->parentNode->removeChild($oElement);
|
||||||
|
|
@ -706,6 +765,7 @@ class HtmlUtils
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$sLinkColor = '';
|
||||||
$aNodes = $oDom->getElementsByTagName('*');
|
$aNodes = $oDom->getElementsByTagName('*');
|
||||||
foreach ($aNodes as /* @var $oElement \DOMElement */ $oElement)
|
foreach ($aNodes as /* @var $oElement \DOMElement */ $oElement)
|
||||||
{
|
{
|
||||||
|
|
@ -715,6 +775,7 @@ class HtmlUtils
|
||||||
if ('body' === $sTagNameLower)
|
if ('body' === $sTagNameLower)
|
||||||
{
|
{
|
||||||
$aAttrs = array(
|
$aAttrs = array(
|
||||||
|
'link' => '',
|
||||||
'text' => '',
|
'text' => '',
|
||||||
'topmargin' => '',
|
'topmargin' => '',
|
||||||
'leftmargin' => '',
|
'leftmargin' => '',
|
||||||
|
|
@ -746,6 +807,13 @@ class HtmlUtils
|
||||||
|
|
||||||
switch ($sIndex)
|
switch ($sIndex)
|
||||||
{
|
{
|
||||||
|
case 'link':
|
||||||
|
$sLinkColor = \trim($aItem[1]);
|
||||||
|
if (!\preg_match('/^#[abcdef0-9]{3,6}$/i', $sLinkColor))
|
||||||
|
{
|
||||||
|
$sLinkColor = '';
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'text':
|
case 'text':
|
||||||
$aStyles[] = 'color: '.$aItem[1];
|
$aStyles[] = 'color: '.$aItem[1];
|
||||||
break;
|
break;
|
||||||
|
|
@ -767,7 +835,7 @@ class HtmlUtils
|
||||||
|
|
||||||
if (0 < \count($aStyles))
|
if (0 < \count($aStyles))
|
||||||
{
|
{
|
||||||
$sStyles = $oElement->hasAttribute('style') ? $oElement->getAttribute('style') : '';
|
$sStyles = $oElement->hasAttribute('style') ? \trim(\trim(\trim($oElement->getAttribute('style')), ';')) : '';
|
||||||
$oElement->setAttribute('style', (empty($sStyles) ? '' : $sStyles.'; ').\implode('; ', $aStyles));
|
$oElement->setAttribute('style', (empty($sStyles) ? '' : $sStyles.'; ').\implode('; ', $aStyles));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -777,6 +845,15 @@ class HtmlUtils
|
||||||
$oElement->setAttribute('src', 'javascript:false');
|
$oElement->setAttribute('src', 'javascript:false');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ('a' === $sTagNameLower && !empty($sLinkColor))
|
||||||
|
{
|
||||||
|
$sStyles = $oElement->hasAttribute('style')
|
||||||
|
? \trim(\trim(\trim($oElement->getAttribute('style')), ';')) : '';
|
||||||
|
|
||||||
|
$oElement->setAttribute('style',
|
||||||
|
'color: '.$sLinkColor.\trim((empty($sStyles) ? '' : '; '.$sStyles)));
|
||||||
|
}
|
||||||
|
|
||||||
if (\in_array($sTagNameLower, array('a', 'form', 'area')))
|
if (\in_array($sTagNameLower, array('a', 'form', 'area')))
|
||||||
{
|
{
|
||||||
$oElement->setAttribute('target', '_blank');
|
$oElement->setAttribute('target', '_blank');
|
||||||
|
|
@ -787,15 +864,10 @@ class HtmlUtils
|
||||||
$oElement->setAttribute('tabindex', '-1');
|
$oElement->setAttribute('tabindex', '-1');
|
||||||
}
|
}
|
||||||
|
|
||||||
// if ('blockquote' === $sTagNameLower)
|
|
||||||
// {
|
|
||||||
// $oElement->removeAttribute('style');
|
|
||||||
// }
|
|
||||||
|
|
||||||
foreach (array(
|
foreach (array(
|
||||||
'id', 'class',
|
'id', 'class',
|
||||||
'contenteditable', 'designmode', 'formaction',
|
'contenteditable', 'designmode', 'formaction',
|
||||||
'data-bind', 'xmlns', 'srcset'
|
'data-bind', 'data-reactid', 'xmlns', 'srcset'
|
||||||
) as $sAttr)
|
) as $sAttr)
|
||||||
{
|
{
|
||||||
@$oElement->removeAttribute($sAttr);
|
@$oElement->removeAttribute($sAttr);
|
||||||
|
|
@ -826,6 +898,35 @@ class HtmlUtils
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($bTryToDetectHiddenImages && 'img' === $sTagNameLower)
|
||||||
|
{
|
||||||
|
$sAlt = $oElement->hasAttribute('alt')
|
||||||
|
? \trim($oElement->getAttribute('alt')) : '';
|
||||||
|
|
||||||
|
$sH = $oElement->hasAttribute('height')
|
||||||
|
? \trim($oElement->getAttribute('height')) : '';
|
||||||
|
|
||||||
|
$sW = $oElement->hasAttribute('width')
|
||||||
|
? \trim($oElement->getAttribute('width')) : '';
|
||||||
|
|
||||||
|
$sStyles = $oElement->hasAttribute('style')
|
||||||
|
? \trim(\trim(\trim($oElement->getAttribute('style')), ';')) : '';
|
||||||
|
|
||||||
|
if ($oElement->hasAttribute('src'))
|
||||||
|
{
|
||||||
|
$aValues = array('4', '3', '2', '1', '0', '4px', '3px', '2px', '1px', '0px');
|
||||||
|
if (('' === $sAlt && \in_array($sH, $aValues) && \in_array($sW, $aValues)) ||
|
||||||
|
\preg_match('/(display:none|visibility:hidden)/i', \preg_replace('/[\s]+/', '', $sStyles)))
|
||||||
|
{
|
||||||
|
if (isset($oElement->parentNode))
|
||||||
|
{
|
||||||
|
@$oElement->parentNode->removeChild($oElement);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($oElement->hasAttribute('src'))
|
if ($oElement->hasAttribute('src'))
|
||||||
{
|
{
|
||||||
$sSrc = \trim($oElement->getAttribute('src'));
|
$sSrc = \trim($oElement->getAttribute('src'));
|
||||||
|
|
@ -884,7 +985,7 @@ class HtmlUtils
|
||||||
{
|
{
|
||||||
$aStyles = array();
|
$aStyles = array();
|
||||||
$sStyles = $oElement->hasAttribute('style')
|
$sStyles = $oElement->hasAttribute('style')
|
||||||
? $oElement->getAttribute('style') : '';
|
? \trim(\trim(\trim($oElement->getAttribute('style')), ';')) : '';
|
||||||
|
|
||||||
if (!empty($sBackground))
|
if (!empty($sBackground))
|
||||||
{
|
{
|
||||||
|
|
@ -922,6 +1023,7 @@ class HtmlUtils
|
||||||
$sResult = '<div data-x-div-type="html" '.$sHtmlAttrs.'>'.$sResult.'</div>';
|
$sResult = '<div data-x-div-type="html" '.$sHtmlAttrs.'>'.$sResult.'</div>';
|
||||||
|
|
||||||
$sResult = \str_replace(\MailSo\Base\HtmlUtils::$KOS, ':', $sResult);
|
$sResult = \str_replace(\MailSo\Base\HtmlUtils::$KOS, ':', $sResult);
|
||||||
|
$sResult = \preg_replace('/[\s]+/u', ' ', $sResult);
|
||||||
|
|
||||||
return \trim($sResult);
|
return \trim($sResult);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,11 @@ class Config
|
||||||
*/
|
*/
|
||||||
public static $PreferStartTlsIfAutoDetect = true;
|
public static $PreferStartTlsIfAutoDetect = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public static $BoundaryPrefix = '_Part_';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \MailSo\Log\Logger|null
|
* @var \MailSo\Log\Logger|null
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -756,7 +756,8 @@ class MailClient
|
||||||
{
|
{
|
||||||
$iUnseenCount = 0; // unneccessery
|
$iUnseenCount = 0; // unneccessery
|
||||||
return \md5('FolderHash/'.$sFolder.'-'.$iCount.'-'.$iUnseenCount.'-'.$sUidNext.'-'.
|
return \md5('FolderHash/'.$sFolder.'-'.$iCount.'-'.$iUnseenCount.'-'.$sUidNext.'-'.
|
||||||
$sHighestModSeq.'-'.$this->GenerateImapClientHash()
|
$sHighestModSeq.'-'.$this->GenerateImapClientHash().'-'.
|
||||||
|
\MailSo\Config::$MessageListPermanentFilter
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1882,6 +1883,7 @@ class MailClient
|
||||||
$oMessageCollection->Limit = $iLimit;
|
$oMessageCollection->Limit = $iLimit;
|
||||||
$oMessageCollection->Search = $sSearch;
|
$oMessageCollection->Search = $sSearch;
|
||||||
$oMessageCollection->ThreadUid = $sThreadUid;
|
$oMessageCollection->ThreadUid = $sThreadUid;
|
||||||
|
$oMessageCollection->Filtered = '' !== \MailSo\Config::$MessageListPermanentFilter;
|
||||||
|
|
||||||
$aUids = array();
|
$aUids = array();
|
||||||
$mAllSortedUids = null;
|
$mAllSortedUids = null;
|
||||||
|
|
@ -1988,7 +1990,9 @@ class MailClient
|
||||||
|
|
||||||
if (0 < \strlen($sSearch) && \is_array($aUids))
|
if (0 < \strlen($sSearch) && \is_array($aUids))
|
||||||
{
|
{
|
||||||
$aSearchedUids = $this->GetUids($oCacher, $sSearch, $sFilter, $oMessageCollection->FolderName, $oMessageCollection->FolderHash);
|
$aSearchedUids = $this->GetUids($oCacher, $sSearch, $sFilter,
|
||||||
|
$oMessageCollection->FolderName, $oMessageCollection->FolderHash);
|
||||||
|
|
||||||
if (\is_array($aSearchedUids) && 0 < \count($aSearchedUids))
|
if (\is_array($aSearchedUids) && 0 < \count($aSearchedUids))
|
||||||
{
|
{
|
||||||
$aFlippedSearchedUids = \array_flip($aSearchedUids);
|
$aFlippedSearchedUids = \array_flip($aSearchedUids);
|
||||||
|
|
|
||||||
|
|
@ -73,9 +73,9 @@ class MessageCollection extends \MailSo\Base\Collection
|
||||||
public $NewMessages;
|
public $NewMessages;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array
|
* @var bool
|
||||||
*/
|
*/
|
||||||
public $LastCollapsedThreadUids;
|
public $Filtered;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @access protected
|
* @access protected
|
||||||
|
|
@ -116,7 +116,7 @@ class MessageCollection extends \MailSo\Base\Collection
|
||||||
$this->ThreadUid = '';
|
$this->ThreadUid = '';
|
||||||
$this->NewMessages = array();
|
$this->NewMessages = array();
|
||||||
|
|
||||||
$this->LastCollapsedThreadUids = array();
|
$this->Filtered = false;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -511,7 +511,8 @@ class Message
|
||||||
*/
|
*/
|
||||||
private function generateNewBoundary()
|
private function generateNewBoundary()
|
||||||
{
|
{
|
||||||
return '----=_Part_'.rand(100, 999).'_'.rand(100000000, 999999999).'.'.time();
|
return '--='.\MailSo\Config::$BoundaryPrefix.
|
||||||
|
\rand(100, 999).'_'.rand(100000000, 999999999).'.'.\time();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -411,16 +411,17 @@ class Actions
|
||||||
/**
|
/**
|
||||||
* @param string $sLine
|
* @param string $sLine
|
||||||
* @param \RainLoop\Model\Account $oAccount = null
|
* @param \RainLoop\Model\Account $oAccount = null
|
||||||
|
* @param bool $bUrlEncode = false
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
private function compileLogParams($sLine, $oAccount = null)
|
private function compileLogParams($sLine, $oAccount = null, $bUrlEncode = false)
|
||||||
{
|
{
|
||||||
if (false !== \strpos($sLine, '{date:'))
|
if (false !== \strpos($sLine, '{date:'))
|
||||||
{
|
{
|
||||||
$iTimeOffset = (int) $this->Config()->Get('logs', 'time_offset', 0);
|
$iTimeOffset = (int) $this->Config()->Get('logs', 'time_offset', 0);
|
||||||
$sLine = \preg_replace_callback('/\{date:([^}]+)\}/', function ($aMatch) use ($iTimeOffset) {
|
$sLine = \preg_replace_callback('/\{date:([^}]+)\}/', function ($aMatch) use ($iTimeOffset, $bUrlEncode) {
|
||||||
return \MailSo\Log\Logger::DateHelper($aMatch[1], $iTimeOffset);
|
return \RainLoop\Utils::UrlEncode(\MailSo\Log\Logger::DateHelper($aMatch[1], $iTimeOffset), $bUrlEncode);
|
||||||
}, $sLine);
|
}, $sLine);
|
||||||
|
|
||||||
$sLine = \preg_replace('/\{date:([^}]*)\}/', 'date', $sLine);
|
$sLine = \preg_replace('/\{date:([^}]*)\}/', 'date', $sLine);
|
||||||
|
|
@ -436,37 +437,38 @@ class Actions
|
||||||
|
|
||||||
if ($oAccount)
|
if ($oAccount)
|
||||||
{
|
{
|
||||||
$sLine = \str_replace('{imap:login}', $oAccount->IncLogin(), $sLine);
|
$sLine = \str_replace('{imap:login}', \RainLoop\Utils::UrlEncode($oAccount->IncLogin(), $bUrlEncode), $sLine);
|
||||||
$sLine = \str_replace('{imap:host}', $oAccount->DomainIncHost(), $sLine);
|
$sLine = \str_replace('{imap:host}', \RainLoop\Utils::UrlEncode($oAccount->DomainIncHost(), $bUrlEncode), $sLine);
|
||||||
$sLine = \str_replace('{imap:port}', $oAccount->DomainIncPort(), $sLine);
|
$sLine = \str_replace('{imap:port}', \RainLoop\Utils::UrlEncode($oAccount->DomainIncPort(), $bUrlEncode), $sLine);
|
||||||
|
|
||||||
$sLine = \str_replace('{smtp:login}', $oAccount->OutLogin(), $sLine);
|
$sLine = \str_replace('{smtp:login}', \RainLoop\Utils::UrlEncode($oAccount->OutLogin(), $bUrlEncode), $sLine);
|
||||||
$sLine = \str_replace('{smtp:host}', $oAccount->DomainOutHost(), $sLine);
|
$sLine = \str_replace('{smtp:host}', \RainLoop\Utils::UrlEncode($oAccount->DomainOutHost(), $bUrlEncode), $sLine);
|
||||||
$sLine = \str_replace('{smtp:port}', $oAccount->DomainOutPort(), $sLine);
|
$sLine = \str_replace('{smtp:port}', \RainLoop\Utils::UrlEncode($oAccount->DomainOutPort(), $bUrlEncode), $sLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
$sLine = \preg_replace('/\{imap:([^}]*)\}/i', 'imap', $sLine);
|
$sLine = \preg_replace('/\{imap:([^}]*)\}/i', 'imap', $sLine);
|
||||||
$sLine = \preg_replace('/\{smtp:([^}]*)\}/i', 'imap', $sLine);
|
$sLine = \preg_replace('/\{smtp:([^}]*)\}/i', 'smtp', $sLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (false !== \strpos($sLine, '{request:'))
|
if (false !== \strpos($sLine, '{request:'))
|
||||||
{
|
{
|
||||||
if (false !== \strpos($sLine, '{request:ip}'))
|
if (false !== \strpos($sLine, '{request:ip}'))
|
||||||
{
|
{
|
||||||
$sLine = \str_replace('{request:ip}', $this->Http()->GetClientIp(
|
$sLine = \str_replace('{request:ip}', \RainLoop\Utils::UrlEncode($this->Http()->GetClientIp(
|
||||||
$this->Config()->Get('labs', 'http_client_ip_check_proxy', false)), $sLine);
|
$this->Config()->Get('labs', 'http_client_ip_check_proxy', false)), $bUrlEncode), $sLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (false !== \strpos($sLine, '{request:domain}'))
|
if (false !== \strpos($sLine, '{request:domain}'))
|
||||||
{
|
{
|
||||||
$sLine = \str_replace('{request:domain}',
|
$sLine = \str_replace('{request:domain}',
|
||||||
$this->Http()->GetHost(false, true, true), $sLine);
|
\RainLoop\Utils::UrlEncode($this->Http()->GetHost(false, true, true), $bUrlEncode), $sLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (false !== \strpos($sLine, '{request:domain-clear}'))
|
if (false !== \strpos($sLine, '{request:domain-clear}'))
|
||||||
{
|
{
|
||||||
$sLine = \str_replace('{request:domain-clear}',
|
$sLine = \str_replace('{request:domain-clear}',
|
||||||
\MailSo\Base\Utils::GetClearDomainName($this->Http()->GetHost(false, true, true)), $sLine);
|
\RainLoop\Utils::UrlEncode(
|
||||||
|
\MailSo\Base\Utils::GetClearDomainName($this->Http()->GetHost(false, true, true)), $bUrlEncode), $sLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
$sLine = \preg_replace('/\{request:([^}]*)\}/i', 'request', $sLine);
|
$sLine = \preg_replace('/\{request:([^}]*)\}/i', 'request', $sLine);
|
||||||
|
|
@ -477,15 +479,16 @@ class Actions
|
||||||
if (false !== \strpos($sLine, '{user:uid}'))
|
if (false !== \strpos($sLine, '{user:uid}'))
|
||||||
{
|
{
|
||||||
$sLine = \str_replace('{user:uid}',
|
$sLine = \str_replace('{user:uid}',
|
||||||
\base_convert(\sprintf('%u', \crc32(\md5(\RainLoop\Utils::GetConnectionToken()))), 10, 32),
|
\RainLoop\Utils::UrlEncode(\base_convert(\sprintf('%u',
|
||||||
|
\crc32(\md5(\RainLoop\Utils::GetConnectionToken()))), 10, 32), $bUrlEncode),
|
||||||
$sLine
|
$sLine
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (false !== \strpos($sLine, '{user:ip}'))
|
if (false !== \strpos($sLine, '{user:ip}'))
|
||||||
{
|
{
|
||||||
$sLine = \str_replace('{user:ip}', $this->Http()->GetClientIp(
|
$sLine = \str_replace('{user:ip}', \RainLoop\Utils::UrlEncode($this->Http()->GetClientIp(
|
||||||
$this->Config()->Get('labs', 'http_client_ip_check_proxy', false)), $sLine);
|
$this->Config()->Get('labs', 'http_client_ip_check_proxy', false)), $bUrlEncode), $sLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\preg_match('/\{user:(email|login|domain)\}/i', $sLine))
|
if (\preg_match('/\{user:(email|login|domain)\}/i', $sLine))
|
||||||
|
|
@ -499,13 +502,15 @@ class Actions
|
||||||
if ($oAccount)
|
if ($oAccount)
|
||||||
{
|
{
|
||||||
$sEmail = $oAccount->Email();
|
$sEmail = $oAccount->Email();
|
||||||
$sLine = \str_replace('{user:email}', $sEmail, $sLine);
|
|
||||||
$sLine = \str_replace('{user:login}',
|
$sLine = \str_replace('{user:email}', \RainLoop\Utils::UrlEncode($sEmail, $bUrlEncode), $sLine);
|
||||||
\MailSo\Base\Utils::GetAccountNameFromEmail($sEmail), $sLine);
|
$sLine = \str_replace('{user:login}', \RainLoop\Utils::UrlEncode(
|
||||||
$sLine = \str_replace('{user:domain}',
|
\MailSo\Base\Utils::GetAccountNameFromEmail($sEmail), $bUrlEncode), $sLine);
|
||||||
\MailSo\Base\Utils::GetDomainFromEmail($sEmail), $sLine);
|
$sLine = \str_replace('{user:domain}', \RainLoop\Utils::UrlEncode(
|
||||||
$sLine = \str_replace('{user:domain-clear}',
|
\MailSo\Base\Utils::GetDomainFromEmail($sEmail), $bUrlEncode), $sLine);
|
||||||
\MailSo\Base\Utils::GetClearDomainName(\MailSo\Base\Utils::GetDomainFromEmail($sEmail)), $sLine);
|
$sLine = \str_replace('{user:domain-clear}', \RainLoop\Utils::UrlEncode(
|
||||||
|
\MailSo\Base\Utils::GetClearDomainName(
|
||||||
|
\MailSo\Base\Utils::GetDomainFromEmail($sEmail)), $bUrlEncode), $sLine);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -997,6 +1002,18 @@ class Actions
|
||||||
|
|
||||||
if (!!$this->Config()->Get('logs', 'enable', false))
|
if (!!$this->Config()->Get('logs', 'enable', false))
|
||||||
{
|
{
|
||||||
|
$sSessionFilter = (string) $this->Config()->Get('logs', 'session_filter', '');
|
||||||
|
if (!empty($sSessionFilter))
|
||||||
|
{
|
||||||
|
$aSessionParts = \explode(':', $sSessionFilter, 2);
|
||||||
|
|
||||||
|
if (empty($aSessionParts[0]) || empty($aSessionParts[1]) ||
|
||||||
|
(string) $aSessionParts[1] !== (string) \RainLoop\Utils::GetCookie($aSessionParts[0], ''))
|
||||||
|
{
|
||||||
|
return $this->oLogger;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$iTimeOffset = (int) $this->Config()->Get('logs', 'time_offset', 0);
|
$iTimeOffset = (int) $this->Config()->Get('logs', 'time_offset', 0);
|
||||||
|
|
||||||
$this->oLogger->SetShowSecter(!$this->Config()->Get('logs', 'hide_passwords', true));
|
$this->oLogger->SetShowSecter(!$this->Config()->Get('logs', 'hide_passwords', true));
|
||||||
|
|
@ -1370,6 +1387,7 @@ class Actions
|
||||||
'LoginDefaultDomain' => $oConfig->Get('login', 'default_domain', ''),
|
'LoginDefaultDomain' => $oConfig->Get('login', 'default_domain', ''),
|
||||||
'DetermineUserLanguage' => (bool) $oConfig->Get('login', 'determine_user_language', true),
|
'DetermineUserLanguage' => (bool) $oConfig->Get('login', 'determine_user_language', true),
|
||||||
'DetermineUserDomain' => (bool) $oConfig->Get('login', 'determine_user_domain', false),
|
'DetermineUserDomain' => (bool) $oConfig->Get('login', 'determine_user_domain', false),
|
||||||
|
'UseLoginWelcomePage' => (bool) $oConfig->Get('login', 'welcome_page', false),
|
||||||
'ForgotPasswordLinkUrl' => \trim($oConfig->Get('login', 'forgot_password_link_url', '')),
|
'ForgotPasswordLinkUrl' => \trim($oConfig->Get('login', 'forgot_password_link_url', '')),
|
||||||
'RegistrationLinkUrl' => \trim($oConfig->Get('login', 'registration_link_url', '')),
|
'RegistrationLinkUrl' => \trim($oConfig->Get('login', 'registration_link_url', '')),
|
||||||
'ContactsIsAllowed' => false,
|
'ContactsIsAllowed' => false,
|
||||||
|
|
@ -1382,6 +1400,7 @@ class Actions
|
||||||
'MaterialDesign' => (bool) $oConfig->Get('labs', 'use_material_design', true),
|
'MaterialDesign' => (bool) $oConfig->Get('labs', 'use_material_design', true),
|
||||||
'FolderSpecLimit' => (int) $oConfig->Get('labs', 'folders_spec_limit', 50),
|
'FolderSpecLimit' => (int) $oConfig->Get('labs', 'folders_spec_limit', 50),
|
||||||
'StartupUrl' => \trim(\ltrim(\trim($oConfig->Get('labs', 'startup_url', '')), '#/')),
|
'StartupUrl' => \trim(\ltrim(\trim($oConfig->Get('labs', 'startup_url', '')), '#/')),
|
||||||
|
'Filtered' => '' !== \trim(\RainLoop\Api::Config()->Get('labs', 'imap_message_list_permanent_filter', '')),
|
||||||
'Community' => true,
|
'Community' => true,
|
||||||
'PremType' => false,
|
'PremType' => false,
|
||||||
'Admin' => array(),
|
'Admin' => array(),
|
||||||
|
|
@ -1397,7 +1416,7 @@ class Actions
|
||||||
$aResult['AttachmentsActions'][] = 'zip';
|
$aResult['AttachmentsActions'][] = 'zip';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\RainLoop\Utils::IsOwnCloudLoggedIn() && \class_exists('\\OCP\\Files'))
|
if (\RainLoop\Utils::IsOwnCloudLoggedIn() && \class_exists('OCP\Files'))
|
||||||
{
|
{
|
||||||
$aResult['AttachmentsActions'][] = 'owncloud';
|
$aResult['AttachmentsActions'][] = 'owncloud';
|
||||||
}
|
}
|
||||||
|
|
@ -1523,7 +1542,15 @@ class Actions
|
||||||
$aResult['WelcomePageDisplay'] = '';
|
$aResult['WelcomePageDisplay'] = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
$aResult['StartupUrl'] = $this->compileLogParams($aResult['StartupUrl'], $oAccount);
|
if (!empty($aResult['StartupUrl']))
|
||||||
|
{
|
||||||
|
$aResult['StartupUrl'] = $this->compileLogParams($aResult['StartupUrl'], $oAccount, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($aResult['UserIframeMessage']))
|
||||||
|
{
|
||||||
|
$aResult['UserIframeMessage'] = $this->compileLogParams($aResult['UserIframeMessage'], $oAccount, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -1543,6 +1570,7 @@ class Actions
|
||||||
|
|
||||||
$aResult['AllowGoogleSocial'] = (bool) $oConfig->Get('social', 'google_enable', false);
|
$aResult['AllowGoogleSocial'] = (bool) $oConfig->Get('social', 'google_enable', false);
|
||||||
$aResult['AllowGoogleSocialAuth'] = (bool) $oConfig->Get('social', 'google_enable_auth', true);
|
$aResult['AllowGoogleSocialAuth'] = (bool) $oConfig->Get('social', 'google_enable_auth', true);
|
||||||
|
$aResult['AllowGoogleSocialAuthFast'] = (bool) $oConfig->Get('social', 'google_enable_auth_fast', true);
|
||||||
$aResult['AllowGoogleSocialDrive'] = (bool) $oConfig->Get('social', 'google_enable_drive', true);
|
$aResult['AllowGoogleSocialDrive'] = (bool) $oConfig->Get('social', 'google_enable_drive', true);
|
||||||
$aResult['AllowGoogleSocialPreview'] = (bool) $oConfig->Get('social', 'google_enable_preview', true);
|
$aResult['AllowGoogleSocialPreview'] = (bool) $oConfig->Get('social', 'google_enable_preview', true);
|
||||||
|
|
||||||
|
|
@ -1553,6 +1581,7 @@ class Actions
|
||||||
'' === \trim($oConfig->Get('social', 'google_client_id', '')) || '' === \trim($oConfig->Get('social', 'google_client_secret', '')))))
|
'' === \trim($oConfig->Get('social', 'google_client_id', '')) || '' === \trim($oConfig->Get('social', 'google_client_secret', '')))))
|
||||||
{
|
{
|
||||||
$aResult['AllowGoogleSocialAuth'] = false;
|
$aResult['AllowGoogleSocialAuth'] = false;
|
||||||
|
$aResult['AllowGoogleSocialAuthFast'] = false;
|
||||||
$aResult['AllowGoogleSocialDrive'] = false;
|
$aResult['AllowGoogleSocialDrive'] = false;
|
||||||
$aResult['GoogleClientID'] = '';
|
$aResult['GoogleClientID'] = '';
|
||||||
$aResult['GoogleApiKey'] = '';
|
$aResult['GoogleApiKey'] = '';
|
||||||
|
|
@ -1563,7 +1592,9 @@ class Actions
|
||||||
$aResult['AllowGoogleSocialPreview'] = false;
|
$aResult['AllowGoogleSocialPreview'] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($aResult['AllowGoogleSocial'] && !$aResult['AllowGoogleSocialAuth'] && !$aResult['AllowGoogleSocialDrive'] && !$aResult['AllowGoogleSocialPreview'])
|
if ($aResult['AllowGoogleSocial'] &&
|
||||||
|
!$aResult['AllowGoogleSocialAuth'] && !$aResult['AllowGoogleSocialAuthFast'] &&
|
||||||
|
!$aResult['AllowGoogleSocialDrive'] && !$aResult['AllowGoogleSocialPreview'])
|
||||||
{
|
{
|
||||||
$aResult['AllowGoogleSocial'] = false;
|
$aResult['AllowGoogleSocial'] = false;
|
||||||
}
|
}
|
||||||
|
|
@ -1639,6 +1670,7 @@ class Actions
|
||||||
|
|
||||||
$aResult['AllowGoogleSocial'] = (bool) $oConfig->Get('social', 'google_enable', false);
|
$aResult['AllowGoogleSocial'] = (bool) $oConfig->Get('social', 'google_enable', false);
|
||||||
$aResult['AllowGoogleSocialAuth'] = (bool) $oConfig->Get('social', 'google_enable_auth', true);
|
$aResult['AllowGoogleSocialAuth'] = (bool) $oConfig->Get('social', 'google_enable_auth', true);
|
||||||
|
$aResult['AllowGoogleSocialAuthFast'] = (bool) $oConfig->Get('social', 'google_enable_auth_fast', true);
|
||||||
$aResult['AllowGoogleSocialDrive'] = (bool) $oConfig->Get('social', 'google_enable_drive', true);
|
$aResult['AllowGoogleSocialDrive'] = (bool) $oConfig->Get('social', 'google_enable_drive', true);
|
||||||
$aResult['AllowGoogleSocialPreview'] = (bool) $oConfig->Get('social', 'google_enable_preview', true);
|
$aResult['AllowGoogleSocialPreview'] = (bool) $oConfig->Get('social', 'google_enable_preview', true);
|
||||||
|
|
||||||
|
|
@ -2882,7 +2914,7 @@ class Actions
|
||||||
|
|
||||||
$mResult = false;
|
$mResult = false;
|
||||||
|
|
||||||
if (\RainLoop\Utils::IsOwnCloudLoggedIn() && \class_exists('\\OCP\\Files'))
|
if (\RainLoop\Utils::IsOwnCloudLoggedIn() && \class_exists('OCP\Files'))
|
||||||
{
|
{
|
||||||
$sSaveFolder = $this->Config()->Get('labs', 'owncloud_save_folder', '');
|
$sSaveFolder = $this->Config()->Get('labs', 'owncloud_save_folder', '');
|
||||||
if (empty($sSaveFolder))
|
if (empty($sSaveFolder))
|
||||||
|
|
@ -3644,6 +3676,7 @@ class Actions
|
||||||
|
|
||||||
$this->setConfigFromParams($oConfig, 'GoogleEnable', 'social', 'google_enable', 'bool');
|
$this->setConfigFromParams($oConfig, 'GoogleEnable', 'social', 'google_enable', 'bool');
|
||||||
$this->setConfigFromParams($oConfig, 'GoogleEnableAuth', 'social', 'google_enable_auth', 'bool');
|
$this->setConfigFromParams($oConfig, 'GoogleEnableAuth', 'social', 'google_enable_auth', 'bool');
|
||||||
|
$this->setConfigFromParams($oConfig, 'GoogleEnableAuthFast', 'social', 'google_enable_auth_fast', 'bool');
|
||||||
$this->setConfigFromParams($oConfig, 'GoogleEnableDrive', 'social', 'google_enable_drive', 'bool');
|
$this->setConfigFromParams($oConfig, 'GoogleEnableDrive', 'social', 'google_enable_drive', 'bool');
|
||||||
$this->setConfigFromParams($oConfig, 'GoogleEnablePreview', 'social', 'google_enable_preview', 'bool');
|
$this->setConfigFromParams($oConfig, 'GoogleEnablePreview', 'social', 'google_enable_preview', 'bool');
|
||||||
$this->setConfigFromParams($oConfig, 'GoogleClientID', 'social', 'google_client_id', 'string');
|
$this->setConfigFromParams($oConfig, 'GoogleClientID', 'social', 'google_client_id', 'string');
|
||||||
|
|
@ -4092,7 +4125,7 @@ class Actions
|
||||||
/**
|
/**
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
private function rainloopRepo()
|
private function rainLoopRepo()
|
||||||
{
|
{
|
||||||
$sUrl = APP_REP_PATH;
|
$sUrl = APP_REP_PATH;
|
||||||
if ('' !== $sUrl)
|
if ('' !== $sUrl)
|
||||||
|
|
@ -4281,7 +4314,7 @@ class Actions
|
||||||
{
|
{
|
||||||
$bRainLoopUpdatable = $this->rainLoopUpdatable();
|
$bRainLoopUpdatable = $this->rainLoopUpdatable();
|
||||||
|
|
||||||
$aResult = $this->getRepositoryDataByUrl($this->rainloopRepo(), $bReal);
|
$aResult = $this->getRepositoryDataByUrl($this->rainLoopRepo(), $bReal);
|
||||||
|
|
||||||
$aSub = array();
|
$aSub = array();
|
||||||
if (\is_array($aResult))
|
if (\is_array($aResult))
|
||||||
|
|
@ -4555,7 +4588,7 @@ class Actions
|
||||||
$bResult = false;
|
$bResult = false;
|
||||||
if ('' !== $sRealFile)
|
if ('' !== $sRealFile)
|
||||||
{
|
{
|
||||||
$sUrl = $this->rainloopRepo().$sRealFile;
|
$sUrl = $this->rainLoopRepo().$sRealFile;
|
||||||
$sTmp = $this->downloadRemotePackageByUrl($sUrl);
|
$sTmp = $this->downloadRemotePackageByUrl($sUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -6424,7 +6457,12 @@ class Actions
|
||||||
*/
|
*/
|
||||||
public function DoQuota()
|
public function DoQuota()
|
||||||
{
|
{
|
||||||
$this->initMailClientConnection();
|
$oAccount = $this->initMailClientConnection();
|
||||||
|
|
||||||
|
if (!$this->GetCapa(false, \RainLoop\Enumerations\Capa::QUOTA, $oAccount))
|
||||||
|
{
|
||||||
|
return $this->DefaultResponse(__FUNCTION__, array(0, 0, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -7960,6 +7998,11 @@ class Actions
|
||||||
$aResult[] = \RainLoop\Enumerations\Capa::RELOAD;
|
$aResult[] = \RainLoop\Enumerations\Capa::RELOAD;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($oConfig->Get('capa', 'quota', true))
|
||||||
|
{
|
||||||
|
$aResult[] = \RainLoop\Enumerations\Capa::QUOTA;
|
||||||
|
}
|
||||||
|
|
||||||
if ($oConfig->Get('capa', 'settings', true))
|
if ($oConfig->Get('capa', 'settings', true))
|
||||||
{
|
{
|
||||||
$aResult[] = \RainLoop\Enumerations\Capa::SETTINGS;
|
$aResult[] = \RainLoop\Enumerations\Capa::SETTINGS;
|
||||||
|
|
@ -9453,6 +9496,24 @@ class Actions
|
||||||
$mResult['InReplyTo'] = $mResponse->InReplyTo();
|
$mResult['InReplyTo'] = $mResponse->InReplyTo();
|
||||||
$mResult['References'] = $mResponse->References();
|
$mResult['References'] = $mResponse->References();
|
||||||
|
|
||||||
|
$fAdditionalDomReader = null;
|
||||||
|
if (0 < \strlen($sHtml) && $this->Config()->Get('labs', 'emogrifier', false))
|
||||||
|
{
|
||||||
|
if (!\class_exists('Pelago\Emogrifier', false))
|
||||||
|
{
|
||||||
|
include_once APP_VERSION_ROOT_PATH.'app/libraries/emogrifier/Emogrifier.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (\class_exists('Pelago\Emogrifier', false))
|
||||||
|
{
|
||||||
|
$fAdditionalDomReader = function ($oDom) {
|
||||||
|
$oEmogrifier = new \Pelago\Emogrifier();
|
||||||
|
$oEmogrifier->preserveEncoding = false;
|
||||||
|
return $oEmogrifier->emogrify($oDom);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$fAdditionalExternalFilter = null;
|
$fAdditionalExternalFilter = null;
|
||||||
if (!!$this->Config()->Get('labs', 'use_local_proxy_for_external_images', false))
|
if (!!$this->Config()->Get('labs', 'use_local_proxy_for_external_images', false))
|
||||||
{
|
{
|
||||||
|
|
@ -9465,9 +9526,13 @@ class Actions
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// $this->Logger()->Write('---');
|
||||||
|
// $this->Logger()->Write($sHtml);
|
||||||
|
|
||||||
$mResult['Html'] = 0 === \strlen($sHtml) ? '' : \MailSo\Base\HtmlUtils::ClearHtml(
|
$mResult['Html'] = 0 === \strlen($sHtml) ? '' : \MailSo\Base\HtmlUtils::ClearHtml(
|
||||||
$sHtml, $bHasExternals, $mFoundedCIDs, $aContentLocationUrls, $mFoundedContentLocationUrls, false, false,
|
$sHtml, $bHasExternals, $mFoundedCIDs, $aContentLocationUrls, $mFoundedContentLocationUrls, false, false,
|
||||||
$fAdditionalExternalFilter
|
$fAdditionalExternalFilter, $fAdditionalDomReader,
|
||||||
|
!!$this->Config()->Get('labs', 'try_to_detect_hidden_images', false)
|
||||||
);
|
);
|
||||||
|
|
||||||
$mResult['ExternalProxy'] = null !== $fAdditionalExternalFilter;
|
$mResult['ExternalProxy'] = null !== $fAdditionalExternalFilter;
|
||||||
|
|
@ -9669,6 +9734,7 @@ class Actions
|
||||||
'UidNext' => $mResponse->UidNext,
|
'UidNext' => $mResponse->UidNext,
|
||||||
'ThreadUid' => $mResponse->ThreadUid,
|
'ThreadUid' => $mResponse->ThreadUid,
|
||||||
'NewMessages' => $this->responseObject($mResponse->NewMessages),
|
'NewMessages' => $this->responseObject($mResponse->NewMessages),
|
||||||
|
'Filtered' => $mResponse->Filtered,
|
||||||
'Offset' => $mResponse->Offset,
|
'Offset' => $mResponse->Offset,
|
||||||
'Limit' => $mResponse->Limit,
|
'Limit' => $mResponse->Limit,
|
||||||
'Search' => $mResponse->Search
|
'Search' => $mResponse->Search
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ class Api
|
||||||
if ($bOne)
|
if ($bOne)
|
||||||
{
|
{
|
||||||
\RainLoop\Api::SetupDefaultMailSoConfig();
|
\RainLoop\Api::SetupDefaultMailSoConfig();
|
||||||
|
|
||||||
$bOne = \RainLoop\Api::RunResult();
|
$bOne = \RainLoop\Api::RunResult();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -97,6 +98,7 @@ class Api
|
||||||
\MailSo\Config::$LargeThreadLimit =
|
\MailSo\Config::$LargeThreadLimit =
|
||||||
(int) \RainLoop\Api::Config()->Get('labs', 'imap_large_thread_limit', 50);
|
(int) \RainLoop\Api::Config()->Get('labs', 'imap_large_thread_limit', 50);
|
||||||
|
|
||||||
|
\MailSo\Config::$BoundaryPrefix = '_RainLoop_';
|
||||||
|
|
||||||
\MailSo\Config::$SystemLogger = \RainLoop\Api::Logger();
|
\MailSo\Config::$SystemLogger = \RainLoop\Api::Logger();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -142,6 +142,7 @@ class Application extends \RainLoop\Config\AbstractConfig
|
||||||
'composer' => array(true),
|
'composer' => array(true),
|
||||||
'contacts' => array(true),
|
'contacts' => array(true),
|
||||||
'settings' => array(true),
|
'settings' => array(true),
|
||||||
|
'quota' => array(true),
|
||||||
'help' => array(true),
|
'help' => array(true),
|
||||||
'reload' => array(true),
|
'reload' => array(true),
|
||||||
'search' => array(true),
|
'search' => array(true),
|
||||||
|
|
@ -164,6 +165,8 @@ class Application extends \RainLoop\Config\AbstractConfig
|
||||||
'determine_user_language' => array(true, ''),
|
'determine_user_language' => array(true, ''),
|
||||||
'determine_user_domain' => array(false, ''),
|
'determine_user_domain' => array(false, ''),
|
||||||
|
|
||||||
|
'welcome_page' => array(false, ''),
|
||||||
|
|
||||||
'forgot_password_link_url' => array('', ''),
|
'forgot_password_link_url' => array('', ''),
|
||||||
'registration_link_url' => array('', ''),
|
'registration_link_url' => array('', ''),
|
||||||
|
|
||||||
|
|
@ -184,7 +187,7 @@ Values:
|
||||||
|
|
||||||
'defaults' => array(
|
'defaults' => array(
|
||||||
'view_editor_type' => array('Html', 'Editor mode used by default (Plain, Html, HtmlForced or PlainForced)'),
|
'view_editor_type' => array('Html', 'Editor mode used by default (Plain, Html, HtmlForced or PlainForced)'),
|
||||||
'view_layout' => array(1, 'layout: 0 - no preview, 1 - side preview, 3 - bottom preview'),
|
'view_layout' => array(1, 'layout: 0 - no preview, 1 - side preview, 2 - bottom preview'),
|
||||||
'view_use_checkboxes' => array(true),
|
'view_use_checkboxes' => array(true),
|
||||||
'autologout' => array(30),
|
'autologout' => array(30),
|
||||||
'show_images' => array(false),
|
'show_images' => array(false),
|
||||||
|
|
@ -205,6 +208,7 @@ Values:
|
||||||
Disabling this option is not recommended.'),
|
Disabling this option is not recommended.'),
|
||||||
|
|
||||||
'time_offset' => array(0),
|
'time_offset' => array(0),
|
||||||
|
'session_filter' => array(''),
|
||||||
|
|
||||||
'filename' => array('log-{date:Y-m-d}.txt',
|
'filename' => array('log-{date:Y-m-d}.txt',
|
||||||
'Log filename.
|
'Log filename.
|
||||||
|
|
@ -246,6 +250,7 @@ Examples:
|
||||||
'social' => array(
|
'social' => array(
|
||||||
'google_enable' => array(false, 'Google'),
|
'google_enable' => array(false, 'Google'),
|
||||||
'google_enable_auth' => array(false),
|
'google_enable_auth' => array(false),
|
||||||
|
'google_enable_auth_fast' => array(false),
|
||||||
'google_enable_drive' => array(false),
|
'google_enable_drive' => array(false),
|
||||||
'google_enable_preview' => array(false),
|
'google_enable_preview' => array(false),
|
||||||
'google_client_id' => array(''),
|
'google_client_id' => array(''),
|
||||||
|
|
@ -298,6 +303,7 @@ Enables caching in the system'),
|
||||||
'allow_html_editor_source_button' => array(false),
|
'allow_html_editor_source_button' => array(false),
|
||||||
'allow_html_editor_biti_buttons' => array(false),
|
'allow_html_editor_biti_buttons' => array(false),
|
||||||
'allow_ctrl_enter_on_compose' => array(false),
|
'allow_ctrl_enter_on_compose' => array(false),
|
||||||
|
'try_to_detect_hidden_images' => array(false),
|
||||||
'hide_dangerous_actions' => array(false),
|
'hide_dangerous_actions' => array(false),
|
||||||
'use_app_debug_js' => array(false),
|
'use_app_debug_js' => array(false),
|
||||||
'use_app_debug_css' => array(false),
|
'use_app_debug_css' => array(false),
|
||||||
|
|
@ -340,6 +346,7 @@ Enables caching in the system'),
|
||||||
'fast_cache_memcache_expire' => array(43200),
|
'fast_cache_memcache_expire' => array(43200),
|
||||||
'use_local_proxy_for_external_images' => array(false),
|
'use_local_proxy_for_external_images' => array(false),
|
||||||
'startup_url' => array(''),
|
'startup_url' => array(''),
|
||||||
|
'emogrifier' => array(true),
|
||||||
'dev_email' => array(''),
|
'dev_email' => array(''),
|
||||||
'dev_password' => array('')
|
'dev_password' => array('')
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ class Capa
|
||||||
const SEARCH = 'SEARCH';
|
const SEARCH = 'SEARCH';
|
||||||
const SEARCH_ADV = 'SEARCH_ADV';
|
const SEARCH_ADV = 'SEARCH_ADV';
|
||||||
const SETTINGS = 'SETTINGS';
|
const SETTINGS = 'SETTINGS';
|
||||||
|
const QUOTA = 'QUOTA';
|
||||||
const HELP = 'HELP';
|
const HELP = 'HELP';
|
||||||
const TEMPLATES = 'TEMPLATES';
|
const TEMPLATES = 'TEMPLATES';
|
||||||
const MESSAGE_ACTIONS = 'MESSAGE_ACTIONS';
|
const MESSAGE_ACTIONS = 'MESSAGE_ACTIONS';
|
||||||
|
|
|
||||||
|
|
@ -304,6 +304,22 @@ abstract class AbstractPlugin
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $sFile
|
||||||
|
* @param bool $bAdminScope = false
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
protected function replaceTemplate($sFile, $bAdminScope = false)
|
||||||
|
{
|
||||||
|
if ($this->oPluginManager)
|
||||||
|
{
|
||||||
|
$this->oPluginManager->AddTemplate($this->sPath.'/'.$sFile, $bAdminScope);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $sActionName
|
* @param string $sActionName
|
||||||
* @param string $sFunctionName
|
* @param string $sFunctionName
|
||||||
|
|
|
||||||
|
|
@ -274,27 +274,24 @@ class Manager
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param array $aList
|
||||||
* @param bool $bAdminScope = false
|
* @param bool $bAdminScope = false
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function CompileTemplate($bAdminScope = false)
|
public function CompileTemplate(&$aList, $bAdminScope = false)
|
||||||
{
|
{
|
||||||
$sResult = '';
|
|
||||||
if ($this->bIsEnabled)
|
if ($this->bIsEnabled)
|
||||||
{
|
{
|
||||||
$aTemplates = $bAdminScope ? $this->aAdminTemplates : $this->aTemplates;
|
$aTemplates = $bAdminScope ? $this->aAdminTemplates : $this->aTemplates;
|
||||||
foreach ($aTemplates as $sFile)
|
foreach ($aTemplates as $sFile)
|
||||||
{
|
{
|
||||||
if (file_exists($sFile))
|
if (\file_exists($sFile))
|
||||||
{
|
{
|
||||||
$sTemplateName = substr(basename($sFile), 0, -5);
|
$sTemplateName = \substr(\basename($sFile), 0, -5);
|
||||||
$sResult .= '<script id="'.preg_replace('/[^a-zA-Z0-9]/', '', $sTemplateName).'" type="text/html" data-cfasync="false">'.
|
$aList[$sTemplateName] = $sFile;
|
||||||
$this->Actions()->ProcessTemplate($sTemplateName, file_get_contents($sFile)).'</script>';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $sResult;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ class OwnCloudSuggestions implements \RainLoop\Providers\Suggestions\ISuggestion
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if ('' === $sQuery || !$oAccount || !\RainLoop\Utils::IsOwnCloudLoggedIn() ||
|
if ('' === $sQuery || !$oAccount || !\RainLoop\Utils::IsOwnCloudLoggedIn() ||
|
||||||
!\class_exists('\\OCP\\Contacts') || !\OCP\Contacts::isEnabled()
|
!\class_exists('OCP\Contacts') || !\OCP\Contacts::isEnabled()
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
return $aResult;
|
return $aResult;
|
||||||
|
|
|
||||||
|
|
@ -1220,11 +1220,23 @@ class ServiceActions
|
||||||
*/
|
*/
|
||||||
public function compileTemplates($bAdmin = false, $bJsOutput = true)
|
public function compileTemplates($bAdmin = false, $bJsOutput = true)
|
||||||
{
|
{
|
||||||
$sHtml =
|
$aTemplates = array();
|
||||||
\RainLoop\Utils::CompileTemplates(APP_VERSION_ROOT_PATH.'app/templates/Views/Components', $this->oActions, 'Component').
|
|
||||||
\RainLoop\Utils::CompileTemplates(APP_VERSION_ROOT_PATH.'app/templates/Views/'.($bAdmin ? 'Admin' : 'User'), $this->oActions).
|
\RainLoop\Utils::CompileTemplates($aTemplates, APP_VERSION_ROOT_PATH.'app/templates/Views/Components', 'Component');
|
||||||
\RainLoop\Utils::CompileTemplates(APP_VERSION_ROOT_PATH.'app/templates/Views/Common', $this->oActions).
|
\RainLoop\Utils::CompileTemplates($aTemplates, APP_VERSION_ROOT_PATH.'app/templates/Views/'.($bAdmin ? 'Admin' : 'User'));
|
||||||
$this->oActions->Plugins()->CompileTemplate($bAdmin);
|
\RainLoop\Utils::CompileTemplates($aTemplates, APP_VERSION_ROOT_PATH.'app/templates/Views/Common');
|
||||||
|
|
||||||
|
$this->oActions->Plugins()->CompileTemplate($aTemplates, $bAdmin);
|
||||||
|
|
||||||
|
$sHtml = '';
|
||||||
|
foreach ($aTemplates as $sName => $sFile)
|
||||||
|
{
|
||||||
|
$sName = \preg_replace('/[^a-zA-Z0-9]/', '', $sName);
|
||||||
|
$sHtml .= '<script id="'.$sName.'" type="text/html" data-cfasync="false">'.
|
||||||
|
$this->oActions->ProcessTemplate($sName, \file_get_contents($sFile)).'</script>';
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($aTemplates);
|
||||||
|
|
||||||
return $bJsOutput ? 'window.rainloopTEMPLATES='.\MailSo\Base\Utils::Php2js(array($sHtml), $this->Logger()).';' : $sHtml;
|
return $bJsOutput ? 'window.rainloopTEMPLATES='.\MailSo\Base\Utils::Php2js(array($sHtml), $this->Logger()).';' : $sHtml;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ class Utils
|
||||||
define('_phpseclib_', true);
|
define('_phpseclib_', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!\class_exists('Crypt_RSA'))
|
if (!\class_exists('Crypt_RSA', false))
|
||||||
{
|
{
|
||||||
include_once 'Crypt/RSA.php';
|
include_once 'Crypt/RSA.php';
|
||||||
\defined('CRYPT_RSA_MODE') || \define('CRYPT_RSA_MODE', CRYPT_RSA_MODE_INTERNAL);
|
\defined('CRYPT_RSA_MODE') || \define('CRYPT_RSA_MODE', CRYPT_RSA_MODE_INTERNAL);
|
||||||
|
|
@ -470,30 +470,22 @@ class Utils
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param array $aList
|
||||||
* @param string $sDirName
|
* @param string $sDirName
|
||||||
* @param \RainLoop\Actions $oAction
|
|
||||||
* @param string $sNameSuffix = ''
|
* @param string $sNameSuffix = ''
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
*/
|
||||||
public static function CompileTemplates($sDirName, $oAction, $sNameSuffix = '')
|
public static function CompileTemplates(&$aList, $sDirName, $sNameSuffix = '')
|
||||||
{
|
{
|
||||||
$sResult = '';
|
|
||||||
if (\file_exists($sDirName))
|
if (\file_exists($sDirName))
|
||||||
{
|
{
|
||||||
$aList = \RainLoop\Utils::FolderFiles($sDirName, '.html');
|
$aFileList = \RainLoop\Utils::FolderFiles($sDirName, '.html');
|
||||||
|
|
||||||
foreach ($aList as $sName)
|
foreach ($aFileList as $sName)
|
||||||
{
|
{
|
||||||
$sTemplateName = \substr($sName, 0, -5).$sNameSuffix;
|
$sTemplateName = \substr($sName, 0, -5).$sNameSuffix;
|
||||||
$sResult .= '<script id="'.\preg_replace('/[^a-zA-Z0-9]/', '', $sTemplateName).'" type="text/html" data-cfasync="false">'.
|
$aList[$sTemplateName] = $sDirName.'/'.$sName;
|
||||||
$oAction->ProcessTemplate($sTemplateName, \file_get_contents($sDirName.'/'.$sName)).'</script>';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$sResult = \trim($sResult);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $sResult;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -539,14 +531,25 @@ class Utils
|
||||||
public static function IsOwnCloud()
|
public static function IsOwnCloud()
|
||||||
{
|
{
|
||||||
return isset($_ENV['RAINLOOP_OWNCLOUD']) && $_ENV['RAINLOOP_OWNCLOUD'] &&
|
return isset($_ENV['RAINLOOP_OWNCLOUD']) && $_ENV['RAINLOOP_OWNCLOUD'] &&
|
||||||
\class_exists('\\OC');
|
\class_exists('OC');
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public static function IsOwnCloudLoggedIn()
|
public static function IsOwnCloudLoggedIn()
|
||||||
{
|
{
|
||||||
return self::IsOwnCloud() && \class_exists('\\OCP\\User') && \OCP\User::isLoggedIn();
|
return self::IsOwnCloud() && \class_exists('OCP\User') && \OCP\User::isLoggedIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $sV
|
||||||
|
* @param bool $bEncode = false
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function UrlEncode($sV, $bEncode = false)
|
||||||
|
{
|
||||||
|
return $bEncode ? \urlencode($sV) : $sV;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -557,7 +560,7 @@ class Utils
|
||||||
$sAppPath = '';
|
$sAppPath = '';
|
||||||
if (\RainLoop\Utils::IsOwnCloud())
|
if (\RainLoop\Utils::IsOwnCloud())
|
||||||
{
|
{
|
||||||
if (\class_exists('\\OC_App'))
|
if (\class_exists('OC_App'))
|
||||||
{
|
{
|
||||||
$sAppPath = \rtrim(\trim(\OC_App::getAppWebPath('rainloop')), '\\/').'/app/';
|
$sAppPath = \rtrim(\trim(\OC_App::getAppWebPath('rainloop')), '\\/').'/app/';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
73
rainloop/v/0.0.0/app/libraries/emogrifier/CHANGELOG.TXT
Normal file
73
rainloop/v/0.0.0/app/libraries/emogrifier/CHANGELOG.TXT
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
2012-05-01
|
||||||
|
Made removal of invisible nodes operate in a case-insensitive manner... Thanks Juha P.!
|
||||||
|
|
||||||
|
2012-02-07
|
||||||
|
Fixed some recent code introductions to use class constants rather than global constants.
|
||||||
|
Fixed some recent code introductions to make it cleaner to read.
|
||||||
|
|
||||||
|
2012-01-31
|
||||||
|
Fixed a bug that was introduced with the 2011-12-22 revision... Thanks Sagi L. and M. Bąkowski!
|
||||||
|
Added extraction of <style> blocks within the HTML due to popular demand.
|
||||||
|
Added several new pseudo-selectors (first-child, last-child, nth-child, and nth-of-type).
|
||||||
|
|
||||||
|
2011-12-22
|
||||||
|
Fixed a bug that was overwriting existing inline styles from the original HTML... Thanks Sagi L.!
|
||||||
|
|
||||||
|
2011-10-26
|
||||||
|
Added an option to allow you to output emogrified code without extended characters being turned into HTML entities.
|
||||||
|
Moved static references to class attributes so they can be manipulated.
|
||||||
|
Added the ability to clear out the (formerly) static cache when CSS is reloaded.
|
||||||
|
|
||||||
|
2011-10-13
|
||||||
|
Fully fixed a bug introduced in 2011-06-08 where selectors at the beginning of the CSS would be parsed incorrectly... Thanks Thomas A.!
|
||||||
|
|
||||||
|
2011-08-03
|
||||||
|
Fixed an error where an empty selector at the beginning of the CSS would cause a parse error on the next selector... Thanks Alexei T.!
|
||||||
|
|
||||||
|
2011-06-08
|
||||||
|
Fixed an error where CSS @media types weren't being parsed correctly... Thanks Will W.!
|
||||||
|
|
||||||
|
2011-04-08
|
||||||
|
Fixed errors in CSS->XPath conversion for adjacent sibling selectors and id/class combinations... Thanks Bob V.!
|
||||||
|
|
||||||
|
2010-09-03
|
||||||
|
Added checks to invisible node removal to ensure that we don't try to remove non-existent child nodes of parents that have already been deleted
|
||||||
|
|
||||||
|
2010-07-26
|
||||||
|
Fixed bug where '0' values were getting discarded because of php's empty() function... Thanks Scott!
|
||||||
|
|
||||||
|
2010-06-16
|
||||||
|
Added static caching for less processing overhead in situations where multiple emogrification takes place
|
||||||
|
|
||||||
|
2010-05-18
|
||||||
|
Fixed bug where full url filenames with protocols wouldn't get split improperly when we explode on ':'... Thanks Mark!
|
||||||
|
Added two new attribute selectors
|
||||||
|
|
||||||
|
2009-11-04
|
||||||
|
Explicitly declared static functions static to get rid of E_STRICT notices.
|
||||||
|
|
||||||
|
2009-10-29
|
||||||
|
Fixed so that selectors appearing later in the CSS will have precedence over identical selectors appearing earlier.
|
||||||
|
|
||||||
|
2009-08-17
|
||||||
|
Fixed CSS selector processing so that selectors are processed by precedence/specificity, and not just in order.
|
||||||
|
|
||||||
|
2009-08-13
|
||||||
|
Added support for subset class values (e.g. "p.class1.class2"). Added better protection for bad css attributes.
|
||||||
|
Fixed support for HTML entities.
|
||||||
|
|
||||||
|
2009-06-03
|
||||||
|
Normalize existing CSS (style) attributes in the HTML before we process the CSS.
|
||||||
|
Made it so that the display:none stripper doesn't require a trailing semi-colon.
|
||||||
|
|
||||||
|
2008-03-02
|
||||||
|
Added licensing terms under the MIT License; Only remove unprocessable HTML tags if they exist in the array
|
||||||
|
|
||||||
|
2008-10-20
|
||||||
|
Fixed bug with bad variable name... Thanks Thomas!
|
||||||
|
|
||||||
|
2008-08-18
|
||||||
|
Added lines instructing DOMDocument to attempt to normalize HTML before processing
|
||||||
|
|
||||||
|
2008-08-10
|
||||||
|
Fixed CSS comment stripping regex to add PCRE_DOTALL (changed from '/\/\*.*\*\//U' to '/\/\*.*\*\//sU')
|
||||||
981
rainloop/v/0.0.0/app/libraries/emogrifier/Emogrifier.php
Normal file
981
rainloop/v/0.0.0/app/libraries/emogrifier/Emogrifier.php
Normal file
|
|
@ -0,0 +1,981 @@
|
||||||
|
<?php
|
||||||
|
namespace Pelago;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class provides functions for converting CSS styles into inline style attributes in your HTML code.
|
||||||
|
*
|
||||||
|
* For more information, please see the README.md file.
|
||||||
|
*
|
||||||
|
* @author Cameron Brooks
|
||||||
|
* @author Jaime Prado
|
||||||
|
* @author Roman Ožana <ozana@omdesign.cz>
|
||||||
|
*/
|
||||||
|
class Emogrifier
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
const ENCODING = 'UTF-8';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
const CACHE_KEY_CSS = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
const CACHE_KEY_SELECTOR = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
const CACHE_KEY_XPATH = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
const CACHE_KEY_CSS_DECLARATION_BLOCK = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* for calculating nth-of-type and nth-child selectors
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
const INDEX = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* for calculating nth-of-type and nth-child selectors
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
const MULTIPLIER = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
const ID_ATTRIBUTE_MATCHER = '/(\\w+)?\\#([\\w\\-]+)/';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
const CLASS_ATTRIBUTE_MATCHER = '/(\\w+|[\\*\\]])?((\\.[\\w\\-]+)+)/';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $html = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $css = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
private $unprocessableHtmlTags = array('wbr');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array[]
|
||||||
|
*/
|
||||||
|
private $caches = array(
|
||||||
|
self::CACHE_KEY_CSS => array(),
|
||||||
|
self::CACHE_KEY_SELECTOR => array(),
|
||||||
|
self::CACHE_KEY_XPATH => array(),
|
||||||
|
self::CACHE_KEY_CSS_DECLARATION_BLOCK => array(),
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the visited nodes with the XPath paths as array keys
|
||||||
|
*
|
||||||
|
* @var \DOMNode[]
|
||||||
|
*/
|
||||||
|
private $visitedNodes = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the styles to apply to the nodes with the XPath paths as array keys for the outer array
|
||||||
|
* and the attribute names/values as key/value pairs for the inner array
|
||||||
|
*
|
||||||
|
* @var array[]
|
||||||
|
*/
|
||||||
|
private $styleAttributesForNodes = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether the "style" attributes of tags in the the HTML passed to this class should be preserved.
|
||||||
|
* If set to false, the value of the style attributes will be discarded.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $isInlineStyleAttributesParsingEnabled = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether the <style> blocks in the HTML passed to this class should be parsed.
|
||||||
|
*
|
||||||
|
* If set to true, the <style> blocks will be removed from the HTML and their contents will be applied to the HTML
|
||||||
|
* via inline styles.
|
||||||
|
*
|
||||||
|
* If set to false, the <style> blocks will be left as they are in the HTML.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $isStyleBlocksParsingEnabled = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This attribute applies to the case where you want to preserve your original text encoding.
|
||||||
|
*
|
||||||
|
* By default, emogrifier translates your text into HTML entities for two reasons:
|
||||||
|
*
|
||||||
|
* 1. Because of client incompatibilities, it is better practice to send out HTML entities
|
||||||
|
* rather than unicode over email.
|
||||||
|
*
|
||||||
|
* 2. It translates any illegal XML characters that DOMDocument cannot work with.
|
||||||
|
*
|
||||||
|
* If you would like to preserve your original encoding, set this attribute to true.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public $preserveEncoding = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The constructor.
|
||||||
|
*
|
||||||
|
* @param string $html the HTML to emogrify, must be UTF-8-encoded
|
||||||
|
* @param string $css the CSS to merge, must be UTF-8-encoded
|
||||||
|
*/
|
||||||
|
public function __construct($html = '', $css = '')
|
||||||
|
{
|
||||||
|
$this->setHtml($html);
|
||||||
|
$this->setCss($css);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The destructor.
|
||||||
|
*/
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
$this->purgeVisitedNodes();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the HTML to emogrify.
|
||||||
|
*
|
||||||
|
* @param string $html the HTML to emogrify, must be UTF-8-encoded
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setHtml($html)
|
||||||
|
{
|
||||||
|
$this->html = $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the CSS to merge with the HTML.
|
||||||
|
*
|
||||||
|
* @param string $css the CSS to merge, must be UTF-8-encoded
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setCss($css)
|
||||||
|
{
|
||||||
|
$this->css = $css;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the CSS you submit to the HTML you submit.
|
||||||
|
*
|
||||||
|
* This method places the CSS inline.
|
||||||
|
*
|
||||||
|
* @param \DOMDocument $dom = null
|
||||||
|
*
|
||||||
|
* @return string|\DOMDocument
|
||||||
|
*
|
||||||
|
* @throws \BadMethodCallException
|
||||||
|
*/
|
||||||
|
public function emogrify($dom = null)
|
||||||
|
{
|
||||||
|
if ($this->html === '' && !$dom) {
|
||||||
|
throw new \BadMethodCallException('Please set some HTML first before calling emogrify.', 1390393096);
|
||||||
|
}
|
||||||
|
|
||||||
|
$xmlDocument = $dom ? $dom : $this->createXmlDocument();
|
||||||
|
$xpath = new \DOMXPath($xmlDocument);
|
||||||
|
$this->clearAllCaches();
|
||||||
|
|
||||||
|
// Before be begin processing the CSS file, parse the document and normalize all existing CSS attributes.
|
||||||
|
// This changes 'DISPLAY: none' to 'display: none'.
|
||||||
|
// We wouldn't have to do this if DOMXPath supported XPath 2.0.
|
||||||
|
// Also store a reference of nodes with existing inline styles so we don't overwrite them.
|
||||||
|
$this->purgeVisitedNodes();
|
||||||
|
|
||||||
|
$nodesWithStyleAttributes = @$xpath->query('//*[@style]');
|
||||||
|
if ($nodesWithStyleAttributes) {
|
||||||
|
/** @var \DOMElement $node */
|
||||||
|
foreach ($nodesWithStyleAttributes as $node) {
|
||||||
|
if ($this->isInlineStyleAttributesParsingEnabled) {
|
||||||
|
$this->normalizeStyleAttributes($node);
|
||||||
|
} else {
|
||||||
|
$node->removeAttribute('style');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// grab any existing style blocks from the html and append them to the existing CSS
|
||||||
|
// (these blocks should be appended so as to have precedence over conflicting styles in the existing CSS)
|
||||||
|
$allCss = $this->css;
|
||||||
|
|
||||||
|
if ($this->isStyleBlocksParsingEnabled) {
|
||||||
|
$allCss .= $this->getCssFromAllStyleNodes($xpath);
|
||||||
|
}
|
||||||
|
|
||||||
|
$cssParts = $this->splitCssAndMediaQuery($allCss);
|
||||||
|
|
||||||
|
$cssKey = md5($cssParts['css']);
|
||||||
|
if (!isset($this->caches[self::CACHE_KEY_CSS][$cssKey])) {
|
||||||
|
// process the CSS file for selectors and definitions
|
||||||
|
preg_match_all('/(?:^|[\\s^{}]*)([^{]+){([^}]*)}/mis', $cssParts['css'], $matches, PREG_SET_ORDER);
|
||||||
|
|
||||||
|
$allSelectors = array();
|
||||||
|
foreach ($matches as $key => $selectorString) {
|
||||||
|
// if there is a blank definition, skip
|
||||||
|
if (!strlen(trim($selectorString[2]))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// else split by commas and duplicate attributes so we can sort by selector precedence
|
||||||
|
$selectors = explode(',', $selectorString[1]);
|
||||||
|
foreach ($selectors as $selector) {
|
||||||
|
// don't process pseudo-elements and behavioral (dynamic) pseudo-classes;
|
||||||
|
// only allow structural pseudo-classes
|
||||||
|
if (strpos($selector, ':') !== false && !preg_match('/:\\S+\\-(child|type)\\(/i', $selector)
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$allSelectors[] = array('selector' => trim($selector),
|
||||||
|
'attributes' => trim($selectorString[2]),
|
||||||
|
// keep track of where it appears in the file, since order is important
|
||||||
|
'line' => $key,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now sort the selectors by precedence
|
||||||
|
usort($allSelectors, array($this,'sortBySelectorPrecedence'));
|
||||||
|
|
||||||
|
$this->caches[self::CACHE_KEY_CSS][$cssKey] = $allSelectors;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->caches[self::CACHE_KEY_CSS][$cssKey] as $value) {
|
||||||
|
// query the body for the xpath selector
|
||||||
|
$nodesMatchingCssSelectors = @$xpath->query($this->translateCssToXpath($value['selector']));
|
||||||
|
if (!$nodesMatchingCssSelectors)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var \DOMElement $node */
|
||||||
|
foreach ($nodesMatchingCssSelectors as $node) {
|
||||||
|
// if it has a style attribute, get it, process it, and append (overwrite) new stuff
|
||||||
|
if ($node->hasAttribute('style')) {
|
||||||
|
// break it up into an associative array
|
||||||
|
$oldStyleDeclarations = $this->parseCssDeclarationBlock($node->getAttribute('style'));
|
||||||
|
} else {
|
||||||
|
$oldStyleDeclarations = array();
|
||||||
|
}
|
||||||
|
$newStyleDeclarations = $this->parseCssDeclarationBlock($value['attributes']);
|
||||||
|
$node->setAttribute(
|
||||||
|
'style',
|
||||||
|
$this->generateStyleStringFromDeclarationsArrays($oldStyleDeclarations, $newStyleDeclarations)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->isInlineStyleAttributesParsingEnabled) {
|
||||||
|
$this->fillStyleAttributesWithMergedStyles();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This removes styles from your email that contain display:none.
|
||||||
|
// We need to look for display:none, but we need to do a case-insensitive search. Since DOMDocument only
|
||||||
|
// supports XPath 1.0, lower-case() isn't available to us. We've thus far only set attributes to lowercase,
|
||||||
|
// not attribute values. Consequently, we need to translate() the letters that would be in 'NONE' ("NOE")
|
||||||
|
// to lowercase.
|
||||||
|
$nodesWithStyleDisplayNone = @$xpath->query(
|
||||||
|
'//*[contains(translate(translate(@style," ",""),"NOE","noe"),"display:none")]'
|
||||||
|
);
|
||||||
|
// The checks on parentNode and is_callable below ensure that if we've deleted the parent node,
|
||||||
|
// we don't try to call removeChild on a nonexistent child node
|
||||||
|
if ($nodesWithStyleDisplayNone && $nodesWithStyleDisplayNone->length > 0) {
|
||||||
|
/** @var \DOMNode $node */
|
||||||
|
foreach ($nodesWithStyleDisplayNone as $node) {
|
||||||
|
if ($node->parentNode && is_callable(array($node->parentNode,'removeChild'))) {
|
||||||
|
$node->parentNode->removeChild($node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->copyCssWithMediaToStyleNode($cssParts, $xmlDocument);
|
||||||
|
|
||||||
|
if ($dom)
|
||||||
|
{
|
||||||
|
return $xmlDocument;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->preserveEncoding) {
|
||||||
|
return mb_convert_encoding($xmlDocument->saveHTML(), self::ENCODING, 'HTML-ENTITIES');
|
||||||
|
} else {
|
||||||
|
return $xmlDocument->saveHTML();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables the parsing of inline styles.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function disableInlineStyleAttributesParsing()
|
||||||
|
{
|
||||||
|
$this->isInlineStyleAttributesParsingEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables the parsing of <style> blocks.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function disableStyleBlocksParsing()
|
||||||
|
{
|
||||||
|
$this->isStyleBlocksParsingEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears all caches.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function clearAllCaches()
|
||||||
|
{
|
||||||
|
$this->clearCache(self::CACHE_KEY_CSS);
|
||||||
|
$this->clearCache(self::CACHE_KEY_SELECTOR);
|
||||||
|
$this->clearCache(self::CACHE_KEY_XPATH);
|
||||||
|
$this->clearCache(self::CACHE_KEY_CSS_DECLARATION_BLOCK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears a single cache by key.
|
||||||
|
*
|
||||||
|
* @param int $key the cache key, must be CACHE_KEY_CSS, CACHE_KEY_SELECTOR, CACHE_KEY_XPATH
|
||||||
|
* or CACHE_KEY_CSS_DECLARATION_BLOCK
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
private function clearCache($key)
|
||||||
|
{
|
||||||
|
$allowedCacheKeys = array(
|
||||||
|
self::CACHE_KEY_CSS,
|
||||||
|
self::CACHE_KEY_SELECTOR,
|
||||||
|
self::CACHE_KEY_XPATH,
|
||||||
|
self::CACHE_KEY_CSS_DECLARATION_BLOCK,
|
||||||
|
);
|
||||||
|
if (!in_array($key, $allowedCacheKeys, true)) {
|
||||||
|
throw new \InvalidArgumentException('Invalid cache key: ' . $key, 1391822035);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->caches[$key] = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Purges the visited nodes.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function purgeVisitedNodes()
|
||||||
|
{
|
||||||
|
$this->visitedNodes = array();
|
||||||
|
$this->styleAttributesForNodes = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks a tag for removal.
|
||||||
|
*
|
||||||
|
* There are some HTML tags that DOMDocument cannot process, and it will throw an error if it encounters them.
|
||||||
|
* In particular, DOMDocument will complain if you try to use HTML5 tags in an XHTML document.
|
||||||
|
*
|
||||||
|
* Note: The tags will not be removed if they have any content.
|
||||||
|
*
|
||||||
|
* @param string $tagName the tag name, e.g., "p"
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function addUnprocessableHtmlTag($tagName)
|
||||||
|
{
|
||||||
|
$this->unprocessableHtmlTags[] = $tagName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drops a tag from the removal list.
|
||||||
|
*
|
||||||
|
* @param string $tagName the tag name, e.g., "p"
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function removeUnprocessableHtmlTag($tagName)
|
||||||
|
{
|
||||||
|
$key = array_search($tagName, $this->unprocessableHtmlTags, true);
|
||||||
|
if ($key !== false) {
|
||||||
|
unset($this->unprocessableHtmlTags[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalizes the value of the "style" attribute and saves it.
|
||||||
|
*
|
||||||
|
* @param \DOMElement $node
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function normalizeStyleAttributes(\DOMElement $node)
|
||||||
|
{
|
||||||
|
$normalizedOriginalStyle = preg_replace_callback(
|
||||||
|
'/[A-z\\-]+(?=\\:)/S',
|
||||||
|
function (array $m) {
|
||||||
|
return strtolower($m[0]);
|
||||||
|
},
|
||||||
|
$node->getAttribute('style')
|
||||||
|
);
|
||||||
|
|
||||||
|
// in order to not overwrite existing style attributes in the HTML, we
|
||||||
|
// have to save the original HTML styles
|
||||||
|
$nodePath = $node->getNodePath();
|
||||||
|
if (!isset($this->styleAttributesForNodes[$nodePath])) {
|
||||||
|
$this->styleAttributesForNodes[$nodePath] = $this->parseCssDeclarationBlock($normalizedOriginalStyle);
|
||||||
|
$this->visitedNodes[$nodePath] = $node;
|
||||||
|
}
|
||||||
|
|
||||||
|
$node->setAttribute('style', $normalizedOriginalStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merges styles from styles attributes and style nodes and applies them to the attribute nodes
|
||||||
|
*
|
||||||
|
* return @void
|
||||||
|
*/
|
||||||
|
private function fillStyleAttributesWithMergedStyles()
|
||||||
|
{
|
||||||
|
foreach ($this->styleAttributesForNodes as $nodePath => $styleAttributesForNode) {
|
||||||
|
$node = $this->visitedNodes[$nodePath];
|
||||||
|
$currentStyleAttributes = $this->parseCssDeclarationBlock($node->getAttribute('style'));
|
||||||
|
$node->setAttribute(
|
||||||
|
'style',
|
||||||
|
$this->generateStyleStringFromDeclarationsArrays(
|
||||||
|
$currentStyleAttributes,
|
||||||
|
$styleAttributesForNode
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method merges old or existing name/value array with new name/value array
|
||||||
|
* and then generates a string of the combined style suitable for placing inline.
|
||||||
|
* This becomes the single point for CSS string generation allowing for consistent
|
||||||
|
* CSS output no matter where the CSS originally came from.
|
||||||
|
*
|
||||||
|
* @param string[] $oldStyles
|
||||||
|
* @param string[] $newStyles
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function generateStyleStringFromDeclarationsArrays(array $oldStyles, array $newStyles)
|
||||||
|
{
|
||||||
|
$combinedStyles = array_merge($oldStyles, $newStyles);
|
||||||
|
$style = '';
|
||||||
|
foreach ($combinedStyles as $attributeName => $attributeValue) {
|
||||||
|
$style .= (strtolower(trim($attributeName)) . ': ' . trim($attributeValue) . '; ');
|
||||||
|
}
|
||||||
|
return trim($style);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies the media part from CSS array parts to $xmlDocument.
|
||||||
|
*
|
||||||
|
* @param string[] $cssParts
|
||||||
|
* @param \DOMDocument $xmlDocument
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function copyCssWithMediaToStyleNode(array $cssParts, \DOMDocument $xmlDocument)
|
||||||
|
{
|
||||||
|
if (isset($cssParts['media']) && $cssParts['media'] !== '') {
|
||||||
|
$this->addStyleElementToDocument($xmlDocument, $cssParts['media']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns CSS content.
|
||||||
|
*
|
||||||
|
* @param \DOMXPath $xpath
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function getCssFromAllStyleNodes(\DOMXPath $xpath)
|
||||||
|
{
|
||||||
|
$styleNodes = @$xpath->query('//style');
|
||||||
|
|
||||||
|
if (!$styleNodes) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$css = '';
|
||||||
|
/** @var \DOMNode $styleNode */
|
||||||
|
foreach ($styleNodes as $styleNode) {
|
||||||
|
$css .= "\n\n" . $styleNode->nodeValue;
|
||||||
|
$styleNode->parentNode->removeChild($styleNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $css;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a style element with $css to $document.
|
||||||
|
*
|
||||||
|
* This method is protected to allow overriding.
|
||||||
|
*
|
||||||
|
* @see https://github.com/jjriv/emogrifier/issues/103
|
||||||
|
*
|
||||||
|
* @param \DOMDocument $document
|
||||||
|
* @param string $css
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function addStyleElementToDocument(\DOMDocument $document, $css)
|
||||||
|
{
|
||||||
|
$styleElement = $document->createElement('style', $css);
|
||||||
|
$styleAttribute = $document->createAttribute('type');
|
||||||
|
$styleAttribute->value = 'text/css';
|
||||||
|
$styleElement->appendChild($styleAttribute);
|
||||||
|
|
||||||
|
$head = $this->getOrCreateHeadElement($document);
|
||||||
|
$head->appendChild($styleElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the existing or creates a new head element in $document.
|
||||||
|
*
|
||||||
|
* @param \DOMDocument $document
|
||||||
|
*
|
||||||
|
* @return \DOMNode the head element
|
||||||
|
*/
|
||||||
|
private function getOrCreateHeadElement(\DOMDocument $document)
|
||||||
|
{
|
||||||
|
$head = $document->getElementsByTagName('head')->item(0);
|
||||||
|
|
||||||
|
if ($head === null) {
|
||||||
|
$head = $document->createElement('head');
|
||||||
|
$html = $document->getElementsByTagName('html')->item(0);
|
||||||
|
$html->insertBefore($head, $document->getElementsByTagName('body')->item(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $head;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splits input CSS code to an array where:
|
||||||
|
*
|
||||||
|
* - key "css" will be contains clean CSS code
|
||||||
|
* - key "media" will be contains all valuable media queries
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* The CSS code
|
||||||
|
*
|
||||||
|
* "@import "file.css"; h1 { color:red; } @media { h1 {}} @media tv { h1 {}}"
|
||||||
|
*
|
||||||
|
* will be parsed into the following array:
|
||||||
|
*
|
||||||
|
* "css" => "h1 { color:red; }"
|
||||||
|
* "media" => "@media { h1 {}}"
|
||||||
|
*
|
||||||
|
* @param string $css
|
||||||
|
*
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
private function splitCssAndMediaQuery($css)
|
||||||
|
{
|
||||||
|
$media = '';
|
||||||
|
|
||||||
|
$css = preg_replace_callback(
|
||||||
|
'#@media\\s+(?:only\\s)?(?:[\\s{\\(]|screen|all)\\s?[^{]+{.*}\\s*}\\s*#misU',
|
||||||
|
function ($matches) use (&$media) {
|
||||||
|
$media .= $matches[0];
|
||||||
|
},
|
||||||
|
$css
|
||||||
|
);
|
||||||
|
|
||||||
|
// filter the CSS
|
||||||
|
$search = array(
|
||||||
|
// get rid of css comment code
|
||||||
|
'/\\/\\*.*\\*\\//sU',
|
||||||
|
// strip out any import directives
|
||||||
|
'/^\\s*@import\\s[^;]+;/misU',
|
||||||
|
// strip remains media enclosures
|
||||||
|
'/^\\s*@media\\s[^{]+{(.*)}\\s*}\\s/misU',
|
||||||
|
);
|
||||||
|
|
||||||
|
$replace = array(
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
);
|
||||||
|
|
||||||
|
// clean CSS before output
|
||||||
|
$css = preg_replace($search, $replace, $css);
|
||||||
|
|
||||||
|
return array('css' => $css, 'media' => $media);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a DOMDocument instance with the current HTML.
|
||||||
|
*
|
||||||
|
* @return \DOMDocument
|
||||||
|
*/
|
||||||
|
private function createXmlDocument()
|
||||||
|
{
|
||||||
|
$xmlDocument = new \DOMDocument;
|
||||||
|
$xmlDocument->encoding = self::ENCODING;
|
||||||
|
$xmlDocument->strictErrorChecking = false;
|
||||||
|
$xmlDocument->formatOutput = true;
|
||||||
|
$libXmlState = libxml_use_internal_errors(true);
|
||||||
|
@$xmlDocument->loadHTML($this->getUnifiedHtml());
|
||||||
|
libxml_clear_errors();
|
||||||
|
libxml_use_internal_errors($libXmlState);
|
||||||
|
$xmlDocument->normalizeDocument();
|
||||||
|
|
||||||
|
return $xmlDocument;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the HTML with the non-ASCII characters converts into HTML entities and the unprocessable
|
||||||
|
* HTML tags removed.
|
||||||
|
*
|
||||||
|
* @return string the unified HTML
|
||||||
|
*
|
||||||
|
* @throws \BadMethodCallException
|
||||||
|
*/
|
||||||
|
private function getUnifiedHtml()
|
||||||
|
{
|
||||||
|
if (!empty($this->unprocessableHtmlTags)) {
|
||||||
|
$unprocessableHtmlTags = implode('|', $this->unprocessableHtmlTags);
|
||||||
|
$bodyWithoutUnprocessableTags = preg_replace(
|
||||||
|
'/<\\/?(' . $unprocessableHtmlTags . ')[^>]*>/i',
|
||||||
|
'',
|
||||||
|
$this->html
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$bodyWithoutUnprocessableTags = $this->html;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mb_convert_encoding($bodyWithoutUnprocessableTags, 'HTML-ENTITIES', self::ENCODING);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string[] $a
|
||||||
|
* @param string[] $b
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
private function sortBySelectorPrecedence(array $a, array $b)
|
||||||
|
{
|
||||||
|
$precedenceA = $this->getCssSelectorPrecedence($a['selector']);
|
||||||
|
$precedenceB = $this->getCssSelectorPrecedence($b['selector']);
|
||||||
|
|
||||||
|
// We want these sorted in ascending order so selectors with lesser precedence get processed first and
|
||||||
|
// selectors with greater precedence get sorted last.
|
||||||
|
$precedenceForEquals = ($a['line'] < $b['line'] ? -1 : 1);
|
||||||
|
$precedenceForNotEquals = ($precedenceA < $precedenceB ? -1 : 1);
|
||||||
|
return ($precedenceA === $precedenceB) ? $precedenceForEquals : $precedenceForNotEquals;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $selector
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
private function getCssSelectorPrecedence($selector)
|
||||||
|
{
|
||||||
|
$selectorKey = md5($selector);
|
||||||
|
if (!isset($this->caches[self::CACHE_KEY_SELECTOR][$selectorKey])) {
|
||||||
|
$precedence = 0;
|
||||||
|
$value = 100;
|
||||||
|
// ids: worth 100, classes: worth 10, elements: worth 1
|
||||||
|
$search = array('\\#','\\.','');
|
||||||
|
|
||||||
|
foreach ($search as $s) {
|
||||||
|
if (trim($selector) === '') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$number = 0;
|
||||||
|
$selector = preg_replace('/' . $s . '\\w+/', '', $selector, -1, $number);
|
||||||
|
$precedence += ($value * $number);
|
||||||
|
$value /= 10;
|
||||||
|
}
|
||||||
|
$this->caches[self::CACHE_KEY_SELECTOR][$selectorKey] = $precedence;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->caches[self::CACHE_KEY_SELECTOR][$selectorKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Right now, we support all CSS 1 selectors and most CSS2/3 selectors.
|
||||||
|
*
|
||||||
|
* @see http://plasmasturm.org/log/444/
|
||||||
|
*
|
||||||
|
* @param string $paramCssSelector
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function translateCssToXpath($paramCssSelector)
|
||||||
|
{
|
||||||
|
$cssSelector = ' ' . $paramCssSelector . ' ';
|
||||||
|
$cssSelector = preg_replace_callback(
|
||||||
|
'/\\s+\\w+\\s+/',
|
||||||
|
function (array $matches) {
|
||||||
|
return strtolower($matches[0]);
|
||||||
|
},
|
||||||
|
$cssSelector
|
||||||
|
);
|
||||||
|
$cssSelector = trim($cssSelector);
|
||||||
|
$xpathKey = md5($cssSelector);
|
||||||
|
if (!isset($this->caches[self::CACHE_KEY_XPATH][$xpathKey])) {
|
||||||
|
// returns an Xpath selector
|
||||||
|
$search = array(
|
||||||
|
// Matches any element that is a child of parent.
|
||||||
|
'/\\s+>\\s+/',
|
||||||
|
// Matches any element that is an adjacent sibling.
|
||||||
|
'/\\s+\\+\\s+/',
|
||||||
|
// Matches any element that is a descendant of an parent element element.
|
||||||
|
'/\\s+/',
|
||||||
|
// first-child pseudo-selector
|
||||||
|
'/([^\\/]+):first-child/i',
|
||||||
|
// last-child pseudo-selector
|
||||||
|
'/([^\\/]+):last-child/i',
|
||||||
|
// Matches attribute only selector
|
||||||
|
'/^\\[(\\w+)\\]/',
|
||||||
|
// Matches element with attribute
|
||||||
|
'/(\\w)\\[(\\w+)\\]/',
|
||||||
|
// Matches element with EXACT attribute
|
||||||
|
'/(\\w)\\[(\\w+)\\=[\'"]?(\\w+)[\'"]?\\]/',
|
||||||
|
);
|
||||||
|
$replace = array(
|
||||||
|
'/',
|
||||||
|
'/following-sibling::*[1]/self::',
|
||||||
|
'//',
|
||||||
|
'*[1]/self::\\1',
|
||||||
|
'*[last()]/self::\\1',
|
||||||
|
'*[@\\1]',
|
||||||
|
'\\1[@\\2]',
|
||||||
|
'\\1[@\\2="\\3"]',
|
||||||
|
);
|
||||||
|
|
||||||
|
$cssSelector = '//' . preg_replace($search, $replace, $cssSelector);
|
||||||
|
|
||||||
|
$cssSelector = preg_replace_callback(
|
||||||
|
self::ID_ATTRIBUTE_MATCHER,
|
||||||
|
array($this, 'matchIdAttributes'),
|
||||||
|
$cssSelector
|
||||||
|
);
|
||||||
|
$cssSelector = preg_replace_callback(
|
||||||
|
self::CLASS_ATTRIBUTE_MATCHER,
|
||||||
|
array($this, 'matchClassAttributes'),
|
||||||
|
$cssSelector
|
||||||
|
);
|
||||||
|
|
||||||
|
// Advanced selectors are going to require a bit more advanced emogrification.
|
||||||
|
// When we required PHP 5.3, we could do this with closures.
|
||||||
|
$cssSelector = preg_replace_callback(
|
||||||
|
'/([^\\/]+):nth-child\\(\\s*(odd|even|[+\\-]?\\d|[+\\-]?\\d?n(\\s*[+\\-]\\s*\\d)?)\\s*\\)/i',
|
||||||
|
array($this, 'translateNthChild'),
|
||||||
|
$cssSelector
|
||||||
|
);
|
||||||
|
$cssSelector = preg_replace_callback(
|
||||||
|
'/([^\\/]+):nth-of-type\\(\s*(odd|even|[+\\-]?\\d|[+\\-]?\\d?n(\\s*[+\\-]\\s*\\d)?)\\s*\\)/i',
|
||||||
|
array($this, 'translateNthOfType'),
|
||||||
|
$cssSelector
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->caches[self::CACHE_KEY_SELECTOR][$xpathKey] = $cssSelector;
|
||||||
|
}
|
||||||
|
return $this->caches[self::CACHE_KEY_SELECTOR][$xpathKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string[] $match
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function matchIdAttributes(array $match)
|
||||||
|
{
|
||||||
|
return (strlen($match[1]) ? $match[1] : '*') . '[@id="' . $match[2] . '"]';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string[] $match
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function matchClassAttributes(array $match)
|
||||||
|
{
|
||||||
|
return (strlen($match[1]) ? $match[1] : '*') . '[contains(concat(" ",@class," "),concat(" ","' .
|
||||||
|
implode(
|
||||||
|
'"," "))][contains(concat(" ",@class," "),concat(" ","',
|
||||||
|
explode('.', substr($match[2], 1))
|
||||||
|
) . '"," "))]';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string[] $match
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function translateNthChild(array $match)
|
||||||
|
{
|
||||||
|
$result = $this->parseNth($match);
|
||||||
|
|
||||||
|
if (isset($result[self::MULTIPLIER])) {
|
||||||
|
if ($result[self::MULTIPLIER] < 0) {
|
||||||
|
$result[self::MULTIPLIER] = abs($result[self::MULTIPLIER]);
|
||||||
|
return sprintf(
|
||||||
|
'*[(last() - position()) mod %u = %u]/self::%s',
|
||||||
|
$result[self::MULTIPLIER],
|
||||||
|
$result[self::INDEX],
|
||||||
|
$match[1]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return sprintf(
|
||||||
|
'*[position() mod %u = %u]/self::%s',
|
||||||
|
$result[self::MULTIPLIER],
|
||||||
|
$result[self::INDEX],
|
||||||
|
$match[1]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return sprintf('*[%u]/self::%s', $result[self::INDEX], $match[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string[] $match
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function translateNthOfType(array $match)
|
||||||
|
{
|
||||||
|
$result = $this->parseNth($match);
|
||||||
|
|
||||||
|
if (isset($result[self::MULTIPLIER])) {
|
||||||
|
if ($result[self::MULTIPLIER] < 0) {
|
||||||
|
$result[self::MULTIPLIER] = abs($result[self::MULTIPLIER]);
|
||||||
|
return sprintf(
|
||||||
|
'%s[(last() - position()) mod %u = %u]',
|
||||||
|
$match[1],
|
||||||
|
$result[self::MULTIPLIER],
|
||||||
|
$result[self::INDEX]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return sprintf(
|
||||||
|
'%s[position() mod %u = %u]',
|
||||||
|
$match[1],
|
||||||
|
$result[self::MULTIPLIER],
|
||||||
|
$result[self::INDEX]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return sprintf('%s[%u]', $match[1], $result[self::INDEX]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string[] $match
|
||||||
|
*
|
||||||
|
* @return int[]
|
||||||
|
*/
|
||||||
|
private function parseNth(array $match)
|
||||||
|
{
|
||||||
|
if (in_array(strtolower($match[2]), array('even','odd'), true)) {
|
||||||
|
$index = strtolower($match[2]) === 'even' ? 0 : 1;
|
||||||
|
return array(self::MULTIPLIER => 2, self::INDEX => $index);
|
||||||
|
} elseif (stripos($match[2], 'n') === false) {
|
||||||
|
// if there is a multiplier
|
||||||
|
$index = (int) str_replace(' ', '', $match[2]);
|
||||||
|
return array(self::INDEX => $index);
|
||||||
|
} else {
|
||||||
|
if (isset($match[3])) {
|
||||||
|
$multipleTerm = str_replace($match[3], '', $match[2]);
|
||||||
|
$index = (int) str_replace(' ', '', $match[3]);
|
||||||
|
} else {
|
||||||
|
$multipleTerm = $match[2];
|
||||||
|
$index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$multiplier = (int) str_ireplace('n', '', $multipleTerm);
|
||||||
|
|
||||||
|
if (!strlen($multiplier)) {
|
||||||
|
$multiplier = 1;
|
||||||
|
} elseif ($multiplier === 0) {
|
||||||
|
return array(self::INDEX => $index);
|
||||||
|
} else {
|
||||||
|
$multiplier = (int) $multiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
while ($index < 0) {
|
||||||
|
$index += abs($multiplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array(self::MULTIPLIER => $multiplier, self::INDEX => $index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a CSS declaration block into property name/value pairs.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* The declaration block
|
||||||
|
*
|
||||||
|
* "color: #000; font-weight: bold;"
|
||||||
|
*
|
||||||
|
* will be parsed into the following array:
|
||||||
|
*
|
||||||
|
* "color" => "#000"
|
||||||
|
* "font-weight" => "bold"
|
||||||
|
*
|
||||||
|
* @param string $cssDeclarationBlock the CSS declaration block without the curly braces, may be empty
|
||||||
|
*
|
||||||
|
* @return string[]
|
||||||
|
* the CSS declarations with the property names as array keys and the property values as array values
|
||||||
|
*/
|
||||||
|
private function parseCssDeclarationBlock($cssDeclarationBlock)
|
||||||
|
{
|
||||||
|
if (isset($this->caches[self::CACHE_KEY_CSS_DECLARATION_BLOCK][$cssDeclarationBlock])) {
|
||||||
|
return $this->caches[self::CACHE_KEY_CSS_DECLARATION_BLOCK][$cssDeclarationBlock];
|
||||||
|
}
|
||||||
|
|
||||||
|
$properties = array();
|
||||||
|
$declarations = explode(';', $cssDeclarationBlock);
|
||||||
|
foreach ($declarations as $declaration) {
|
||||||
|
$matches = array();
|
||||||
|
if (!preg_match('/ *([A-Za-z\\-]+) *: *([^;]+) */', $declaration, $matches)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$propertyName = strtolower($matches[1]);
|
||||||
|
$propertyValue = $matches[2];
|
||||||
|
$properties[$propertyName] = $propertyValue;
|
||||||
|
}
|
||||||
|
$this->caches[self::CACHE_KEY_CSS_DECLARATION_BLOCK][$cssDeclarationBlock] = $properties;
|
||||||
|
|
||||||
|
return $properties;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
rainloop/v/0.0.0/app/libraries/emogrifier/LICENSE
Normal file
21
rainloop/v/0.0.0/app/libraries/emogrifier/LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
Emogrifier is copyright (c) 2008-2014 Pelago and licensed under the MIT license.
|
||||||
|
|
||||||
|
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
185
rainloop/v/0.0.0/app/libraries/emogrifier/README.md
Normal file
185
rainloop/v/0.0.0/app/libraries/emogrifier/README.md
Normal file
|
|
@ -0,0 +1,185 @@
|
||||||
|
# Emogrifier
|
||||||
|
|
||||||
|
[](https://travis-ci.org/jjriv/emogrifier)
|
||||||
|
[](https://packagist.org/packages/pelago/emogrifier)
|
||||||
|
[](https://packagist.org/packages/pelago/emogrifier)
|
||||||
|
[](https://packagist.org/packages/pelago/emogrifier)
|
||||||
|
[](https://packagist.org/packages/pelago/emogrifier)
|
||||||
|
|
||||||
|
_n. e•mog•ri•fi•er [\ē-'mä-grƏ-,fī-Ər\] - a utility for changing completely the nature or appearance of HTML email,
|
||||||
|
esp. in a particularly fantastic or bizarre manner_
|
||||||
|
|
||||||
|
Emogrifier converts CSS styles into inline style attributes in your HTML code. This ensures proper display on email
|
||||||
|
and mobile device readers that lack stylesheet support.
|
||||||
|
|
||||||
|
This utility was developed as part of [Intervals](http://www.myintervals.com/) to deal with the problems posed by
|
||||||
|
certain email clients (namely Outlook 2007 and Google Gmail) when it comes to the way they handle styling contained
|
||||||
|
in HTML emails. As many web developers and designers already know, certain email clients are notorious for their
|
||||||
|
lack of CSS support. While attempts are being made to develop common
|
||||||
|
[email standards](http://www.email-standards.org/), implementation is still a ways off.
|
||||||
|
|
||||||
|
The primary problem with uncooperative email clients is that most tend to only regard inline CSS, discarding all
|
||||||
|
`<style>` elements and links to stylesheets in `<link>` elements. Emogrifier solves this problem by converting CSS
|
||||||
|
styles into inline style attributes in your HTML code.
|
||||||
|
|
||||||
|
|
||||||
|
- [How it works](#how-it-works)
|
||||||
|
- [Usage](#usage)
|
||||||
|
- [Installing with Composer](#installing-with-composer)
|
||||||
|
- [Usage](#usage)
|
||||||
|
- [Supported CSS selectors](#supported-css-selectors)
|
||||||
|
- [Caveats](#caveats)
|
||||||
|
- [Maintainer](#maintainer)
|
||||||
|
- [Contributing](#contributing)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## How it Works
|
||||||
|
|
||||||
|
Emogrifier automagically transmogrifies your HTML by parsing your CSS and inserting your CSS definitions into tags
|
||||||
|
within your HTML based on your CSS selectors.
|
||||||
|
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
First, you provide Emogrifier with the HTML and CSS you would like to merge. This can happen directly during
|
||||||
|
instantiation:
|
||||||
|
|
||||||
|
$html = '<html><h1>Hello world!</h1></html>';
|
||||||
|
$css = 'h1 {font-size: 32px;}';
|
||||||
|
$emogrifier = new \Pelago\Emogrifier($html, $css);
|
||||||
|
|
||||||
|
You could also use the setters for providing this data after instantiation:
|
||||||
|
|
||||||
|
$emogrifier = new \Pelago\Emogrifier();
|
||||||
|
|
||||||
|
$html = '<html><h1>Hello world!</h1></html>';
|
||||||
|
$css = 'h1 {font-size: 32px;}';
|
||||||
|
|
||||||
|
$emogrifier->setHtml($html);
|
||||||
|
$emogrifier->setCss($css);
|
||||||
|
|
||||||
|
After you have set the HTML and CSS, you can call the `emogrify` method to merge both:
|
||||||
|
|
||||||
|
$mergedHtml = $emogrifier->emogrify();
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
There are several options that you can set on the Emogrifier object before
|
||||||
|
calling the `emogrify` method:
|
||||||
|
|
||||||
|
* `$emogrifier->disableStyleBlocksParsing()` - By default, Emogrifier will grab
|
||||||
|
all `<style>` blocks in the HTML and will apply the CSS styles as inline
|
||||||
|
"style" attributes to the HTML. The `<style>` blocks will then be removed
|
||||||
|
from the HTML. If you want to disable this functionality so that Emogrifier
|
||||||
|
leaves these `<style>` blocks in the HTML and does not parse them, you should
|
||||||
|
use this option.
|
||||||
|
* `$emogrifier->disableInlineStylesParsing()` - By default, Emogrifier
|
||||||
|
preserves all of the "style" attributes on tags in the HTML you pass to it.
|
||||||
|
However if you want to discard all existing inline styles in the HTML before
|
||||||
|
the CSS is applied, you should use this option.
|
||||||
|
|
||||||
|
|
||||||
|
## Installing with Composer
|
||||||
|
|
||||||
|
Download the [`composer.phar`](https://getcomposer.org/composer.phar) locally or install [Composer](https://getcomposer.org/) globally:
|
||||||
|
|
||||||
|
curl -s https://getcomposer.org/installer | php
|
||||||
|
|
||||||
|
Run the following command for a local installation:
|
||||||
|
|
||||||
|
php composer.phar require pelago/emogrifier:@dev
|
||||||
|
|
||||||
|
Or for a global installation, run the following command:
|
||||||
|
|
||||||
|
composer require pelago/emogrifier:@dev
|
||||||
|
|
||||||
|
You can also add follow lines to your `composer.json` and run the `composer update` command:
|
||||||
|
|
||||||
|
"require": {
|
||||||
|
"pelago/emogrifier": "@dev"
|
||||||
|
}
|
||||||
|
|
||||||
|
See https://getcomposer.org/ for more information and documentation.
|
||||||
|
|
||||||
|
|
||||||
|
## Supported CSS selectors
|
||||||
|
|
||||||
|
Emogrifier currently support the following [CSS selectors](http://www.w3.org/TR/CSS2/selector.html):
|
||||||
|
|
||||||
|
* ID
|
||||||
|
* class
|
||||||
|
* type
|
||||||
|
* descendant
|
||||||
|
* child
|
||||||
|
* adjacent
|
||||||
|
* attribute presence
|
||||||
|
* attribute value
|
||||||
|
* attribute only
|
||||||
|
|
||||||
|
The following selectors are implemented, but currently are broken:
|
||||||
|
|
||||||
|
* first-child (currently broken)
|
||||||
|
* last-child (currently broken)
|
||||||
|
|
||||||
|
The following selectors are not implemented yet:
|
||||||
|
|
||||||
|
* universal
|
||||||
|
|
||||||
|
|
||||||
|
## Caveats
|
||||||
|
|
||||||
|
* Emogrifier requires the HTML and the CSS to be UTF-8. Encodings like ISO8859-1 or ISO8859-15 are not supported.
|
||||||
|
* **NEW:** Emogrifier now preserves all valuable @media queries. Media queries can be very useful in
|
||||||
|
responsive email design. See [media query support](https://litmus.com/help/email-clients/media-query-support/).
|
||||||
|
* **NEW:** Emogrifier will grab existing inline style attributes _and_ will grab `<style>` blocks from your HTML, but it
|
||||||
|
will not grab CSS files referenced in <link> elements (the problem email clients are going to ignore these tags
|
||||||
|
anyway, so why leave them in your HTML?).
|
||||||
|
* Even with styles inline, certain CSS properties are ignored by certain email clients. For more information,
|
||||||
|
refer to these resources:
|
||||||
|
* [http://www.email-standards.org/](http://www.email-standards.org/)
|
||||||
|
* [https://www.campaignmonitor.com/css/](https://www.campaignmonitor.com/css/)
|
||||||
|
* [http://templates.mailchimp.com/resources/email-client-css-support/](http://templates.mailchimp.com/resources/email-client-css-support/)
|
||||||
|
* All CSS attributes that apply to a node will be applied, even if they are redundant. For example, if you define a
|
||||||
|
font attribute _and_ a font-size attribute, both attributes will be applied to that node (in other words, the more
|
||||||
|
specific attribute will not be combined into the more general attribute).
|
||||||
|
* There's a good chance you might encounter problems if your HTML is not well formed and valid (DOMDocument might
|
||||||
|
complain). If you get problems like this, consider running your HTML through [Tidy](http://php.net/manual/en/book.tidy.php)
|
||||||
|
before you pass it to Emogrifier.
|
||||||
|
* Finally, Emogrifier only supports CSS1 level selectors and a few CSS2 level selectors (but not all of them). It
|
||||||
|
does not support pseudo selectors (Emogrifier works by converting CSS selectors to XPath selectors, and pseudo
|
||||||
|
selectors cannot be converted accurately).
|
||||||
|
* `!important` currently is not supported yet.
|
||||||
|
|
||||||
|
|
||||||
|
## Maintainer
|
||||||
|
|
||||||
|
Emogrifier is maintained by the good people at [Pelago](http://www.pelagodesign.com/), info AT pelagodesign DOT com.
|
||||||
|
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Those that wish to contribute bug fixes, new features, refactorings and clean-up to Emogrifier are more than welcome.
|
||||||
|
When you contribute, please take the following things into account:
|
||||||
|
|
||||||
|
* Please cover all changes with unit tests and make sure that your code does not break any existing tests.
|
||||||
|
* To run the existing PHPUnit tests, run the following
|
||||||
|
command:
|
||||||
|
|
||||||
|
composer install
|
||||||
|
|
||||||
|
and then run this command:
|
||||||
|
|
||||||
|
vendor/bin/phpunit Tests/
|
||||||
|
|
||||||
|
If you want to uninstall the development packages that were installed when
|
||||||
|
you ran `composer install`, you can run this command:
|
||||||
|
|
||||||
|
composer install --no-dev
|
||||||
|
* Please use the same coding style (PSR-2) as the rest of the code. Indentation is four spaces.
|
||||||
|
* Please make your code clean, well-readable and easy to understand.
|
||||||
|
* If you add new methods or fields, please use proper PHPDoc for the new methods/fields.
|
||||||
|
* Git commits should have a <= 50 character summary, optionally followed by a blank line
|
||||||
|
and a more in depth description of 79 characters per line.
|
||||||
|
* [Please squash related commits together](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html).
|
||||||
|
* Please use grammatically correct, complete sentences in the code documentation and the commit messages.
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<meta name="Author" content="RainLoop Team" />
|
<meta name="Author" content="RainLoop Team" />
|
||||||
<meta name="robots" content="noindex, nofollow, noodp" />
|
<meta name="robots" content="noindex,nofollow,noodp" />
|
||||||
<meta name="google" content="notranslate" />
|
<meta name="google" content="notranslate" />
|
||||||
<title></title>
|
<title></title>
|
||||||
<style>#rl-content{display:none;}</style>
|
<style>#rl-content{display:none;}</style>
|
||||||
|
|
|
||||||
|
|
@ -59,12 +59,15 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.thm-login {
|
.thm-login {
|
||||||
color: @login-color !important;
|
|
||||||
border: @login-border !important;
|
border: @login-border !important;
|
||||||
.thm-rgba-background-color(@login-background-color, @login-rgba-background-color);
|
.thm-rgba-background-color(@login-background-color, @login-rgba-background-color);
|
||||||
.thm-linear-gradient-mixin(@login-gradient-start, @login-gradient-end);
|
.thm-linear-gradient-mixin(@login-gradient-start, @login-gradient-end);
|
||||||
.thm-border-radius(@login-border-radius);
|
.thm-border-radius(@login-border-radius);
|
||||||
.thm-box-shadow(@login-box-shadow);
|
.thm-box-shadow(@login-box-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.thm-login-text {
|
||||||
|
color: @login-color !important;
|
||||||
|
|
||||||
.legend, .e-checkbox-icon, .g-ui-link, .social-button, .language-button {
|
.legend, .e-checkbox-icon, .g-ui-link, .social-button, .language-button {
|
||||||
color: @login-color !important;
|
color: @login-color !important;
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
<button type="button" class="close" data-bind="click: function () { submitError('') }">×</button>
|
<button type="button" class="close" data-bind="click: function () { submitError('') }">×</button>
|
||||||
<span data-bind="text: submitError"></span>
|
<span data-bind="text: submitError"></span>
|
||||||
</div>
|
</div>
|
||||||
<form class="wrapper loginForm thm-login" action="#/" data-bind="submit: submitForm, css: {'errorAnimated': formError}">
|
<form class="wrapper loginForm thm-login thm-login-text" action="#/" data-bind="submit: submitForm, css: {'errorAnimated': formError}">
|
||||||
<div class="controls" data-bind="css: {'error': loginError, 'animated': loginErrorAnimation}">
|
<div class="controls" data-bind="css: {'error': loginError, 'animated': loginErrorAnimation}">
|
||||||
<div class="input-append">
|
<div class="input-append">
|
||||||
<input type="text" class="input-block-level inputLogin checkAutocomplete"
|
<input type="text" class="input-block-level inputLogin checkAutocomplete"
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
<div class="alert alert-info span8" data-bind="visible: 0 === plugins().length">
|
<div class="alert alert-info span8" data-bind="visible: 0 === plugins().length">
|
||||||
<span data-i18n="TAB_PLUGINS/ALERT_NO_PLUGINS"></span>
|
<span data-i18n="TAB_PLUGINS/ALERT_NO_PLUGINS"></span>
|
||||||
<br />
|
<br />
|
||||||
|
<br />
|
||||||
<strong><a href="#/packages" data-i18n="TAB_PLUGINS/LINK_INSTALL_NEW"></a></strong>
|
<strong><a href="#/packages" data-i18n="TAB_PLUGINS/LINK_INSTALL_NEW"></a></strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="span8" data-bind="visible: 0 < plugins().length">
|
<div class="span8" data-bind="visible: 0 < plugins().length">
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,11 @@
|
||||||
<span data-bind="text: submitErrorAddidional"></span>
|
<span data-bind="text: submitErrorAddidional"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<form class="wrapper loginForm thm-login" action="#/" data-bind="submit: submitForm, css: {'errorAnimated': formError}">
|
<div class="wrapper-parent">
|
||||||
|
<div class="wrapper loginWelcomeForm thm-login-text" data-bind="css: {'welcome-on': welcome}">
|
||||||
|
<!-- ko template: { name: 'LoginWelcome' } --><!-- /ko -->
|
||||||
|
</div>
|
||||||
|
<form class="wrapper loginForm thm-login thm-login-text" action="#/" data-bind="submit: submitForm, css: {'errorAnimated': formError, 'welcome-off': welcome}">
|
||||||
{{INCLUDE/TopControlGroup/PLACE}}
|
{{INCLUDE/TopControlGroup/PLACE}}
|
||||||
<div class="controls" data-bind="css: {'error': emailError, 'animated': emailErrorAnimation}">
|
<div class="controls" data-bind="css: {'error': emailError, 'animated': emailErrorAnimation}">
|
||||||
<div class="input-append">
|
<div class="input-append">
|
||||||
|
|
@ -110,6 +114,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
<div class="e-powered thm-powered" data-bind="visible: logoPowered">
|
<div class="e-powered thm-powered" data-bind="visible: logoPowered">
|
||||||
Powered by <a href="http://rainloop.net" target="_blank">RainLoop</a>
|
Powered by <a href="http://rainloop.net" target="_blank">RainLoop</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<div class="b-login--welcome-content">
|
||||||
|
<i class="icon-google iconsize50" style="cursor: pointer; padding-right: 20px" data-bind="command: googleFastCommand, visible: googleFastLoginEnabled"></i>
|
||||||
|
<i class="icon-mail iconsize50" style="cursor: pointer" data-bind="click: displayMainForm"></i>
|
||||||
|
<br /><br />
|
||||||
|
</div>
|
||||||
|
|
@ -295,10 +295,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="messageItem fixIndex" data-bind="css: viewLineAsCss(), nano: true, attr: {'style': 'top:' + viewBodyTopValue() + 'px' }">
|
<div class="messageItem fixIndex" data-bind="css: viewLineAsCss(), nano: true, attr: {'style': 'top:' + viewBodyTopValue() + 'px' }">
|
||||||
<div class="content g-scrollbox" tabindex="0" data-bind="hasfocus: messageDomFocused, css: { 'focused': messageDomFocused }">
|
<div class="content g-scrollbox" tabindex="0" data-bind="hasfocus: messageDomFocused, css: { 'focused': messageDomFocused },
|
||||||
<div class="content-wrapper">
|
style: {'background-color': messageLoadingThrottle() ? '' : bodyBackgroundColor()}">
|
||||||
<div>
|
|
||||||
|
|
||||||
|
<div class="content-wrapper">
|
||||||
|
|
||||||
|
<div>
|
||||||
<span class="buttonUp" data-bind="click: scrollMessageToTop">
|
<span class="buttonUp" data-bind="click: scrollMessageToTop">
|
||||||
<i class="icon-up"></i>
|
<i class="icon-up"></i>
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -316,7 +318,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="g-ui-min-height-300" data-bind="visible: !messageLoadingThrottle()">
|
<div class="g-ui-min-height-300 content-content" data-bind="visible: !messageLoadingThrottle()">
|
||||||
|
<div class="bodySubHeader">
|
||||||
<div class="showImages" data-bind="visible: message() && message().hasImages(), click: function() { showImages(message()); }">
|
<div class="showImages" data-bind="visible: message() && message().hasImages(), click: function() { showImages(message()); }">
|
||||||
<i class="icon-image"></i>
|
<i class="icon-image"></i>
|
||||||
|
|
||||||
|
|
@ -396,6 +399,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
<i class="icon-cog controls-handle" data-bind="visible: allowAttachmnetControls() && !showAttachmnetControls(), click: function () { showAttachmnetControls(true) }"></i>
|
<i class="icon-cog controls-handle" data-bind="visible: allowAttachmnetControls() && !showAttachmnetControls(), click: function () { showAttachmnetControls(true) }"></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="attachmentsControls"
|
<div class="attachmentsControls"
|
||||||
data-bind="visible: showAttachmnetControls() && message() && message().hasVisibleAttachments()">
|
data-bind="visible: showAttachmnetControls() && message() && message().hasVisibleAttachments()">
|
||||||
|
|
||||||
|
|
@ -433,11 +437,13 @@
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<!-- // --->
|
|
||||||
<button type="button" class="close" style="margin-right: 5px;"
|
<button type="button" class="close" style="margin-right: 5px;"
|
||||||
data-bind="click: function () { showAttachmnetControls(false); }">×</button>
|
data-bind="click: function () { showAttachmnetControls(false); }">×</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="bodyText g-ui-min-height-300 rl-mv-root" data-bind="initDom: messagesBodiesDom"></div>
|
</div>
|
||||||
|
|
||||||
|
<div class="bodyText g-ui-min-height-300 rl-mv-root"
|
||||||
|
data-bind="initDom: messagesBodiesDom"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,8 @@
|
||||||
</label>
|
</label>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<div style="display: inline-block; margin-top: 5px">
|
<div style="display: inline-block; margin-top: 5px">
|
||||||
<b data-bind="text: identityMainDesc"></b>
|
<b data-bind="click: editMainIdentity, text: identityMainDesc"
|
||||||
|
style="border-bottom: 1px dashed #555; cursor: pointer; padding: 2px 0px;"></b>
|
||||||
<span data-bind="click: editMainIdentity" style="cursor: pointer">
|
|
||||||
<i class="icon-cog"></i>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ LABEL_BRANDING_NAME = "Брендинг"
|
||||||
LABEL_CONTACTS_NAME = "Контакты"
|
LABEL_CONTACTS_NAME = "Контакты"
|
||||||
LABEL_DOMAINS_NAME = "Домены"
|
LABEL_DOMAINS_NAME = "Домены"
|
||||||
LABEL_SECURITY_NAME = "Безопасность"
|
LABEL_SECURITY_NAME = "Безопасность"
|
||||||
LABEL_INTEGRATION_NAME = "Инеграция"
|
LABEL_INTEGRATION_NAME = "Интеграция"
|
||||||
LABEL_PLUGINS_NAME = "Плагины"
|
LABEL_PLUGINS_NAME = "Плагины"
|
||||||
LABEL_PACKAGES_NAME = "Пакеты"
|
LABEL_PACKAGES_NAME = "Пакеты"
|
||||||
LABEL_LICENSING_NAME = "Лицензия"
|
LABEL_LICENSING_NAME = "Лицензия"
|
||||||
|
|
|
||||||
3
vendors/bootstrap/less/tables.less
vendored
3
vendors/bootstrap/less/tables.less
vendored
|
|
@ -6,7 +6,8 @@
|
||||||
// BASE TABLES
|
// BASE TABLES
|
||||||
// -----------------
|
// -----------------
|
||||||
|
|
||||||
table {
|
// table {
|
||||||
|
.table {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
background-color: @tableBackground;
|
background-color: @tableBackground;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
|
|
|
||||||
3
vendors/normalize/normalize.css
vendored
3
vendors/normalize/normalize.css
vendored
|
|
@ -498,7 +498,10 @@ textarea {
|
||||||
* Remove most spacing between table cells
|
* Remove most spacing between table cells
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
table {
|
table {
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue