From 74d01c803b94c84daa9e39525783d4960f609bb7 Mon Sep 17 00:00:00 2001 From: bharakm Date: Sun, 19 Oct 2025 16:28:14 -0500 Subject: [PATCH 1/3] Create Readme.md --- .../UI Actions/Knowledge Link Validator/Readme.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 Client-Side Components/UI Actions/Knowledge Link Validator/Readme.md diff --git a/Client-Side Components/UI Actions/Knowledge Link Validator/Readme.md b/Client-Side Components/UI Actions/Knowledge Link Validator/Readme.md new file mode 100644 index 0000000000..c71181de73 --- /dev/null +++ b/Client-Side Components/UI Actions/Knowledge Link Validator/Readme.md @@ -0,0 +1,8 @@ +This utility script helps ServiceNow administrators and content managers ensure the integrity and usability of hyperlinks embedded within knowledge articles. It scans article content to identify and classify links pointing to catalog items and other knowledge articles, providing detailed insights into: + +Catalog Item Links: Detects and categorizes links as active, inactive, or not found. +Knowledge Article Links: Flags outdated articles based on workflow state and expiration (valid_to). +Non-Permalink KB Links: Identifies knowledge article links that do not follow the recommended permalink format (i.e., missing sysparm_article=KBxxxxxxx), even if they use kb_view.do. +The solution includes a Jelly-based UI that displays categorized results with direct links to the affected records, enabling quick remediation. It's ideal for improving content quality, ensuring consistent user experience, and maintaining best practices in knowledge management. + +image From 0a68c82d4a519655671d906b387d26069c100440 Mon Sep 17 00:00:00 2001 From: bharakm Date: Sun, 19 Oct 2025 16:29:26 -0500 Subject: [PATCH 2/3] Create uiaction.js --- .../UI Actions/Knowledge Link Validator/uiaction.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 Client-Side Components/UI Actions/Knowledge Link Validator/uiaction.js diff --git a/Client-Side Components/UI Actions/Knowledge Link Validator/uiaction.js b/Client-Side Components/UI Actions/Knowledge Link Validator/uiaction.js new file mode 100644 index 0000000000..cd6aa46b55 --- /dev/null +++ b/Client-Side Components/UI Actions/Knowledge Link Validator/uiaction.js @@ -0,0 +1,13 @@ +/* +This script should be placed in the UI action on the table kb_knowledge form view. +This UI action should be marked as client. +Use validateLinksInArticle() function in the Onclick field. +*/ + +function validateLinksInArticle() { + var articleSysId = g_form.getUniqueValue(); + var gdw = new GlideDialogWindow('validate_links_dialog'); + gdw.setTitle('Validate Article Links'); + gdw.setPreference('sysparm_article_id', articleSysId); + gdw.render(); +} From 7c17b6ba1b91538fe215cb0cf73eac45f19513b4 Mon Sep 17 00:00:00 2001 From: bharakm Date: Sun, 19 Oct 2025 16:29:55 -0500 Subject: [PATCH 3/3] Create uipage.js --- .../Knowledge Link Validator/uipage.js | 193 ++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 Client-Side Components/UI Actions/Knowledge Link Validator/uipage.js diff --git a/Client-Side Components/UI Actions/Knowledge Link Validator/uipage.js b/Client-Side Components/UI Actions/Knowledge Link Validator/uipage.js new file mode 100644 index 0000000000..2a8cd9e8c2 --- /dev/null +++ b/Client-Side Components/UI Actions/Knowledge Link Validator/uipage.js @@ -0,0 +1,193 @@ + + + + tags + var regex = /]+href=["']([^"']+)["']/gi; + var urls = []; + var match; + while ((match = regex.exec(content)) !== null) { + + urls.push(match[1]); + } + for (var i = 0; i < urls.length; i++) { + var url = urls[i]; + + // --- 1. Check if link is a Catalog Item --- + var sysId = extractSysId(url, 'sysparm_id') || extractSysId(url, 'sys_id'); + if (sysId) { + var grItem = new GlideRecord('sc_cat_item'); + if (grItem.get(sysId)) { + if (grItem.active){ + activeIds.push(sysId); + activeCount++; + } + else if(grItem.active == false){ + inactiveIds.push(sysId); + inActiveCount++; + } + } else { + notFoundIds.push(sysId); + notFoundCount++; + } + } + // --- 2. Check if link is a Knowledge Article --- + // --- 1. Check for outdated knowledge articles via permalink --- + +// --- 1. Check for outdated knowledge articles via permalink --- +var decodedUrl = decodeURIComponent(url + ''); +decodedUrl = decodedUrl.replace(/&amp;amp;amp;/g, '&'); + +// Extract KB number or sys_id +var kbNumber = extractSysId(decodedUrl, 'sysparm_article'); +var kbSysId = extractSysId(decodedUrl, 'sys_kb_id') || extractSysId(decodedUrl, 'sys_id'); + +var grKb = new GlideRecord('kb_knowledge'); + +if (kbNumber && grKb.get('number', kbNumber)) { + var isOutdated = false; + if (grKb.workflow_state != 'published') { + isOutdated = true; + } else if (grKb.valid_to && grKb.valid_to.getGlideObject()) { + var now = new GlideDateTime(); + if (grKb.valid_to.getGlideObject().compareTo(now) <= 0) { + isOutdated = true; + } + } + + if (isOutdated) { + outdatedArticles.push(grKb.sys_id.toString()); + outdatedCount++; + } +} else if (kbSysId && grKb.get(kbSysId)) { + var isOutdated = false; + if (grKb.workflow_state != 'published') { + isOutdated = true; + } else if (grKb.valid_to && grKb.valid_to.getGlideObject()) { + var now = new GlideDateTime(); + if (grKb.valid_to.getGlideObject().compareTo(now) <= 0) { + isOutdated = true; + } + } + + if (isOutdated) { + outdatedArticles.push(grKb.sys_id.toString()); + outdatedCount++; + } +} + +// --- 2. Check for non-permalink knowledge links --- +if ( + decodedUrl.indexOf('kb_knowledge.do?sys_id=') !== -1 || // form view + ( + decodedUrl.indexOf('/kb_view.do') !== -1 && + decodedUrl.indexOf('sysparm_article=KB') === -1 // missing KB number + ) +) { + var kbSysId = extractSysId(decodedUrl, 'sys_kb_id') || extractSysId(decodedUrl, 'sys_id'); + if (kbSysId) { + var grBadKB = new GlideRecord('kb_knowledge'); + if (grBadKB.get(kbSysId)) { + badPermalinks.push(kbSysId); + badPermalinkCount++; + } + } +} + } + } + } + function extractSysId(url, param) { + try { + var decoded = decodeURIComponent(url + ''); + decoded = decoded + .replace(/&amp;amp;/g, '&') + .replace(/&amp;/g, '&') + .replace(/&/g, '&') + .replace(/=/g, '=') + .replace(/&#61;/g, '='); + + var parts = decoded.split(param + '='); + if (parts.length > 1) { + var id = parts[1].split('&')[0]; + return id && id.length === 32 ? id : null; + } + } catch (e) { + var parts = url.split(param + '='); + if (parts.length > 1) { + var id = parts[1].split('&')[0]; + return id && id.length === 32 ? id : null; + } + } + return null; +} + // Expose variables to Jelly +inactiveQuery = "sys_idIN"+inactiveIds.join(','); +activeQuery = "sys_idIN"+activeIds.join(','); +notFoundQuery = "sys_idIN"+notFoundIds.join(','); +outdatedQuery = "sys_idIN"+outdatedArticles.join(','); +badPermalinkQuery = "sys_idIN"+badPermalinks.join(','); + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ModuleRecordsDetails
+