Name
sn_ux_seo_sitemap.SitemapGenerator
Description
Retrieve Sitemap definition Record with sitemapConfigId
Script
var SitemapGenerator = Class.create();
SitemapGenerator.prototype = {
initialize: function() {},
generateSitemap: function(sitemapConfigId) {
var sitemapConfigName = "";
try {
if (sitemapConfigId == '') {
gs.error(gs.getMessage('Missing Sitemap ConfigId.'));
gs.addErrorMessage(gs.getMessage('Missing Sitemap ConfigId.'));
} else {
var sitemapConfig = new GlideRecord('sys_ux_seo_sitemap_config');
if (!sitemapConfig.get(sitemapConfigId)) {
// if sitemap does not exists, log an error
gs.error(gs.getMessage('Invalid Sitemap ConfigId'));
gs.addErrorMessage(gs.getMessage('Invalid Sitemap ConfigId.'));
} else {
// Retrieve active sitemap config definitions only when the sitemap configuration is active
if (!sitemapConfig.active) {
gs.info(gs.getMessage('Sitemap Configuration {0} is not active .', sitemapConfig.name));
gs.addInfoMessage(gs.getMessage('Sitemap Configuration {0} is not active .', sitemapConfig.name));
} else {
sitemapConfigName = sitemapConfig.name;
var sitemapDefinition = new GlideRecord('sys_ux_sitemap_definition');
sitemapDefinition.addQuery('sitemap_config', sitemapConfig.sys_id);
sitemapDefinition.addQuery('active', 'true');
sitemapDefinition.query();
if (sitemapDefinition.hasNext()) {
var content = "";
var isXMLContentValid = null;
var invalidXMLCount = 0;
while (sitemapDefinition.next()) {
// logic for different definition types - table/script/static
if (sitemapDefinition.type == 'static') {
content += sitemapDefinition.xml;
} else if (sitemapDefinition.type == 'script') {
if (gs.nil(sitemapDefinition.script)) {
continue;
}
var contentFromScriptType = this.executeScript(sitemapDefinition);
isXMLContentValid = this.validateXML(contentFromScriptType);
// The object `isXMLContentValid` returns null when the xml string is valid
if (!gs.nil(isXMLContentValid)) {
invalidXMLCount += 1;
gs.info(gs.getMessage('Invalid xml for Script type. Please resolve the errors: {0}', isXMLContentValid));
continue;
}
content += this.executeScript(sitemapDefinition);
} else if (sitemapDefinition.type == 'table') {
var contentFromTable = this.buildSitemapXMLContent(sitemapConfig, sitemapDefinition);
isXMLContentValid = this.validateXML(contentFromTable);
if (!gs.nil(isXMLContentValid)) {
invalidXMLCount += 1;
gs.info(gs.getMessage('Invalid xml for Table type. Please resolve the errors: {0}', isXMLContentValid));
continue;
}
content += this.buildSitemapXMLContent(sitemapConfig, sitemapDefinition);
}
}
// if the count is = 0, only then paginate the content
if (invalidXMLCount == 0) {
// performs pagination with set record limit
var fileContent = this.paginate(content);
this.writeXMLAsAttachment(sitemapConfig, fileContent);
} else {
// this function deletes existing attachments, if any
var sysAttachment = new GlideSysAttachment();
this.deleteAttachments(sitemapConfig, sysAttachment);
gs.error(gs.getMessage('There are invalid sitemap definitions. Please resolve the errors.'));
}
} else {
gs.addInfoMessage(gs.getMessage('No active sitemap definitions found for {0}' , sitemapConfig.name));
gs.info(gs.getMessage('No active sitemap definitions found for {0}', sitemapConfig.name));
}
}
}
}
} catch (error) {
gs.error( '{0}: {1} {2}',sitemapConfigName , gs.getMessage('Unable to generate sitemap --- ') , error);
}
},
getUrlParams: function(urlString) {
var urlParts = urlString.split('?');
var urlPreQP = urlParts[0];
var urlQP = urlParts[1];
var queryParams = [];
urlQP.split('&').forEach(function(splitStr) {
pair = splitStr.split('=');
queryParams.push({
key: pair[0],
value: pair[1]
});
});
return {
'urlPreQP': urlPreQP,
'queryParams': queryParams
};
},
appendContentWithHeaderAndFooter: function(content) {
var sitemapXMLHeader =
'<?xml version="1.0" encoding="UTF-8"?>\n' +
'<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n' +
' xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"\n' +
' xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n';
var sitemapXMLFooter = '\n</urlset>\n';
return sitemapXMLHeader + content + sitemapXMLFooter;
},
buildSitemapXMLContent: function(sitemapConfig, sitemapDefinition) {
var URL_PARAM_DELIM_DOT = '.';
var URL_PARAM_DELIM_QUESTION = '?';
var URL_PARAM_DELIM_AMPERSAND = '&';
var URL_PARAM_DELIM_EQUAL = '=';
var baseUrl = gs.getProperty("glide.servlet.uri");
if (baseUrl)
baseUrl = baseUrl.slice(0, -1);
if (sitemapConfig && sitemapConfig.url_prefix)
baseUrl = sitemapConfig.url_prefix;
// this function shall handle table type of sitemap content generation
var tableName = sitemapDefinition.table;
var queryCondition = sitemapDefinition.query_condition;
var urlPattern = sitemapDefinition.url_pattern;
var getQP = this.getUrlParams(urlPattern);
var queryParams = getQP.queryParams;
var urlPreQP = getQP.urlPreQP;
var queryColumns = [];
var queryGR = new GlideRecord(tableName);
queryGR.addEncodedQuery(queryCondition);
queryGR.query();
var content = '';
while (queryGR.next()) {
var locationStr = baseUrl + urlPreQP;
var firstParam = true;
queryParams.forEach(function(queryParam) {
if (firstParam)
locationStr += URL_PARAM_DELIM_QUESTION;
else
locationStr += URL_PARAM_DELIM_AMPERSAND;
firstParam = false;
var key = queryParam.key;
var value = queryParam.value;
if (value.startsWith(tableName + URL_PARAM_DELIM_DOT)) {
value = queryGR.getValue(value.substring(value.indexOf(URL_PARAM_DELIM_DOT) + 1));
}
locationStr += key + URL_PARAM_DELIM_EQUAL + value;
});
content += ' <url>\n' +
' <loc>' + locationStr + '</loc>\n' +
' <lastmod>' + queryGR.sys_updated_on.getDisplayValue().substring(0, 10) + '</lastmod>\n' +
' <changefreq>monthly</changefreq>\n' +
' </url>\n';
}
return content;
},
writeXMLAsAttachment: function(sitemapConfig, content) {
// this function persists the sitemap xml content as an attachment with sitemapConfig reference
// this function deletes existing attachments, if any
var sysAttachment = new GlideSysAttachment();
this.deleteAttachments(sitemapConfig, sysAttachment);
// writing content to the attachment glide record
var pages = content.length;
var contentType = 'text/xml';
var fileName = "";
var attachmentGlideRecord = "";
var fileContent = "";
if (pages == 1){
// if there is a single sitemap file , generate a file with name 'sitemap-{sitemapConfigId}'
fileName = 'sitemap-' + sitemapConfig.sys_id + '.xml';
fileContent = this.appendContentWithHeaderAndFooter(content[0]);
attachmentGlideRecord = sysAttachment.write(sitemapConfig, fileName, contentType, fileContent);
} else if (pages > 1){
for (var i = 0 ; i < pages ; i++){
// if there are more than 1 sitemap xml pages , generate a file with name 'sitemap-{pageNo}-{sitemapConfigId}'
fileName = 'sitemap-' + (i+1) + '-' + sitemapConfig.sys_id + '.xml';
fileContent = this.appendContentWithHeaderAndFooter(content[i]);
attachmentGlideRecord = sysAttachment.write(sitemapConfig, fileName, contentType, fileContent);
}
// create index file for the paginated sitemap files with file name - 'sitemap-{sitemapConfigId}'
this.generateIndexFile(sitemapConfig);
}
gs.addInfoMessage(gs.getMessage('Sitemap XML generated successfully.'));
gs.info('{0}: {1}',sitemapConfig.name, gs.getMessage('Sitemap XML generated successfully.'));
},
generateIndexFile : function(sitemapConfig){
var pageNo = "";
var locationStr = "";
var xmlHeader = '<?xml version="1.0" encoding="UTF-8"?>\n' +
'<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n';
var xmlFooter = '</sitemapindex>';
var fileContent = "";
var fileName = "";
var contentType = 'text/xml';
var indexFileName = 'sitemap-' + sitemapConfig.sys_id + '.xml';
var baseUrl = gs.getProperty("glide.servlet.uri");
if (baseUrl)
baseUrl = baseUrl.slice(0, -1);
var sysAttachment = new GlideSysAttachment();
var sitemapAttachments = sysAttachment.getAttachments('sys_ux_seo_sitemap_config', sitemapConfig.sys_id);
while (sitemapAttachments.next()) {
// get the page number from file name, "sitemap-{pageNo}-{sitemapConfigId}"
fileName = sitemapAttachments.getValue('file_name');
pageNo = fileName.split('-')[1];
locationStr = baseUrl + '/sitemap.do?sitemapConfigId=' + sitemapConfig.sys_id + '&pageNo=' + pageNo;
fileContent += ' <sitemap>\n' +
' <loc>' + locationStr + '</loc>\n' +
' <lastmod>' + sitemapAttachments.sys_updated_on.getDisplayValue().substring(0, 10) + '</lastmod>\n' +
' <changefreq>monthly</changefreq>\n' +
' </sitemap>\n';
}
// append header and footer to the xml content
fileContent = xmlHeader + fileContent + xmlFooter;
//write the content to index file attachment
var attachmentGlideRecord = sysAttachment.write(sitemapConfig, indexFileName, contentType, fileContent);
gs.info('{0}: {1}', sitemapConfig.name, gs.getMessage('Sitemap index file generated successfully.'));
},
paginate: function(content) {
// sets the record limit for pagination
var rowCount = 5000;
var fileContent = [];
var xmlContent = "";
var recordCount = 0;
var iterNext;
content = '<urlset>' + content + '</urlset>';
// Use the `XMLDocument2()` JavaScript Object wrapper for parsing and extracting XML data from an XML string.
var xmlParser = new XMLDocument2();
xmlParser.parseXML(content);
var recordList = xmlParser.getNode("urlset");
var iter = recordList.getChildNodeIterator();
// Iterating over the content to count the records and fetch the value
while (iter.hasNext()) {
iterNext = iter.next();
if (iterNext.getNodeName() == 'url') {
recordCount++;
// When the `recordCount` is greater than limit , reset the xmlContent and recordCount to push the content to the new file
if (recordCount > rowCount) {
fileContent.push(xmlContent);
xmlContent = "";
recordCount = 1;
}
xmlContent += iterNext.toString() + '\n';
}
}
// check if there are any url records left. If yes , add them to a new file
if (recordCount > 0 && !gs.nil(xmlContent)){
fileContent.push(xmlContent);
}
return fileContent;
},
validateXML: function(content) {
// validate the generated xml string content using GlideXMLUtil API
return GlideXMLUtil.validateXML(content, false, true);
},
deleteAttachments: function(sitemapConfig, sysAttachment) {
// check if an attachment exists, if yes delete it
var existingAttachments = sysAttachment.getAttachments('sys_ux_seo_sitemap_config', sitemapConfig.sys_id);
while (existingAttachments.next()) {
sysAttachment.deleteAttachment(existingAttachments.sys_id);
}
},
executeScript: function(sitemapDefinition) {
try {
// execute script from sitemap definition 'script' field
var evaluator = new GlideScopedEvaluator();
return evaluator.evaluateScript(sitemapDefinition, 'script', null);
} catch (error) {
gs.error('{0} {1}',gs.getMessage('Unable to execute the script: '), error);
}
},
type: 'SitemapGenerator'
};
Sys ID
2422524d774a0110ff643a91fa5a9955