diff --git a/README.md b/README.md index 1c1467f..b647d90 100644 --- a/README.md +++ b/README.md @@ -310,6 +310,16 @@ To see all the types you can choose from, run `strapi content-types:list`. > `required:` NO | `type:` array +### Exclude invalid relations relational objects +This setting allow you to exclude invalid entries when the pattern is not valid for the entry + +Example : You have added a `slug` property to the configuration entry `allowedFields`. +If a content doesn't have the field `slug` filled, no entry in the sitemap will be generated for this content (to avoid duplicate content) + +###### Key: `discardInvalidRelations ` + +> `required:` NO | `type:` boolean + ## 🤝 Contributing Feel free to fork and make a pull request of this plugin. All the input is welcome! diff --git a/server/config.js b/server/config.js index 8ac019b..07c6445 100644 --- a/server/config.js +++ b/server/config.js @@ -8,6 +8,7 @@ module.exports = { autoGenerate: false, caching: true, allowedFields: ['id', 'uid'], + discardInvalidRelations: false, excludedTypes: [ 'admin::permission', 'admin::role', diff --git a/server/services/__tests__/pattern.test.js b/server/services/__tests__/pattern.test.js index e277e06..f051372 100644 --- a/server/services/__tests__/pattern.test.js +++ b/server/services/__tests__/pattern.test.js @@ -3,6 +3,18 @@ const patternService = require('../pattern'); +const get = function baseGet(object, path) { + let index = 0; + path = path.split('.'); + const length = path.length; + + while (object != null && index < length) { + const newKey = path[index++]; + object = object[newKey]; + } + return (index && index === length) ? object : undefined; +}; + global.strapi = { contentTypes: { 'another-test-relation:target:api': { @@ -28,6 +40,14 @@ global.strapi = { }, }, }, + config: { + plugin: { + sitemap: { + discardInvalidRelations: false + } + }, + get: ((key) => get(global.strapi.config, key)), + } }; describe('Pattern service', () => { @@ -151,6 +171,22 @@ describe('Pattern service', () => { expect(result).toMatch('/en/my-page-slug'); }); + + test('Resolve pattern with missing relation', async () => { + const pattern = '/en/[slug]'; + const entity = { + title: "my-page-title", + }; + + global.strapi.config.plugin.sitemap.discardInvalidRelations = true; + + const result = await patternService().resolvePattern(pattern, entity); + + expect(result).toBe(null); + + // Restore + global.strapi.config.plugin.sitemap.discardInvalidRelations = false; + }); }); describe('Validate pattern', () => { test('Should return { valid: true } for a valid pattern', async () => { diff --git a/server/services/core.js b/server/services/core.js index 7e8450b..b3e5565 100644 --- a/server/services/core.js +++ b/server/services/core.js @@ -67,6 +67,7 @@ const getLanguageLinks = async (config, page, contentType, defaultURL) => { const { pattern } = config.contentTypes[contentType]['languages'][locale]; const translationUrl = await strapi.plugins.sitemap.services.pattern.resolvePattern(pattern, translation); + if (!translationUrl) return null; let hostnameOverride = config.hostname_overrides[translation.locale] || ''; hostnameOverride = hostnameOverride.replace(/\/+$/, ''); links.push({ @@ -112,6 +113,7 @@ const getSitemapPageData = async (config, page, contentType) => { const { pattern } = config.contentTypes[contentType]['languages'][locale]; const path = await strapi.plugins.sitemap.services.pattern.resolvePattern(pattern, page); + if (!path) return null; let hostnameOverride = config.hostname_overrides[page.locale] || ''; hostnameOverride = hostnameOverride.replace(/\/+$/, ''); const url = `${hostnameOverride}${path}`; diff --git a/server/services/pattern.js b/server/services/pattern.js index bf658b0..58aa427 100644 --- a/server/services/pattern.js +++ b/server/services/pattern.js @@ -124,19 +124,33 @@ const getRelationsFromPattern = (pattern) => { const resolvePattern = async (pattern, entity) => { const fields = getFieldsFromPattern(pattern); + let errorInPattern = false; fields.map((field) => { const relationalField = field.split('.').length > 1 ? field.split('.') : null; if (!relationalField) { - pattern = pattern.replace(`[${field}]`, entity[field] || ''); + const replacement = entity[field] || ''; + if (strapi.config.get('plugin.sitemap.discardInvalidRelations') && !replacement) { + errorInPattern = true; + return; + } + pattern = pattern.replace(`[${field}]`, replacement); } else if (Array.isArray(entity[relationalField[0]])) { strapi.log.error(logMessage('Something went wrong whilst resolving the pattern.')); } else if (typeof entity[relationalField[0]] === 'object') { - pattern = pattern.replace(`[${field}]`, entity[relationalField[0]] && entity[relationalField[0]][relationalField[1]] ? entity[relationalField[0]][relationalField[1]] : ''); + const replacement = entity[relationalField[0]] && entity[relationalField[0]][relationalField[1]] ? entity[relationalField[0]][relationalField[1]] : ''; + if (strapi.config.get('plugin.sitemap.discardInvalidRelations') && !replacement) { + errorInPattern = true; + return; + } + + pattern = pattern.replace(`[${field}]`, replacement); } }); + if (errorInPattern) return null; // Return null if there was an error in the pattern due to invalid relation and avoid duplicate content + pattern = pattern.replace(/\/+/g, '/'); // Remove duplicate forward slashes. pattern = pattern.startsWith('/') ? pattern : `/${pattern}`; // Make sure we only have on forward slash. return pattern;