2017-03-10 08:59:25 +00:00
'use strict' ;
2019-07-26 15:05:49 +00:00
const config = require ( '../lib/config' ) ;
2018-05-09 02:07:01 +00:00
const path = require ( 'path' ) ;
2018-11-03 20:46:23 +00:00
const express = require ( 'express' ) ;
2018-04-22 15:33:43 +00:00
const routerFactory = require ( '../lib/router-async' ) ;
2018-02-25 19:54:15 +00:00
const passport = require ( '../lib/passport' ) ;
const clientHelpers = require ( '../lib/client-helpers' ) ;
2018-03-24 22:55:50 +00:00
const gm = require ( 'gm' ) . subClass ( {
imageMagick : true
} ) ;
2018-04-02 09:58:32 +00:00
const users = require ( '../models/users' ) ;
2019-04-03 21:39:10 +00:00
const capitalize = require ( 'capitalize' ) ;
2017-03-10 08:59:25 +00:00
2018-11-17 22:26:45 +00:00
const fs = require ( 'fs-extra' )
2018-02-25 19:54:15 +00:00
2018-03-24 22:55:50 +00:00
const files = require ( '../models/files' ) ;
const fileHelpers = require ( '../lib/file-helpers' ) ;
2018-05-09 02:07:01 +00:00
const templates = require ( '../models/templates' ) ;
const mosaicoTemplates = require ( '../models/mosaico-templates' ) ;
const contextHelpers = require ( '../lib/context-helpers' ) ;
2018-11-18 14:38:52 +00:00
const interoperableErrors = require ( '../../shared/interoperable-errors' ) ;
2018-05-09 02:07:01 +00:00
2019-01-12 10:20:58 +00:00
const bluebird = require ( 'bluebird' ) ;
2018-09-29 20:07:24 +00:00
const { getTrustedUrl , getSandboxUrl , getPublicUrl } = require ( '../lib/urls' ) ;
2018-11-18 14:38:52 +00:00
const { base } = require ( '../../shared/templates' ) ;
const { AppType } = require ( '../../shared/app' ) ;
2018-05-09 02:07:01 +00:00
2018-09-29 11:30:29 +00:00
const { castToInteger } = require ( '../lib/helpers' ) ;
2019-04-22 00:41:40 +00:00
const { fileCache } = require ( '../lib/file-cache' ) ;
2018-04-02 09:58:32 +00:00
2018-04-22 07:00:04 +00:00
users . registerRestrictedAccessTokenMethod ( 'mosaico' , async ( { entityTypeId , entityId } ) => {
2018-05-09 02:07:01 +00:00
if ( entityTypeId === 'template' ) {
const tmpl = await templates . getById ( contextHelpers . getAdminContext ( ) , entityId , false ) ;
if ( tmpl . type === 'mosaico' ) {
return {
permissions : {
'template' : {
2018-11-10 18:40:20 +00:00
[ entityId ] : new Set ( [ 'viewFiles' , 'manageFiles' , 'view' ] )
2018-05-09 02:07:01 +00:00
} ,
'mosaicoTemplate' : {
[ tmpl . data . mosaicoTemplate ] : new Set ( [ 'view' ] )
}
2018-04-02 09:58:32 +00:00
}
2018-05-09 02:07:01 +00:00
} ;
}
2018-04-02 09:58:32 +00:00
}
} ) ;
2019-04-03 21:39:10 +00:00
async function placeholderImage ( width , height , labelText , labelColor ) {
2018-03-24 22:55:50 +00:00
const magick = gm ( width , height , '#707070' ) ;
const streamAsync = bluebird . promisify ( magick . stream . bind ( magick ) ) ;
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 ;
}
}
2019-04-03 21:39:10 +00:00
labelText = labelText || ` ${ width } x ${ height } ` ;
labelColor = labelColor || '#B0B0B0' ;
2018-03-24 22:55:50 +00:00
// text
magick
2019-04-03 21:39:10 +00:00
. fill ( labelColor )
2018-03-24 22:55:50 +00:00
. fontSize ( 20 )
2019-04-03 21:39:10 +00:00
. drawText ( 0 , 0 , labelText , 'center' ) ;
2018-03-24 22:55:50 +00:00
const stream = await streamAsync ( 'png' ) ;
return {
format : 'png' ,
stream
} ;
}
2018-05-09 02:07:01 +00:00
async function resizedImage ( filePath , method , width , height ) {
2018-03-24 22:55:50 +00:00
const magick = gm ( filePath ) ;
const streamAsync = bluebird . promisify ( magick . stream . bind ( magick ) ) ;
const formatAsync = bluebird . promisify ( magick . format . bind ( magick ) ) ;
const format = ( await formatAsync ( ) ) . toLowerCase ( ) ;
if ( method === 'resize' ) {
magick
. autoOrient ( )
. resize ( width , height ) ;
} else if ( method === 'cover' ) {
magick
. autoOrient ( )
. resize ( width , height + '^' )
. gravity ( 'Center' )
. extent ( width , height + '>' ) ;
} else {
throw new Error ( ` Method ${ method } not supported ` ) ;
}
const stream = await streamAsync ( ) ;
return {
format ,
stream
} ;
}
function 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 ;
}
2019-04-22 00:41:40 +00:00
async function getRouter ( appType ) {
2018-04-22 15:33:43 +00:00
const router = routerFactory . create ( ) ;
2018-05-09 02:07:01 +00:00
2018-09-18 08:30:13 +00:00
if ( appType === AppType . SANDBOXED ) {
2018-05-13 20:40:34 +00:00
router . getAsync ( '/templates/:mosaicoTemplateId/index.html' , passport . loggedIn , async ( req , res ) => {
2018-09-29 11:30:29 +00:00
const tmpl = await mosaicoTemplates . getById ( req . context , castToInteger ( req . params . mosaicoTemplateId ) ) ;
2018-05-09 02:07:01 +00:00
2018-05-13 20:40:34 +00:00
res . set ( 'Content-Type' , 'text/html' ) ;
2019-08-11 19:50:06 +00:00
res . send ( base ( tmpl . data . html , tmpl . tag _language , getTrustedUrl ( ) , getSandboxUrl ( '' , req . context ) , getPublicUrl ( ) ) ) ;
2018-05-13 20:40:34 +00:00
} ) ;
2018-03-24 22:55:50 +00:00
2018-05-13 20:40:34 +00:00
// Mosaico looks for block thumbnails in edres folder relative to index.html of the template. We respond to such requests here.
router . getAsync ( '/templates/:mosaicoTemplateId/edres/:fileName' , async ( req , res , next ) => {
try {
2018-09-29 11:30:29 +00:00
const file = await files . getFileByOriginalName ( contextHelpers . getAdminContext ( ) , 'mosaicoTemplate' , 'block' , castToInteger ( req . params . mosaicoTemplateId ) , req . params . fileName ) ;
2018-05-13 20:40:34 +00:00
res . type ( file . mimetype ) ;
return res . download ( file . path , file . name ) ;
} catch ( err ) {
if ( err instanceof interoperableErrors . NotFoundError ) {
next ( ) ;
} else {
throw err ;
}
}
} ) ;
2018-03-24 22:55:50 +00:00
2018-05-13 20:40:34 +00:00
// This is a fallback to versafix-1 if the block thumbnail is not defined by the template
2018-11-18 20:31:22 +00:00
router . use ( '/templates/:mosaicoTemplateId/edres' , express . static ( path . join ( _ _dirname , '..' , '..' , 'client' , 'static' , 'mosaico' , 'templates' , 'versafix-1' , 'edres' ) ) ) ;
2018-05-09 02:07:01 +00:00
2019-04-03 21:39:10 +00:00
// This is the final fallback for a block thumbnail, so that at least something gets returned
2019-04-22 00:41:40 +00:00
router . getAsync ( '/templates/:mosaicoTemplateId/edres/:fileName' , await fileCache ( 'mosaico-block-thumbnails' , config . mosaico . fileCache . blockThumbnails , req => req . params . fileName ) , async ( req , res ) => {
2019-04-03 21:39:10 +00:00
let labelText = req . params . fileName . replace ( /\.png$/ , '' ) ;
labelText = labelText . replace ( /[_]/g , ' ' ) ;
labelText = capitalize . words ( labelText ) ;
const image = await placeholderImage ( 340 , 100 , labelText , '#ffffff' ) ;
res . set ( 'Content-Type' , 'image/' + image . format ) ;
2019-04-22 00:41:40 +00:00
image . stream . pipe ( res . fileCacheResponse ) ;
2019-04-03 21:39:10 +00:00
} ) ;
2018-11-10 18:40:20 +00:00
fileHelpers . installUploadHandler ( router , '/upload/:type/:entityId' , files . ReplacementBehavior . RENAME , null , 'file' , resp => {
return {
files : resp . files . map ( f => ( { name : f . name , url : f . url , size : f . size , thumbnailUrl : f . thumbnailUrl } ) )
} ;
} ) ;
2018-03-24 22:55:50 +00:00
2018-05-09 02:07:01 +00:00
router . getAsync ( '/upload/:type/:entityId' , passport . loggedIn , async ( req , res ) => {
2018-09-29 11:30:29 +00:00
const id = castToInteger ( req . params . entityId ) ;
const entries = await files . list ( req . context , req . params . type , 'file' , id ) ;
2018-03-24 22:55:50 +00:00
2018-04-22 15:33:43 +00:00
const filesOut = [ ] ;
for ( const entry of entries ) {
filesOut . push ( {
name : entry . originalname ,
2018-09-29 11:30:29 +00:00
url : files . getFileUrl ( req . context , req . params . type , 'file' , id , entry . filename ) ,
2018-04-22 15:33:43 +00:00
size : entry . size ,
2018-09-29 11:30:29 +00:00
thumbnailUrl : files . getFileUrl ( req . context , req . params . type , 'file' , id , entry . filename ) // TODO - use smaller thumbnails
2018-04-22 15:33:43 +00:00
} )
}
2018-02-25 19:54:15 +00:00
2018-04-22 15:33:43 +00:00
res . json ( {
files : filesOut
} ) ;
} ) ;
2018-02-25 19:54:15 +00:00
2018-04-22 15:33:43 +00:00
router . getAsync ( '/editor' , passport . csrfProtection , async ( req , res ) => {
2018-09-18 08:30:13 +00:00
const mailtrainConfig = await clientHelpers . getAnonymousConfig ( req . context , appType ) ;
2018-04-22 15:33:43 +00:00
let languageStrings = null ;
2018-12-15 14:15:48 +00:00
const lang = req . locale . language ;
2018-11-18 20:31:22 +00:00
if ( lang && lang !== 'en' ) {
2018-04-22 15:33:43 +00:00
try {
2019-05-08 17:54:19 +00:00
const file = path . join ( _ _dirname , '..' , '..' , 'client' , 'static' , 'mosaico' , 'rs' , 'lang' , 'mosaico-' + lang + '.json' ) ;
2018-11-17 22:26:45 +00:00
languageStrings = await fs . readFile ( file , 'utf8' ) ;
2018-04-22 15:33:43 +00:00
} catch ( err ) {
}
}
res . render ( 'mosaico/root' , {
layout : 'mosaico/layout' ,
editorConfig : config . mosaico ,
languageStrings : languageStrings ,
reactCsrfToken : req . csrfToken ( ) ,
2018-05-09 02:07:01 +00:00
mailtrainConfig : JSON . stringify ( mailtrainConfig ) ,
scriptFiles : [
2019-01-05 22:56:16 +00:00
getSandboxUrl ( 'client/mosaico-root.js' )
2018-05-09 02:07:01 +00:00
] ,
2018-11-03 20:46:23 +00:00
publicPath : getSandboxUrl ( )
2018-04-22 15:33:43 +00:00
} ) ;
} ) ;
2018-05-13 20:40:34 +00:00
2018-09-29 20:07:24 +00:00
} else if ( appType === AppType . TRUSTED || appType === AppType . PUBLIC ) { // Mosaico editor loads the images from TRUSTED endpoint. This is hard to change because the index.html has to come from TRUSTED.
// So we serve /mosaico/img under both endpoints. There is no harm in it.
2019-04-22 09:41:37 +00:00
const trustedUrlPrefix = getTrustedUrl ( ) ;
const publicUrlPrefix = getPublicUrl ( ) ;
const imgCacheFileName = req => {
const method = req . query . method || '' ;
const params = req . query . params || '' ;
const src = req . query . src || '' ;
if ( method === 'placeholder' ) {
return ` ${ method } _ ${ params } ` ;
} else if ( src . startsWith ( trustedUrlPrefix ) ) {
return ` ${ src . substring ( trustedUrlPrefix . length ) } _ ${ method } _ ${ params } ` ;
} else if ( src . startsWith ( publicUrlPrefix ) ) {
return ` ${ src . substring ( publicUrlPrefix . length ) } _ ${ method } _ ${ params } ` ;
} else {
return null ;
}
} ;
router . getAsync ( '/img' , await fileCache ( 'mosaico-images' , config . mosaico . fileCache . images , imgCacheFileName ) , async ( req , res ) => {
2018-05-13 20:40:34 +00:00
const method = req . query . method ;
const params = req . query . params ;
let [ width , height ] = params . split ( ',' ) ;
let image ;
if ( method === 'placeholder' ) {
width = sanitizeSize ( width , 1 , 2048 , 600 , false ) ;
height = sanitizeSize ( height , 1 , 2048 , 300 , false ) ;
2019-01-12 10:20:58 +00:00
try {
image = await placeholderImage ( width , height ) ;
} catch ( err ) {
console . log ( err ) ;
}
2018-05-13 20:40:34 +00:00
} else {
2019-04-03 10:13:49 +00:00
width = sanitizeSize ( width , 1 , 2048 , 600 , true ) ;
2018-05-13 20:40:34 +00:00
height = sanitizeSize ( height , 1 , 2048 , 300 , true ) ;
2018-07-22 09:32:43 +00:00
let filePath ;
2019-04-22 00:41:40 +00:00
const url = req . query . src || '' ;
2018-07-22 09:32:43 +00:00
const mosaicoLegacyUrlPrefix = getTrustedUrl ( ` mosaico/uploads/ ` ) ;
if ( url . startsWith ( mosaicoLegacyUrlPrefix ) ) {
2018-11-18 20:31:22 +00:00
filePath = path . join ( _ _dirname , '..' , '..' , 'client' , 'static' , 'mosaico' , 'uploads' , url . substring ( mosaicoLegacyUrlPrefix . length ) ) ;
2018-07-22 09:32:43 +00:00
} else {
2018-08-03 11:35:55 +00:00
const file = await files . getFileByUrl ( contextHelpers . getAdminContext ( ) , url ) ;
2018-07-22 09:32:43 +00:00
filePath = file . path ;
}
image = await resizedImage ( filePath , method , width , height ) ;
2018-05-13 20:40:34 +00:00
}
res . set ( 'Content-Type' , 'image/' + image . format ) ;
2019-04-22 00:41:40 +00:00
image . stream . pipe ( res . fileCacheResponse ) ;
2018-05-13 20:40:34 +00:00
} ) ;
2018-04-22 15:33:43 +00:00
}
2017-03-10 08:59:25 +00:00
2018-04-22 15:33:43 +00:00
return router ;
}
2018-03-24 22:55:50 +00:00
2018-09-18 08:30:13 +00:00
module . exports . getRouter = getRouter ;