@wordpress/core-data
Version:
Access to and manipulation of core WordPress entities.
780 lines (779 loc) • 24.4 kB
JavaScript
// packages/core-data/src/resolvers.js
import { camelCase } from "change-case";
import { addQueryArgs } from "@wordpress/url";
import { decodeEntities } from "@wordpress/html-entities";
import apiFetch from "@wordpress/api-fetch";
import { STORE_NAME } from "./name";
import { additionalEntityConfigLoaders, DEFAULT_ENTITY_KEY } from "./entities";
import { getSyncManager } from "./sync";
import {
forwardResolver,
getNormalizedCommaSeparable,
getUserPermissionCacheKey,
getUserPermissionsFromAllowHeader,
ALLOWED_RESOURCE_ACTIONS,
RECEIVE_INTERMEDIATE_RESULTS,
isNumericID
} from "./utils";
import { fetchBlockPatterns } from "./fetch";
var getAuthors = (query) => async ({ dispatch }) => {
const path = addQueryArgs(
"/wp/v2/users/?who=authors&per_page=100",
query
);
const users = await apiFetch({ path });
dispatch.receiveUserQuery(path, users);
};
var getCurrentUser = () => async ({ dispatch }) => {
const currentUser = await apiFetch({ path: "/wp/v2/users/me" });
dispatch.receiveCurrentUser(currentUser);
};
var getEntityRecord = (kind, name, key = "", query) => async ({ select, dispatch, registry, resolveSelect }) => {
const configs = await resolveSelect.getEntitiesConfig(kind);
const entityConfig = configs.find(
(config) => config.name === name && config.kind === kind
);
if (!entityConfig) {
return;
}
const lock = await dispatch.__unstableAcquireStoreLock(
STORE_NAME,
["entities", "records", kind, name, key],
{ exclusive: false }
);
try {
if (query !== void 0 && query._fields) {
query = {
...query,
_fields: [
.../* @__PURE__ */ new Set([
...getNormalizedCommaSeparable(query._fields) || [],
entityConfig.key || DEFAULT_ENTITY_KEY
])
].join()
};
}
if (query !== void 0 && query._fields) {
const hasRecord = select.hasEntityRecord(
kind,
name,
key,
query
);
if (hasRecord) {
return;
}
}
let { baseURL } = entityConfig;
if (kind === "postType" && name === "wp_template" && (key && typeof key === "string" && !/^\d+$/.test(key) || !window?.__experimentalTemplateActivate)) {
baseURL = baseURL.slice(0, baseURL.lastIndexOf("/")) + "/templates";
}
const path = addQueryArgs(baseURL + (key ? "/" + key : ""), {
...entityConfig.baseURLParams,
...query
});
const response = await apiFetch({ path, parse: false });
const record = await response.json();
const permissions = getUserPermissionsFromAllowHeader(
response.headers?.get("allow")
);
const canUserResolutionsArgs = [];
const receiveUserPermissionArgs = {};
for (const action of ALLOWED_RESOURCE_ACTIONS) {
receiveUserPermissionArgs[getUserPermissionCacheKey(action, {
kind,
name,
id: key
})] = permissions[action];
canUserResolutionsArgs.push([
action,
{ kind, name, id: key }
]);
}
if (window.__experimentalEnableSync && entityConfig.syncConfig && isNumericID(key) && !query) {
if (globalThis.IS_GUTENBERG_PLUGIN) {
const objectType = `${kind}/${name}`;
const objectId = key;
const recordWithTransients = { ...record };
Object.entries(entityConfig.transientEdits ?? {}).filter(
([propName, transientConfig]) => void 0 === recordWithTransients[propName] && transientConfig && "object" === typeof transientConfig && "read" in transientConfig && "function" === typeof transientConfig.read
).forEach(([propName, transientConfig]) => {
recordWithTransients[propName] = transientConfig.read(recordWithTransients);
});
await getSyncManager()?.load(
entityConfig.syncConfig,
objectType,
objectId,
recordWithTransients,
{
// Handle edits sourced from the sync manager.
editRecord: (edits) => {
if (!Object.keys(edits).length) {
return;
}
dispatch({
type: "EDIT_ENTITY_RECORD",
kind,
name,
recordId: key,
edits,
meta: {
undo: void 0
}
});
},
// Get the current entity record (with edits)
getEditedRecord: async () => await resolveSelect.getEditedEntityRecord(
kind,
name,
key
),
// Save the current entity record's unsaved edits.
saveRecord: () => {
dispatch.saveEditedEntityRecord(
kind,
name,
key
);
}
}
);
}
}
registry.batch(() => {
dispatch.receiveEntityRecords(kind, name, record, query);
dispatch.receiveUserPermissions(receiveUserPermissionArgs);
dispatch.finishResolutions("canUser", canUserResolutionsArgs);
});
} finally {
dispatch.__unstableReleaseStoreLock(lock);
}
};
getEntityRecord.shouldInvalidate = (action, kind, name) => {
return kind === "root" && name === "site" && (action.type === "RECEIVE_ITEMS" && // Making sure persistedEdits is set seems to be the only way of
// knowing whether it's an update or fetch. Only an update would
// have persistedEdits.
action.persistedEdits && action.persistedEdits.status !== "auto-draft" || action.type === "REMOVE_ITEMS") && action.kind === "postType" && action.name === "wp_template";
};
var getRawEntityRecord = forwardResolver("getEntityRecord");
var getEditedEntityRecord = forwardResolver("getEntityRecord");
var getEntityRecords = (kind, name, query = {}) => async ({ dispatch, registry, resolveSelect }) => {
const configs = await resolveSelect.getEntitiesConfig(kind);
const entityConfig = configs.find(
(config) => config.name === name && config.kind === kind
);
if (!entityConfig) {
return;
}
const lock = await dispatch.__unstableAcquireStoreLock(
STORE_NAME,
["entities", "records", kind, name],
{ exclusive: false }
);
const rawQuery = { ...query };
const key = entityConfig.key || DEFAULT_ENTITY_KEY;
function getResolutionsArgs(records, recordsQuery) {
const queryArgs = Object.fromEntries(
Object.entries(recordsQuery).filter(([k, v]) => {
return ["context", "_fields"].includes(k) && !!v;
})
);
return records.filter((record) => record?.[key]).map((record) => [
kind,
name,
record[key],
Object.keys(queryArgs).length > 0 ? queryArgs : void 0
]);
}
try {
if (query._fields) {
query = {
...query,
_fields: [
.../* @__PURE__ */ new Set([
...getNormalizedCommaSeparable(query._fields) || [],
key
])
].join()
};
}
let { baseURL } = entityConfig;
const { combinedTemplates = true } = query;
if (kind === "postType" && name === "wp_template" && combinedTemplates) {
baseURL = baseURL.slice(0, baseURL.lastIndexOf("/")) + "/templates";
}
const path = addQueryArgs(baseURL, {
...entityConfig.baseURLParams,
...query
});
let records = [], meta;
if (entityConfig.supportsPagination && query.per_page !== -1) {
const response = await apiFetch({ path, parse: false });
records = Object.values(await response.json());
meta = {
totalItems: parseInt(
response.headers.get("X-WP-Total")
),
totalPages: parseInt(
response.headers.get("X-WP-TotalPages")
)
};
} else if (query.per_page === -1 && query[RECEIVE_INTERMEDIATE_RESULTS] === true) {
let page = 1;
let totalPages;
do {
const response = await apiFetch({
path: addQueryArgs(path, { page, per_page: 100 }),
parse: false
});
const pageRecords = Object.values(await response.json());
totalPages = parseInt(
response.headers.get("X-WP-TotalPages")
);
if (!meta) {
meta = {
totalItems: parseInt(
response.headers.get("X-WP-Total")
),
totalPages: 1
};
}
records.push(...pageRecords);
registry.batch(() => {
dispatch.receiveEntityRecords(
kind,
name,
records,
query,
false,
void 0,
meta
);
dispatch.finishResolutions(
"getEntityRecord",
getResolutionsArgs(pageRecords, rawQuery)
);
});
page++;
} while (page <= totalPages);
} else {
records = Object.values(await apiFetch({ path }));
meta = {
totalItems: records.length,
totalPages: 1
};
}
if (query._fields) {
records = records.map((record) => {
query._fields.split(",").forEach((field) => {
if (!record.hasOwnProperty(field)) {
record[field] = void 0;
}
});
return record;
});
}
registry.batch(() => {
dispatch.receiveEntityRecords(
kind,
name,
records,
query,
false,
void 0,
meta
);
const targetHints = records.filter(
(record) => !!record?.[key] && !!record?._links?.self?.[0]?.targetHints?.allow
).map((record) => ({
id: record[key],
permissions: getUserPermissionsFromAllowHeader(
record._links.self[0].targetHints.allow
)
}));
const canUserResolutionsArgs = [];
const receiveUserPermissionArgs = {};
for (const targetHint of targetHints) {
for (const action of ALLOWED_RESOURCE_ACTIONS) {
canUserResolutionsArgs.push([
action,
{ kind, name, id: targetHint.id }
]);
receiveUserPermissionArgs[getUserPermissionCacheKey(action, {
kind,
name,
id: targetHint.id
})] = targetHint.permissions[action];
}
}
if (targetHints.length > 0) {
dispatch.receiveUserPermissions(
receiveUserPermissionArgs
);
dispatch.finishResolutions(
"canUser",
canUserResolutionsArgs
);
}
dispatch.finishResolutions(
"getEntityRecord",
getResolutionsArgs(records, rawQuery)
);
dispatch.__unstableReleaseStoreLock(lock);
});
} catch (e) {
dispatch.__unstableReleaseStoreLock(lock);
}
};
getEntityRecords.shouldInvalidate = (action, kind, name) => {
return (action.type === "RECEIVE_ITEMS" || action.type === "REMOVE_ITEMS") && action.invalidateCache && kind === action.kind && name === action.name;
};
var getEntityRecordsTotalItems = forwardResolver("getEntityRecords");
var getEntityRecordsTotalPages = forwardResolver("getEntityRecords");
var getCurrentTheme = () => async ({ dispatch, resolveSelect }) => {
const activeThemes = await resolveSelect.getEntityRecords(
"root",
"theme",
{ status: "active" }
);
dispatch.receiveCurrentTheme(activeThemes[0]);
};
var getThemeSupports = forwardResolver("getCurrentTheme");
var getEmbedPreview = (url) => async ({ dispatch }) => {
try {
const embedProxyResponse = await apiFetch({
path: addQueryArgs("/oembed/1.0/proxy", { url })
});
dispatch.receiveEmbedPreview(url, embedProxyResponse);
} catch (error) {
dispatch.receiveEmbedPreview(url, false);
}
};
var canUser = (requestedAction, resource, id) => async ({ dispatch, registry, resolveSelect }) => {
if (!ALLOWED_RESOURCE_ACTIONS.includes(requestedAction)) {
throw new Error(`'${requestedAction}' is not a valid action.`);
}
const { hasStartedResolution } = registry.select(STORE_NAME);
for (const relatedAction of ALLOWED_RESOURCE_ACTIONS) {
if (relatedAction === requestedAction) {
continue;
}
const isAlreadyResolving = hasStartedResolution("canUser", [
relatedAction,
resource,
id
]);
if (isAlreadyResolving) {
return;
}
}
let resourcePath = null;
if (typeof resource === "object") {
if (!resource.kind || !resource.name) {
throw new Error("The entity resource object is not valid.");
}
const configs = await resolveSelect.getEntitiesConfig(
resource.kind
);
const entityConfig = configs.find(
(config) => config.name === resource.name && config.kind === resource.kind
);
if (!entityConfig) {
return;
}
resourcePath = entityConfig.baseURL + (resource.id ? "/" + resource.id : "");
} else {
resourcePath = `/wp/v2/${resource}` + (id ? "/" + id : "");
}
let response;
try {
response = await apiFetch({
path: resourcePath,
method: "OPTIONS",
parse: false
});
} catch (error) {
return;
}
const permissions = getUserPermissionsFromAllowHeader(
response.headers?.get("allow")
);
registry.batch(() => {
for (const action of ALLOWED_RESOURCE_ACTIONS) {
const key = getUserPermissionCacheKey(action, resource, id);
dispatch.receiveUserPermission(key, permissions[action]);
if (action !== requestedAction) {
dispatch.finishResolution("canUser", [
action,
resource,
id
]);
}
}
});
};
var canUserEditEntityRecord = (kind, name, recordId) => async ({ dispatch }) => {
await dispatch(canUser("update", { kind, name, id: recordId }));
};
var getAutosaves = (postType, postId) => async ({ dispatch, resolveSelect }) => {
const {
rest_base: restBase,
rest_namespace: restNamespace = "wp/v2",
supports
} = await resolveSelect.getPostType(postType);
if (!supports?.autosave) {
return;
}
const autosaves = await apiFetch({
path: `/${restNamespace}/${restBase}/${postId}/autosaves?context=edit`
});
if (autosaves && autosaves.length) {
dispatch.receiveAutosaves(postId, autosaves);
}
};
var getAutosave = (postType, postId) => async ({ resolveSelect }) => {
await resolveSelect.getAutosaves(postType, postId);
};
var __experimentalGetCurrentGlobalStylesId = () => async ({ dispatch, resolveSelect }) => {
const activeThemes = await resolveSelect.getEntityRecords(
"root",
"theme",
{ status: "active" }
);
const globalStylesURL = activeThemes?.[0]?._links?.["wp:user-global-styles"]?.[0]?.href;
if (!globalStylesURL) {
return;
}
const matches = globalStylesURL.match(/\/(\d+)(?:\?|$)/);
const id = matches ? Number(matches[1]) : null;
if (id) {
dispatch.__experimentalReceiveCurrentGlobalStylesId(id);
}
};
var __experimentalGetCurrentThemeBaseGlobalStyles = () => async ({ resolveSelect, dispatch }) => {
const currentTheme = await resolveSelect.getCurrentTheme();
const themeGlobalStyles = await apiFetch({
path: `/wp/v2/global-styles/themes/${currentTheme.stylesheet}?context=view`
});
dispatch.__experimentalReceiveThemeBaseGlobalStyles(
currentTheme.stylesheet,
themeGlobalStyles
);
};
var __experimentalGetCurrentThemeGlobalStylesVariations = () => async ({ resolveSelect, dispatch }) => {
const currentTheme = await resolveSelect.getCurrentTheme();
const variations = await apiFetch({
path: `/wp/v2/global-styles/themes/${currentTheme.stylesheet}/variations?context=view`
});
dispatch.__experimentalReceiveThemeGlobalStyleVariations(
currentTheme.stylesheet,
variations
);
};
var getCurrentThemeGlobalStylesRevisions = () => async ({ resolveSelect, dispatch }) => {
const globalStylesId = await resolveSelect.__experimentalGetCurrentGlobalStylesId();
const record = globalStylesId ? await resolveSelect.getEntityRecord(
"root",
"globalStyles",
globalStylesId
) : void 0;
const revisionsURL = record?._links?.["version-history"]?.[0]?.href;
if (revisionsURL) {
const resetRevisions = await apiFetch({
url: revisionsURL
});
const revisions = resetRevisions?.map(
(revision) => Object.fromEntries(
Object.entries(revision).map(([key, value]) => [
camelCase(key),
value
])
)
);
dispatch.receiveThemeGlobalStyleRevisions(
globalStylesId,
revisions
);
}
};
getCurrentThemeGlobalStylesRevisions.shouldInvalidate = (action) => {
return action.type === "SAVE_ENTITY_RECORD_FINISH" && action.kind === "root" && !action.error && action.name === "globalStyles";
};
var getBlockPatterns = () => async ({ dispatch }) => {
const patterns = await fetchBlockPatterns();
dispatch({ type: "RECEIVE_BLOCK_PATTERNS", patterns });
};
var getBlockPatternCategories = () => async ({ dispatch }) => {
const categories = await apiFetch({
path: "/wp/v2/block-patterns/categories"
});
dispatch({ type: "RECEIVE_BLOCK_PATTERN_CATEGORIES", categories });
};
var getUserPatternCategories = () => async ({ dispatch, resolveSelect }) => {
const patternCategories = await resolveSelect.getEntityRecords(
"taxonomy",
"wp_pattern_category",
{
per_page: -1,
_fields: "id,name,description,slug",
context: "view"
}
);
const mappedPatternCategories = patternCategories?.map((userCategory) => ({
...userCategory,
label: decodeEntities(userCategory.name),
name: userCategory.slug
})) || [];
dispatch({
type: "RECEIVE_USER_PATTERN_CATEGORIES",
patternCategories: mappedPatternCategories
});
};
var getNavigationFallbackId = () => async ({ dispatch, select, registry }) => {
const fallback = await apiFetch({
path: addQueryArgs("/wp-block-editor/v1/navigation-fallback", {
_embed: true
})
});
const record = fallback?._embedded?.self;
registry.batch(() => {
dispatch.receiveNavigationFallbackId(fallback?.id);
if (!record) {
return;
}
const existingFallbackEntityRecord = select.getEntityRecord(
"postType",
"wp_navigation",
fallback.id
);
const invalidateNavigationQueries = !existingFallbackEntityRecord;
dispatch.receiveEntityRecords(
"postType",
"wp_navigation",
record,
void 0,
invalidateNavigationQueries
);
dispatch.finishResolution("getEntityRecord", [
"postType",
"wp_navigation",
fallback.id
]);
});
};
var getDefaultTemplateId = (query) => async ({ dispatch, registry, resolveSelect }) => {
const template = await apiFetch({
path: addQueryArgs("/wp/v2/templates/lookup", query)
});
await resolveSelect.getEntitiesConfig("postType");
const id = window?.__experimentalTemplateActivate ? template?.wp_id || template?.id : template?.id;
if (id) {
template.id = id;
registry.batch(() => {
dispatch.receiveDefaultTemplateId(query, id);
dispatch.receiveEntityRecords("postType", template.type, [
template
]);
dispatch.finishResolution("getEntityRecord", [
"postType",
template.type,
id
]);
});
}
};
getDefaultTemplateId.shouldInvalidate = (action) => {
return action.type === "RECEIVE_ITEMS" && action.kind === "root" && action.name === "site";
};
var getRevisions = (kind, name, recordKey, query = {}) => async ({ dispatch, registry, resolveSelect }) => {
const configs = await resolveSelect.getEntitiesConfig(kind);
const entityConfig = configs.find(
(config) => config.name === name && config.kind === kind
);
if (!entityConfig) {
return;
}
if (query._fields) {
query = {
...query,
_fields: [
.../* @__PURE__ */ new Set([
...getNormalizedCommaSeparable(query._fields) || [],
entityConfig.revisionKey || DEFAULT_ENTITY_KEY
])
].join()
};
}
const path = addQueryArgs(
entityConfig.getRevisionsUrl(recordKey),
query
);
let records, response;
const meta = {};
const isPaginated = entityConfig.supportsPagination && query.per_page !== -1;
try {
response = await apiFetch({ path, parse: !isPaginated });
} catch (error) {
return;
}
if (response) {
if (isPaginated) {
records = Object.values(await response.json());
meta.totalItems = parseInt(
response.headers.get("X-WP-Total")
);
} else {
records = Object.values(response);
}
if (query._fields) {
records = records.map((record) => {
query._fields.split(",").forEach((field) => {
if (!record.hasOwnProperty(field)) {
record[field] = void 0;
}
});
return record;
});
}
registry.batch(() => {
dispatch.receiveRevisions(
kind,
name,
recordKey,
records,
query,
false,
meta
);
if (!query?._fields && !query.context) {
const key = entityConfig.key || DEFAULT_ENTITY_KEY;
const resolutionsArgs = records.filter((record) => record[key]).map((record) => [
kind,
name,
recordKey,
record[key]
]);
dispatch.finishResolutions(
"getRevision",
resolutionsArgs
);
}
});
}
};
getRevisions.shouldInvalidate = (action, kind, name, recordKey) => action.type === "SAVE_ENTITY_RECORD_FINISH" && name === action.name && kind === action.kind && !action.error && recordKey === action.recordId;
var getRevision = (kind, name, recordKey, revisionKey, query) => async ({ dispatch, resolveSelect }) => {
const configs = await resolveSelect.getEntitiesConfig(kind);
const entityConfig = configs.find(
(config) => config.name === name && config.kind === kind
);
if (!entityConfig) {
return;
}
if (query !== void 0 && query._fields) {
query = {
...query,
_fields: [
.../* @__PURE__ */ new Set([
...getNormalizedCommaSeparable(query._fields) || [],
entityConfig.revisionKey || DEFAULT_ENTITY_KEY
])
].join()
};
}
const path = addQueryArgs(
entityConfig.getRevisionsUrl(recordKey, revisionKey),
query
);
let record;
try {
record = await apiFetch({ path });
} catch (error) {
return;
}
if (record) {
dispatch.receiveRevisions(kind, name, recordKey, record, query);
}
};
var getRegisteredPostMeta = (postType) => async ({ dispatch, resolveSelect }) => {
let options;
try {
const {
rest_namespace: restNamespace = "wp/v2",
rest_base: restBase
} = await resolveSelect.getPostType(postType) || {};
options = await apiFetch({
path: `${restNamespace}/${restBase}/?context=edit`,
method: "OPTIONS"
});
} catch (error) {
return;
}
if (options) {
dispatch.receiveRegisteredPostMeta(
postType,
options?.schema?.properties?.meta?.properties
);
}
};
var getEntitiesConfig = (kind) => async ({ dispatch }) => {
const loader = additionalEntityConfigLoaders.find(
(l) => l.kind === kind
);
if (!loader) {
return;
}
try {
const configs = await loader.loadEntities();
if (!configs.length) {
return;
}
dispatch.addEntities(configs);
} catch {
}
};
var getEditorSettings = () => async ({ dispatch }) => {
const settings = await apiFetch({
path: "/wp-block-editor/v1/settings"
});
dispatch.receiveEditorSettings(settings);
};
var getEditorAssets = () => async ({ dispatch }) => {
const assets = await apiFetch({
path: "/wp-block-editor/v1/assets"
});
dispatch.receiveEditorAssets(assets);
};
export {
__experimentalGetCurrentGlobalStylesId,
__experimentalGetCurrentThemeBaseGlobalStyles,
__experimentalGetCurrentThemeGlobalStylesVariations,
canUser,
canUserEditEntityRecord,
getAuthors,
getAutosave,
getAutosaves,
getBlockPatternCategories,
getBlockPatterns,
getCurrentTheme,
getCurrentThemeGlobalStylesRevisions,
getCurrentUser,
getDefaultTemplateId,
getEditedEntityRecord,
getEditorAssets,
getEditorSettings,
getEmbedPreview,
getEntitiesConfig,
getEntityRecord,
getEntityRecords,
getEntityRecordsTotalItems,
getEntityRecordsTotalPages,
getNavigationFallbackId,
getRawEntityRecord,
getRegisteredPostMeta,
getRevision,
getRevisions,
getThemeSupports,
getUserPatternCategories
};
//# sourceMappingURL=resolvers.js.map