payload
Version:
Node, React, Headless CMS and Application Framework built on Next.js
244 lines (243 loc) • 11.5 kB
JavaScript
// @ts-strict-ignore
import { deepMergeSimple } from '@payloadcms/translations/utilities';
import { v4 as uuid } from 'uuid';
import { DuplicateFieldName, InvalidFieldName, InvalidFieldRelationship, MissingEditorProp, MissingFieldType } from '../../errors/index.js';
import { formatLabels, toWords } from '../../utilities/formatLabels.js';
import { baseBlockFields } from '../baseFields/baseBlockFields.js';
import { baseIDField } from '../baseFields/baseIDField.js';
import { baseTimezoneField } from '../baseFields/timezone/baseField.js';
import { defaultTimezones } from '../baseFields/timezone/defaultTimezones.js';
import { setDefaultBeforeDuplicate } from '../setDefaultBeforeDuplicate.js';
import { validations } from '../validations.js';
import { sanitizeJoinField } from './sanitizeJoinField.js';
import { fieldAffectsData, fieldIsLocalized, tabHasName } from './types.js';
export const sanitizeFields = async ({ config, existingFieldNames = new Set(), fields, joinPath = '', joins, parentIsLocalized, polymorphicJoins, requireFieldLevelRichTextEditor = false, richTextSanitizationPromises, validRelationships })=>{
if (!fields) {
return [];
}
for(let i = 0; i < fields.length; i++){
const field = fields[i];
if ('_sanitized' in field && field._sanitized === true) {
continue;
}
if ('_sanitized' in field) {
field._sanitized = true;
}
if (!field.type) {
throw new MissingFieldType(field);
}
// assert that field names do not contain forbidden characters
if (fieldAffectsData(field) && field.name.includes('.')) {
throw new InvalidFieldName(field, field.name);
}
// Auto-label
if ('name' in field && field.name && typeof field.label !== 'object' && typeof field.label !== 'string' && typeof field.label !== 'function' && field.label !== false) {
field.label = toWords(field.name);
}
if (field.type === 'checkbox' && typeof field.defaultValue === 'undefined' && field.required === true) {
field.defaultValue = false;
}
if (field.type === 'join') {
sanitizeJoinField({
config,
field,
joinPath,
joins,
parentIsLocalized,
polymorphicJoins
});
}
if (field.type === 'relationship' || field.type === 'upload') {
if (validRelationships) {
const relationships = Array.isArray(field.relationTo) ? field.relationTo : [
field.relationTo
];
relationships.forEach((relationship)=>{
if (!validRelationships.includes(relationship)) {
throw new InvalidFieldRelationship(field, relationship);
}
});
}
if (field.min && !field.minRows) {
console.warn(`(payload): The "min" property is deprecated for the Relationship field "${field.name}" and will be removed in a future version. Please use "minRows" instead.`);
field.minRows = field.min;
}
if (field.max && !field.maxRows) {
console.warn(`(payload): The "max" property is deprecated for the Relationship field "${field.name}" and will be removed in a future version. Please use "maxRows" instead.`);
field.maxRows = field.max;
}
}
if (field.type === 'upload') {
if (!field.admin || !('isSortable' in field.admin)) {
field.admin = {
isSortable: true,
...field.admin
};
}
}
if (field.type === 'array' && field.fields) {
field.fields.push(baseIDField);
}
if ((field.type === 'blocks' || field.type === 'array') && field.label) {
field.labels = field.labels || formatLabels(field.name);
}
if (fieldAffectsData(field)) {
if (existingFieldNames.has(field.name)) {
throw new DuplicateFieldName(field.name);
} else if (![
'blockName',
'id'
].includes(field.name)) {
existingFieldNames.add(field.name);
}
if (typeof field.localized !== 'undefined') {
let shouldDisableLocalized = !config.localization;
if (process.env.NEXT_PUBLIC_PAYLOAD_COMPATIBILITY_allowLocalizedWithinLocalized !== 'true' && parentIsLocalized && // @todo PAYLOAD_DO_NOT_SANITIZE_LOCALIZED_PROPERTY=true will be the default in 4.0
process.env.PAYLOAD_DO_NOT_SANITIZE_LOCALIZED_PROPERTY !== 'true') {
shouldDisableLocalized = true;
}
if (shouldDisableLocalized) {
delete field.localized;
}
}
if (typeof field.validate === 'undefined') {
const defaultValidate = validations[field.type];
if (defaultValidate) {
field.validate = (val, options)=>defaultValidate(val, {
...field,
...options
});
} else {
field.validate = ()=>true;
}
}
if (!field.hooks) {
field.hooks = {};
}
if (!field.access) {
field.access = {};
}
setDefaultBeforeDuplicate(field, parentIsLocalized);
}
if (!field.admin) {
field.admin = {};
}
// Make sure that the richText field has an editor
if (field.type === 'richText') {
const sanitizeRichText = async (_config)=>{
if (!field.editor) {
if (_config.editor && !requireFieldLevelRichTextEditor) {
// config.editor should be sanitized at this point
field.editor = _config.editor;
} else {
throw new MissingEditorProp(field) // while we allow disabling editor functionality, you should not have any richText fields defined if you do not have an editor
;
}
}
if (typeof field.editor === 'function') {
field.editor = await field.editor({
config: _config,
isRoot: requireFieldLevelRichTextEditor,
parentIsLocalized: parentIsLocalized || field.localized
});
}
if (field.editor.i18n && Object.keys(field.editor.i18n).length >= 0) {
config.i18n.translations = deepMergeSimple(config.i18n.translations, field.editor.i18n);
}
};
if (richTextSanitizationPromises) {
richTextSanitizationPromises.push(sanitizeRichText);
} else {
await sanitizeRichText(config);
}
}
if (field.type === 'blocks' && field.blocks) {
if (field.blockReferences && field.blocks?.length) {
throw new Error('You cannot have both blockReferences and blocks in the same blocks field');
}
for (const block of field.blockReferences ?? field.blocks){
if (typeof block === 'string') {
continue;
}
if (block._sanitized === true) {
continue;
}
block._sanitized = true;
block.fields = block.fields.concat(baseBlockFields);
block.labels = !block.labels ? formatLabels(block.slug) : block.labels;
block.fields = await sanitizeFields({
config,
existingFieldNames: new Set(),
fields: block.fields,
parentIsLocalized: parentIsLocalized || field.localized,
requireFieldLevelRichTextEditor,
richTextSanitizationPromises,
validRelationships
});
}
}
if ('fields' in field && field.fields) {
field.fields = await sanitizeFields({
config,
existingFieldNames: fieldAffectsData(field) ? new Set() : existingFieldNames,
fields: field.fields,
joinPath: fieldAffectsData(field) ? `${joinPath ? joinPath + '.' : ''}${field.name}` : joinPath,
joins,
parentIsLocalized: parentIsLocalized || fieldIsLocalized(field),
polymorphicJoins,
requireFieldLevelRichTextEditor,
richTextSanitizationPromises,
validRelationships
});
}
if (field.type === 'tabs') {
for(let j = 0; j < field.tabs.length; j++){
const tab = field.tabs[j];
if (tabHasName(tab) && typeof tab.label === 'undefined') {
tab.label = toWords(tab.name);
}
if ('admin' in tab && tab.admin?.condition && typeof tab.admin.condition === 'function' && !tab.id) {
// Always attach a UUID to tabs with a condition so there's no conflicts even if there are duplicate nested names
tab.id = tabHasName(tab) ? `${tab.name}_${uuid()}` : uuid();
}
tab.fields = await sanitizeFields({
config,
existingFieldNames: tabHasName(tab) ? new Set() : existingFieldNames,
fields: tab.fields,
joinPath: tabHasName(tab) ? `${joinPath ? joinPath + '.' : ''}${tab.name}` : joinPath,
joins,
parentIsLocalized: parentIsLocalized || tabHasName(tab) && tab.localized,
polymorphicJoins,
requireFieldLevelRichTextEditor,
richTextSanitizationPromises,
validRelationships
});
field.tabs[j] = tab;
}
}
if (field.type === 'ui' && typeof field.admin.disableBulkEdit === 'undefined') {
field.admin.disableBulkEdit = true;
}
fields[i] = field;
// Insert our field after assignment
if (field.type === 'date' && field.timezone) {
const name = field.name + '_tz';
const defaultTimezone = config.admin.timezones.defaultTimezone;
const supportedTimezones = config.admin.timezones.supportedTimezones;
const options = typeof supportedTimezones === 'function' ? supportedTimezones({
defaultTimezones
}) : supportedTimezones;
// Need to set the options here manually so that any database enums are generated correctly
// The UI component will import the options from the config
const timezoneField = baseTimezoneField({
name,
defaultValue: defaultTimezone,
options,
required: field.required
});
fields.splice(++i, 0, timezoneField);
}
}
return fields;
};
//# sourceMappingURL=sanitize.js.map