diff --git a/.gitignore b/.gitignore index 47825b2b..b9a299f9 100644 --- a/.gitignore +++ b/.gitignore @@ -15,19 +15,6 @@ dump.rdb # generate POT file every time you want to update your PO file languages/mailtrain.pot -public/mosaico/uploads/* -!public/mosaico/uploads/README.md -public/mosaico/custom/* -!public/mosaico/custom/README.md -public/mosaico/templates/* -!public/mosaico/templates/versafix-1 - -public/grapejs/uploads/* -!public/grapejs/uploads/README.md -public/grapejs/templates/* -!public/grapejs/templates/demo -!public/grapejs/templates/aves - config/production.toml workers/reports/config/production.toml docker-compose.override.yml diff --git a/UPGRADE.md b/UPGRADE.md index 9d16d0dd..4e5b8d8f 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -6,8 +6,8 @@ The migration should almost happen automatically. There are however the followin and update your configs accordingly. 2. Images uploaded in a template editor (Mosaico, GrapeJS, etc.) need to be manually moved to a new destination (under `client`). - For Mosaico, this means to move folders named by a number from `public/mosaico` to `client/public/mosaico`. + For Mosaico, this means to move folders named by a number from `public/mosaico` to `client/static/mosaico`. -3. Directory for custom Mosaico templates has changed from `public/mosaico/templates` to `client/public/mosaico/templates`. +3. Directory for custom Mosaico templates has changed from `public/mosaico/templates` to `client/static/mosaico/templates`. 4. Imports are not migrated. If you have any pending imports, complete them before migration to v2. \ No newline at end of file diff --git a/app-builder.js b/app-builder.js index 2c173562..fbf03ef5 100644 --- a/app-builder.js +++ b/app-builder.js @@ -54,6 +54,9 @@ const index = require('./routes/index'); const interoperableErrors = require('./shared/interoperable-errors'); +const { getTrustedUrl } = require('./lib/urls'); +const { AppType } = require('./shared/app'); + hbs.registerPartials(__dirname + '/views/partials'); hbs.registerPartials(__dirname + '/views/subscription/partials/'); @@ -104,7 +107,8 @@ hbs.registerHelper('flash_messages', function () { // eslint-disable-line prefer handlebarsHelpers.registerHelpers(hbs.handlebars); -function createApp(trusted) { + +function createApp(appType) { const app = express(); function install404Fallback(url) { @@ -171,9 +175,9 @@ function createApp(trusted) { limit: config.www.postSize })); - if (trusted) { + if (appType === AppType.TRUSTED) { passport.setupRegularAuth(app); - } else { + } else if (appType === AppType.SANDBOXED) { app.use(passport.tryAuthByRestrictedAccessToken); } @@ -189,52 +193,6 @@ function createApp(trusted) { next(); }); - /* FIXME - can we remove this??? - app.use((req, res, next) => { - res.locals.flash = req.flash.bind(req); - res.locals.user = req.user; - res.locals.admin = req.user && req.user.id == 1; // FIXME, this should verify the admin privileges and set this accordingly - res.locals.ldap = { - enabled: config.ldap.enabled, - passwordresetlink: config.ldap.passwordresetlink - }; - - let menu = [{ - title: _('Home'), - url: '/', - selected: true - }]; - - res.setSelectedMenu = key => { - menu.forEach(item => { - item.selected = (item.key === key); - }); - }; - - res.locals.menu = menu; - tools.updateMenu(res); - - res.locals.customStyles = config.customstyles || []; - res.locals.customScripts = config.customscripts || []; - - let bodyClasses = []; - if (req.user) { - bodyClasses.push('logged-in user-' + req.user.username); - } - res.locals.bodyClass = bodyClasses.join(' '); - - getSettings(['uaCode', 'shoutout'], (err, configItems) => { - if (err) { - return next(err); - } - Object.keys(configItems).forEach(key => { - res.locals[key] = configItems[key]; - }); - next(); - }); - }); - */ - // Endpoint under /api are authenticated by access token app.all('/api/*', passport.authByAccessToken); @@ -244,62 +202,64 @@ function createApp(trusted) { next(); }); - app.all('/rest/*', (req, res, next) => { req.needsRESTJSONResponse = true; next(); }); - // Initializes the request context to be used for authorization app.use((req, res, next) => { req.context = contextHelpers.getRequestContext(req); next(); }); - - // Regular endpoints - useWith404Fallback('/subscription', subscription); - useWith404Fallback('/files', files); - useWith404Fallback('/mosaico', mosaico.getRouter(trusted)); - - if (config.reports && config.reports.enabled === true) { - useWith404Fallback('/reports', reports); + if (appType === AppType.PUBLIC) { + useWith404Fallback('/subscription', subscription); } + if (appType === AppType.TRUSTED || appType === AppType.SANDBOXED) { + // Regular endpoints + useWith404Fallback('/files', files); + useWith404Fallback('/mosaico', mosaico.getRouter(appType)); - // API endpoints - useWith404Fallback('/api', api); + if (config.reports && config.reports.enabled === true) { + useWith404Fallback('/reports', reports); + } - // REST endpoints - app.use('/rest', namespacesRest); - app.use('/rest', sendConfigurationsRest); - app.use('/rest', usersRest); - app.use('/rest', accountRest); - app.use('/rest', campaignsRest); - app.use('/rest', triggersRest); - app.use('/rest', listsRest); - app.use('/rest', formsRest); - app.use('/rest', fieldsRest); - app.use('/rest', importsRest); - app.use('/rest', importRunsRest); - app.use('/rest', sharesRest); - app.use('/rest', segmentsRest); - app.use('/rest', subscriptionsRest); - app.use('/rest', templatesRest); - app.use('/rest', mosaicoTemplatesRest); - app.use('/rest', blacklistRest); - app.use('/rest', editorsRest); - app.use('/rest', filesRest); - app.use('/rest', settingsRest); - if (config.reports && config.reports.enabled === true) { - app.use('/rest', reportTemplatesRest); - app.use('/rest', reportsRest); + // API endpoints + useWith404Fallback('/api', api); + + // REST endpoints + app.use('/rest', namespacesRest); + app.use('/rest', sendConfigurationsRest); + app.use('/rest', usersRest); + app.use('/rest', accountRest); + app.use('/rest', campaignsRest); + app.use('/rest', triggersRest); + app.use('/rest', listsRest); + app.use('/rest', formsRest); + app.use('/rest', fieldsRest); + app.use('/rest', importsRest); + app.use('/rest', importRunsRest); + app.use('/rest', sharesRest); + app.use('/rest', segmentsRest); + app.use('/rest', subscriptionsRest); + app.use('/rest', templatesRest); + app.use('/rest', mosaicoTemplatesRest); + app.use('/rest', blacklistRest); + app.use('/rest', editorsRest); + app.use('/rest', filesRest); + app.use('/rest', settingsRest); + + if (config.reports && config.reports.enabled === true) { + app.use('/rest', reportTemplatesRest); + app.use('/rest', reportsRest); + } + install404Fallback('/rest'); } - install404Fallback('/rest'); - app.use('/', index.getRouter(trusted)); + app.use('/', index.getRouter(appType)); // Error handlers if (app.get('env') === 'development') { @@ -333,7 +293,7 @@ function createApp(trusted) { } else { if (err instanceof interoperableErrors.NotLoggedInError) { - return res.redirect('/account/login?next=' + encodeURIComponent(req.originalUrl)); + return res.redirect(getTrustedUrl('/account/login?next=' + encodeURIComponent(req.originalUrl))); } else { res.status(err.status || 500); res.render('error', { @@ -378,7 +338,7 @@ function createApp(trusted) { // TODO: Render interoperable errors using a special client that does internationalization of the error message if (err instanceof interoperableErrors.NotLoggedInError) { - return res.redirect('/account/login?next=' + encodeURIComponent(req.originalUrl)); + return res.redirect(getTrustedUrl('/account/login?next=' + encodeURIComponent(req.originalUrl))); } else { res.status(err.status || 500); res.render('error', { @@ -393,6 +353,4 @@ function createApp(trusted) { return app; } -module.exports = { - createApp -}; +module.exports.createApp = createApp; diff --git a/client/src/campaigns/Content.js b/client/src/campaigns/Content.js index af42eddc..c421281a 100644 --- a/client/src/campaigns/Content.js +++ b/client/src/campaigns/Content.js @@ -40,7 +40,6 @@ export default class CustomContent extends Component { const t = props.t; - console.log(props); this.templateTypes = getTemplateTypes(props.t, 'data_sourceCustom_', ResourceType.CAMPAIGN); this.customTemplateTypeOptions = []; diff --git a/client/src/campaigns/helpers.js b/client/src/campaigns/helpers.js index b78cbeb2..f4fd10de 100644 --- a/client/src/campaigns/helpers.js +++ b/client/src/campaigns/helpers.js @@ -29,3 +29,56 @@ export function getCampaignLabels(t) { campaignTypeLabels }; } + +/* FIXME - this is not used at the moment, but it's kept here because it will be probably needed at some later point of time. +export function getDefaultMergeTags(t) { + return [{ + key: 'LINK_UNSUBSCRIBE', + value: t('URL that points to the unsubscribe page') + }, { + key: 'LINK_PREFERENCES', + value: t('URL that points to the preferences page of the subscriber') + }, { + key: 'LINK_BROWSER', + value: t('URL to preview the message in a browser') + }, { + key: 'EMAIL', + value: t('Email address') + }, { + key: 'SUBSCRIPTION_ID', + value: t('Unique ID that identifies the recipient') + }, { + key: 'LIST_ID', + value: t('Unique ID that identifies the list used for this campaign') + }, { + key: 'CAMPAIGN_ID', + value: t('Unique ID that identifies current campaign') + }]; +} + +export function getRSSMergeTags(t) { + return [{ + key: 'RSS_ENTRY', + value: t('content from an RSS entry') + }, { + key: 'RSS_ENTRY_TITLE', + value: t('RSS entry title') + }, { + key: 'RSS_ENTRY_DATE', + value: t('RSS entry date') + }, { + key: 'RSS_ENTRY_LINK', + value: t('RSS entry link') + }, { + key: 'RSS_ENTRY_CONTENT', + value: t('content from an RSS entry') + }, { + key: 'RSS_ENTRY_SUMMARY', + value: t('RSS entry summary') + }, { + key: 'RSS_ENTRY_IMAGE_URL', + value: t('RSS entry image URL') + }]; +} +*/ + diff --git a/client/src/lib/mosaico.js b/client/src/lib/mosaico.js index fada9847..cd4c53f9 100644 --- a/client/src/lib/mosaico.js +++ b/client/src/lib/mosaico.js @@ -70,7 +70,7 @@ export class MosaicoEditor extends Component { return (
- {this.state.fullscreen && } + {this.state.fullscreen && }
{this.props.title}
@@ -142,7 +142,7 @@ export class MosaicoSandbox extends Component { plugins.unshift(vm => { // This is an override of the default paths in Mosaico - vm.logoPath = getTrustedUrl('public/mosaico/img/mosaico32.png'); + vm.logoPath = getTrustedUrl('static/mosaico/img/mosaico32.png'); vm.logoUrl = '#'; }); diff --git a/client/src/lib/urls.js b/client/src/lib/urls.js index d42c112e..4374498a 100644 --- a/client/src/lib/urls.js +++ b/client/src/lib/urls.js @@ -1,6 +1,7 @@ 'use strict'; import {anonymousRestrictedAccessToken} from '../../../shared/urls'; +import {AppType} from '../../../shared/app'; import mailtrainConfig from "mailtrainConfig"; let restrictedAccessToken = anonymousRestrictedAccessToken; @@ -17,25 +18,34 @@ function getSandboxUrl(path) { return mailtrainConfig.sandboxUrlBase + restrictedAccessToken + '/' + (path || ''); } +function getPublicUrl(path) { + return mailtrainConfig.publicUrlBase + (path || ''); +} + function getUrl(path) { - if (mailtrainConfig.trusted) { + if (mailtrainConfig.appType === AppType.TRUSTED) { return getTrustedUrl(path); - } else { + } else if (mailtrainConfig.appType === AppType.SANDBOXED) { return getSandboxUrl(path); + } else if (mailtrainConfig.appType === AppType.PUBLIC) { + return getPublicUrl(path); } } function getBaseDir() { - if (mailtrainConfig.trusted) { + if (mailtrainConfig.appType === AppType.TRUSTED) { return mailtrainConfig.trustedUrlBaseDir; - } else { + } else if (mailtrainConfig.appType === AppType.SANDBOXED) { return mailtrainConfig.sandboxUrlBaseDir + anonymousRestrictedAccessToken; + } else if (mailtrainConfig.appType === AppType.PUBLIC) { + return mailtrainConfig.publicUrlBaseDir; } } export { getTrustedUrl, getSandboxUrl, + getPublicUrl, getUrl, getBaseDir, setRestrictedAccessToken diff --git a/client/src/lists/CUD.js b/client/src/lists/CUD.js index 877cd3da..119e82bd 100644 --- a/client/src/lists/CUD.js +++ b/client/src/lists/CUD.js @@ -52,7 +52,8 @@ export default class CUD extends Component { contact_email: '', homepage: '', unsubscription_mode: UnsubscriptionMode.ONE_STEP, - namespace: mailtrainConfig.user.namespace + namespace: mailtrainConfig.user.namespace, + to_name: '[FIRST_NAME] [LAST_NAME]' }); } } @@ -186,6 +187,7 @@ export default class CUD extends Component { + diff --git a/client/src/lists/subscriptions/List.js b/client/src/lists/subscriptions/List.js index 50fe95df..fe0faa6d 100644 --- a/client/src/lists/subscriptions/List.js +++ b/client/src/lists/subscriptions/List.js @@ -15,7 +15,7 @@ import { import {Icon, Button} from "../../lib/bootstrap-components"; import axios from '../../lib/axios'; import {getFieldTypes, getSubscriptionStatusLabels} from './helpers'; -import {getUrl} from "../../lib/urls"; +import {getUrl, getPublicUrl} from "../../lib/urls"; @translate() @withForm @@ -158,7 +158,7 @@ export default class List extends Component { return (
-