UNPKG

jsharmony-cms

Version:
877 lines (826 loc) 45.5 kB
/* 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 jsHarmony = require('jsharmony'); var jsHarmonyModule = require('jsharmony/jsHarmonyModule'); var jsHarmonyFactory = require('jsharmony-factory'); var jsHarmonyCMSConfig = require('./jsHarmonyCMSConfig.js'); var express = require('jsharmony/lib/express'); var Helper = require('jsharmony/Helper'); var HelperFS = require('jsharmony/HelperFS'); var path = require('path'); var _ = require('lodash'); var async = require('async'); var fs = require('fs'); var ejs = require('ejs'); var funcs = require('./models/_funcs.js'); var jsHarmonyRouter = require('jsharmony/jsHarmonyRouter'); var jsHarmonyCMSTransform = require('./jsHarmonyCMSTransform.js'); function jsHarmonyCMS(name, options){ options = _.extend({ schema: 'cms', factory: null, createRoles: true, isSubmodule: false, }, options); var _this = this; jsHarmonyModule.call(this, name); _this.Config = new jsHarmonyCMSConfig(); _this.typename = 'jsHarmonyCMS'; _this.basepath = path.dirname(module.filename); _this.schema = options.schema; _this.createRoles = options.createRoles; _this.isSubmodule = options.isSubmodule; _this.SystemPageTemplates = {}; _this.SystemComponentTemplates = {}; _this.SystemSiteConfig = {}; _this.Layouts = {}; _this.Elements = {}; _this.BranchItems = this.getDefaultBranchItems(); _this.DeploymentJobReady = false; _this.DeploymentJobPending = false; _this.SFTPServer = null; _this.PreviewServer = null; _this.funcs = new funcs(_this); _this.transform = new jsHarmonyCMSTransform(_this); _this._factoryOptions = options.factory; } jsHarmonyCMS.prototype = new jsHarmonyModule(); jsHarmonyCMS.prototype.Application = function(){ var _this = this; var jsh = new jsHarmony(); var factory = new jsHarmonyFactory(null, _this._factoryOptions); jsh.AddModule(factory); jsh.AddModule(this); jsh.Sites[factory.mainSiteID] = _.extend(this.getFactoryConfig(),jsh.Sites[factory.mainSiteID]); jsh.Config.valid_extensions.push('.js'); jsh.Config.valid_extensions.push('.pem'); jsh.Config.debug_params.ignore_globals.push('d3'); jsh.Config.onConfigLoaded.push(function(cb){ _this.LoadTemplates(); _this.LoadClientJS(); return cb(); }); jsh.Config.onDBDriverLoaded.push(function(cb){ _this.initBranchItems(cb); }); jsh.Config.onQueueSubscribe.push(function(cb, req, res, queueid){ _this.funcs.deployment_target_queue_onSubscribe(cb, req, res, queueid); }); return jsh; }; jsHarmonyCMS.Application = function(options){ return (new jsHarmonyCMS(null, options)).Application(); }; jsHarmonyCMS.prototype.Init = function(cb){ var _this = this; var jsh = _this.jsh; HelperFS.createFolderIfNotExistsSync(path.join(jsh.Config.datadir,'page')); HelperFS.createFolderIfNotExistsSync(path.join(jsh.Config.datadir,'media')); HelperFS.createFolderIfNotExistsSync(path.join(jsh.Config.datadir,'menu')); HelperFS.createFolderIfNotExistsSync(path.join(jsh.Config.datadir,'sitemap')); HelperFS.createFolderIfNotExistsSync(path.join(jsh.Config.datadir,'publish_log')); HelperFS.createFolderIfNotExistsSync(path.join(jsh.Config.datadir,'site')); HelperFS.createFolderIfNotExistsSync(path.join(jsh.Config.datadir,'deployment_target')); if(!_.isEmpty(_this.Config.media_thumbnails)){ jsh.Extensions.logDependency('image', 'jsHarmonyCMS > Media Thumbnails'); } jsh.AppSrv.modelsrv.srcfiles['jsHarmonyCMS.EditorSelection'] = jsh.getEJS('jsHarmonyCMS.EditorSelection'); jsh.AppSrv.modelsrv.srcfiles['jsHarmonyCMS.ComponentPlaceholder'] = jsh.getEJS('jsHarmonyCMS.ComponentPlaceholder'); jsh.AppSrv.modelsrv.srcfiles['jsHarmonyCMS.SiteSelection'] = jsh.getEJS('jsHarmonyCMS.SiteSelection'); jsh.Config.onServerReady.push(function (cb, servers){ async.parallel([ //Reset failed deployments function(ready_cb){ var sql = 'update '+(_this.schema?_this.schema+'.':'')+"deployment set deployment_sts='FAILED' where deployment_sts='RUNNING';"; if(_this.Config.debug_params.auto_restart_failed_publish){ sql = 'insert into '+(_this.schema?_this.schema+'.':'')+"deployment(deployment_tag, branch_id, deployment_date, deployment_target_id, deployment_params) select concat(deployment_tag,'#'), branch_id, deployment_date, deployment_target_id, deployment_params from "+(_this.schema?_this.schema+'.':'')+"deployment where deployment_sts='RUNNING';" + sql; } jsh.AppSrv.ExecCommand('system', sql, [], { }, function (err, rslt) { if (err) { jsh.Log.error(err); } _this.DeploymentJobReady = true; return ready_cb(); }); }, //Start SFTP function(ready_cb){ if(!_this.Config.sftp || !_this.Config.sftp.enabled) return ready_cb(); var jsHarmonyCMSSFTPServer = require('./jsHarmonyCMSSFTPServer.js'); _this.SFTPServer = new jsHarmonyCMSSFTPServer(_this); _this.SFTPServer.Run(ready_cb); }, //Start Preview Server function(ready_cb){ if(!_this.Config.preview_server || !_this.Config.preview_server.enabled) return ready_cb(); var jsHarmonyCMSPreviewServer = require('./jsHarmonyCMSPreviewServer.js'); _this.PreviewServer = new jsHarmonyCMSPreviewServer(_this); _this.PreviewServer.Run(ready_cb); }, ], function(err){ if (err) { jsh.Log.error(err); } return cb(); }); }); return cb(); }; //Load Client JS jsHarmonyCMS.prototype.LoadClientJS = function(){ var _this = this; var editorjs = fs.readFileSync(path.join(_this.basepath, 'public/js/jsHarmonyCMS.js'), 'utf8'); var editorjsEJS = fs.readFileSync(path.join(_this.basepath, 'public/js/jsHarmonyCMS.js.ejs'), 'utf8'); var editorjsLocal = ''; var modeldirs = _this.jsh.getModelDirs(); for (var i = 0; i < modeldirs.length; i++) { var jspath = path.join(modeldirs[i].path, '../public/js/jsHarmonyCMS.local.js'); if (fs.existsSync(jspath)) editorjsLocal += '\r\n' + fs.readFileSync(jspath); } _this.jsh.Cache['js/jsHarmonyCMS.js'] = editorjs; _this.jsh.Cache['js/jsHarmonyCMS.js.ejs'] = editorjsEJS; _this.jsh.Cache['js/jsHarmonyCMS.local.js'] = editorjsLocal; }; //Load Templates jsHarmonyCMS.prototype.LoadTemplates = function(){ var _this = this; //Prepend file content to property. Add property if it does not exist and content exists function prependPropFile(obj, prop, path){ if (fs.existsSync(path)) { var fcontent = fs.readFileSync(path, 'utf8'); if(_.isString(prop)){ if (prop in obj) fcontent += '\r\n' + obj[prop]; obj[prop] = fcontent; } else if(_.isArray(prop)){ _.each(prop, function(key){ if (key in obj) fcontent += '\r\n' + obj[key]; obj[key] = fcontent; }); } } } function LoadTemplatesFolder(templateType, dpath, prefix){ if(!prefix) prefix = ''; if (!fs.existsSync(dpath)) return; var files = fs.readdirSync(dpath, { withFileTypes: true }); for (var i = 0; i < files.length; i++) { var fname = files[i].name; var fext = ''; var fpath = path.join(dpath, fname); var frootname = fname; var idx_lastdot = fname.lastIndexOf('.'); if(idx_lastdot >= 0){ fext = fname.substr(idx_lastdot+1); frootname = fname.substr(0,idx_lastdot); } if(files[i].isDirectory()){ if(templateType=='component') LoadTemplatesFolder(templateType, path.join(dpath, fname), prefix + fname + '/'); } else if(fext=='json'){ var tmplname = prefix + frootname; var tmplbasepath = path.join(dpath, frootname); _this.jsh.LogInit_INFO('Loading ' + templateType + ' template: ' + tmplname); try{ var tmpl = _this.jsh.ParseJSON(fpath, _this.name, templateType + ' template "' + tmplname + '"', undefined, { fatalError: false }); if(!tmpl.title) tmpl.title = tmplname; if(templateType=='page'){ //Load page template files prependPropFile(tmpl, 'header', tmplbasepath + '.header.ejs'); prependPropFile(tmpl, 'footer', tmplbasepath + '.footer.ejs'); prependPropFile(tmpl, 'css', tmplbasepath + '.css'); prependPropFile(tmpl, 'js', tmplbasepath + '.js'); tmpl.templates = tmpl.templates || {}; prependPropFile(tmpl.templates, 'editor', tmplbasepath + '.templates.editor.ejs'); prependPropFile(tmpl.templates, 'publish', tmplbasepath + '.templates.publish.ejs'); if(!tmpl.content_elements) tmpl.content_elements = { body: { type: 'htmleditor', title: 'Body' } }; if(!tmpl.content) tmpl.content = {}; var reserved_content_element_names = ['header', 'footer', 'css', 'js']; for(var content_element_name in tmpl.content_elements){ if(_.includes(reserved_content_element_names, content_element_name)) _this.jsh.LogInit_ERROR('Template '+tmplname+' content_element ' + content_element_name + ': Invalid content_element name, cannot be any of the following: '+reserved_content_element_names.join(', ')); if(content_element_name != Helper.escapeCSSClass(content_element_name)) _this.jsh.LogInit_ERROR('Template '+tmplname+' content_element ' + content_element_name + ': Invalid content_element name, can only contain alpha-numeric characters, underscores, or dashes'); else prependPropFile(tmpl.content, content_element_name, tmplbasepath + '.' + content_element_name + '.ejs'); if(!('title' in tmpl.content_elements[content_element_name])) tmpl.content_elements[content_element_name].title = content_element_name; } _this.SystemPageTemplates[tmplname] = tmpl; } else if(templateType=='component'){ prependPropFile(tmpl, 'css', tmplbasepath + '.css'); prependPropFile(tmpl, 'js', tmplbasepath + '.js'); tmpl.templates = tmpl.templates || {}; prependPropFile(tmpl.templates, 'editor', tmplbasepath + '.templates.editor.ejs'); prependPropFile(tmpl.templates, 'publish', tmplbasepath + '.templates.publish.ejs'); tmpl.properties = tmpl.properties || {}; prependPropFile(tmpl.properties, 'ejs', tmplbasepath + '.properties.ejs'); prependPropFile(tmpl.properties, 'css', tmplbasepath + '.properties.css'); prependPropFile(tmpl.properties, 'js', tmplbasepath + '.properties.js'); tmpl.data = tmpl.data || {}; prependPropFile(tmpl.data, 'ejs', tmplbasepath + '.data.ejs'); prependPropFile(tmpl.data, 'css', tmplbasepath + '.data.css'); prependPropFile(tmpl.data, 'js', tmplbasepath + '.data.js'); _this.SystemComponentTemplates[tmplname] = tmpl; } } catch(ex){ _this.jsh.LogInit_ERROR(ex.toString()); } } } } var modeldirs = this.jsh.getModelDirs(); for (var i = 0; i < modeldirs.length; i++ ) { LoadTemplatesFolder('page', path.normalize(modeldirs[i].path + '../views/templates/pages/')); LoadTemplatesFolder('component', path.normalize(modeldirs[i].path + '../views/templates/components/')); var systemSiteConfigPath = path.normalize(modeldirs[i].path + '../views/templates/site_config.json'); if(fs.existsSync(systemSiteConfigPath)){ try{ var systemSiteConfig = _this.jsh.ParseJSON(systemSiteConfigPath, _this.name, 'System site_config.json', undefined, { fatalError: false }); _this.SystemSiteConfig = _this.funcs.mergeSiteConfig(_this.SystemSiteConfig, systemSiteConfig); } catch(ex){ _this.jsh.LogInit_ERROR(ex.toString()); } } } _this.SystemPageTemplates['<Raw Text>'] = { title: '<Raw Text>', raw: true, content_elements: { body: { type: 'texteditor', title: 'Body' } }, content: {} }; _this.SystemPageTemplates['<Standalone>'] = { title: '<Standalone>', standalone: true, templates: { publish: 'format:json' }, content_elements: { body: { type: 'htmleditor', title: 'Body' } }, content: {} }; //List of Values for Page Templates this.jsh.Config.macros.LOV_CMS_PAGE_TEMPLATES = { 'sql':'select '+(this.schema?this.schema+'.':'')+"my_current_site_id() code_val,'site_id' code_txt", 'post_process':"return jsh.Modules['"+this.name+"'].funcs.getCurrentPageTemplatesLOV(req._DBContext, values, {}, callback);", }; this.jsh.Config.macros.LOV_CMS_PAGE_TEMPLATES_BLANK = _.extend({ 'blank': '(None)' }, this.jsh.Config.macros.LOV_CMS_PAGE_TEMPLATES); this.jsh.Config.macros.LOV_CMS_PAGE_TEMPLATES_BLANK.post_process = "return jsh.Modules['"+this.name+"'].funcs.getCurrentPageTemplatesLOV(req._DBContext, values, { blank: true }, callback);"; this.jsh.Sites['main'].globalparams.isWebmaster = function (req) { return Helper.HasRole(req, 'WEBMASTER'); }; this.jsh.Sites['main'].globalparams.site_id = function (req) { return req.gdata.site_id; }; this.jsh.Sites['main'].globalparams.site_name = function (req) { return req.gdata.site_name; }; this.jsh.Sites['main'].globalparams.branch_items = function(req){ var branch_items = {}; _.each(_this.BranchItems, function(branch_item, branch_item_name){ branch_items[branch_item_name] = { name: branch_item.name, caption: branch_item.caption, editor_url: (branch_item.getEditorUrl ? branch_item.getEditorUrl(req) : undefined), }; }); return branch_items; }; //Chain to onAuthComplete if(!this.jsh.Sites['main'].auth) this.jsh.Sites['main'].auth = {}; this.jsh.Sites['main'].auth.onAuthComplete = Helper.chainToEnd(this.jsh.Sites['main'].auth.onAuthComplete, function(req, user_info, jsh){ if(!req.gdata) req.gdata = {}; req.gdata.site_id = user_info.site_id; req.gdata.site_name = user_info.site_name; }); //Add defaultEditorConfig to client-side variables this.jsh.Sites['main'].globalparams.defaultEditorConfig = _this.Config.defaultEditorConfig; }; jsHarmonyCMS.prototype.getCmsBaseUrlFromReq = function(req){ var _this = this; var jsh = _this.jsh; var mainBaseUrl = jsh.Sites['main'].baseurl; if(mainBaseUrl){ if(mainBaseUrl.indexOf('://') >= 0) return mainBaseUrl; if(mainBaseUrl.indexOf('//')==0) return mainBaseUrl; } return jsh.Servers['default'].getURLFromReq(req)+mainBaseUrl; }; jsHarmonyCMS.prototype.initBranchItems = function(cb){ var _this = this; var sql_branch_items = []; for(var key in _this.BranchItems){ var branchItem = _this.BranchItems[key]; sql_branch_items.push({ item: branchItem.name, tbl_branch_item: branchItem.tbl_branch_item, tbl_item: branchItem.tbl_item, item_caption_1: branchItem.caption[1], item_caption_2: branchItem.caption[2], conflicts_unique: (branchItem.conflicts.unique || '0=1') }); } _this.jsh.DB['default'].SQLExt.Funcs[_this.schema+'.branch_items'] = JSON.stringify(sql_branch_items); return cb(); }; jsHarmonyCMS.prototype.getDefaultBranchItems = function(){ var _this = this; return { 'page': { name: 'page', caption: ['','Page','Pages'], getEditorUrl: function(req){ return req.baseurl+_this.namespace+'Sitemap_Listing_Redirect'; }, tbl_branch_item: (_this.schema?_this.schema+'.':'')+'branch_page', tbl_item: (_this.schema?_this.schema+'.':'')+'page', diff: { columns: ['page_path','page_title','page_tags','page_file_id','page_filename','page_template_id','page_template_path'], sqlwhere: '(old_page.page_is_folder=0 or new_page.page_is_folder=0)', onBeforeDiff: function(branch_data, callback){ async.parallel([ function(page_cb){ _this.funcs.getPageTemplates('deployment', branch_data.site_id, { continueOnConfigError: true }, function(err, pageTemplates){ if(err) return page_cb(err); branch_data.page_templates = pageTemplates; return page_cb(); }); }, function(page_cb){ return _this.funcs.diff_getPages(branch_data, page_cb); } ], callback); }, onDiff: function(branch_items, branch_data, callback){ return _this.funcs.diff_page(branch_items, branch_data, callback); }, field_mapping: { page_seo: { 'title' : 'Title', 'keywords': 'Keywords', 'metadesc': 'Meta Description', 'canonical_url': 'Canonical URL' }, page: { 'css': 'CSS', 'header': 'Header Code', 'footer': 'Footer Code', 'page_title': 'Page Title', 'page_tags': 'Page Tags', 'properties': 'Page Properties', 'template_title': 'Template', 'page_template_path': 'Template URL', } } }, conflicts: { columns: ['page_path','page_title','page_tags','page_file_id','page_filename','page_template_id','page_template_path'], unique: 'dst_page.page_path = src_page.page_path', sqlwhere: '(src_orig_{item}.{item}_is_folder=0 or dst_orig_{item}.{item}_is_folder=0 or src_{item}.{item}_is_folder=0 or dst_{item}.{item}_is_folder=0)', onBeforeConflicts: function(branch_data, callback){ async.parallel([ function(page_cb){ _this.funcs.getPageTemplates('deployment', branch_data.site_id, { continueOnConfigError: true }, function(err, pageTemplates){ if(err) return page_cb(err); branch_data.page_templates = pageTemplates; return page_cb(); }); }, function(page_cb){ return _this.funcs.conflicts_getPages(branch_data, page_cb); } ], callback); }, onConflicts: function(branch_items, branch_data, callback){ return _this.funcs.conflicts_page(branch_items, branch_data, callback); }, }, validate: { error_columns: ['page_id','page_key','page_title','page_path','page_template_id','page_template_path','page_filename'], onBeforeValidate: function(item_errors, branchData, callback){ _this.funcs.webRequestGate(branchData.deployment.deployment_target_publish_path, branchData.publish_params, function(addWebRequest, performWebRequests, gate, downloadTemplates){ var onRemoteTemplatesReady = []; var remoteTemplatesTrigger = new Helper.triggerCounter(2); async.parallel([ //CMS Deployment Host Request function(cb){ remoteTemplatesTrigger.action = (function(){ performWebRequests(function(err){ if(err) return cb(err); Helper.trigger(onRemoteTemplatesReady); gate.close(); return cb(); }); }); }, //Page Templates function(page_cb){ async.waterfall([ //Get page templates function(page_template_cb){ _this.funcs.getPageTemplates('deployment', branchData.site_id, { }, function(err, pageTemplates){ if(err) return page_template_cb(err); branchData.page_templates = pageTemplates; return page_template_cb(); }); }, //Download local template definitions function(page_template_cb){ _this.funcs.downloadLocalTemplates(branchData, branchData.page_templates, {}, {}, page_template_cb); }, //Download remote template definitions function(page_template_cb){ _this.funcs.downloadRemoteTemplates(branchData, branchData.page_templates, {}, { addWebRequest: addWebRequest }, page_template_cb); }, //Wait for Remote Template Milestone function(page_template_cb){ onRemoteTemplatesReady.push(page_template_cb); remoteTemplatesTrigger.increment(); }, ], page_cb); }, //Component Templates function(cb){ async.waterfall([ //Get component templates function(component_template_cb){ _this.funcs.getComponentTemplates('deployment', branchData.site_id, {}, function(err, componentTemplates){ if(err) return component_template_cb(err); branchData.component_templates = componentTemplates; return component_template_cb(); }); }, //Download local template definitions function(component_template_cb){ _this.funcs.downloadLocalTemplates(branchData, branchData.component_templates, {}, { templateType: 'COMPONENT', exportTemplates: {} }, component_template_cb); }, //Download remote template definitions function(component_template_cb){ _this.funcs.downloadRemoteTemplates(branchData, branchData.component_templates, {}, { templateType: 'COMPONENT', exportTemplates: {}, addWebRequest: addWebRequest }, component_template_cb); }, //Wait for Remote Template Milestone function(component_template_cb){ onRemoteTemplatesReady.push(component_template_cb); remoteTemplatesTrigger.increment(); }, ], cb); }, //Get all branch pages from the database function(page_cb){ return _this.funcs.validate_getPages(item_errors, branchData, page_cb); } ], callback); }); }, onValidate: function(item_errors, branchData, callback){ return _this.funcs.validate_page(item_errors, branchData, callback); }, }, deploy: { onBeforeDeploy: function(jsh, branchData, publish_params, callback){ _this.funcs.webRequestGate(branchData.deployment.deployment_target_publish_path, publish_params, function(addWebRequest, performWebRequests, gate, downloadTemplates){ var onRemoteTemplatesReady = []; var onPublishTemplatesReady = []; var remoteTemplatesTrigger = new Helper.triggerCounter(2); var publishTemplatesTrigger = new Helper.triggerCounter(2); async.parallel([ //CMS Deployment Host Request function(cb){ remoteTemplatesTrigger.action = (function(){ performWebRequests(function(err){ if(err) return cb(err); gate.close(); Helper.trigger(onRemoteTemplatesReady); }); }); publishTemplatesTrigger.action = (function(){ performWebRequests(function(err){ if(err) return cb(err); gate.close(); Helper.trigger(onPublishTemplatesReady); return cb(); }); }); }, //Page Templates function(cb){ async.waterfall([ //Get page templates function(page_template_cb){ _this.funcs.getPageTemplates('deployment', branchData.site_id, {}, function(err, pageTemplates){ if(err) return page_template_cb(err); branchData.page_templates = pageTemplates; return page_template_cb(); }); }, //Download local template definitions function(page_template_cb){ _this.funcs.downloadLocalTemplates(branchData, branchData.page_templates, branchData.page_template_html, {}, page_template_cb); }, //Download remote template definitions function(page_template_cb){ _this.funcs.downloadRemoteTemplates(branchData, branchData.page_templates, branchData.page_template_html, { addWebRequest: addWebRequest }, page_template_cb); }, //Wait for Remote Template Milestone function(page_template_cb){ onRemoteTemplatesReady.push(page_template_cb); remoteTemplatesTrigger.increment(); }, //Download remote template content function(page_template_cb){ _this.funcs.downloadPublishTemplates(branchData, branchData.page_templates, branchData.page_template_html, { addWebRequest: addWebRequest }, page_template_cb); }, //Wait for Publish Template Milestone function(page_template_cb){ onPublishTemplatesReady.push(page_template_cb); publishTemplatesTrigger.increment(); }, ], cb); }, //Component Templates function(cb){ async.waterfall([ //Get component templates function(component_template_cb){ _this.funcs.getComponentTemplates('deployment', branchData.site_id, { withContent: true, includeLocalPath: true }, function(err, componentTemplates){ if(err) return component_template_cb(err); branchData.component_templates = componentTemplates; return component_template_cb(); }); }, //Download local template definitions function(component_template_cb){ _this.funcs.downloadLocalTemplates(branchData, branchData.component_templates, branchData.component_template_html, { templateType: 'COMPONENT', exportTemplates: branchData.component_export_template_html }, component_template_cb); }, //Download remote template definitions function(component_template_cb){ _this.funcs.downloadRemoteTemplates(branchData, branchData.component_templates, branchData.component_template_html, { templateType: 'COMPONENT', exportTemplates: branchData.component_export_template_html, addWebRequest: addWebRequest }, component_template_cb); }, //Wait for Remote Template Milestone function(component_template_cb){ onRemoteTemplatesReady.push(component_template_cb); remoteTemplatesTrigger.increment(); }, //Download remote template content function(component_template_cb){ _this.funcs.downloadPublishTemplates(branchData, branchData.component_templates, branchData.component_template_html, { templateType: 'COMPONENT', exportTemplates: branchData.component_export_template_html, addWebRequest: addWebRequest }, component_template_cb); }, //Wait for Publish Template Milestone function(component_template_cb){ onPublishTemplatesReady.push(component_template_cb); publishTemplatesTrigger.increment(); }, //Generate templates function(component_template_cb){ _this.funcs.generateComponentDeploymentTemplates(branchData, component_template_cb); }, ], cb); }, //Get all branch pages from the database function(cb){ _this.funcs.deploy_getPages(jsh, branchData, publish_params, cb); }, ], callback); }); }, onDeploy: function(jsh, branchData, publish_params, callback){ return _this.funcs.deploy_page(jsh, branchData, publish_params, callback); }, onDeploy_PostBuild: function(jsh, branchData, publish_params, callback){ async.waterfall([ function(build_cb){ _this.funcs.deploy_exportComponents(jsh, branchData, publish_params, build_cb); }, function(build_cb){ _this.funcs.deploy_pageIncludes(jsh, branchData, publish_params, build_cb); }, ], callback); }, }, download: { columns: ['page_path','page_is_folder','page_title','page_tags','page_file_id','page_template_id','page_template_path','page_seo_title','page_seo_canonical_url','page_seo_metadesc','page_seo_keywords','page_lang'], onGenerate: function(branch_items, branch_data, callback){ _.each(branch_items, function(branch_item){ if(branch_item.page_file_id) branch_data.data_files.push('page/'+branch_item.page_file_id+'.json'); }); return callback(); }, }, upload: { onValidate: function(errors, branchItems, branchData, callback){ return _this.funcs.branch_upload_validatePage(errors, branchItems, branchData, callback); }, onImportDB: function(dbtasks, branchItems, branchData, callback){ return _this.funcs.branch_upload_importPage(dbtasks, branchItems, branchData, callback); }, }, search: { columns: ['page_path','page_is_folder','page_title','page_tags','page_file_id','page_template_id','page_template_path','page_filename','page_seo_title','page_seo_canonical_url','page_seo_metadesc','page_seo_keywords'], onBeforeSearch: function(searchItems, searchData, callback){ _this.funcs.search_getPages(searchItems, searchData, callback); }, onSearch: function(searchItems, searchData, callback){ return _this.funcs.search_pages(searchItems, searchData, callback); }, }, }, 'media': { name: 'media', caption: ['','Media','Media'], getEditorUrl: function(req){ return req.baseurl+_this.namespace+'Media_Tree'; }, tbl_branch_item: (_this.schema?_this.schema+'.':'')+'branch_media', tbl_item: (_this.schema?_this.schema+'.':'')+'media', diff: { columns: ['media_path','media_file_id','media_ext','media_width','media_height','media_desc','media_tags','media_type'], sqlwhere: '(old_{item}.{item}_is_folder=0 or new_{item}.{item}_is_folder=0)', onBeforeDiff: function(branch_data, callback){ return _this.funcs.diff_getMedia(branch_data, callback); }, onDiff: function(branch_items, branch_data, callback){ return _this.funcs.diff_media(branch_items, branch_data, callback); }, field_mapping: { media: { 'media_desc': 'Media Desc', 'media_tags': 'Media Tags', 'media_type': 'Media Type', } } }, conflicts: { columns: ['media_path','media_file_id','media_ext','media_width','media_height','media_desc','media_tags','media_type'], unique: 'dst_media.media_path = src_media.media_path', sqlwhere: '(src_orig_{item}.{item}_is_folder=0 or dst_orig_{item}.{item}_is_folder=0 or src_{item}.{item}_is_folder=0 or dst_{item}.{item}_is_folder=0)', onBeforeConflicts: function(branch_data, callback){ return _this.funcs.conflicts_getMedia(branch_data, callback); }, onConflicts: function(branch_items, branch_data, callback){ return _this.funcs.conflicts_media(branch_items, branch_data, callback); }, }, validate: { error_columns: ['media_id','media_key','media_desc','media_path','media_ext','media_width','media_height'], onBeforeValidate: function(item_errors, branchData, callback){ return _this.funcs.validate_getMedia(item_errors, branchData, callback); }, onValidate: function(item_errors, branchData, callback){ return _this.funcs.validate_media(item_errors, branchData, callback); }, }, deploy: { onBeforeDeploy: function(jsh, branchData, publish_params, callback){ _this.funcs.deploy_getMedia(jsh, branchData, publish_params, callback); }, onDeploy: function(jsh, branchData, publish_params, callback){ return _this.funcs.deploy_media(jsh, branchData, publish_params, callback); }, }, download: { columns: ['media_path','media_is_folder','media_file_id','media_ext','media_size','media_width','media_height','media_desc','media_tags','media_type','media_lang'], onGenerate: function(branch_items, branch_data, callback){ _.each(branch_items, function(branch_item){ if(branch_item.media_file_id) branch_data.data_files.push('media/'+branch_item.media_file_id.toString()+branch_item.media_ext); }); return callback(); }, }, upload: { onValidate: function(errors, branchItems, branchData, callback){ return _this.funcs.branch_upload_validateMedia(errors, branchItems, branchData, callback); }, onImportDB: function(dbtasks, branchItems, branchData, callback){ return _this.funcs.branch_upload_importMedia(dbtasks, branchItems, branchData, callback); }, }, search: { columns: ['media_path','media_is_folder','media_file_id','media_ext','media_width','media_height','media_desc','media_tags','media_type'], onBeforeSearch: function(searchItems, searchData, callback){ _this.funcs.search_getMedia(searchItems, searchData, callback); }, onSearch: function(searchItems, searchData, callback){ return _this.funcs.search_media(searchItems, searchData, callback); }, }, }, 'menu': { name: 'menu', caption: ['','Menu','Menus'], getEditorUrl: function(req){ return req.baseurl+_this.namespace+'Menu_Listing'; }, tbl_branch_item: (_this.schema?_this.schema+'.':'')+'branch_menu', tbl_item: (_this.schema?_this.schema+'.':'')+'menu', diff: { columns: ['menu_name','menu_tag','menu_file_id'], onDiff: function(branch_items, branch_data, callback){ return _this.funcs.diff_menu(branch_items, branch_data, callback); }, field_mapping: { menu: { 'menu_name': 'Menu Name', 'menu_items': 'Menu Items' } } }, conflicts: { columns: ['menu_name','menu_tag','menu_file_id'], unique: 'dst_menu.menu_tag = src_menu.menu_tag', onConflicts: function(branch_items, branch_data, callback){ return _this.funcs.conflicts_menu(branch_items, branch_data, callback); }, }, validate: { error_columns: ['menu_id','menu_key','menu_name','menu_tag'], onValidate: function(item_errors, branchData, callback){ return _this.funcs.validate_menu(item_errors, branchData, callback); }, }, deploy: { onDeploy: function(jsh, branchData, publish_params, callback){ return _this.funcs.deploy_menu(jsh, branchData, publish_params, callback); }, onDeploy_seq: -100, }, download: { columns: ['menu_name','menu_tag','menu_file_id','menu_notes','menu_lang'], onGenerate: function(branch_items, branch_data, callback){ _.each(branch_items, function(branch_item){ if(branch_item.menu_file_id) branch_data.data_files.push('menu/'+branch_item.menu_file_id+'.json'); }); return callback(); }, }, upload: { onImportDB: function(dbtasks, branchItems, branchData, callback){ return _this.funcs.branch_upload_importMenu(dbtasks, branchItems, branchData, callback); }, }, }, 'redirect': { name: 'redirect', caption: ['','Redirect','Redirects'], getEditorUrl: function(req){ return req.baseurl+_this.namespace+'Redirect_Listing'; }, tbl_branch_item: (_this.schema?_this.schema+'.':'')+'branch_redirect', tbl_item: (_this.schema?_this.schema+'.':'')+'redirect', diff: { columns: ['redirect_url','redirect_dest'] }, conflicts: { columns: ['redirect_url','redirect_dest'] }, deploy: { onDeploy: function(jsh, branchData, publish_params, callback){ return _this.funcs.deploy_redirect(jsh, branchData, publish_params, callback); }, }, download: { columns: ['redirect_url','redirect_url_type','redirect_seq','redirect_dest','redirect_http_code'] }, upload: { onValidate: function(errors, branchItems, branchData, callback){ return _this.funcs.branch_upload_validateRedirect(errors, branchItems, branchData, callback); }, onImportDB: function(dbtasks, branchItems, branchData, callback){ return _this.funcs.branch_upload_importRedirect(dbtasks, branchItems, branchData, callback); }, }, }, 'sitemap': { name: 'sitemap', caption: ['','Sitemap','Sitemaps'], getEditorUrl: function(req){ return req.baseurl+_this.namespace+'Sitemap_Listing'; }, tbl_branch_item: (_this.schema?_this.schema+'.':'')+'branch_sitemap', tbl_item: (_this.schema?_this.schema+'.':'')+'sitemap', diff: { columns: ['sitemap_name','sitemap_type','sitemap_file_id'], onDiff: function(branch_items, branch_data, callback){ return _this.funcs.diff_sitemap(branch_items, branch_data, callback); }, field_mapping: { sitemap: { 'sitemap_name': 'Sitemap Name', 'sitemap_items': 'Sitemap Items' } }, }, conflicts: { columns: ['sitemap_name','sitemap_type','sitemap_file_id'], unique: 'dst_sitemap.sitemap_type = src_sitemap.sitemap_type', onConflicts: function(branch_items, branch_data, callback){ return _this.funcs.conflicts_sitemap(branch_items, branch_data, callback); }, }, validate: { error_columns: ['sitemap_id','sitemap_key','sitemap_name','sitemap_type'], onValidate: function(item_errors, branchData, callback){ return _this.funcs.validate_sitemap(item_errors, branchData, callback); }, }, deploy: { onBeforeDeploy: function(jsh, branchData, publish_params, callback){ return _this.funcs.deploy_getSitemaps(jsh, branchData, publish_params, callback); }, }, download: { columns: ['sitemap_name','sitemap_type','sitemap_file_id','sitemap_notes','sitemap_lang'], onGenerate: function(branch_items, branch_data, callback){ _.each(branch_items, function(branch_item){ if(branch_item.sitemap_file_id) branch_data.data_files.push('sitemap/'+branch_item.sitemap_file_id+'.json'); }); return callback(); }, }, upload: { onValidate: function(errors, branchItems, branchData, callback){ return _this.funcs.branch_upload_validateSitemap(errors, branchItems, branchData, callback); }, onImportDB: function(dbtasks, branchItems, branchData, callback){ return _this.funcs.branch_upload_importSitemap(dbtasks, branchItems, branchData, callback); }, }, }, }; }; jsHarmonyCMS.prototype.applyBranchItemSQL = function(item_type, sql){ var _this = this; if(!(item_type in _this.BranchItems)) throw new Error('Branch item_type not defined: ' + item_type); var branchItem = _this.BranchItems[item_type]; var keys = { item: branchItem.name, tbl_branch_item: branchItem.tbl_branch_item, tbl_item: branchItem.tbl_item, }; for(var key in keys){ sql = Helper.ReplaceAll(sql, '{' + key + '}', keys[key]); } return sql; }; jsHarmonyCMS.prototype.getFactoryConfig = function(){ var _this = this; var jsh = _this.jsh; var configFactory = jsh.Modules['jsHarmonyFactory'].Config; jsh.Modules['jsHarmonyFactory'].onCreateServer.push(function(server){ server.app.use('/js/jsHarmonyCMS.js', function(req, res){ if(_this.Config.debug_params.no_cache_client_js) _this.LoadClientJS(); req.jshsite = jsh.Sites['main']; var baseurl = req.jshsite.baseurl||''; if(baseurl.indexOf('//')<0) baseurl = req.protocol + '://' + req.get('host') + baseurl; var cookie_suffix = Helper.GetCookieSuffix(req, jsh); var jsEJS = ejs.render(jsh.Cache['js/jsHarmonyCMS.js.ejs'], { jsh: jsh, req: req, baseurl: baseurl, cookie_suffix: cookie_suffix, _: _, Helper: Helper }); return res.end(jsh.Cache['js/jsHarmonyCMS.js'] + '\n' + jsEJS + '\n' + jsh.Cache['js/jsHarmonyCMS.local.js']); }); server.app.use(jsHarmonyRouter.PublicRoot(path.join(__dirname, 'public'))); }); /********************** *** TASK SCHEDULER *** **********************/ configFactory.scheduled_tasks['deploy'] = { action: configFactory.Helper.JobProc.ExecuteSQL('jsHarmonyCMS_GetNextDeployment', function(rslt){ if(_this.funcs.deploymentQueue.length()) return; _.each(rslt && rslt.length && rslt[0], function(deployment){ _this.funcs.deploy.call(_this.jsh.AppSrv, deployment.deployment_id); }); }), options: { quiet: true }, when: function (curdt, lastdt) { //return true if the job should run //return false; //Debug - Disable deployment if(!_this.DeploymentJobReady) return false; if(_this.DeploymentJobPending){ _this.DeploymentJobPending = false; return true; } return (curdt.getTime() - lastdt.getTime() > _this.Config.deploymentJobDelay); } }; return { instance: 'jshInstance_CMS', cookie_samesite: 'none', globalparams: {}, title: 'Content Management System', public_apps: [ { '*': express.static(path.join(_this.basepath, 'public')) }, { '/jsharmony.css': function (req, res) { //Concatenate jsh css with system css _this.jsh.getSystemCSS(function(systemCSS){ HelperFS.outputContent(req, res, ejs.render(systemCSS, { req: req, rootcss: req.jshsite.rootcss, _: _ }),'text/css'); }); }, '/js/jsharmony-cms.js': function (req, res) { return Helper.Redirect302(res, '/js/jsHarmonyCMS.js'); }, } ], private_apps: [ { '/_funcs/page/:page_key': _this.funcs.page, '/_funcs/pageDev': _this.funcs.pageDev, '/_funcs/search': _this.funcs.search, '/_funcs/templates/components': _this.funcs.templates_components, '/_funcs/templates/components/:branch_id': _this.funcs.templates_components, '/_funcs/templates/compile_components': _this.funcs.templates_compile_components, '/_funcs/editor_url': _this.funcs.getPageEditorUrl, '/_funcs/site_config': _this.funcs.getPageSiteConfig, '/_funcs/media/:media_key/:thumbnail': _this.funcs.media, '/_funcs/media/:media_key/': _this.funcs.media, '/_funcs/media/': _this.funcs.media, '/_funcs/menu/:menu_key/': _this.funcs.menu, '/_funcs/sitemap/:sitemap_key/': _this.funcs.sitemap, '/_funcs/deployment_unique_tag': _this.funcs.deployment_unique_tag, '/_funcs/deployment_log/:deployment_id': _this.funcs.deployment_log, '/_funcs/deployment_host/:deployment_id/log': _this.funcs.deployment_host_log, '/_funcs/deployment_host/:deployment_id/download': _this.funcs.req_deployment_host_download, '/_funcs/deployment_host/:request_id/response': _this.funcs.deployment_host_response, '/_funcs/deployment_change_log/:deployment_id': _this.funcs.deployment_change_log, '/_funcs/deployment/download/:deployment_id': _this.funcs.req_deployment_download, '/_funcs/deployment/trigger': _this.funcs.req_deployment_trigger, '/_funcs/deployment_target/:deployment_target_id/public_key': _this.funcs.req_deployment_target_public_key, '/_funcs/deployment_target/:deployment_target_id/private_key': _this.funcs.req_deployment_target_private_key, '/_funcs/deployment_target/:deployment_target_id/regenerate_access_key': _this.funcs.req_deployment_target_regenerate_access_key, '/_funcs/deployment_target/parse_url': _this.funcs.req_parse_deployment_target_url, '/_funcs/deployment_target/defaults': _this.funcs.req_deployment_target_defaults, '/_funcs/diff': _this.funcs.diff, '/_funcs/validate': _this.funcs.validate_req, '/_funcs/conflicts': _this.funcs.req_conflicts, '/_funcs/conflicts/resolve': _this.funcs.req_conflicts_resolve, '/_funcs/merge/:merge_type': _this.funcs.req_merge, '/_funcs/begin_merge': _this.funcs.req_begin_merge, '/_funcs/branch/download/:branch_id': _this.funcs.branch_download, '/_funcs/branch/upload': _this.funcs.branch_upload, '/_funcs/site/checkout': _this.funcs.site_checkout, } ] }; }; module.exports = exports = jsHarmonyCMS;