Merge branch 'master' of github.com:Mailtrain-org/mailtrain into access
This commit is contained in:
commit
e7856bfb73
8 changed files with 368 additions and 364 deletions
|
@ -5,7 +5,7 @@ module.exports = function (grunt) {
|
||||||
// Project configuration.
|
// Project configuration.
|
||||||
grunt.initConfig({
|
grunt.initConfig({
|
||||||
eslint: {
|
eslint: {
|
||||||
all: ['lib/**/*.js', 'test/**/*.js', 'config/**/*.js', 'Gruntfile.js', 'app.js', 'index.js']
|
all: ['lib/**/*.js', 'test/**/*.js', 'config/**/*.js', 'Gruntfile.js', 'app.js', 'index.js', 'routes/editorapi.js']
|
||||||
},
|
},
|
||||||
|
|
||||||
nodeunit: {
|
nodeunit: {
|
||||||
|
|
|
@ -67,7 +67,6 @@
|
||||||
"express-session": "^1.15.2",
|
"express-session": "^1.15.2",
|
||||||
"faker": "^4.1.0",
|
"faker": "^4.1.0",
|
||||||
"feedparser": "^2.1.0",
|
"feedparser": "^2.1.0",
|
||||||
"file-type": "^5.2.0",
|
|
||||||
"fs-extra": "^3.0.1",
|
"fs-extra": "^3.0.1",
|
||||||
"geoip-ultralight": "^0.1.5",
|
"geoip-ultralight": "^0.1.5",
|
||||||
"gettext-parser": "^1.2.2",
|
"gettext-parser": "^1.2.2",
|
||||||
|
|
|
@ -1395,78 +1395,78 @@
|
||||||
<td align="right" valign="middle" class="links-color socialLinks mobile-textcenter" data-ko-display="socialIconType eq 'colors'">
|
<td align="right" valign="middle" class="links-color socialLinks mobile-textcenter" data-ko-display="socialIconType eq 'colors'">
|
||||||
<span data-ko-display="fbVisible" data-ko-wrap="false"> </span>
|
<span data-ko-display="fbVisible" data-ko-wrap="false"> </span>
|
||||||
<a data-ko-display="fbVisible" href="" style="-ko-attr-href: @fbUrl">
|
<a data-ko-display="fbVisible" href="" style="-ko-attr-href: @fbUrl">
|
||||||
<img src="/mosaico/templates/versafix-1/img/social_def/facebook_ok.png" alt="Facebook" border="0" class="socialIcon" />
|
<img src="./img/social_def/facebook_ok.png" alt="Facebook" border="0" class="socialIcon" />
|
||||||
</a>
|
</a>
|
||||||
<span data-ko-display="twVisible" data-ko-wrap="false"> </span>
|
<span data-ko-display="twVisible" data-ko-wrap="false"> </span>
|
||||||
<a data-ko-display="twVisible" href="" style="-ko-attr-href: @twUrl">
|
<a data-ko-display="twVisible" href="" style="-ko-attr-href: @twUrl">
|
||||||
<img src="/mosaico/templates/versafix-1/img/social_def/twitter_ok.png" alt="Twitter" border="0" class="socialIcon" />
|
<img src="./img/social_def/twitter_ok.png" alt="Twitter" border="0" class="socialIcon" />
|
||||||
</a>
|
</a>
|
||||||
<span data-ko-display="ggVisible" data-ko-wrap="false"> </span>
|
<span data-ko-display="ggVisible" data-ko-wrap="false"> </span>
|
||||||
<a data-ko-display="ggVisible" href="" style="-ko-attr-href: @ggUrl">
|
<a data-ko-display="ggVisible" href="" style="-ko-attr-href: @ggUrl">
|
||||||
<img src="/mosaico/templates/versafix-1/img/social_def/google+_ok.png" alt="Google+" border="0" class="socialIcon" />
|
<img src="./img/social_def/google+_ok.png" alt="Google+" border="0" class="socialIcon" />
|
||||||
</a>
|
</a>
|
||||||
<span data-ko-display="webVisible" data-ko-wrap="false" style="display: none"> </span>
|
<span data-ko-display="webVisible" data-ko-wrap="false" style="display: none"> </span>
|
||||||
<a data-ko-display="webVisible" href="" style="-ko-attr-href: @webUrl; display: none">
|
<a data-ko-display="webVisible" href="" style="-ko-attr-href: @webUrl; display: none">
|
||||||
<img src="/mosaico/templates/versafix-1/img/social_def/web_ok.png" alt="Web" border="0" class="socialIcon" />
|
<img src="./img/social_def/web_ok.png" alt="Web" border="0" class="socialIcon" />
|
||||||
</a>
|
</a>
|
||||||
<span data-ko-display="inVisible" data-ko-wrap="false" style="display: none"> </span>
|
<span data-ko-display="inVisible" data-ko-wrap="false" style="display: none"> </span>
|
||||||
<a data-ko-display="inVisible" href="" style="-ko-attr-href: @inUrl; display: none">
|
<a data-ko-display="inVisible" href="" style="-ko-attr-href: @inUrl; display: none">
|
||||||
<img src="/mosaico/templates/versafix-1/img/social_def/linkedin_ok.png" alt="Linkedin" border="0" class="socialIcon" />
|
<img src="./img/social_def/linkedin_ok.png" alt="Linkedin" border="0" class="socialIcon" />
|
||||||
</a>
|
</a>
|
||||||
<span data-ko-display="flVisible" data-ko-wrap="false" style="display: none"> </span>
|
<span data-ko-display="flVisible" data-ko-wrap="false" style="display: none"> </span>
|
||||||
<a data-ko-display="flVisible" href="" style="-ko-attr-href: @flUrl; display: none">
|
<a data-ko-display="flVisible" href="" style="-ko-attr-href: @flUrl; display: none">
|
||||||
<img src="/mosaico/templates/versafix-1/img/social_def/flickr_ok.png" alt="Flickr" border="0" class="socialIcon" />
|
<img src="./img/social_def/flickr_ok.png" alt="Flickr" border="0" class="socialIcon" />
|
||||||
</a>
|
</a>
|
||||||
<span data-ko-display="viVisible" data-ko-wrap="false" style="display: none"> </span>
|
<span data-ko-display="viVisible" data-ko-wrap="false" style="display: none"> </span>
|
||||||
<a data-ko-display="viVisible" href="" style="-ko-attr-href: @viUrl; display: none">
|
<a data-ko-display="viVisible" href="" style="-ko-attr-href: @viUrl; display: none">
|
||||||
<img src="/mosaico/templates/versafix-1/img/social_def/vimeo_ok.png" alt="Vimeo" border="0" class="socialIcon" />
|
<img src="./img/social_def/vimeo_ok.png" alt="Vimeo" border="0" class="socialIcon" />
|
||||||
</a>
|
</a>
|
||||||
<span data-ko-display="instVisible" data-ko-wrap="false" style="display: none"> </span>
|
<span data-ko-display="instVisible" data-ko-wrap="false" style="display: none"> </span>
|
||||||
<a data-ko-display="instVisible" href="" style="-ko-attr-href: @instUrl; display: none">
|
<a data-ko-display="instVisible" href="" style="-ko-attr-href: @instUrl; display: none">
|
||||||
<img src="/mosaico/templates/versafix-1/img/social_def/instagram_ok.png" alt="Instagram" border="0" class="socialIcon" />
|
<img src="./img/social_def/instagram_ok.png" alt="Instagram" border="0" class="socialIcon" />
|
||||||
</a>
|
</a>
|
||||||
<span data-ko-display="youVisible" data-ko-wrap="false" style="display: none"> </span>
|
<span data-ko-display="youVisible" data-ko-wrap="false" style="display: none"> </span>
|
||||||
<a data-ko-display="youVisible" href="" style="-ko-attr-href: @youUrl; display: none">
|
<a data-ko-display="youVisible" href="" style="-ko-attr-href: @youUrl; display: none">
|
||||||
<img src="/mosaico/templates/versafix-1/img/social_def/youtube_ok.png" alt="Youtube" border="0" class="socialIcon" />
|
<img src="./img/social_def/youtube_ok.png" alt="Youtube" border="0" class="socialIcon" />
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td align="right" valign="middle" class="links-color socialLinks mobile-textcenter" data-ko-display="socialIconType eq 'bw'"
|
<td align="right" valign="middle" class="links-color socialLinks mobile-textcenter" data-ko-display="socialIconType eq 'bw'"
|
||||||
style="display: none">
|
style="display: none">
|
||||||
<span data-ko-display="fbVisible" data-ko-wrap="false"> </span>
|
<span data-ko-display="fbVisible" data-ko-wrap="false"> </span>
|
||||||
<a data-ko-display="fbVisible" href="" style="-ko-attr-href: @fbUrl">
|
<a data-ko-display="fbVisible" href="" style="-ko-attr-href: @fbUrl">
|
||||||
<img src="/mosaico/templates/versafix-1/img/social_def/facebook_bw_ok.png" alt="Facebook" border="0" class="socialIcon" />
|
<img src="./img/social_def/facebook_bw_ok.png" alt="Facebook" border="0" class="socialIcon" />
|
||||||
</a>
|
</a>
|
||||||
<span data-ko-display="twVisible" data-ko-wrap="false"> </span>
|
<span data-ko-display="twVisible" data-ko-wrap="false"> </span>
|
||||||
<a data-ko-display="twVisible" href="" style="-ko-attr-href: @twUrl">
|
<a data-ko-display="twVisible" href="" style="-ko-attr-href: @twUrl">
|
||||||
<img src="/mosaico/templates/versafix-1/img/social_def/twitter_bw_ok.png" alt="Twitter" border="0" class="socialIcon" />
|
<img src="./img/social_def/twitter_bw_ok.png" alt="Twitter" border="0" class="socialIcon" />
|
||||||
</a>
|
</a>
|
||||||
<span data-ko-display="ggVisible" data-ko-wrap="false"> </span>
|
<span data-ko-display="ggVisible" data-ko-wrap="false"> </span>
|
||||||
<a data-ko-display="ggVisible" href="" style="-ko-attr-href: @ggUrl">
|
<a data-ko-display="ggVisible" href="" style="-ko-attr-href: @ggUrl">
|
||||||
<img src="/mosaico/templates/versafix-1/img/social_def/google+_bw_ok.png" alt="Google+" border="0" class="socialIcon" />
|
<img src="./img/social_def/google+_bw_ok.png" alt="Google+" border="0" class="socialIcon" />
|
||||||
</a>
|
</a>
|
||||||
<span data-ko-display="webVisible" data-ko-wrap="false" style="display: none"> </span>
|
<span data-ko-display="webVisible" data-ko-wrap="false" style="display: none"> </span>
|
||||||
<a data-ko-display="webVisible" href="" style="-ko-attr-href: @webUrl; display: none">
|
<a data-ko-display="webVisible" href="" style="-ko-attr-href: @webUrl; display: none">
|
||||||
<img src="/mosaico/templates/versafix-1/img/social_def/web_bw_ok.png" alt="Web" border="0" class="socialIcon" />
|
<img src="./img/social_def/web_bw_ok.png" alt="Web" border="0" class="socialIcon" />
|
||||||
</a>
|
</a>
|
||||||
<span data-ko-display="inVisible" data-ko-wrap="false" style="display: none"> </span>
|
<span data-ko-display="inVisible" data-ko-wrap="false" style="display: none"> </span>
|
||||||
<a data-ko-display="inVisible" href="" style="-ko-attr-href: @inUrl; display: none">
|
<a data-ko-display="inVisible" href="" style="-ko-attr-href: @inUrl; display: none">
|
||||||
<img src="/mosaico/templates/versafix-1/img/social_def/linkedin_bw_ok.png" alt="Linkedin" border="0" class="socialIcon" />
|
<img src="./img/social_def/linkedin_bw_ok.png" alt="Linkedin" border="0" class="socialIcon" />
|
||||||
</a>
|
</a>
|
||||||
<span data-ko-display="flVisible" data-ko-wrap="false" style="display: none"> </span>
|
<span data-ko-display="flVisible" data-ko-wrap="false" style="display: none"> </span>
|
||||||
<a data-ko-display="flVisible" href="" style="-ko-attr-href: @flUrl; display: none">
|
<a data-ko-display="flVisible" href="" style="-ko-attr-href: @flUrl; display: none">
|
||||||
<img src="/mosaico/templates/versafix-1/img/social_def/flickr_bw_ok.png" alt="Flickr" border="0" class="socialIcon" />
|
<img src="./img/social_def/flickr_bw_ok.png" alt="Flickr" border="0" class="socialIcon" />
|
||||||
</a>
|
</a>
|
||||||
<span data-ko-display="viVisible" data-ko-wrap="false" style="display: none"> </span>
|
<span data-ko-display="viVisible" data-ko-wrap="false" style="display: none"> </span>
|
||||||
<a data-ko-display="viVisible" href="" style="-ko-attr-href: @viUrl; display: none">
|
<a data-ko-display="viVisible" href="" style="-ko-attr-href: @viUrl; display: none">
|
||||||
<img src="/mosaico/templates/versafix-1/img/social_def/vimeo_bw_ok.png" alt="Vimeo" border="0" class="socialIcon" />
|
<img src="./img/social_def/vimeo_bw_ok.png" alt="Vimeo" border="0" class="socialIcon" />
|
||||||
</a>
|
</a>
|
||||||
<span data-ko-display="instVisible" data-ko-wrap="false" style="display: none"> </span>
|
<span data-ko-display="instVisible" data-ko-wrap="false" style="display: none"> </span>
|
||||||
<a data-ko-display="instVisible" href="" style="-ko-attr-href: @instUrl; display: none">
|
<a data-ko-display="instVisible" href="" style="-ko-attr-href: @instUrl; display: none">
|
||||||
<img src="/mosaico/templates/versafix-1/img/social_def/instagram_bw_ok.png" alt="Instagram" border="0" class="socialIcon" />
|
<img src="./img/social_def/instagram_bw_ok.png" alt="Instagram" border="0" class="socialIcon" />
|
||||||
</a>
|
</a>
|
||||||
<span data-ko-display="youVisible" data-ko-wrap="false" style="display: none"> </span>
|
<span data-ko-display="youVisible" data-ko-wrap="false" style="display: none"> </span>
|
||||||
<a data-ko-display="youVisible" href="" style="-ko-attr-href: @youUrl; display: none">
|
<a data-ko-display="youVisible" href="" style="-ko-attr-href: @youUrl; display: none">
|
||||||
<img src="/mosaico/templates/versafix-1/img/social_def/youtube_bw_ok.png" alt="Youtube" border="0" class="socialIcon" />
|
<img src="./img/social_def/youtube_bw_ok.png" alt="Youtube" border="0" class="socialIcon" />
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -1514,7 +1514,7 @@
|
||||||
|
|
||||||
<tr data-ko-display="_root_.sponsor.visible" style="display: none;text-align:center">
|
<tr data-ko-display="_root_.sponsor.visible" style="display: none;text-align:center">
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="http://www.void.it" target="_blank" rel="noreferrer"><img border="0" hspace="0" vspace="0" src="/mosaico/templates/versafix-1/img/sponsor.gif" alt="sponsor"
|
<a href="http://www.void.it" target="_blank" rel="noreferrer"><img border="0" hspace="0" vspace="0" src="./img/sponsor.gif" alt="sponsor"
|
||||||
style="Margin:auto;display:inline !important;" /></a>
|
style="Margin:auto;display:inline !important;" /></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -1,38 +1,34 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
let log = require('npmlog');
|
const log = require('npmlog');
|
||||||
let config = require('config');
|
const config = require('config');
|
||||||
let express = require('express');
|
const express = require('express');
|
||||||
let router = new express.Router();
|
const router = new express.Router();
|
||||||
let passport = require('../lib/passport');
|
const passport = require('../lib/passport');
|
||||||
let os = require('os');
|
const os = require('os');
|
||||||
let fs = require('fs');
|
const fs = require('fs');
|
||||||
let path = require('path');
|
const path = require('path');
|
||||||
let mkdirp = require('mkdirp');
|
const mkdirp = require('mkdirp');
|
||||||
let cache = require('memory-cache');
|
const crypto = require('crypto');
|
||||||
let crypto = require('crypto');
|
const events = require('events');
|
||||||
let fetch = require('node-fetch');
|
const httpMocks = require('node-mocks-http');
|
||||||
let events = require('events');
|
const multiparty = require('multiparty');
|
||||||
let httpMocks = require('node-mocks-http');
|
const escapeStringRegexp = require('escape-string-regexp');
|
||||||
let multiparty = require('multiparty');
|
const jqueryFileUpload = require('jquery-file-upload-middleware');
|
||||||
let fileType = require('file-type');
|
const gm = require('gm').subClass({
|
||||||
let escapeStringRegexp = require('escape-string-regexp');
|
|
||||||
let jqueryFileUpload = require('jquery-file-upload-middleware');
|
|
||||||
let gm = require('gm').subClass({
|
|
||||||
imageMagick: true
|
imageMagick: true
|
||||||
});
|
});
|
||||||
let url = require('url');
|
const url = require('url');
|
||||||
let htmlToText = require('html-to-text');
|
const htmlToText = require('html-to-text');
|
||||||
let premailerApi = require('premailer-api');
|
const premailerApi = require('premailer-api');
|
||||||
let editorHelpers = require('../lib/editor-helpers');
|
const _ = require('../lib/translate')._;
|
||||||
let _ = require('../lib/translate')._;
|
const mailer = require('../lib/mailer');
|
||||||
let mailer = require('../lib/mailer');
|
const settings = require('../lib/models/settings');
|
||||||
let settings = require('../lib/models/settings');
|
const templates = require('../lib/models/templates');
|
||||||
let templates = require('../lib/models/templates');
|
const campaigns = require('../lib/models/campaigns');
|
||||||
let campaigns = require('../lib/models/campaigns');
|
|
||||||
|
|
||||||
router.all('/*', (req, res, next) => {
|
router.all('/*', (req, res, next) => {
|
||||||
if (!req.user && !cache.get(req.get('If-Match'))) {
|
if (!req.user) {
|
||||||
return res.status(403).send(_('Need to be logged in to access restricted content'));
|
return res.status(403).send(_('Need to be logged in to access restricted content'));
|
||||||
}
|
}
|
||||||
if (req.originalUrl.startsWith('/editorapi/img?')) {
|
if (req.originalUrl.startsWith('/editorapi/img?')) {
|
||||||
|
@ -48,30 +44,150 @@ jqueryFileUpload.on('begin', fileInfo => {
|
||||||
fileInfo.name = fileInfo.name
|
fileInfo.name = fileInfo.name
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.replace(/ /g, '-')
|
.replace(/ /g, '-')
|
||||||
.replace(/[^a-z0-9+-\.]+/g, '');
|
.replace(/[^a-z0-9+-.]+/g, '');
|
||||||
});
|
});
|
||||||
|
|
||||||
let listImages = (dir, dirURL, callback) => {
|
const listImages = (dir, dirURL, callback) => {
|
||||||
fs.readdir(dir, (err, files = []) => {
|
fs.readdir(dir, (err, files = []) => {
|
||||||
if (err && err.code !== 'ENOENT') {
|
if (err && err.code !== 'ENOENT') {
|
||||||
return callback(err.message || err);
|
return callback(err.message || err);
|
||||||
}
|
}
|
||||||
files = files.filter(name => /\.(jpe?g|png|gif)$/i.test(name));
|
files = files.filter(name => /\.(jpe?g|png|gif)$/i.test(name));
|
||||||
files = files.map(name => {
|
files = files.map(name => ({
|
||||||
return {
|
// mosaico
|
||||||
// mosaico
|
name,
|
||||||
name,
|
url: dirURL + '/' + name,
|
||||||
url: dirURL + '/' + name,
|
thumbnailUrl: dirURL + '/thumbnail/' + name,
|
||||||
thumbnailUrl: dirURL + '/thumbnail/' + name,
|
// grapejs
|
||||||
// grapejs
|
src: dirURL + '/' + name
|
||||||
src: dirURL + '/' + name,
|
}));
|
||||||
};
|
|
||||||
});
|
|
||||||
callback(null, files);
|
callback(null, files);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const placeholderImage = (width, height, callback) => {
|
||||||
|
const magick = gm(width, height, '#707070');
|
||||||
|
const size = 40;
|
||||||
|
let x = 0;
|
||||||
|
let y = 0;
|
||||||
|
|
||||||
|
// stripes
|
||||||
|
while (y < height) {
|
||||||
|
magick
|
||||||
|
.fill('#808080')
|
||||||
|
.drawPolygon([x, y], [x + size, y], [x + size * 2, y + size], [x + size * 2, y + size * 2])
|
||||||
|
.drawPolygon([x, y + size], [x + size, y + size * 2], [x, y + size * 2]);
|
||||||
|
x = x + size * 2;
|
||||||
|
if (x > width) {
|
||||||
|
x = 0;
|
||||||
|
y = y + size * 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// text
|
||||||
|
magick
|
||||||
|
.fill('#B0B0B0')
|
||||||
|
.fontSize(20)
|
||||||
|
.drawText(0, 0, width + ' x ' + height, 'center');
|
||||||
|
|
||||||
|
magick.stream('png', (err, stream) => {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const image = {
|
||||||
|
format: 'PNG',
|
||||||
|
stream
|
||||||
|
};
|
||||||
|
|
||||||
|
callback(null, image);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const resizedImage = (src, method, width, height, callback) => {
|
||||||
|
const pathname = path.join('/', url.parse(src).pathname);
|
||||||
|
const filePath = path.join(__dirname, '..', 'public', pathname);
|
||||||
|
const magick = gm(filePath);
|
||||||
|
|
||||||
|
magick.format((err, format) => {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const streamHandler = (err, stream) => {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const image = {
|
||||||
|
format,
|
||||||
|
stream
|
||||||
|
};
|
||||||
|
|
||||||
|
callback(null, image);
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (method) {
|
||||||
|
case 'resize':
|
||||||
|
return magick
|
||||||
|
.autoOrient()
|
||||||
|
.resize(width, height)
|
||||||
|
.stream(streamHandler);
|
||||||
|
|
||||||
|
case 'cover':
|
||||||
|
return magick
|
||||||
|
.autoOrient()
|
||||||
|
.resize(width, height + '^')
|
||||||
|
.gravity('Center')
|
||||||
|
.extent(width, height + '>')
|
||||||
|
.stream(streamHandler);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return callback(new Error(_('Method not supported')));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getProcessedImage = (dynamicUrl, callback) => {
|
||||||
|
if (!dynamicUrl.includes('/editorapi/img?')) {
|
||||||
|
return callback(new Error('Invalid dynamicUrl'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
src,
|
||||||
|
method,
|
||||||
|
params = '600,null'
|
||||||
|
} = url.parse(dynamicUrl, true).query;
|
||||||
|
|
||||||
|
let width = params.split(',')[0];
|
||||||
|
let height = params.split(',')[1];
|
||||||
|
|
||||||
|
const sanitizeSize = (val, min, max, defaultVal, allowNull) => {
|
||||||
|
if (val === 'null' && allowNull) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
val = Number(val) || defaultVal;
|
||||||
|
val = Math.max(min, val);
|
||||||
|
val = Math.min(max, val);
|
||||||
|
return val;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (method === 'placeholder') {
|
||||||
|
width = sanitizeSize(width, 1, 2048, 600, false);
|
||||||
|
height = sanitizeSize(height, 1, 2048, 300, false);
|
||||||
|
placeholderImage(width, height, callback);
|
||||||
|
} else {
|
||||||
|
width = sanitizeSize(width, 1, 2048, 600, false);
|
||||||
|
height = sanitizeSize(height, 1, 2048, 300, true);
|
||||||
|
resizedImage(src, method, width, height, callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const getStaticImageUrl = (dynamicUrl, staticDir, staticDirUrl, callback) => {
|
const getStaticImageUrl = (dynamicUrl, staticDir, staticDirUrl, callback) => {
|
||||||
|
if (!dynamicUrl.includes('/editorapi/img?')) {
|
||||||
|
return callback(null, dynamicUrl);
|
||||||
|
}
|
||||||
|
|
||||||
mkdirp(staticDir, err => {
|
mkdirp(staticDir, err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
@ -82,242 +198,126 @@ const getStaticImageUrl = (dynamicUrl, staticDir, staticDirUrl, callback) => {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
let hash = crypto.createHash('md5').update(dynamicUrl).digest('hex');
|
const hash = crypto.createHash('md5').update(dynamicUrl).digest('hex');
|
||||||
let match = files.find(el => el.startsWith(hash));
|
const match = files.find(el => el.startsWith(hash));
|
||||||
let headers = {};
|
|
||||||
|
|
||||||
if (match) {
|
if (match) {
|
||||||
return callback(null, staticDirUrl + '/' + match);
|
return callback(null, staticDirUrl + '/' + match);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dynamicUrl.includes('/editorapi/img?')) {
|
getProcessedImage(dynamicUrl, (err, image) => {
|
||||||
let token = crypto.randomBytes(16).toString('hex');
|
if (err) {
|
||||||
cache.put(token, true, 1000);
|
return callback(err);
|
||||||
headers['If-Match'] = token;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fetch(dynamicUrl, {
|
const fileName = hash + '.' + image.format.toLowerCase();
|
||||||
headers
|
const filePath = path.join(staticDir, fileName);
|
||||||
})
|
const fileUrl = staticDirUrl + '/' + fileName;
|
||||||
.then(res => {
|
|
||||||
if (res.status < 200 || res.status >= 300) {
|
const writeStream = fs.createWriteStream(filePath);
|
||||||
throw new Error(`Received HTTP status code ${res.status} while fetching image ${dynamicUrl}`);
|
writeStream.on('error', err => callback(err));
|
||||||
}
|
writeStream.on('finish', () => callback(null, fileUrl));
|
||||||
return res.buffer();
|
image.stream.pipe(writeStream);
|
||||||
})
|
});
|
||||||
.then(buffer => {
|
|
||||||
let ft = fileType(buffer);
|
|
||||||
if (ft && ['image/jpeg', 'image/png', 'image/gif'].includes(ft.mime)) {
|
|
||||||
fs.writeFile(path.join(staticDir, hash + '.' + ft.ext), buffer, err => {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
let staticUrl = staticDirUrl + '/' + hash + '.' + ft.ext;
|
|
||||||
callback(null, staticUrl);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw new Error(`Unsupported image MIME type for ${dynamicUrl}`);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
let prepareHtml = ({
|
const prepareHtml = (html, editorName, callback) => {
|
||||||
editorName,
|
|
||||||
html
|
|
||||||
}, callback) => {
|
|
||||||
settings.get('serviceUrl', (err, serviceUrl) => {
|
settings.get('serviceUrl', (err, serviceUrl) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err.message || err);
|
return callback(err.message || err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const srcs = new Map();
|
||||||
|
const re = /<img[^>]+src="([^"]*\/editorapi\/img\?[^"]+)"/ig;
|
||||||
let jobs = 0;
|
let jobs = 0;
|
||||||
let srcs = {};
|
|
||||||
let re = /<img[^>]+src="([^"]+)"/g;
|
|
||||||
let result;
|
let result;
|
||||||
|
|
||||||
while ((result = re.exec(html)) !== null) {
|
while ((result = re.exec(html)) !== null) {
|
||||||
srcs[result[1]] = result[1];
|
srcs.set(result[1], result[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let done = () => {
|
const done = () => {
|
||||||
if (jobs === 0) {
|
if (jobs === 0) {
|
||||||
Object.keys(srcs).forEach(src => {
|
for (const [key, value] of srcs) {
|
||||||
// console.log(`replace dynamic - ${src} - with static - ${srcs[src]}`);
|
// console.log(`replace dynamicUrl: ${key} - with staticUrl: ${value}`);
|
||||||
html = html.replace(new RegExp(escapeStringRegexp(src), 'g'), srcs[src]);
|
html = html.replace(new RegExp(escapeStringRegexp(key), 'g'), value);
|
||||||
});
|
}
|
||||||
callback(null, html);
|
return callback(null, html);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const staticDir = path.join(__dirname, '..', 'public', editorName, 'uploads', 'static');
|
const staticDir = path.join(__dirname, '..', 'public', editorName, 'uploads', 'static');
|
||||||
const staticDirUrl = url.resolve(serviceUrl, editorName + '/uploads/static');
|
const staticDirUrl = url.resolve(serviceUrl, editorName + '/uploads/static');
|
||||||
|
|
||||||
Object.keys(srcs).forEach(src => {
|
for (const key of srcs.keys()) {
|
||||||
jobs++;
|
jobs++;
|
||||||
let dynamicUrl = src.replace(/&/g, '&');
|
const dynamicUrl = key.replace(/&/g, '&');
|
||||||
dynamicUrl = /^https?:\/\/|^\/\//i.test(dynamicUrl) ? dynamicUrl : url.resolve(serviceUrl, dynamicUrl);
|
|
||||||
|
|
||||||
getStaticImageUrl(dynamicUrl, staticDir, staticDirUrl, (err, staticUrl) => {
|
getStaticImageUrl(dynamicUrl, staticDir, staticDirUrl, (err, staticUrl) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
// TODO: Send a warning back to the editor. For now we just skip image resizing.
|
// TODO: Send a warning back to the editor. For now we just skip image resizing.
|
||||||
log.error('editorapi', err.message || err);
|
log.error('editorapi', err);
|
||||||
|
|
||||||
if (dynamicUrl.includes('/editorapi/img?')) {
|
if (dynamicUrl.includes('/editorapi/img?')) {
|
||||||
staticUrl = url.parse(dynamicUrl, true).query.src || dynamicUrl;
|
staticUrl = url.parse(dynamicUrl, true).query.src || dynamicUrl;
|
||||||
} else {
|
} else {
|
||||||
staticUrl = dynamicUrl;
|
staticUrl = dynamicUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!/^https?:\/\/|^\/\//i.test(staticUrl)) {
|
||||||
|
staticUrl = url.resolve(serviceUrl, staticUrl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
srcs[src] = staticUrl;
|
srcs.set(key, staticUrl);
|
||||||
jobs--;
|
jobs--;
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
let placeholderImage = (req, res, {
|
// URL structure defined by Mosaico
|
||||||
width,
|
|
||||||
height
|
|
||||||
}) => {
|
|
||||||
let magick = gm(width, height, '#707070');
|
|
||||||
let x = 0;
|
|
||||||
let y = 0;
|
|
||||||
let size = 40;
|
|
||||||
// stripes
|
|
||||||
while (y < height) {
|
|
||||||
magick = magick
|
|
||||||
.fill('#808080')
|
|
||||||
.drawPolygon([x, y], [x + size, y], [x + size * 2, y + size], [x + size * 2, y + size * 2])
|
|
||||||
.drawPolygon([x, y + size], [x + size, y + size * 2], [x, y + size * 2]);
|
|
||||||
x = x + size * 2;
|
|
||||||
if (x > width) {
|
|
||||||
x = 0;
|
|
||||||
y = y + size * 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// text
|
|
||||||
magick = magick
|
|
||||||
.fill('#B0B0B0')
|
|
||||||
.fontSize(20)
|
|
||||||
.drawText(0, 0, width + ' x ' + height, 'center');
|
|
||||||
|
|
||||||
res.set('Content-Type', 'image/png');
|
|
||||||
magick.stream('png').pipe(res);
|
|
||||||
};
|
|
||||||
|
|
||||||
let resizedImage = (req, res, {
|
|
||||||
src,
|
|
||||||
method,
|
|
||||||
width,
|
|
||||||
height
|
|
||||||
}) => {
|
|
||||||
let magick = gm(src);
|
|
||||||
magick.format((err, format) => {
|
|
||||||
if (err) {
|
|
||||||
return res.status(500).send(err.message || err);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (method) {
|
|
||||||
case 'resize':
|
|
||||||
res.set('Content-Type', 'image/' + format.toLowerCase());
|
|
||||||
magick.autoOrient()
|
|
||||||
.resize(width, height)
|
|
||||||
.stream()
|
|
||||||
.pipe(res);
|
|
||||||
return;
|
|
||||||
|
|
||||||
case 'cover':
|
|
||||||
res.set('Content-Type', 'image/' + format.toLowerCase());
|
|
||||||
magick.autoOrient()
|
|
||||||
.resize(width, height + '^')
|
|
||||||
.gravity('Center')
|
|
||||||
.extent(width, height + '>')
|
|
||||||
.stream()
|
|
||||||
.pipe(res);
|
|
||||||
return;
|
|
||||||
|
|
||||||
default:
|
|
||||||
res.status(501).send(_('Method not supported'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// /editorapi/img?src=" + encodeURIComponent(src) + "&method=" + encodeURIComponent(method) + "¶ms=" + encodeURIComponent(width + "," + height);
|
// /editorapi/img?src=" + encodeURIComponent(src) + "&method=" + encodeURIComponent(method) + "¶ms=" + encodeURIComponent(width + "," + height);
|
||||||
router.get('/img', passport.csrfProtection, (req, res) => {
|
router.get('/img', (req, res) => {
|
||||||
settings.get('serviceUrl', (err, serviceUrl) => {
|
getProcessedImage(req.originalUrl, (err, image) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return res.status(500).send(err.message || err);
|
res.status(err.status || 500);
|
||||||
|
res.send(err.message || err);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
res.set('Content-Type', 'image/' + image.format.toLowerCase());
|
||||||
src,
|
image.stream.pipe(res);
|
||||||
method,
|
|
||||||
params = '600,null'
|
|
||||||
} = req.query;
|
|
||||||
let width = params.split(',')[0];
|
|
||||||
let height = params.split(',')[1];
|
|
||||||
width = (width === 'null') ? null : Number(width);
|
|
||||||
height = (height === 'null') ? null : Number(height);
|
|
||||||
|
|
||||||
switch (method) {
|
|
||||||
case 'placeholder':
|
|
||||||
return placeholderImage(req, res, {
|
|
||||||
width,
|
|
||||||
height
|
|
||||||
});
|
|
||||||
case 'resize':
|
|
||||||
case 'cover':
|
|
||||||
src = /^https?:\/\/|^\/\//i.test(src) ? src : url.resolve(serviceUrl, src);
|
|
||||||
return resizedImage(req, res, {
|
|
||||||
src,
|
|
||||||
method,
|
|
||||||
width,
|
|
||||||
height
|
|
||||||
});
|
|
||||||
default:
|
|
||||||
return res.status(501).send(_('Method not supported'));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/update', passport.parseForm, passport.csrfProtection, (req, res) => {
|
router.post('/update', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||||
prepareHtml({
|
const sendResponse = err => {
|
||||||
editorName: req.query.editor,
|
|
||||||
html: req.body.html
|
|
||||||
}, (err, html) => {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
return res.status(500).send(err.message || err);
|
return res.status(500).send(err.message || err);
|
||||||
}
|
}
|
||||||
|
res.send('ok');
|
||||||
|
};
|
||||||
|
|
||||||
|
prepareHtml(req.body.html, req.query.editor, (err, html) => {
|
||||||
|
if (err) {
|
||||||
|
return sendResponse(err);
|
||||||
|
}
|
||||||
|
|
||||||
req.body.html = html;
|
req.body.html = html;
|
||||||
|
|
||||||
if (req.query.type === 'template') {
|
switch (req.query.type) {
|
||||||
templates.update(req.body.id, req.body, (err, updated) => {
|
case 'template':
|
||||||
if (err) {
|
return templates.update(req.body.id, req.body, sendResponse);
|
||||||
return res.status(500).send(err.message || err);
|
case 'campaign':
|
||||||
}
|
return campaigns.update(req.body.id, req.body, sendResponse);
|
||||||
res.send('ok');
|
default:
|
||||||
});
|
return sendResponse(new Error(_('Invalid resource type')));
|
||||||
|
|
||||||
} else if (req.query.type === 'campaign') {
|
|
||||||
campaigns.update(req.body.id, req.body, (err, updated) => {
|
|
||||||
if (err) {
|
|
||||||
return res.status(500).send(err.message || err);
|
|
||||||
}
|
|
||||||
res.send('ok');
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
res.status(500).send(_('Invalid resource type'));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -331,8 +331,8 @@ router.get('/upload', passport.csrfProtection, (req, res) => {
|
||||||
return res.status(500).send(err.message || err);
|
return res.status(500).send(err.message || err);
|
||||||
}
|
}
|
||||||
|
|
||||||
let baseDir = path.join(__dirname, '..', 'public', req.query.editor, 'uploads');
|
const baseDir = path.join(__dirname, '..', 'public', req.query.editor, 'uploads');
|
||||||
let baseDirUrl = serviceUrl + req.query.editor + '/uploads';
|
const baseDirUrl = serviceUrl + req.query.editor + '/uploads';
|
||||||
|
|
||||||
listImages(path.join(baseDir, '0'), baseDirUrl + '/0', (err, sharedImages) => {
|
listImages(path.join(baseDir, '0'), baseDirUrl + '/0', (err, sharedImages) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -358,55 +358,78 @@ router.get('/upload', passport.csrfProtection, (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/upload', passport.csrfProtection, (req, res) => {
|
router.post('/upload', passport.csrfProtection, (req, res) => {
|
||||||
let dirName = req.query.type === 'template' ? '0' :
|
settings.get('serviceUrl', (err, serviceUrl) => {
|
||||||
req.query.type === 'campaign' && Number(req.query.id) > 0 ? req.query.id :
|
if (err) {
|
||||||
null;
|
return res.status(500).send(err.message || err);
|
||||||
|
|
||||||
if (dirName === null) {
|
|
||||||
return res.status(500).send(_('Invalid resource type or ID'));
|
|
||||||
}
|
|
||||||
|
|
||||||
let opts = {
|
|
||||||
tmpDir: config.www.tmpdir || os.tmpdir(),
|
|
||||||
imageVersions: req.query.editor === 'mosaico' ? {
|
|
||||||
thumbnail: {
|
|
||||||
width: 90,
|
|
||||||
height: 90
|
|
||||||
}
|
|
||||||
} : {},
|
|
||||||
uploadDir: path.join(__dirname, '..', 'public', req.query.editor, 'uploads', dirName),
|
|
||||||
uploadUrl: '/' + req.query.editor + '/uploads/' + dirName, // must be root relative
|
|
||||||
acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mockres = httpMocks.createResponse({
|
|
||||||
eventEmitter: events.EventEmitter
|
|
||||||
});
|
|
||||||
|
|
||||||
mockres.on('end', () => {
|
|
||||||
if (req.query.editor === 'grapejs') {
|
|
||||||
let data = [];
|
|
||||||
JSON.parse(mockres._getData()).files.forEach(file => {
|
|
||||||
data.push({
|
|
||||||
src: file.url
|
|
||||||
});
|
|
||||||
});
|
|
||||||
res.json({
|
|
||||||
data
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.send(mockres._getData());
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
jqueryFileUpload.fileHandler(opts)(req, mockres);
|
const getDirName = () => {
|
||||||
|
switch (req.query.type) {
|
||||||
|
case 'template':
|
||||||
|
return '0';
|
||||||
|
case 'campaign':
|
||||||
|
return Number(req.query.id) > 0 ? req.query.id : false;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const dirName = getDirName();
|
||||||
|
|
||||||
|
if (dirName === false) {
|
||||||
|
return res.status(500).send(_('Invalid resource type or ID'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const opts = {
|
||||||
|
tmpDir: config.www.tmpdir || os.tmpdir(),
|
||||||
|
imageVersions: req.query.editor === 'mosaico' ? {
|
||||||
|
thumbnail: {
|
||||||
|
width: 90,
|
||||||
|
height: 90
|
||||||
|
}
|
||||||
|
} : {},
|
||||||
|
uploadDir: path.join(__dirname, '..', 'public', req.query.editor, 'uploads', dirName),
|
||||||
|
uploadUrl: '/' + req.query.editor + '/uploads/' + dirName, // must be root relative
|
||||||
|
acceptFileTypes: /\.(gif|jpe?g|png)$/i,
|
||||||
|
hostname: url.parse(serviceUrl).host // include port
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockres = httpMocks.createResponse({
|
||||||
|
eventEmitter: events.EventEmitter
|
||||||
|
});
|
||||||
|
|
||||||
|
mockres.on('error', err => {
|
||||||
|
res.status(500).json({
|
||||||
|
error: err.message || err,
|
||||||
|
data: []
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
mockres.on('end', () => {
|
||||||
|
const data = [];
|
||||||
|
try {
|
||||||
|
JSON.parse(mockres._getData()).files.forEach(file => {
|
||||||
|
data.push({
|
||||||
|
src: file.url
|
||||||
|
});
|
||||||
|
});
|
||||||
|
res.json({
|
||||||
|
data
|
||||||
|
});
|
||||||
|
} catch(err) {
|
||||||
|
res.status(500).json({
|
||||||
|
error: err.message || err,
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
jqueryFileUpload.fileHandler(opts)(req, req.query.editor === 'grapejs' ? mockres : res);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/download', passport.csrfProtection, (req, res) => {
|
router.post('/download', passport.csrfProtection, (req, res) => {
|
||||||
prepareHtml({
|
prepareHtml(req.body.html, req.query.editor, (err, html) => {
|
||||||
editorName: req.query.editor,
|
|
||||||
html: req.body.html
|
|
||||||
}, (err, html) => {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
return res.status(500).send(err.message || err);
|
return res.status(500).send(err.message || err);
|
||||||
}
|
}
|
||||||
|
@ -416,9 +439,12 @@ router.post('/download', passport.csrfProtection, (req, res) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
let parseGrapejsMultipartTestForm = (req, res, next) => {
|
const parseGrapejsMultipartTestForm = (req, res, next) => {
|
||||||
if (req.query.editor === 'grapejs') {
|
if (req.query.editor === 'grapejs') {
|
||||||
new multiparty.Form().parse(req, (err, fields, files) => {
|
new multiparty.Form().parse(req, (err, fields) => {
|
||||||
|
if (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
req.body.email = fields.email[0];
|
req.body.email = fields.email[0];
|
||||||
req.body.subject = fields.subject[0];
|
req.body.subject = fields.subject[0];
|
||||||
req.body.html = fields.html[0];
|
req.body.html = fields.html[0];
|
||||||
|
@ -431,7 +457,7 @@ let parseGrapejsMultipartTestForm = (req, res, next) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
router.post('/test', parseGrapejsMultipartTestForm, passport.csrfProtection, (req, res) => {
|
router.post('/test', parseGrapejsMultipartTestForm, passport.csrfProtection, (req, res) => {
|
||||||
let sendError = err => {
|
const sendError = err => {
|
||||||
if (req.query.editor === 'grapejs') {
|
if (req.query.editor === 'grapejs') {
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
errors: err.message || err
|
errors: err.message || err
|
||||||
|
@ -441,10 +467,7 @@ router.post('/test', parseGrapejsMultipartTestForm, passport.csrfProtection, (re
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
prepareHtml({
|
prepareHtml(req.body.html, req.query.editor, (err, html) => {
|
||||||
editorName: req.query.editor,
|
|
||||||
html: req.body.html
|
|
||||||
}, (err, html) => {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
return sendError(err);
|
return sendError(err);
|
||||||
}
|
}
|
||||||
|
@ -459,29 +482,31 @@ router.post('/test', parseGrapejsMultipartTestForm, passport.csrfProtection, (re
|
||||||
return sendError(err);
|
return sendError(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
let opts = {
|
const opts = {
|
||||||
from: {
|
from: {
|
||||||
name: configItems.defaultFrom,
|
name: configItems.defaultFrom,
|
||||||
address: configItems.defaultAddress,
|
address: configItems.defaultAddress
|
||||||
},
|
},
|
||||||
to: req.body.email,
|
to: req.body.email,
|
||||||
subject: req.body.subject,
|
subject: req.body.subject,
|
||||||
text: htmlToText.fromString(html, {
|
text: htmlToText.fromString(html, {
|
||||||
wordwrap: 100
|
wordwrap: 100
|
||||||
}),
|
}),
|
||||||
html,
|
html
|
||||||
};
|
};
|
||||||
|
|
||||||
transport.sendMail(opts, (err, info) => {
|
transport.sendMail(opts, err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return sendError(err);
|
return sendError(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
req.query.editor === 'grapejs' ?
|
if (req.query.editor === 'grapejs') {
|
||||||
res.json({
|
res.json({
|
||||||
data: 'ok'
|
data: 'ok'
|
||||||
}) :
|
});
|
||||||
|
} else {
|
||||||
res.send('ok');
|
res.send('ok');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,6 +7,7 @@ const passport = require('../lib/passport');
|
||||||
const _ = require('../lib/translate')._;
|
const _ = require('../lib/translate')._;
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const settings = require('../lib/models/settings');
|
||||||
const editorHelpers = require('../lib/editor-helpers')
|
const editorHelpers = require('../lib/editor-helpers')
|
||||||
|
|
||||||
router.all('/*', (req, res, next) => {
|
router.all('/*', (req, res, next) => {
|
||||||
|
@ -18,44 +19,52 @@ router.all('/*', (req, res, next) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/editor', passport.csrfProtection, (req, res) => {
|
router.get('/editor', passport.csrfProtection, (req, res) => {
|
||||||
editorHelpers.getResource(req.query.type, req.query.id, (err, resource) => {
|
settings.get('serviceUrl', (err, serviceUrl) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
req.flash('danger', err.message || err);
|
req.flash('danger', err.message || err);
|
||||||
return res.redirect('/');
|
return res.redirect('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
editorHelpers.getResource(req.query.type, req.query.id, (err, resource) => {
|
||||||
resource.editorData = JSON.parse(resource.editorData);
|
if (err) {
|
||||||
} catch (err) {
|
req.flash('danger', err.message || err);
|
||||||
resource.editorData = {
|
return res.redirect('/');
|
||||||
template: req.query.template || 'demo'
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!resource.html && !resource.editorData.html && !resource.editorData.mjml) {
|
|
||||||
const base = path.join(__dirname, '..', 'public', 'grapejs', 'templates', resource.editorData.template);
|
|
||||||
try {
|
try {
|
||||||
resource.editorData.mjml = fs.readFileSync(path.join(base, 'index.mjml'), 'utf8');
|
resource.editorData = JSON.parse(resource.editorData);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
try {
|
resource.editorData = {
|
||||||
resource.html = fs.readFileSync(path.join(base, 'index.html'), 'utf8');
|
template: req.query.template || 'demo'
|
||||||
} catch (err) {
|
|
||||||
resource.html = err.message || err;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
res.render('grapejs/editor', {
|
if (!resource.html && !resource.editorData.html && !resource.editorData.mjml) {
|
||||||
layout: 'grapejs/layout-editor',
|
const base = path.join(__dirname, '..', 'public', 'grapejs', 'templates', path.join('/', resource.editorData.template));
|
||||||
type: req.query.type,
|
try {
|
||||||
stringifiedResource: JSON.stringify(resource),
|
resource.editorData.mjml = fs.readFileSync(path.join(base, 'index.mjml'), 'utf8');
|
||||||
resource,
|
} catch (err) {
|
||||||
editor: {
|
try {
|
||||||
name: resource.editorName || 'grapejs',
|
resource.html = fs.readFileSync(path.join(base, 'index.html'), 'utf8');
|
||||||
mode: resource.editorData.mjml ? 'mjml' : 'html',
|
} catch (err) {
|
||||||
config: config.grapejs
|
resource.html = err.message || err;
|
||||||
},
|
}
|
||||||
csrfToken: req.csrfToken()
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.render('grapejs/editor', {
|
||||||
|
layout: 'grapejs/layout-editor',
|
||||||
|
type: req.query.type,
|
||||||
|
stringifiedResource: JSON.stringify(resource),
|
||||||
|
resource,
|
||||||
|
editor: {
|
||||||
|
name: resource.editorName || 'grapejs',
|
||||||
|
mode: resource.editorData.mjml ? 'mjml' : 'html',
|
||||||
|
config: config.grapejs
|
||||||
|
},
|
||||||
|
csrfToken: req.csrfToken(),
|
||||||
|
serviceUrl
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -129,6 +129,7 @@
|
||||||
<script>
|
<script>
|
||||||
$.ajaxSetup({ headers: { 'X-CSRF-TOKEN': '{{csrfToken}}' } });
|
$.ajaxSetup({ headers: { 'X-CSRF-TOKEN': '{{csrfToken}}' } });
|
||||||
|
|
||||||
|
var serviceUrl = '{{{serviceUrl}}}';
|
||||||
var resource = {{{stringifiedResource}}};
|
var resource = {{{stringifiedResource}}};
|
||||||
|
|
||||||
var config = (function(mode) {
|
var config = (function(mode) {
|
||||||
|
@ -155,7 +156,6 @@
|
||||||
|
|
||||||
// convert relative to absolute urls
|
// convert relative to absolute urls
|
||||||
['mj-wrapper', 'mj-section', 'mj-navbar', 'mj-hero', 'mj-image'].forEach(function(tagName) {
|
['mj-wrapper', 'mj-section', 'mj-navbar', 'mj-hero', 'mj-image'].forEach(function(tagName) {
|
||||||
var serviceUrl = window.location.protocol + '//' + window.location.host + '/';
|
|
||||||
var elements = doc.getElementsByTagName(tagName);
|
var elements = doc.getElementsByTagName(tagName);
|
||||||
|
|
||||||
for (var i = 0; i < elements.length; i++) {
|
for (var i = 0; i < elements.length; i++) {
|
||||||
|
@ -246,6 +246,14 @@
|
||||||
document.body.appendChild(frame);
|
document.body.appendChild(frame);
|
||||||
var frameDoc = frame.contentDocument || frame.contentWindow.document;
|
var frameDoc = frame.contentDocument || frame.contentWindow.document;
|
||||||
|
|
||||||
|
var isLocalImage = function(src) {
|
||||||
|
var a1 = document.createElement('a');
|
||||||
|
var a2 = document.createElement('a');
|
||||||
|
a1.href = serviceUrl;
|
||||||
|
a2.href = src;
|
||||||
|
return a1.host === a2.host;
|
||||||
|
};
|
||||||
|
|
||||||
frame.onload = function() {
|
frame.onload = function() {
|
||||||
var imgs = frameDoc.querySelectorAll('img');
|
var imgs = frameDoc.querySelectorAll('img');
|
||||||
|
|
||||||
|
@ -253,7 +261,9 @@
|
||||||
var img = imgs[i];
|
var img = imgs[i];
|
||||||
var m = img.src.match(/\/editorapi\/img\?src=([^&]*)/);
|
var m = img.src.match(/\/editorapi\/img\?src=([^&]*)/);
|
||||||
var encodedSrc = m && m[1] || encodeURIComponent(img.src);
|
var encodedSrc = m && m[1] || encodeURIComponent(img.src);
|
||||||
img.src = '/editorapi/img?src=' + encodedSrc + '&method=resize¶ms=' + img.clientWidth + '%2C' + img.clientHeight;
|
if (isLocalImage(decodeURIComponent(encodedSrc))) {
|
||||||
|
img.src = '/editorapi/img?src=' + encodedSrc + '&method=resize¶ms=' + img.clientWidth + '%2C' + img.clientHeight;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
html = '<!doctype html>' + frameDoc.documentElement.outerHTML;
|
html = '<!doctype html>' + frameDoc.documentElement.outerHTML;
|
||||||
|
|
|
@ -109,7 +109,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<h2><span class="glyphicon glyphicon-send"></span> {{#translate}}Send via Any Provider{{/translate}}</h2>
|
<h2><span class="glyphicon glyphicon-send"></span> {{#translate}}Send via Any Provider{{/translate}}</h2>
|
||||||
<p>{{#translate}}Mailtrain recommends <a href="https://sendpulse.com/?utm_source=mailtrain&utm_medium=providerlist">SendPulse</a> even though you can use any provider that supports SMTP protocol to send out your newsletters. Bounce and complaints handling via webhooks is supported for SES, SparkPost, SendGrid and Mailgun, also for Postfix and ZoneMTA.{{/translate}}</p>
|
<p>{{#translate}}You can use any provider that supports SMTP protocol to send out your newsletters. Bounce and complaints handling via webhooks is supported for SES, SparkPost, SendGrid and Mailgun, also for Postfix and ZoneMTA.{{/translate}}</p>
|
||||||
<button type="button" class="btn btn-default btn-xs" data-toggle="modal" data-target=".modal-send-via-any-provider">{{#translate}}Show more{{/translate}}</button>
|
<button type="button" class="btn btn-default btn-xs" data-toggle="modal" data-target=".modal-send-via-any-provider">{{#translate}}Show more{{/translate}}</button>
|
||||||
{{> modal_carousel
|
{{> modal_carousel
|
||||||
title='Send via Any Provider'
|
title='Send via Any Provider'
|
||||||
|
@ -130,40 +130,4 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p><br></p>
|
|
||||||
|
|
||||||
<div class="row" style="background: #eee;">
|
|
||||||
<div class="col-md-8">
|
|
||||||
<h3>{{#translate}}Donate to Author{{/translate}}</h3>
|
|
||||||
<p>{{#translate}}Mailtrain is available under GPLv3 license and completely open source.{{/translate}}</p>
|
|
||||||
<p>{{#translate}}If you really like Mailtrain or your business benefits from it financially then I would really appreciate a small donation to keep the Mailtrain development engines running. You can either use Bitcoin or PayPal for donations. My Bitcoin wallet is{{/translate}}: <code>15Z8ADxhssKUiwP3jbbqJwA21744KMCfTM</code></p>
|
|
||||||
<p style="margin-bottom: 20px;"><a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=DB26KWR2BQX5W" class="btn btn-info">{{#translate}}Or Donate Using Paypal{{/translate}}</a></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p><br></p>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-12">
|
|
||||||
<h3>{{#translate}}Official Mailtrain Partners{{/translate}}</h3>
|
|
||||||
<hr />
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<p>
|
|
||||||
<a href="https://sendpulse.com/?utm_source=mailtrain&utm_medium=logo">
|
|
||||||
<img class="media-object" src="/images/sendpulse-logo.png" alt="SendPulse">
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
<p>{{#translate}}A reliable SMTP server, easy integration, and 12,000 messages a month free{{/translate}}</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<p>
|
|
||||||
<a href="http://www.iredmail.org/">
|
|
||||||
<img class="media-object" src="/images/iredmail-logo.png" alt="iRedMail">
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
<p>{{#translate}}Free, open source mail server solution{{/translate}}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p><br><br><br></p>
|
<p><br><br><br></p>
|
||||||
|
|
|
@ -252,12 +252,9 @@
|
||||||
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="pull-right">
|
<div class="text-right">
|
||||||
<button type="submit" id="verify-button" form="smtp-verify" class="btn btn-info" data-loading-text="{{#translate}}Checking{{/translate}}…"><span class="glyphicon glyphicon-refresh" aria-hidden="true"></span> {{#translate}}Check Mailer config{{/translate}}</button>
|
<button type="submit" id="verify-button" form="smtp-verify" class="btn btn-info" data-loading-text="{{#translate}}Checking{{/translate}}…"><span class="glyphicon glyphicon-refresh" aria-hidden="true"></span> {{#translate}}Check Mailer config{{/translate}}</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-offset-2 col-xs-6">
|
|
||||||
<p class="form-control-static">{{#translate}}Don't have an SMTP account yet? Create a free SendPulse account{{/translate}} <a href="https://sendpulse.com/?utm_source=mailtrain&utm_medium=settings">{{#translate}}here{{/translate}}</a></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue