@contentrain/wp-import 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -0
- package/dist/index.d.mts +4 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +211 -16
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -140,6 +140,40 @@ contains `menus_require_auth` — an empty menu list is never presented as the
|
|
|
140
140
|
site having none. The `menus` model gains a `locations` field only when the
|
|
141
141
|
source named them.
|
|
142
142
|
|
|
143
|
+
A menu item that names a public post or term links to that post's or term's
|
|
144
|
+
public address (its REST `link`). The address the item stored is not used: a
|
|
145
|
+
block keeps the URL it had when it was saved, and a hand-typed `?page_id=` is a
|
|
146
|
+
form the migrated site cannot serve. A hand-typed `?page_id=` / `?p=` counts
|
|
147
|
+
as this site's with or without a leading `www.`.
|
|
148
|
+
|
|
149
|
+
### Redirects over REST
|
|
150
|
+
|
|
151
|
+
REST reads one source of redirects: the Redirection plugin (`redirection/v1`).
|
|
152
|
+
This needs an application password of a user who may manage the plugin. Its
|
|
153
|
+
rules are shaped as the Bridge shapes them. Each rule lands in exactly one of
|
|
154
|
+
two places:
|
|
155
|
+
|
|
156
|
+
- `redirects`: the rules the site serves, with the plugin's query, case and
|
|
157
|
+
trailing-slash modes. A "gone" rule (410, 451) is served with an empty `to`.
|
|
158
|
+
- `redirects_excluded`: every other rule, with its reason (disabled,
|
|
159
|
+
conditional, a 404 or pass-through action).
|
|
160
|
+
|
|
161
|
+
Yoast Premium, Rank Math, Safe Redirect Manager and `.htaccess` keep their
|
|
162
|
+
rules where only the Bridge export reads them. So `gaps` always contains
|
|
163
|
+
`redirects_partial`. It also contains `redirects_require_auth` when the site
|
|
164
|
+
runs Redirection but its rules could not be read with the credential given.
|
|
165
|
+
|
|
166
|
+
A conditional rule (login, cookie, header, IP, role…) is excluded. Only a login
|
|
167
|
+
rule keeps its `condition`, which is its two addresses. Any other condition
|
|
168
|
+
holds the value a visitor must present, often a secret, so it is never
|
|
169
|
+
exported; the reason names its type.
|
|
170
|
+
|
|
171
|
+
Each attachment carries its attachment page as `link`, so the migrated site
|
|
172
|
+
can redirect it. The link is kept only when the attachment has no parent or
|
|
173
|
+
its parent is proven public, because the page sits under the parent's address
|
|
174
|
+
and would otherwise carry a draft's, a private post's or a scheduled post's
|
|
175
|
+
slug.
|
|
176
|
+
|
|
143
177
|
### ACF fields, custom post types and taxonomies over REST
|
|
144
178
|
|
|
145
179
|
ACF / Secure Custom Fields values arrive on a post's `acf` key — only for field
|
package/dist/index.d.mts
CHANGED
|
@@ -71,6 +71,10 @@ interface RestImportResult {
|
|
|
71
71
|
* What this rung could not read, as codes a caller can act on (the text is in `warnings`).
|
|
72
72
|
* `menus_require_auth`: menus and block navigation are only readable with an application password
|
|
73
73
|
* of a user who may edit theme options — none was given, the site rejected it, or it lacks that right.
|
|
74
|
+
* `redirects_partial`: always, over REST — it reads only the Redirection plugin's rules; Yoast Premium, Rank Math,
|
|
75
|
+
* Safe Redirect Manager and `.htaccess` keep theirs where only the Bridge export reads them.
|
|
76
|
+
* `redirects_require_auth`: the site runs Redirection, and its rules need an application password of a user who
|
|
77
|
+
* may manage it — none was given, the site rejected it, or it lacks that right.
|
|
74
78
|
* `acf_partial`: the site runs ACF / Secure Custom Fields; REST shows only the field groups set to
|
|
75
79
|
* `show_in_rest` (off by default) and no options page — the Bridge export reads the rest. Where the site
|
|
76
80
|
* states no field types (plain ACF, no `<name>_source`), a secret field is recognised by its name only.
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/wxr.ts","../src/rest.ts","../src/contentrain.ts","../src/comments.ts","../src/php-unserialize.ts","../src/core.ts","../src/delta.ts"],"mappings":";;;;UA2BiB,QAAA;EACf,UAAA;IAAc,QAAA;IAAkB,OAAA;IAAiB,MAAA;IAAgB,IAAA;EAAA;EACjE,UAAA;EACA,gBAAA;EACA,MAAA,EAAQ,MAAA;AAAA;AAAA,UAGO,cAAA;EACf,GAAA,EAAK,KAAA;EACL,KAAA,EAAO,QAAA;AAAA;AAAA,iBAiBa,QAAA,CAAS,KAAA,WAAgB,QAAA,EAAU,IAAA;EAAS,IAAA;AAAA,IAAkB,OAAA,CAAQ,cAAA;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/wxr.ts","../src/rest.ts","../src/contentrain.ts","../src/comments.ts","../src/php-unserialize.ts","../src/core.ts","../src/delta.ts"],"mappings":";;;;UA2BiB,QAAA;EACf,UAAA;IAAc,QAAA;IAAkB,OAAA;IAAiB,MAAA;IAAgB,IAAA;EAAA;EACjE,UAAA;EACA,gBAAA;EACA,MAAA,EAAQ,MAAA;AAAA;AAAA,UAGO,cAAA;EACf,GAAA,EAAK,KAAA;EACL,KAAA,EAAO,QAAA;AAAA;AAAA,iBAiBa,QAAA,CAAS,KAAA,WAAgB,QAAA,EAAU,IAAA;EAAS,IAAA;AAAA,IAAkB,OAAA,CAAQ,cAAA;;;UCnC3E,iBAAA;EACf,MAAA;;EAEA,SAAA,UAAmB,KAAA;EDMI;;;;;;;;ECGvB,IAAA;IAAS,IAAA;IAAc,WAAA;EAAA;EACvB,OAAA;EDAc;AAGhB;;;;ECGE,WAAA;EDFK;;;;ECOL,QAAA;EACA,IAAA;AAAA;AAAA,UAGe,gBAAA;EACf,GAAA,EAAK,KAAA;EACL,QAAA;EDKkF;;;;;;;;ECIlF,UAAA;IAAc,MAAA;IAA0C,SAAA;EAAA;;;AAvC1D;;;;;;;;;;EAoDE,IAAA;AAAA;AAAA,iBA2EoB,cAAA,CAAe,OAAA,EAAS,iBAAA,GAAoB,OAAA,CAAQ,gBAAA;;;UCrHzD,YAAA;EACf,cAAA;EACA,aAAA;EACA,cAAA;EACA,WAAA,EAAa,MAAA;EACb,UAAA,EAAY,MAAA;EFFE;EEId,mBAAA,EAAqB,MAAA;EFPP;EESd,oBAAA;EACA,aAAA;EACA,iBAAA;EACA,MAAA,EAAQ,MAAA;IAAiB,IAAA;IAAc,MAAA;IAAgB,MAAA;IAAgB,OAAA;EAAA;EFNxD;EEQf,OAAA;;EAEA,kBAAA;EFTA;;;;;;EEgBA,yBAAA;EFE4B;;;;EEG5B,kBAAA;AAAA;AAAA,UAGe,iBAAA;EACf,KAAA,EAAO,MAAA;EACP,gBAAA,EAAkB,cAAA;EAClB,MAAA,EAAQ,YAAA;AAAA;AAAA,iBA0CM,gBAAA,CAAiB,GAAA,EAAK,KAAA,EAAO,IAAA;EAAS,SAAA;AAAA,IAAuB,iBAAA;;;;;;AF7E7E;;;iBGbgB,cAAA,CAAe,GAAA,EAAK,KAAA;EAAU,QAAA,EAAU,UAAA;EAAc,QAAA,EAAU,WAAA,CAAY,cAAA;AAAA;AAAA,iBAgB5E,mBAAA,CAAoB,GAAA,EAAK,KAAA,EAAO,OAAA,EAAS,cAAA,EAAgB,IAAA;EAAS,YAAA;AAAA,IAA0B,cAAA;;iBAiB5F,iBAAA,CAAkB,GAAA,EAAK,cAAA,GAAiB,eAAA;;;UC3CvC,iBAAA;EACf,EAAA;EACA,UAAA;EACA,KAAA;EACA,KAAA;AAAA;AAAA,iBAGc,eAAA,CAAgB,CAAA,YAAa,CAAA;AAAA,iBAI7B,cAAA,CAAe,GAAA;;iBAyFf,cAAA,CAAe,CAAA,WAAY,iBAAA;;;cC9F9B,KAAA,GAAS,CAAA;;cAST,KAAA,GAAS,CAAA;;cAcT,KAAA,GAAS,CAAA;;;;;;;;;ALCtB;cK8Ba,UAAA,EAAU,MAAA;;cAIV,WAAA,EAAW,MAAA;;cAOX,UAAA,GAAc,CAAA;AAAA,cAGd,OAAA,GAAW,CAAA;;;;UC3DP,UAAA;EACf,KAAA,EAAO,MAAA;EACP,gBAAA,EAAkB,cAAA;AAAA;AAAA,UAGH,oBAAA;ENOD;EMLd,KAAA,EAAO,eAAA;ENEO;EMAd,KAAA,EAAO,UAAA;ENA0C;EMEjD,QAAA,GAAW,UAAA;ENDX;;;;;EMOA,UAAA;ENFe;;;;EMOf,SAAA;AAAA;AAAA,cAGW,kBAAA;AAAA,cACA,iBAAA;;;ANQb;;;;;;;;;;iBM8NgB,eAAA,CAAgB,KAAA,EAAO,oBAAA,GAAuB,eAAA;;iBAkC9C,uBAAA,CAAwB,IAAA,EAAM,eAAA"}
|
package/dist/index.mjs
CHANGED
|
@@ -525,6 +525,7 @@ async function parseWxr(input, opts) {
|
|
|
525
525
|
mime: guessMime(r.attachment_url ?? r.guid ?? ""),
|
|
526
526
|
parent: r.parent,
|
|
527
527
|
parent_resolved: r.parent ? byId.has(r.parent) : null,
|
|
528
|
+
link: !r.parent || byId.get(r.parent)?.status === "publish" && !byId.get(r.parent)?.password ? r.link || null : null,
|
|
528
529
|
author: r.author,
|
|
529
530
|
date: r.date,
|
|
530
531
|
status: r.status,
|
|
@@ -1033,6 +1034,121 @@ function acfRows(value) {
|
|
|
1033
1034
|
}))(r) : r) : value;
|
|
1034
1035
|
}
|
|
1035
1036
|
//#endregion
|
|
1037
|
+
//#region src/rest-redirects.ts
|
|
1038
|
+
/** Redirection's own defaults, under a site that states none. */
|
|
1039
|
+
const DEFAULTS = {
|
|
1040
|
+
flag_query: "exact",
|
|
1041
|
+
flag_case: false,
|
|
1042
|
+
flag_trailing: false
|
|
1043
|
+
};
|
|
1044
|
+
const MODULES = {
|
|
1045
|
+
2: "apache",
|
|
1046
|
+
3: "nginx"
|
|
1047
|
+
};
|
|
1048
|
+
/** Codes that say an address is gone: served as that answer, with no target. */
|
|
1049
|
+
const GONE = new Set([410, 451]);
|
|
1050
|
+
/** Redirection's query modes; `exactorder` (the same parameters in the same order) is read as `exact`. */
|
|
1051
|
+
const QUERY = {
|
|
1052
|
+
exact: "exact",
|
|
1053
|
+
exactorder: "exact",
|
|
1054
|
+
ignore: "ignore",
|
|
1055
|
+
pass: "pass"
|
|
1056
|
+
};
|
|
1057
|
+
/** The flags one layer states, as Redirection accepts them: a known query mode, a true boolean (not `"1"`). */
|
|
1058
|
+
const valid = (f) => ({
|
|
1059
|
+
...typeof f?.flag_query === "string" && f.flag_query in QUERY ? { flag_query: f.flag_query } : {},
|
|
1060
|
+
...typeof f?.flag_case === "boolean" ? { flag_case: f.flag_case } : {},
|
|
1061
|
+
...typeof f?.flag_trailing === "boolean" ? { flag_trailing: f.flag_trailing } : {}
|
|
1062
|
+
});
|
|
1063
|
+
/** By id as bytes, as the Bridge sorts (`strcmp`). */
|
|
1064
|
+
const order = (a, b) => (a.id ?? "") < (b.id ?? "") ? -1 : (a.id ?? "") > (b.id ?? "") ? 1 : 0;
|
|
1065
|
+
const on = (x) => x.enabled ?? (x.status === void 0 || x.status === "enabled");
|
|
1066
|
+
/** Same-site targets become root-relative, like every `from`; other hosts stay absolute (as the Bridge does). */
|
|
1067
|
+
function relative(url, origin) {
|
|
1068
|
+
try {
|
|
1069
|
+
const home = new URL(origin);
|
|
1070
|
+
const u = new URL(url, `${origin}/`);
|
|
1071
|
+
if (u.host.toLowerCase() !== home.host.toLowerCase()) return url;
|
|
1072
|
+
return `${u.pathname}${u.search}${u.hash}`;
|
|
1073
|
+
} catch {
|
|
1074
|
+
return url;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* Redirection's rules and groups → the rules the site serves and the ones it does not, sorted by id.
|
|
1079
|
+
* A rule's query, case and trailing-slash modes are what Redirection applies to it: its own flags, else the site's
|
|
1080
|
+
* (`options`), else the plugin's defaults. Without `options` (not readable) only a rule's own flags are stated.
|
|
1081
|
+
*/
|
|
1082
|
+
function redirectionRules(items, groups, origin, options) {
|
|
1083
|
+
const site = options ? {
|
|
1084
|
+
...DEFAULTS,
|
|
1085
|
+
...valid(options)
|
|
1086
|
+
} : {};
|
|
1087
|
+
const byId = new Map(groups.map((g) => [g.id, g]));
|
|
1088
|
+
const redirects = [];
|
|
1089
|
+
const excluded = [];
|
|
1090
|
+
for (const item of items) {
|
|
1091
|
+
const group = item.group_id === void 0 ? void 0 : byId.get(item.group_id);
|
|
1092
|
+
const code = Number(item.action_code ?? 301);
|
|
1093
|
+
const data = item.action_data;
|
|
1094
|
+
const target = typeof data === "string" ? data : data?.url ?? "";
|
|
1095
|
+
const own = item.match_data?.source ?? {};
|
|
1096
|
+
const flags = {
|
|
1097
|
+
...site,
|
|
1098
|
+
...valid(own)
|
|
1099
|
+
};
|
|
1100
|
+
const rule = {
|
|
1101
|
+
id: `redirection:${item.id}`,
|
|
1102
|
+
from: item.url ?? "",
|
|
1103
|
+
source: "redirection",
|
|
1104
|
+
match: "url",
|
|
1105
|
+
regex: item.regex === true || own.flag_regex === true
|
|
1106
|
+
};
|
|
1107
|
+
const served = group?.module_id !== void 0 ? MODULES[group.module_id] : void 0;
|
|
1108
|
+
if (served) rule.served_by = served;
|
|
1109
|
+
if (rule.regex) rule.query = "exact";
|
|
1110
|
+
else if (typeof flags.flag_query === "string") rule.query = QUERY[flags.flag_query];
|
|
1111
|
+
if (typeof flags.flag_case === "boolean") rule.case_insensitive = flags.flag_case;
|
|
1112
|
+
if (typeof flags.flag_trailing === "boolean") rule.trailing_slash = flags.flag_trailing ? "ignore" : "exact";
|
|
1113
|
+
const matchType = item.match_type ?? "url";
|
|
1114
|
+
const actionType = item.action_type ?? "url";
|
|
1115
|
+
if (!on(item) || group && !on(group)) excluded.push({
|
|
1116
|
+
...rule,
|
|
1117
|
+
reason: "disabled"
|
|
1118
|
+
});
|
|
1119
|
+
else if (actionType === "error" && GONE.has(code) && matchType === "url") redirects.push({
|
|
1120
|
+
...rule,
|
|
1121
|
+
to: "",
|
|
1122
|
+
status: code
|
|
1123
|
+
});
|
|
1124
|
+
else if (actionType !== "url") excluded.push({
|
|
1125
|
+
...rule,
|
|
1126
|
+
status: code,
|
|
1127
|
+
reason: `not-a-redirect:${actionType}`
|
|
1128
|
+
});
|
|
1129
|
+
else if (matchType !== "url") excluded.push({
|
|
1130
|
+
...rule,
|
|
1131
|
+
match: matchType,
|
|
1132
|
+
status: code,
|
|
1133
|
+
...matchType === "login" ? { condition: data ?? null } : {},
|
|
1134
|
+
reason: `conditional-match:${matchType}`
|
|
1135
|
+
});
|
|
1136
|
+
else {
|
|
1137
|
+
const move = code >= 300 && code < 400;
|
|
1138
|
+
redirects.push({
|
|
1139
|
+
...rule,
|
|
1140
|
+
to: relative(target, origin),
|
|
1141
|
+
status: move ? code : 301,
|
|
1142
|
+
...move ? {} : { status_note: `source status ${code} is not a redirect code; 301 assumed` }
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
return {
|
|
1147
|
+
redirects: redirects.toSorted(order),
|
|
1148
|
+
excluded: excluded.toSorted(order)
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
//#endregion
|
|
1036
1152
|
//#region src/rest-menus.ts
|
|
1037
1153
|
const targetOf = (kind, object, id, url, ctx) => {
|
|
1038
1154
|
const objectId = id && id > 0 ? id : null;
|
|
@@ -1071,6 +1187,12 @@ const targetOf = (kind, object, id, url, ctx) => {
|
|
|
1071
1187
|
resolved: false
|
|
1072
1188
|
};
|
|
1073
1189
|
};
|
|
1190
|
+
/** The public address of the post or term a target names, when it is one this import proved public. */
|
|
1191
|
+
const publicLink = (target, ctx) => {
|
|
1192
|
+
if (!target.resolved || !("id" in target) || !target.id) return void 0;
|
|
1193
|
+
if (target.kind === "post") return ctx.postLink?.(target.id);
|
|
1194
|
+
if (target.kind === "term") return ctx.termLink?.(target.taxonomy, target.id);
|
|
1195
|
+
};
|
|
1074
1196
|
/**
|
|
1075
1197
|
* Items that are left out: a draft item, or one whose post target is not proven visible. Their children move up
|
|
1076
1198
|
* to the nearest kept ancestor, as WordPress's walker shows them.
|
|
@@ -1091,15 +1213,17 @@ function hiddenItems(items, drop) {
|
|
|
1091
1213
|
lift
|
|
1092
1214
|
};
|
|
1093
1215
|
}
|
|
1216
|
+
/** A host without case or a leading `www.`: example.com and www.example.com are one site (as the Bridge reads it). */
|
|
1217
|
+
const bareHost = (u) => u.hostname.toLowerCase().replace(/^www\./, "");
|
|
1094
1218
|
/**
|
|
1095
|
-
* The post a plain permalink names (`/?page_id=14`, `/?p=14`) on this site: a custom link typed by
|
|
1096
|
-
* post id, but it points at a post all the same, and its label is often that post's title.
|
|
1219
|
+
* The post a plain permalink names (`/?page_id=14`, `/?p=14`) on this site, `www.` or not: a custom link typed by
|
|
1220
|
+
* hand carries no post id, but it points at a post all the same, and its label is often that post's title.
|
|
1097
1221
|
*/
|
|
1098
1222
|
function plainPostId(url, origin) {
|
|
1099
1223
|
if (!url || !url.includes("?")) return void 0;
|
|
1100
1224
|
try {
|
|
1101
1225
|
const u = new URL(url, `${origin}/`);
|
|
1102
|
-
if (u
|
|
1226
|
+
if (bareHost(u) !== bareHost(new URL(origin))) return void 0;
|
|
1103
1227
|
const id = Number(u.searchParams.get("page_id") ?? u.searchParams.get("p"));
|
|
1104
1228
|
return Number.isSafeInteger(id) && id > 0 ? id : void 0;
|
|
1105
1229
|
} catch {
|
|
@@ -1126,13 +1250,15 @@ function classicMenus(menus, items, ctx, dropped = { count: 0 }) {
|
|
|
1126
1250
|
slug: m.slug ?? "",
|
|
1127
1251
|
name: strip(m.name ?? m.slug ?? ""),
|
|
1128
1252
|
items: own.map((i) => {
|
|
1253
|
+
const target = targetOf(i.type, i.object, i.object_id, i.url, ctx);
|
|
1254
|
+
const typed = target.kind === "url" ? plainPostId(i.url, ctx.origin) : void 0;
|
|
1129
1255
|
const item = {
|
|
1130
1256
|
id: i.id,
|
|
1131
1257
|
title: i.title?.raw || i.title?.rendered || "",
|
|
1132
1258
|
order: i.menu_order ?? 0,
|
|
1133
1259
|
parent: lift(i.parent || null),
|
|
1134
|
-
url: i.url || null,
|
|
1135
|
-
target
|
|
1260
|
+
url: publicLink(target, ctx) ?? (typed !== void 0 ? ctx.postLink?.(typed) : void 0) ?? (i.url || null),
|
|
1261
|
+
target,
|
|
1136
1262
|
target_attr: i.target || null,
|
|
1137
1263
|
classes: (i.classes ?? []).filter(Boolean),
|
|
1138
1264
|
description: i.description ?? "",
|
|
@@ -1210,13 +1336,14 @@ function blockItems(tree, ctx, nextId, dropped) {
|
|
|
1210
1336
|
const push = (b, parent) => {
|
|
1211
1337
|
const id = nextId();
|
|
1212
1338
|
const url = absolute(b.url, ctx.origin);
|
|
1339
|
+
const target = targetOf(b.kind, b.object, b.id, url, ctx);
|
|
1213
1340
|
items.push({
|
|
1214
1341
|
id,
|
|
1215
1342
|
title: b.title,
|
|
1216
1343
|
order: items.length + 1,
|
|
1217
1344
|
parent,
|
|
1218
|
-
url: url ?? null,
|
|
1219
|
-
target
|
|
1345
|
+
url: publicLink(target, ctx) ?? (b.typed !== void 0 ? ctx.postLink?.(b.typed) : void 0) ?? url ?? null,
|
|
1346
|
+
target,
|
|
1220
1347
|
target_attr: b.newTab ? "_blank" : null,
|
|
1221
1348
|
classes: (b.className ?? "").split(/\s+/).filter(Boolean),
|
|
1222
1349
|
description: b.description ?? "",
|
|
@@ -1255,6 +1382,7 @@ function blockItems(tree, ctx, nextId, dropped) {
|
|
|
1255
1382
|
kind,
|
|
1256
1383
|
object,
|
|
1257
1384
|
id: typeof a.id === "number" ? a.id : void 0,
|
|
1385
|
+
typed: kind === "custom" ? postId : void 0,
|
|
1258
1386
|
newTab: a.opensInNewTab === true,
|
|
1259
1387
|
className: str(a.className),
|
|
1260
1388
|
description: str(a.description)
|
|
@@ -1625,19 +1753,24 @@ async function fetchRestRawIR(options) {
|
|
|
1625
1753
|
const extraTerms = lists.slice(0, extraTaxonomies.length);
|
|
1626
1754
|
const postLists = lists.slice(extraTaxonomies.length);
|
|
1627
1755
|
const termsById = /* @__PURE__ */ new Map();
|
|
1756
|
+
/** A term's public address, for menu items that name it; RawTerm carries none. */
|
|
1757
|
+
const termLinks = /* @__PURE__ */ new Map();
|
|
1628
1758
|
for (const [tax, list] of [
|
|
1629
1759
|
["category", categories],
|
|
1630
1760
|
["post_tag", tags],
|
|
1631
1761
|
...extraTaxonomies.map((t, i) => [t.taxonomy, extraTerms[i] ?? []])
|
|
1632
|
-
]) for (const t of list)
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1762
|
+
]) for (const t of list) {
|
|
1763
|
+
if (t.link) termLinks.set(t.id, t.link);
|
|
1764
|
+
termsById.set(t.id, {
|
|
1765
|
+
id: t.id,
|
|
1766
|
+
taxonomy: tax,
|
|
1767
|
+
slug: t.slug,
|
|
1768
|
+
name: strip(t.name),
|
|
1769
|
+
parent: t.parent ? list.find((x) => x.id === t.parent)?.slug ?? null : null,
|
|
1770
|
+
parent_resolved: t.parent ? list.some((x) => x.id === t.parent) : null,
|
|
1771
|
+
description: t.description ?? ""
|
|
1772
|
+
});
|
|
1773
|
+
}
|
|
1641
1774
|
const userById = new Map(users.map((u) => [u.id, u]));
|
|
1642
1775
|
const posts = [];
|
|
1643
1776
|
const languagePairs = [];
|
|
@@ -1725,6 +1858,7 @@ async function fetchRestRawIR(options) {
|
|
|
1725
1858
|
mime: m.mime_type ?? null,
|
|
1726
1859
|
parent: m.post ?? null,
|
|
1727
1860
|
parent_resolved: m.post ? posts.some((p) => p.id === m.post) : null,
|
|
1861
|
+
link: !m.post || posts.some((p) => p.id === m.post && visible(p)) ? m.link || null : null,
|
|
1728
1862
|
date: iso(m.date_gmt)
|
|
1729
1863
|
}));
|
|
1730
1864
|
const gaps = [];
|
|
@@ -1789,6 +1923,7 @@ async function fetchRestRawIR(options) {
|
|
|
1789
1923
|
["navigation", navs]
|
|
1790
1924
|
]) if (!l.status || l.status >= 400 && !DENIED.has(l.status) && l.status !== 404) warnings.push(`${name}: HTTP ${l.status} — skipped`);
|
|
1791
1925
|
const slugOf = new Map(posts.filter(visible).map((p) => [p.id, p.slug]));
|
|
1926
|
+
const linkOf = new Map(posts.filter(visible).flatMap((p) => p.link ? [[p.id, p.link]] : []));
|
|
1792
1927
|
const ctx = {
|
|
1793
1928
|
origin,
|
|
1794
1929
|
postSlug: (id) => slugOf.get(id),
|
|
@@ -1796,6 +1931,8 @@ async function fetchRestRawIR(options) {
|
|
|
1796
1931
|
const t = termsById.get(id);
|
|
1797
1932
|
return t && t.taxonomy === taxonomy ? t.slug : void 0;
|
|
1798
1933
|
},
|
|
1934
|
+
postLink: (id) => linkOf.get(id),
|
|
1935
|
+
termLink: (taxonomy, id) => termsById.get(id)?.taxonomy === taxonomy ? termLinks.get(id) : void 0,
|
|
1799
1936
|
pages: posts.filter((p) => p.type === "page" && p.status === "publish" && !p.password).map((p) => ({
|
|
1800
1937
|
id: p.id,
|
|
1801
1938
|
parent: p.parent ?? null,
|
|
@@ -1829,6 +1966,62 @@ async function fetchRestRawIR(options) {
|
|
|
1829
1966
|
user_id: c.author || null
|
|
1830
1967
|
}));
|
|
1831
1968
|
for (const c of comments) if (!postIds.has(c.post)) warnings.push(`comment ${c.id}: post ${c.post} not in fetched set`);
|
|
1969
|
+
let redirects = [];
|
|
1970
|
+
let redirectsExcluded = [];
|
|
1971
|
+
gaps.push("redirects_partial");
|
|
1972
|
+
const namespaces = Array.isArray(about?.namespaces) ? about.namespaces : void 0;
|
|
1973
|
+
const redirection = namespaces ? namespaces.includes("redirection/v1") : void 0;
|
|
1974
|
+
if (redirection && !authed) gaps.push("redirects_require_auth");
|
|
1975
|
+
else if (authed && redirection !== false) {
|
|
1976
|
+
const all = async (path) => {
|
|
1977
|
+
const items = [];
|
|
1978
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1979
|
+
for (let page = 0; page < (maxPages ?? 500); page++) {
|
|
1980
|
+
const r = await schedule(() => doFetch(`${origin}/wp-json/redirection/v1/${path}?per_page=200&page=${page}`, { headers }));
|
|
1981
|
+
if (!r.ok) {
|
|
1982
|
+
await r.body?.cancel();
|
|
1983
|
+
return {
|
|
1984
|
+
items,
|
|
1985
|
+
status: r.status
|
|
1986
|
+
};
|
|
1987
|
+
}
|
|
1988
|
+
const body = await r.json();
|
|
1989
|
+
const fresh = (Array.isArray(body.items) ? body.items : []).filter((x) => !seen.has(x.id));
|
|
1990
|
+
for (const x of fresh) {
|
|
1991
|
+
seen.add(x.id);
|
|
1992
|
+
items.push(x);
|
|
1993
|
+
}
|
|
1994
|
+
if (!fresh.length || items.length >= Number(body.total ?? 0)) break;
|
|
1995
|
+
}
|
|
1996
|
+
return {
|
|
1997
|
+
items,
|
|
1998
|
+
status: 200
|
|
1999
|
+
};
|
|
2000
|
+
};
|
|
2001
|
+
const settings = async () => {
|
|
2002
|
+
try {
|
|
2003
|
+
const r = await schedule(() => doFetch(`${origin}/wp-json/redirection/v1/setting`, { headers }));
|
|
2004
|
+
if (!r.ok) {
|
|
2005
|
+
await r.body?.cancel();
|
|
2006
|
+
return;
|
|
2007
|
+
}
|
|
2008
|
+
const body = await r.json();
|
|
2009
|
+
return body?.settings && typeof body.settings === "object" ? body.settings : void 0;
|
|
2010
|
+
} catch {
|
|
2011
|
+
return;
|
|
2012
|
+
}
|
|
2013
|
+
};
|
|
2014
|
+
const [rules, groups, siteFlags] = await Promise.all([
|
|
2015
|
+
all("redirect"),
|
|
2016
|
+
all("group"),
|
|
2017
|
+
settings()
|
|
2018
|
+
]);
|
|
2019
|
+
if (DENIED.has(rules.status)) {
|
|
2020
|
+
gaps.push("redirects_require_auth");
|
|
2021
|
+
warnings.push(`redirects: HTTP ${rules.status} with the credential — the user may not manage Redirection; its rules not read`);
|
|
2022
|
+
} else if (rules.status === 200) ({redirects, excluded: redirectsExcluded} = redirectionRules(rules.items, groups.status === 200 ? groups.items : [], origin, siteFlags));
|
|
2023
|
+
else if (redirection) warnings.push(`redirects: HTTP ${rules.status} — Redirection's rules not read`);
|
|
2024
|
+
}
|
|
1832
2025
|
return {
|
|
1833
2026
|
raw: {
|
|
1834
2027
|
version: MIGRATION_CONTRACT_VERSION,
|
|
@@ -1856,6 +2049,8 @@ async function fetchRestRawIR(options) {
|
|
|
1856
2049
|
attachments,
|
|
1857
2050
|
comments,
|
|
1858
2051
|
...menus.length ? { menus } : {},
|
|
2052
|
+
...redirects.length ? { redirects } : {},
|
|
2053
|
+
...redirectsExcluded.length ? { redirects_excluded: redirectsExcluded } : {},
|
|
1859
2054
|
...languagePairs.length ? { language_pairs: languagePairs } : {}
|
|
1860
2055
|
},
|
|
1861
2056
|
warnings,
|