jsharmony-cms
Version:
1,074 lines (941 loc) • 61.2 kB
JavaScript
/*
Copyright 2019 apHarmony
This file is part of jsHarmony.
jsHarmony is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
jsHarmony is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this package. If not, see <http://www.gnu.org/licenses/>.
*/
var Helper = require('jsharmony/Helper');
var HelperFS = require('jsharmony/HelperFS');
var _ = require('lodash');
var async = require('async');
var path = require('path');
var fs = require('fs');
var urlparser = require('url');
var crypto = require('crypto');
var querystring = require('querystring');
module.exports = exports = function(module, funcs){
var exports = {};
var _t = module._t;
exports.getPageFile = function(page_file_id){
if(!page_file_id) throw new Error('Invalid page_file_id');
return path.join(path.join(module.jsh.Config.datadir,'page'),page_file_id.toString()+'.json');
};
exports.getClientPage = function(dbcontext, page, sitemaps, site_id, options, cb){
options = _.extend({ includeAllExtraContent: false, includeExtraRemoteContent: false, pageTemplates: null, ignoreInvalidPageTemplate: false }, options);
var page_file_id = page.page_file_id;
var page_template_id = page.page_template_id;
if(!page_template_id) return cb(new Error('Page template: '+page_template_id));
var template = null;
if(options.pageTemplates) template = options.pageTemplates[page_template_id];
Helper.execif(!options.pageTemplates && (options.pageTemplates !== false),
function(f){
funcs.getPageTemplate(dbcontext, site_id, page_template_id, {}, function(err, pageTemplate){
if(err) return cb(err);
template = pageTemplate;
return f();
});
},
function(){
if(!template && options.ignoreInvalidPageTemplate) template = {};
if(!template) return cb(new Error('Invalid page template "'+page_template_id+'" in page '+page.page_path));
//Load Page Content from disk
module.jsh.ParseJSON(funcs.getPageFile(page_file_id), module.name, 'Page File ID#'+page_file_id, function(err, page_file){
if(err && !HelperFS.fileNotFound(err)) return cb(err);
//Template options
template.options = _.extend({
title_element_required: true,
}, template.options);
template.options.page_toolbar = _.extend({
dock: 'top_offset'
}, template.options.page_toolbar);
template.sys_options = {
component_content_elements: {},
};
//Add content elements if they do not exist
var template_content_elements = template.content_elements;
if(!template_content_elements){
if(!options.pageTemplates && (template.location == 'REMOTE')){ /* Do nothing */ }
else {
template_content_elements = { body: { type: 'htmleditor', title: 'Body' } };
}
}
for(let key in template_content_elements){
template_content_elements[key] = _.extend({
type: 'htmleditor',
}, template_content_elements[key]);
template_content_elements[key].editor_toolbar = _.extend({
dock: 'auto',
show_menu: true,
show_toolbar: true,
}, template_content_elements[key].editor_toolbar);
}
//Parse content
var page_file_content = {};
try{
page_file_content = JSON.parse(JSON.stringify(template.default_content||'')) || {};
}
catch(ex){
module.jsh.Log.error('Error parsing JSON: '+ex.toString()+' :: '+template.default_content);
}
for(let key in template.content){
if(template.content[key] && template.content[key].component){
page_file_content[key] = '<div cms-component-remove-container cms-component='+JSON.stringify(template.content[key].component)+'></div>';
template.sys_options.component_content_elements[key] = template.content[key].component;
}
else{
page_file_content[key] = template.content[key];
}
}
page_file = page_file || { };
if(!('content' in page_file)) page_file.content = {};
for(let key in template_content_elements){
if(key in page_file.content) page_file_content[key] = page_file.content[key];
}
var extraContent = !!options.includeAllExtraContent;
if(template.standalone){
extraContent = true;
}
else if(template.location == 'REMOTE'){
//Add extra content_areas that are not defined in template (if using in-template script config)
if(options.includeExtraRemoteContent) extraContent = true;
}
if(extraContent){
for(let key in page_file.content){
if(!(key in page_file_content)) page_file_content[key] = page_file.content[key];
}
}
page_file.content = page_file_content;
//Parse properties
var default_properties = {};
try{
default_properties = _.extend(default_properties, JSON.parse(JSON.stringify(template.default_properties||'')) || {});
}
catch(ex){
module.jsh.Log.error('Error parsing JSON: '+ex.toString()+' :: '+template.default_properties);
}
if(template.properties){
_.each(template.properties.fields, function(field){
if(field && field.name){
if(!(field.name in default_properties)){
default_properties[field.name] = ('default' in field) ? field.default : '';
}
}
});
}
if(!page_file.seo) page_file.seo = {};
var client_page = {
title: page.page_title||'',
css: page_file.css||'',
header: page_file.header||'',
footer: page_file.footer||'',
properties: _.extend({}, default_properties, page_file.properties||{}),
content: page_file.content||{},
seo: {
title: page_file.seo.title||'',
keywords: page_file.seo.keywords||'',
metadesc: page_file.seo.metadesc||'',
canonical_url: page_file.seo.canonical_url||'',
},
lang: page.page_lang||'',
tags: page.page_tags||'',
author: page.page_author,
page_template_id: page_template_id,
};
var client_template = {
title: template.title||'',
css: template.css||'',
header: template.header||'',
footer: template.footer||'',
properties: template.properties||{},
default_properties: default_properties||{},
js: template.js||'',
content_elements: template_content_elements,
raw: template.raw||false,
standalone: template.standalone||false,
options: template.options||{},
components: template.components||{},
sys_options: template.sys_options||{},
};
return cb(null,{
page: client_page,
template: client_template,
sitemap: funcs.getPageSitemapRelatives((sitemaps||{}).PRIMARY, page.page_key)
});
}, { fatalError: false });
}
);
};
exports.replaceBranchURLs = function(content, options){
return funcs.replaceURLs('#@JSHCMS', content, options, function(rtag, url, getLinkContent){
if(!url) return url;
var orig_url = url;
function addUrlSuffix(url){
if(!url || (url==orig_url)) return url;
var orig_urlparts = urlparser.parse(orig_url, true);
var suffix = '';
if(Helper.beginsWith(orig_urlparts.hash || '', rtag)){
suffix = orig_urlparts.hash.substr(rtag.length);
}
if(!suffix) return url;
var urlparts = urlparser.parse(url, true);
if(urlparts.hash) return url + suffix;
if(urlparts.search){
if(suffix[0]=='?') return url + '&' + suffix.substr(1);
}
return url + suffix;
}
if(module.Config.onReplaceBranchURL){
var customURL = module.Config.onReplaceBranchURL(url, options.branchData, getLinkContent, addUrlSuffix, options);
if(typeof customURL != 'undefined') return customURL;
}
var urlparts = urlparser.parse(url, true);
if(!urlparts.path) return url;
var patharr = (urlparts.pathname||'').split('/');
if((urlparts.path.indexOf('/_funcs/media/')==0) && (patharr.length>=4)){
var media_key = patharr[3];
if(parseInt(media_key).toString()==media_key){
try{
var thumbnail_id = ((patharr.length >= 5) ? patharr[4] : '');
var media_url = options.getMediaURL(media_key, thumbnail_id, options.branchData, getLinkContent, urlparts);
}
catch(ex){
if(options.onError) options.onError(ex);
else throw ex;
return '';
}
return addUrlSuffix(media_url);
}
}
if((urlparts.path.indexOf('/_funcs/page/')==0) && (patharr.length>=4)){
var page_key = patharr[3];
if(parseInt(page_key).toString()==page_key){
try{
var page_url = options.getPageURL(page_key, options.branchData, getLinkContent, urlparts);
}
catch(ex){
if(options.onError) options.onError(ex);
else throw ex;
return '';
}
return addUrlSuffix(page_url);
}
}
return url;
});
};
exports.replaceURLs = function(rtag, content, options, replaceURL /* function(rtag, url, getLinkContent){ return url; } */){
options = _.extend({
getMediaURL: function(media_key, thumbnail_id, branchData, getLinkContent, thumbnail, urlparts){ return ''; },
getPageURL: function(page_key, branchData, getLinkContent, urlparts){ return ''; },
onError: function(err){ },
onComponentData: null, //function(content){ return content; },
onComponentProperties: null, //function(content){ return content; },
removeClass: false,
replaceComponents: false,
branchData: {}
}, options);
var rtagidx = content.indexOf(rtag);
while(rtagidx >= 0){
var startofstr = rtagidx;
var endofstr = rtagidx;
var urlchar = /[a-zA-Z0-9/_#\-:=?@%&.~[\]!$*+,;]/;
do{ if(!urlchar.test(content[startofstr])) break; } while(--startofstr >= 0);
do{ if(!urlchar.test(content[endofstr])) break; } while(++endofstr < content.length);
startofstr++;
endofstr--;
var url = content.substr(startofstr, endofstr - startofstr + 1);
var lowerurl = url.toLowerCase();
//Remove " + shift startofstr / endofstr
var quotstr = '"';
if(lowerurl.indexOf(quotstr) >= 0){
var relrtagidx = rtagidx - startofstr;
var endquotidx = lowerurl.indexOf(quotstr, relrtagidx);
var startquotidx = lowerurl.lastIndexOf(quotstr, relrtagidx);
if(endquotidx >= 0) endofstr -= (url.length - endquotidx);
if(startquotidx >= 0) startofstr += startquotidx + quotstr.length;
url = content.substr(startofstr, endofstr - startofstr + 1);
lowerurl = url.toLowerCase();
}
var newURL = url;
//Decode HTML entities
var escapeHtmlEntities = ((lowerurl.indexOf('/') >= 0) || (lowerurl.indexOf('/') >= 0));
if(escapeHtmlEntities){
newURL = Helper.unescapeHTMLEntity(newURL);
}
newURL = replaceURL(rtag, newURL, function(){
//Get start of link
var startOfLine = startofstr - 1;
var startchar = /[\n<]/;
do{ if(startchar.test(content[startOfLine])) break; } while((--startOfLine > 0) && ((startofstr - startOfLine) < 50));
//Get end of link
var endOfLine = endofstr;
var endchar = /[\n]/;
do{ if(endchar.test(content[endOfLine]) || (content.substr(Math.max(0,endOfLine-2), 3)=='/a>')) break; } while((++endOfLine < content.length) && ((endOfLine - endofstr) < 50));
return content.substr(startOfLine, endOfLine - startOfLine + 1);
});
newURL = Helper.ReplaceAll(newURL, ' ', '%20');
//Re-encode HTML entities
if(escapeHtmlEntities){
newURL = Helper.escapeHTML(newURL);
}
if(newURL!=url){
content = content.substr(0, startofstr) + newURL + content.substr(endofstr + 1);
rtagidx = endofstr + (newURL.length - url.length);
}
rtagidx = content.indexOf(rtag, rtagidx + 1);
}
function parseAttribute(content, attrs, f){
if(!_.isArray(attrs)) attrs = [attrs];
_.each(attrs, function(attr){
var idx = 0;
var attrIdx = content.indexOf(attr);
while(attrIdx >= 0){
idx = attrIdx + attr.length;
var chr = content[idx];
while((idx < content.length)&&((chr=='\t')||(chr=='\n')||(chr=='\r')||(chr=='\f')||(chr==' '))) chr = content[++idx];
var delim = '';
var validAttr = (chr == '=');
var firstChar = true;
var isEscaped = false;
if(validAttr){
chr = content[++idx];
while((idx < content.length)&&((chr=='\t')||(chr=='\n')||(chr=='\r')||(chr=='\f')||(chr==' '))) chr = content[++idx];
if(firstChar){
firstChar = false;
isEscaped = (chr == '\\');
if(isEscaped){
chr = content[++idx];
}
}
if(chr=='"'){ delim = '"'; chr = content[++idx]; }
else if(chr=="'"){ delim = "'"; chr = content[++idx]; }
else if(chr=='>'){ validAttr = false; }
//Read attribute value
if(validAttr){
var startPos = idx;
var endPos = idx;
var atEnd = false;
while(!atEnd && (idx < content.length)){
chr = content[idx];
if(isEscaped && (chr == '\\')) chr = content[++idx];
if(delim == '"'){
if(chr == '"') atEnd = true;
else endPos++;
}
else if(delim == "'"){
if(chr == "'") atEnd = true;
else endPos++;
}
else {
if(chr == '>') atEnd = true;
else endPos++;
}
idx++;
}
if(f){
var oldVal = content.substr(startPos, endPos - startPos);
var newVal = f(oldVal);
content = content.substr(0, startPos) + newVal + content.substr(endPos);
}
}
}
attrIdx = content.indexOf(attr, idx);
}
});
return content;
}
function replaceBase64Attribute(content, attr, onReplace){
return parseAttribute(content, attr, function(val){
if(!val) return val;
var strval = val;
try{
strval = Buffer.from(strval,'base64').toString();
}
catch(ex){
throw new Error('Error parsing base 64 data: '+ex);
}
strval = funcs.replaceURLs(rtag, strval, options, replaceURL);
if(onReplace) strval = onReplace(strval);
return Buffer.from(strval).toString('base64');
});
}
if(options.replaceComponents){
try{
if(options.branchData && options.branchData.component_templates){
parseAttribute(content, ['data-component'],function(val){
var componentType = val;
if(!(componentType in options.branchData.component_templates)){
var err = new Error('Undefined component "' + componentType + '" used in page');
if(options.onError) options.onError(err);
else throw err;
}
return val;
});
}
content = replaceBase64Attribute(content, 'data-component-data', options.onComponentData);
content = replaceBase64Attribute(content, 'data-component-properties', options.onComponentProperties);
}
catch(ex){
if(options.onError) options.onError(ex);
else throw ex;
}
}
return content;
};
exports.parseDeploymentUrl = function(url, template_variables, baseUrl){
var rslt = funcs.replaceTemplateVariables(template_variables, url || '');
try{
if(baseUrl) rslt = new urlparser.URL(rslt, baseUrl).toString();
}
catch(ex){ /* Do nothing */ }
return rslt;
};
exports.replacePageContent = function(page, options, replaceFunc){ /* replaceFunc: function(content){ return newContent; } */
options = _.extend({ isRaw: false, replaceHeaderFooterCSS: true }, options);
if(options.isRaw) {
if(page.content && page.content.body) page.content.body = replaceFunc(page.content.body);
}
else {
if(page.content) for(var key in page.content){ page.content[key] = replaceFunc(page.content[key]); }
if(options.replaceHeaderFooterCSS){
_.each(['css','header','footer'], function(key){
if(page[key]) page[key] = replaceFunc(page[key]);
});
}
}
};
exports.localizePageURLs = function(page, baseurl, isRaw, branch_id, media_files){
function replaceURLs(content, options){
var rslt = funcs.replaceBranchURLs(content, _.extend({ replaceComponents: true }, options, {
getMediaURL: function(media_key, thumbnail_id, branchData, getLinkContent, urlparts){
if(!media_files){
return baseurl + urlparts.pathname.substr(1) + '#@JSHCMS';
}
var media_id = media_files[media_key] ? media_files[media_key].media_id : media_key;
return baseurl+'_funcs/media/'+media_key+(thumbnail_id?'/'+thumbnail_id:'')+'/?media_id='+media_id+'#@JSHCMS';
},
getPageURL: function(page_key, branchData, getLinkContent, urlparts){
if(branch_id) return baseurl+'_funcs/page/'+page_key+'/?branch_id='+branch_id+'&view=1#@JSHCMS';
return baseurl+'_funcs/page/'+page_key+'/#@JSHCMS';
}
}));
return rslt;
}
funcs.replacePageContent(page, { isRaw: isRaw }, function(content){
return replaceURLs(content);
});
};
exports.page = function (req, res, next) {
var verb = req.method.toLowerCase();
var Q = req.query;
var P = req.body;
var jsh = module.jsh;
var appsrv = jsh.AppSrv;
var XValidate = jsh.XValidate;
var sql = '';
var sql_ptypes = [];
var sql_params = {};
var verrors = {};
var dbtypes = appsrv.DB.types;
var validate = null;
var model = jsh.getModel(req, module.namespace + 'Page_Editor');
if(!req.params || !req.params.page_key) return next();
var page_key = req.params.page_key;
var validateKeys = (verb == 'get') ?
{ page_template_id: Q.page_template_id } :
{ page_key: req.params.page_key, page_id: Q.page_id, page_template_id: Q.page_template_id };
funcs.validateClientToken(req, res, next, ['get','post'], validateKeys, function(){
if (!Helper.hasModelAction(req, model, 'BU')) { Helper.GenError(req, res, -11, _t('Invalid Model Access')); return; }
//Get page
sql_ptypes = [dbtypes.BigInt];
sql_params = { 'page_key': page_key };
validate = new XValidate();
verrors = {};
validate.AddValidator('_obj.page_key', 'Page Key', 'B', [XValidate._v_IsNumeric(), XValidate._v_Required()]);
sql = 'select page_id,page_key,page_file_id,page_title,page_path,page_tags,page_author,page_template_id,page_seo_title,page_seo_canonical_url,page_seo_metadesc,page_seo_keywords,page_review_sts,page_lang';
if(Q.page_id && Q.branch_id){
sql_ptypes.push(dbtypes.BigInt);
sql_params.page_id = Q.page_id;
validate.AddValidator('_obj.page_id', 'Page ID', 'B', [XValidate._v_IsNumeric()]);
sql_ptypes.push(dbtypes.BigInt);
sql_params.branch_id = Q.branch_id;
validate.AddValidator('_obj.branch_id', 'Branch ID', 'B', [XValidate._v_IsNumeric()]);
sql += ',@branch_id branch_id from {schema}.page where page_key=@page_key and page_id=@page_id and site_id={schema}.my_current_site_id()';
}
else if(Q.branch_id && Q.view){
sql_ptypes.push(dbtypes.BigInt);
sql_params.branch_id = Q.branch_id;
validate.AddValidator('_obj.branch_id', 'Branch ID', 'B', [XValidate._v_IsNumeric()]);
sql += ',branch_id from (select page.*,branch_page.branch_id from {schema}.page inner join {schema}.branch_page on branch_page.branch_id=@branch_id and branch_page.page_id=page.page_id where page.page_key=@page_key and site_id={schema}.my_current_site_id()) page';
}
else if(Q.page_id) return Helper.GenError(req, res, -4, 'Invalid parameters - missing branch_id');
else{
sql += ',(select {schema}.my_current_branch_id()) branch_id';
sql += ' from {schema}.v_my_page where page_key=@page_key';
}
var page_role = '';
if(Helper.HasRole(req, 'PUBLISHER')) page_role = 'PUBLISHER';
else if(Helper.HasRole(req, 'AUTHOR')) page_role = 'AUTHOR';
else if(Helper.HasRole(req, 'VIEWER')) page_role = 'VIEWER';
var fields = [];
var datalockstr = '';
appsrv.getDataLockSQL(req, model, fields, sql_ptypes, sql_params, verrors, function (datalockquery) { datalockstr += ' and ' + datalockquery; });
sql = Helper.ReplaceAll(sql, '%%%DATALOCKS%%%', datalockstr);
verrors = _.merge(verrors, validate.Validate('B', sql_params));
if (!_.isEmpty(verrors)) { Helper.GenError(req, res, -2, verrors[''].join('\n')); return; }
appsrv.ExecRecordset(req._DBContext, funcs.replaceSchema(sql), sql_ptypes, sql_params, function (err, rslt) {
if (err != null) { err.sql = sql; err.model = model; appsrv.AppDBError(req, res, err); return; }
if(!rslt || !rslt.length || !rslt[0] || (rslt[0].length != 1)){
if(Q.page_id || Q.view) return Helper.GenError(req, res, -4, 'Page not found in revision');
return Helper.GenError(req, res, -4, 'Page not found in current site revision');
}
var page = rslt[0][0];
var baseurl = req.baseurl;
if(baseurl.indexOf('//')<0) baseurl = req.protocol + '://' + req.get('host') + baseurl;
if(!Q.page_id){
if(Q.branch_id){
if(Q.branch_id.toString()!=(page.branch_id||'').toString()){ return Helper.GenError(req, res, -4, 'Please close and reopen editor. The revision has changed.'); }
}
if(Q.page_template_id){
if(Q.page_template_id.toString()!=(page.page_template_id||'').toString()){ return Helper.GenError(req, res, -4, 'Please close and reopen editor. The template has changed.'); }
}
}
if (verb == 'get'){
if (!Helper.hasModelAction(req, model, 'B')) { Helper.GenError(req, res, -11, _t('Invalid Model Access')); return; }
//Validate parameters
if (!appsrv.ParamCheck('P', P, [])) { Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
if (!appsrv.ParamCheck('Q', Q, ['|page_id','|branch_id','|page_template_id','|view','|jshcms_token'])) { Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
var authors = null;
var clientPage = null;
var media_files = {};
var sitemaps = {};
var menus = {};
var site_id = null;
var branch_id = page.branch_id;
if(!branch_id){ return Helper.GenError(req, res, -4, 'Invalid Parameters'); }
async.waterfall([
//Get site
function(cb){
sql = "select v_my_branch_access.branch_id,branch_name,site_id from {schema}.v_my_branch_access inner join {schema}.branch_page on branch_page.branch_id=v_my_branch_access.branch_id where v_my_branch_access.branch_id=@branch_id and branch_access like 'R%' and (branch_page.page_key=(select page_key from {schema}.page where page_id=@page_id))";
appsrv.ExecRecordset(req._DBContext, funcs.replaceSchema(sql), [dbtypes.BigInt,dbtypes.BigInt], { page_id: page.page_id, branch_id: branch_id }, function (err, rslt) {
if (err != null) { err.sql = sql; err.model = model; appsrv.AppDBError(req, res, err); return; }
if(!rslt || !rslt.length || !rslt[0] || !rslt[0].length){ return Helper.GenError(req, res, -12, 'Could not access Site Revision or Page'); }
site_id = rslt[0][0].site_id;
return cb();
});
},
//Get authors
function(cb){
if(Helper.HasRole(req, 'PUBLISHER')){
sql = "select sys_user_id code_val,concat(sys_user_fname,' ',sys_user_lname) code_txt from jsharmony.sys_user where sys_user_id in (select sys_user_id from {schema}.v_sys_user_site_access where site_id=@site_id and sys_user_site_access in ('WEBMASTER','PUBLISHER','AUTHOR')) order by code_txt";
}
else {
sql = "select sys_user_id code_val,concat(sys_user_fname,' ',sys_user_lname) code_txt from jsharmony.sys_user where sys_user_id = (select page_author from "+(module.schema?module.schema+'.':'')+'page where page_id=@page_id) order by code_txt';
}
appsrv.ExecRecordset(req._DBContext, funcs.replaceSchema(sql), [dbtypes.BigInt, dbtypes.BigInt], { page_id: page.page_id, site_id: site_id }, function (err, rslt) {
if (err != null) { err.sql = sql; err.model = model; appsrv.AppDBError(req, res, err); return; }
if(!rslt || !rslt.length || !rslt[0]){ return Helper.GenError(req, res, -4, 'Invalid Page ID'); }
authors = rslt[0];
return cb();
});
},
//Get media
function(cb){
var sql = 'select media.media_key, media.media_id, media_file_id from {schema}.media inner join {schema}.branch_media on branch_media.branch_id=@branch_id and branch_media.media_id=media.media_id where (media_file_id is not null) and (media_is_folder = 0)';
appsrv.ExecRecordset(req._DBContext, funcs.replaceSchema(sql), [dbtypes.BigInt], {branch_id: branch_id}, function (err, rslt) {
if (err != null) { err.sql = sql; err.model = model; appsrv.AppDBError(req, res, err); return; }
if(!rslt || !rslt.length || !rslt[0]){ return cb(); }
_.each(rslt[0], function(media){
media_files[media.media_key] = media;
});
return cb();
});
},
//Get sitemaps
function(cb){
var sql = 'select sitemap.sitemap_key, sitemap_file_id, sitemap_type from {schema}.sitemap inner join {schema}.branch_sitemap on branch_sitemap.branch_id=@branch_id and branch_sitemap.sitemap_id=sitemap.sitemap_id where (sitemap_file_id is not null)';
appsrv.ExecRecordset(req._DBContext, funcs.replaceSchema(sql), [dbtypes.BigInt], {branch_id: branch_id}, function (err, rslt) {
if (err != null) { err.sql = sql; err.model = model; return cb(err); }
if(!rslt || !rslt.length || !rslt[0]){ return cb(); }
_.each(rslt[0], function(sitemap){
sitemaps[sitemap.sitemap_type] = sitemap;
});
async.eachOfSeries(sitemaps, function(sitemap, sitemap_type, sitemap_cb){
funcs.getClientSitemap(sitemap, function(err, sitemap_content){
if(err) return sitemap_cb(err);
if(!sitemap_content) return sitemap_cb(null);
sitemap.sitemap_items = sitemap_content.sitemap_items;
return sitemap_cb();
});
}, cb);
});
},
//Get menus
function(cb){
var sql = 'select menu.menu_key, menu_file_id, menu_name, menu_tag from {schema}.menu inner join {schema}.branch_menu on branch_menu.branch_id=@branch_id and branch_menu.menu_id=menu.menu_id where (menu_file_id is not null)';
appsrv.ExecRecordset(req._DBContext, funcs.replaceSchema(sql), [dbtypes.BigInt], {branch_id: branch_id}, function (err, rslt) {
if (err != null) { err.sql = sql; err.model = model; return cb(err); }
if(!rslt || !rslt.length || !rslt[0]){ return cb(); }
_.each(rslt[0], function(menu){
menus[menu.menu_tag] = menu;
});
async.eachOfSeries(menus, function(menu, menu_tag, menu_cb){
funcs.getClientMenu(menu, function(err, menu_content){
if(err) return menu_cb(err);
if(!menu_content) return menu_cb(null);
menu.menu_items = menu_content.menu_items;
return menu_cb();
});
}, cb);
});
},
//Get page
function(cb){
funcs.getClientPage(req._DBContext, page, sitemaps, site_id, { includeExtraRemoteContent: true }, function(err, _clientPage){
if(err) { Helper.GenError(req, res, -9, err.toString()); return; }
clientPage = _clientPage;
if(!clientPage.page.content || _.isString(clientPage.page.content)) { Helper.GenError(req, res, -99999, 'page.content must be a data structure'); return; }
funcs.localizePageURLs(clientPage.page, baseurl, !!clientPage.template.raw, ((Q.page_id || Q.view) ? branch_id : undefined), media_files);
return cb();
});
}
], function(err){
if(err){ Helper.GenError(req, res, -9, err.toString()); return; }
res.type('json');
res.end(JSON.stringify({
_success: 1,
page: clientPage.page,
template: clientPage.template,
sitemap: clientPage.sitemap,
menus: menus,
authors: authors,
role: page_role,
views: {
'jsh_cms_editor.css': (jsh.getEJS('jsh_cms_editor.css')||'')+(jsh.getEJS('jsh_cms_editor.css.ext')||''),
'jsh_cms_editor': jsh.getEJS('jsh_cms_editor')
}
}));
});
}
else if(verb == 'post'){
if (!Helper.hasModelAction(req, model, 'U')) { Helper.GenError(req, res, -11, _t('Invalid Model Access')); return; }
/*
var client_page = {
title: page.page_title||'',
css: page_file.css||'',
header: page_file.header||'',
footer: page_file.footer||'',
properties: page_file.properties||{},
content: page_file.content||{},
seo_title: page_file.seo_title||'',
seo_keywords: page_file.seo_keywords||'',
seo_metadesc: page_file.seo_metadesc||'',
seo_canonical_url: page_file.seo_canonical_url||'',
lang: page.page_lang||'',
tags: page.page_tags||'',
author: page.page_author,
};
*/
//Validate parameters
if(!('properties' in P)) P.properties = {};
if (!appsrv.ParamCheck('P', P, ['&title','&css','&header','&footer','&properties','&content','&seo','&lang','&tags','&author','|page_template_id'])) { Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
if (!appsrv.ParamCheck('Q', Q, ['|branch_id','|page_template_id','|jshcms_token'])) { Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
//XValidate
var client_page = P;
P.page_template_id = page.page_template_id;
validate = new XValidate();
verrors = {};
validate.AddValidator('_obj.title', 'Title', 'B', [XValidate._v_MaxLength(1024)]);
validate.AddValidator('_obj.css', 'CSS', 'B', []);
validate.AddValidator('_obj.header', 'Header', 'B', []);
validate.AddValidator('_obj.footer', 'Footer', 'B', []);
validate.AddValidator('_obj.properties', 'Properties', 'B', []);
validate.AddValidator('_obj.content', 'Content', 'B', []);
validate.AddValidator('_obj.seo.title', 'SEO Title', 'B', [XValidate._v_MaxLength(2048)]);
validate.AddValidator('_obj.seo.keywords', 'SEO Keywords', 'B', []);
validate.AddValidator('_obj.seo.metadesc', 'SEO Meta Description', 'B', []);
validate.AddValidator('_obj.seo.canonical_url', 'SEO Canonical URL', 'B', [XValidate._v_MaxLength(2048)]);
validate.AddValidator('_obj.lang', 'Language', 'B', [XValidate._v_MaxLength(32)]);
validate.AddValidator('_obj.tags', 'Tags', 'B', []);
validate.AddValidator('_obj.author', 'Author', 'B', [ XValidate._v_Required() ]);
verrors = _.merge(verrors, validate.Validate('B', client_page));
if (!_.isEmpty(verrors)) { Helper.GenError(req, res, -2, verrors[''].join('\n')); return; }
//Save to database
sql_ptypes = [
dbtypes.VarChar(1024),
dbtypes.VarChar(dbtypes.MAX),
dbtypes.BigInt,
dbtypes.VarChar(2048),
dbtypes.VarChar(2048),
dbtypes.VarChar(dbtypes.MAX),
dbtypes.VarChar(dbtypes.MAX),
dbtypes.VarChar(32),
dbtypes.BigInt,
];
sql_params = {
page_title: client_page.title,
page_tags: client_page.tags,
page_author: client_page.author,
page_seo_title: client_page.seo.title,
page_seo_canonical_url: client_page.seo.canonical_url,
page_seo_metadesc: client_page.seo.metadesc,
page_seo_keywords: client_page.seo.keywords,
page_lang: client_page.lang
};
sql = 'update '+(module.schema?module.schema+'.':'')+'v_my_page set page_file_id=null,'+_.map(sql_params, function(val, key){ return key + '=@' + key; }).join(',')+' where page_key=@page_key;';
sql += 'select page_file_id, page_path, page_folder from '+(module.schema?module.schema+'.':'')+'v_my_page where page_key=@page_key;';
sql_params.page_key = page_key;
fields = [];
datalockstr = '';
verrors = {};
appsrv.getDataLockSQL(req, model, fields, sql_ptypes, sql_params, verrors, function (datalockquery) { datalockstr += ' and ' + datalockquery; });
sql = Helper.ReplaceAll(sql, '%%%DATALOCKS%%%', datalockstr);
if (!_.isEmpty(verrors)) { Helper.GenError(req, res, -2, verrors[''].join('\n')); return; }
//Run updates in a transaction
var db = jsh.getDB('default');
var dbtasks = {};
//Create new branch (test error on duplicate, etc)
dbtasks['page_create'] = function (dbtrans, db_callback, transtbl) {
db.Recordset(req._DBContext, sql, sql_ptypes, sql_params, dbtrans, function (err, rslt) {
if (err != null) { err.sql = sql; return db_callback(err); }
if(!rslt || !rslt.length || !rslt[0]) return db_callback(new Error('Invalid database result'));
page.page_file_id = rslt[0].page_file_id;
page.page_folder = rslt[0].page_folder;
return db_callback();
});
};
funcs.getSiteConfig(req._DBContext, null, { continueOnConfigError: true }, function(err, siteConfig){
var mediaId = 1;
funcs.replacePasteImages(req._DBContext, db, dbtasks, function(){ return 'page_media_'+(mediaId++); }, baseurl, siteConfig, client_page, funcs.replacePageContent);
db.ExecTransTasks(dbtasks, function (err, rslt) {
if (err != null) { return appsrv.AppDBError(req, res, err); }
//Write file
fs.writeFile(funcs.getPageFile(page.page_file_id), JSON.stringify(client_page), 'utf8', function(err){
if(err) return Helper.GenError(req, res, -99999, err.toString());
res.type('json');
res.end(JSON.stringify({ '_success': 1, page_folder: page.page_folder }));
});
});
});
return;
}
else return Helper.GenError(req, res, -4, 'Invalid Operation');
});
});
};
exports.pageDev = function (req, res, next) {
var verb = req.method.toLowerCase();
var Q = req.query;
var P = req.body;
var jsh = module.jsh;
var appsrv = jsh.AppSrv;
var XValidate = jsh.XValidate;
var sql = '';
var sql_ptypes = [];
var sql_params = {};
var verrors = {};
var validate = null;
var model = jsh.getModel(req, module.namespace + 'Page_Editor');
funcs.validateClientToken(req, res, next, ['get'], { page_template_id: Q.page_template_id }, function(){
if (!Helper.hasModelAction(req, model, 'BU')) { Helper.GenError(req, res, -11, _t('Invalid Model Access')); return; }
//Get Branch ID
sql_ptypes = [];
sql_params = { };
validate = new XValidate();
verrors = {};
sql = 'select {schema}.my_current_branch_id() branch_id, {schema}.my_current_site_id() site_id';
var page_role = '';
if(Helper.HasRole(req, 'PUBLISHER')) page_role = 'PUBLISHER';
else if(Helper.HasRole(req, 'AUTHOR')) page_role = 'AUTHOR';
else if(Helper.HasRole(req, 'VIEWER')) page_role = 'VIEWER';
var fields = [];
var datalockstr = '';
appsrv.getDataLockSQL(req, model, fields, sql_ptypes, sql_params, verrors, function (datalockquery) { datalockstr += ' and ' + datalockquery; });
sql = Helper.ReplaceAll(sql, '%%%DATALOCKS%%%', datalockstr);
verrors = _.merge(verrors, validate.Validate('B', sql_params));
if (!_.isEmpty(verrors)) { Helper.GenError(req, res, -2, verrors[''].join('\n')); return; }
appsrv.ExecRecordset(req._DBContext, funcs.replaceSchema(sql), sql_ptypes, sql_params, function (err, rslt) {
if (err != null) { err.sql = sql; err.model = model; appsrv.AppDBError(req, res, err); return; }
if(!rslt || !rslt.length || !rslt[0] || (rslt[0].length != 1)){ return Helper.GenError(req, res, -9, 'Please checkout a site to use Dev Mode'); }
var devInfo = rslt[0][0];
if (verb == 'get'){
if (!Helper.hasModelAction(req, model, 'B')) { Helper.GenError(req, res, -11, _t('Invalid Model Access')); return; }
//Validate parameters
if (!appsrv.ParamCheck('P', P, [])) { Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
if (!appsrv.ParamCheck('Q', Q, ['|page_template_id','|jshcms_token'])) { Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
var menus = {};
var page_template = {};
async.waterfall([
//Get menus
function(cb){
appsrv.ExecRecordset(req._DBContext, 'select menu_key, menu_file_id, menu_name, menu_tag from '+(module.schema?module.schema+'.':'')+'v_my_menu where (menu_file_id is not null)', [], {}, function (err, rslt) {
if (err != null) { err.sql = sql; err.model = model; return cb(err); }
if(!rslt || !rslt.length || !rslt[0]){ return cb(); }
_.each(rslt[0], function(menu){
menus[menu.menu_tag] = menu;
});
async.eachOfSeries(menus, function(menu, menu_tag, menu_cb){
funcs.getClientMenu(menu, function(err, menu_content){
if(err) return menu_cb(err);
if(!menu_content) return menu_cb(null);
menu.menu_items = menu_content.menu_items;
return menu_cb();
});
}, cb);
});
},
//Get page template
function(cb){
if(!Q.page_template_id) return cb();
funcs.getPageTemplate(req._DBContext, devInfo.site_id, Q.page_template_id, {}, function(err, template){
if(err) return cb(err);
if (!template) return cb();
page_template = template;
return cb();
});
},
], function(err){
if(err){ Helper.GenError(req, res, -9, err.toString()); return; }
res.type('json');
res.end(JSON.stringify({
'_success': 1,
'branch_id': devInfo.branch_id,
'site_id': devInfo.site_id,
'sitemap': funcs.getSampleSitemap(),
'menus': menus,
'role': page_role,
'template': page_template,
'views': {
'jsh_cms_editor.css': (jsh.getEJS('jsh_cms_editor.css')||'')+(jsh.getEJS('jsh_cms_editor.css.ext')||''),
'jsh_cms_editor': jsh.getEJS('jsh_cms_editor')
}
}));
});
}
else return Helper.GenError(req, res, -4, 'Invalid Operation');
});
});
};
exports.getPageEditorUrl = function(req, res, next){
var verb = req.method.toLowerCase();
if (!req.body) req.body = {};
var Q = req.query;
var P = {};
var jsh = module.jsh;
var appsrv = jsh.AppSrv;
var cms = module;
var dbtypes = appsrv.DB.types;
var XValidate = jsh.XValidate;
var model = jsh.getModel(req, module.namespace + 'Page_Tree');
if (!Helper.hasModelAction(req, model, 'B')) { Helper.GenError(req, res, -11, _t('Invalid Model Access')); return; }
//Validate parameters
if (!appsrv.ParamCheck('Q', Q, ['&page_template_id', '|page_key', '|page_id', '|branch_id', '|devMode', '|site_id', '|render', '|page_template_path'])) { Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
if (!appsrv.ParamCheck('P', P, [])) { Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
var validate = new XValidate();
var verrors = {};
validate.AddValidator('_obj.page_template_id', 'Page Template ID', 'B', [ XValidate._v_Required(), XValidate._v_MaxLength(255) ]);
validate.AddValidator('_obj.page_key', 'Page Key', 'B', [ XValidate._v_IsNumeric() ]);
validate.AddValidator('_obj.page_id', 'Page ID', 'B', [ XValidate._v_IsNumeric() ]);
validate.AddValidator('_obj.branch_id', 'Branch ID', 'B', [ XValidate._v_IsNumeric() ]);
validate.AddValidator('_obj.site_id', 'Site ID', 'B', [ XValidate._v_IsNumeric() ]);
verrors = _.merge(verrors, validate.Validate('B', Q));
if (!_.isEmpty(verrors)) { Helper.GenError(req, res, -2, verrors[''].join('\n')); return; }
//Only dev mode uses devMode and site_id parameters
if (verb == 'get') {
var sql = 'select site_id current_site_id,site_id,deployment_target_template_variables,deployment_target_id from {schema}.v_my_site where site_id={schema}.my_current_site_id()';
var sql_ptypes = [];
var sql_params = {};
if(Q.devMode){
if(!Q.site_id) { Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
sql = 'select {schema}.my_current_site_id() current_site_id,v_my_site.site_id,v_my_site.deployment_target_template_variables,deployment_target_publish_config,deployment_target.deployment_target_id from {schema}.v_my_site left outer join {schema}.deployment_target on deployment_target.deployment_target_id = v_my_site.deployment_target_id where v_my_site.site_id=@site_id';
sql_ptypes = [dbtypes.BigInt];
sql_params = { site_id: Q.site_id };
}
appsrv.ExecRow(req._DBContext, funcs.replaceSchema(sql), sql_ptypes, sql_params, function (err, rslt) {
if (err != null) { err.sql = sql; err.model = model; appsrv.AppDBError(req, res, err); return; }
if (!rslt || !rslt.length || !rslt[0]) {
if(Q.devMode) Helper.GenError(req, res, -1, 'Site not found');
else Helper.GenError(req, res, -1, 'Site not checked out');
return;
}
var site_id = rslt[0].site_id || null;
var current_site_id = rslt[0].current_site_id || null;
var deployment_target_template_variables_str = rslt[0].deployment_target_template_variables || '';
var deployment_target_publish_config = {};
try{
deployment_target_publish_config = funcs.parseDeploymentTargetPublishConfig(site_id, rslt[0].deployment_target_publish_config, 'publish');
}
catch(ex){
return Helper.GenError(req, res, -99999, ex.toString());
}
var deployment_target_id = rslt[0].deployment_target_id;
if(!site_id) { Helper.GenError(req, res, -1, 'Site not found'); return; }
funcs.getPageTemplate(req._DBContext, site_id, Q.page_template_id, { continueOnConfigError: true }, function(err, page_template){
if(err) { Helper.GenError(req, res, -99999, err.toString()); return; }
if (!page_template) { Helper.GenError(req, res, -9, 'Page Template not found'); return; }
if(page_template.raw){
res.type('json');
return res.end(JSON.stringify({ '_success': 1, raw: true }));
}
var url = '';
if(page_template.location == 'LOCAL'){
if(!cms.PreviewServer) return Helper.GenError(req, res, -9, 'LOCAL Templates require a Preview Server to be running. Set configCMS.preview_server.enabled = true in app.config.js');
url = cms.PreviewServer.getURL((req.headers.host||'').split(':')[0]) + page_template.path;
//Generate an error if the preview server revision is not checked out
if(current_site_id != site_id) return Helper.GenError(req, res, -9, 'Please checkout this site to preview the template');
}
if(page_template.standalone && Q.page_template_path){
url = Q.page_template_path;
}
if(page_template.remote_templates && page_template.remote_templates.editor) {
url = page_template.remote_templates.editor;
}
var deployment_target_template_variables = {
branch_id: (Q.branch_id||'')
};
if(deployment_target_template_variables_str){
try{
deployment_target_template_variables = _.extend(deployment_target_template_variables, JSON.parse(deployment_target_template_variables_str));
}
catch(ex){
Helper.GenError(req, res, -9, 'Error reading deployment_target_template_variables. Please make sure the JSON syntax is correct');
return;
}
}
deployment_target_template_variables = _.extend(deployment_target_template_variables, {
page_template_id: Q.page_template_id,
page_key: (Q.page_key||''),
page_id: (Q.page_id||'')
});
funcs.parseTemplateVariables('editor', req._DBContext, site_id, undefined, deployment_target_template_variables, deployment_target_publish_config, function(err, template_variables){
if(err){ Helper.GenError(req, res, -99999, err.toString()); return; }
if(!url){
if(page_template.templates && page_template.templates.editor){
//Return full page
if(Q.render){
var cmsBaseUrl = cms.getCmsBaseUrlFromReq(req);
var content = '';
try{
content = funcs.generateEditorTemplate(page_template.templates.editor, { cmsBaseUrl: cmsBaseUrl, template_variables: template_variables });
}
catch(ex){
res.end('Error loading template: '+ex.toString());
return;
}
return res.end(content);
}
//Append render=1 to current URL
url = req.protocol + '://' + req.get('host') + req.originalUrl + '&render=1';
res.type('json');
return res.end(JSON.stringify({ '_success': 1, editor: url }));
}
else return Helper.GenError(req, res, -9, 'Page Template does not have an editor defined');
}