fix(attachments): add PMCID fallback for PubMed resolver lookup

PubMed saves can include a PMCID even when DOI-based full-text lookup
fails to produce a PDF. In that case the connector asks the desktop
attachment resolver flow for fallback sources, but getFileResolvers()
only considered DOI and URL data, so Zotero never tried the matching
PMC article URL.

Treat PMCID as a valid signal for file lookup eligibility and add a
PubMed Central OA resolver before DOI-based OA lookup. Cover the new
fallback path in attachment resolver tests and the connector
hasAttachmentResolvers endpoint.
This commit is contained in:
pi-dal 2026-05-09 19:21:11 +08:00
parent 504447de41
commit 9fb88bf087
No known key found for this signature in database
GPG key ID: 2ECD633DF1BA031F
3 changed files with 102 additions and 1 deletions

View file

@ -1239,9 +1239,10 @@ Zotero.Attachments = new function () {
this.canFindFileForItem = function (item) {
let pmcid = item.getField('PMCID') || item.getExtraField('PMCID');
return item.isRegularItem()
&& !item.isFeedItem
&& (!!item.getField('DOI') || !!item.getField('url') || !!item.getExtraField('DOI'))
&& (!!item.getField('DOI') || !!item.getField('url') || !!item.getExtraField('DOI') || !!pmcid)
&& this.FIND_AVAILABLE_FILE_TYPES.every(type => item.numFileAttachmentsWithContentType(type) == 0);
};
@ -1275,7 +1276,12 @@ Zotero.Attachments = new function () {
var resolvers = [];
var doi = item.getField('DOI') || item.getExtraField('DOI');
var pmcid = item.getField('PMCID') || item.getExtraField('PMCID');
doi = Zotero.Utilities.cleanDOI(doi);
if (pmcid) {
let matches = pmcid.match(/\bPMC\d+\b/i);
pmcid = matches && matches[0].toUpperCase();
}
if (useDOI && doi) {
doi = Zotero.Utilities.cleanDOI(doi);
@ -1300,6 +1306,13 @@ Zotero.Attachments = new function () {
}
}
if (useOA && pmcid) {
resolvers.push({
pageURL: `https://pmc.ncbi.nlm.nih.gov/articles/${pmcid}/`,
accessMethod: 'oa'
});
}
if (useOA && doi) {
resolvers.push(async function () {
let urls = await Zotero.Utilities.Internal.getOpenAccessPDFURLs(doi);

View file

@ -1031,6 +1031,48 @@ describe("Zotero.Attachments", function () {
assert.equal(await OS.File.stat(attachment.getFilePath()).size, pdfSize);
});
it("should include a PubMed Central resolver from a PMCID in Extra", async function () {
var item = createUnsavedDataObject('item', { itemType: 'journalArticle' });
item.setField('title', 'Test');
item.setField('extra', 'PMCID: PMC9262588');
await item.saveTx();
var resolvers = Zotero.Attachments.getFileResolvers(item, ['oa']);
assert.deepEqual(resolvers, [
{
pageURL: 'https://pmc.ncbi.nlm.nih.gov/articles/PMC9262588/',
accessMethod: 'oa'
}
]);
});
it("should include a PubMed Central resolver alongside DOI OA resolvers", async function () {
var item = createUnsavedDataObject('item', { itemType: 'journalArticle' });
item.setField('title', 'Test');
item.setField('DOI', '10.1093/nar/gkac173');
item.setField('extra', 'PMCID: PMC9262588');
await item.saveTx();
var resolvers = Zotero.Attachments.getFileResolvers(item, ['oa']);
assert.lengthOf(resolvers, 2);
assert.deepEqual(resolvers[0], {
pageURL: 'https://pmc.ncbi.nlm.nih.gov/articles/PMC9262588/',
accessMethod: 'oa'
});
assert.isFunction(resolvers[1]);
});
it("should allow finding files for an item with a PMCID in Extra", async function () {
var item = createUnsavedDataObject('item', { itemType: 'journalArticle' });
item.setField('title', 'Test');
item.setField('extra', 'PMCID: PMC9262588');
await item.saveTx();
assert.isTrue(Zotero.Attachments.canFindFileForItem(item));
});
it("should add a PDF from a URL", async function () {
var url = pageURL1;
var item = createUnsavedDataObject('item', { itemType: 'journalArticle' });

View file

@ -701,6 +701,52 @@ describe("Connector Server", function () {
assert.isTrue(JSON.parse(response.responseText));
});
it("should respond with 'true' if the item has a PMCID fallback", async function () {
const sessionID = Zotero.Utilities.randomString();
const itemID = Zotero.Utilities.randomString();
const body = {
sessionID,
items: [
{
id: itemID,
itemType: "journalArticle",
title: "Test Article with PMCID",
extra: "PMCID: PMC9262588",
}
]
};
let response = await httpRequest(
"POST",
connectorServerPath + "/connector/saveItems",
{
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(body)
}
);
assert.equal(response.status, 201);
response = await httpRequest(
"POST",
connectorServerPath + "/connector/hasAttachmentResolvers",
{
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
sessionID,
itemID
}),
}
);
assert.equal(response.status, 200);
assert.isTrue(JSON.parse(response.responseText));
});
it("should respond with 'false' if the item has no OA attachments", async function () {
const sessionID = Zotero.Utilities.randomString();
const itemID = Zotero.Utilities.randomString();