diff --git a/chrome/content/zotero/xpcom/schema.js b/chrome/content/zotero/xpcom/schema.js index f8dd3df05d..0ad232ca6e 100644 --- a/chrome/content/zotero/xpcom/schema.js +++ b/chrome/content/zotero/xpcom/schema.js @@ -53,6 +53,7 @@ Zotero.Schema = new function(){ var _nextRepositoryUpdate; var _remoteUpdateInProgress = false; var _localUpdateInProgress = false; + var _hiddenNoticesWithoutIDs = new Map(); var self = this; @@ -2364,6 +2365,8 @@ Zotero.Schema = new function(){ var translatorUpdates = xmlhttp.responseXML.getElementsByTagName('translator'); var styleUpdates = xmlhttp.responseXML.getElementsByTagName('style'); + _showRepositoryMessage(xmlhttp.responseXML); + if (!translatorUpdates.length && !styleUpdates.length){ await Zotero.DB.executeTransaction(function* (conn) { // Store the timestamp provided by the server @@ -2415,6 +2418,111 @@ Zotero.Schema = new function(){ } + /** + * Show dialog if repo returns a message + */ + function _showRepositoryMessage(responseXML) { + try { + var messageElem = responseXML.querySelector('message'); + if (!messageElem || !messageElem.textContent) { + return; + } + + let hiddenNotices = Zotero.Prefs.get('hiddenNotices') || '{}'; + try { + hiddenNotices = JSON.parse(hiddenNotices); + } + catch (e) { + Zotero.logError(e); + hiddenNotices = {}; + } + + let id = messageElem.getAttribute('id'); + let title = messageElem.getAttribute('title'); + let text = messageElem.textContent; + let url = messageElem.getAttribute('infoURL'); + let now = Math.round(Date.now() / 1000); + let thirtyDays = 86400 * 30; + + if (id) { + if (hiddenNotices[id] && hiddenNotices[id] > now) { + Zotero.debug("Not showing hidden notice " + id, 2); + Zotero.debug(text, 2); + return; + } + } + else { + Zotero.debug("CHECKING"); + let exp = _hiddenNoticesWithoutIDs.get(text); + Zotero.debug(exp); + Zotero.debug(now); + if (exp && exp > now) { + Zotero.debug("Not showing hidden notice", 2); + Zotero.debug(text, 2); + return; + } + } + + setTimeout(() => { + Zotero.debug(text, 2); + + var ps = Services.prompt; + var buttonFlags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_OK + + ps.BUTTON_POS_1 * ps.BUTTON_TITLE_IS_STRING; + var checkState = {}; + var index = ps.confirmEx( + null, + title || Zotero.getString('general.warning'), + text, + buttonFlags, + "", + // Show "More Information" button if repo includes a URL + url ? Zotero.getString('general.moreInformation') : "", + "", + // Show "Don't show again for 30 days" if repo includes an id + id ? Zotero.getString('general.dontShowAgainFor', 30, 30) : null, + checkState + ); + + if (index == 1) { + setTimeout(function () { + Zotero.launchURL(url); + }, 1); + } + // Handle "Don't show again for 30 days" checkbox + if (id) { + if (checkState.value) { + hiddenNotices[id] = now + thirtyDays; + } + // If not checked, still don't show again for a day + else { + hiddenNotices[id] = now + 86400; + } + // Remove expired hidden notices + for (let i in hiddenNotices) { + if (hiddenNotices[i] < now) { + delete hiddenNotices[i]; + } + } + if (Object.keys(hiddenNotices).length) { + Zotero.Prefs.set('hiddenNotices', JSON.stringify(hiddenNotices)); + } + else { + Zotero.Prefs.clear('hiddenNotices'); + } + } + else { + // Don't show id-less messages again for a day + _hiddenNoticesWithoutIDs.set(text, now + 86400); + } + }, 500); + } + catch (e) { + Zotero.logError(e); + } + } + + /** * Set the interval between repository queries * diff --git a/test/tests/schemaTest.js b/test/tests/schemaTest.js index 53b392d638..ade697d5d4 100644 --- a/test/tests/schemaTest.js +++ b/test/tests/schemaTest.js @@ -240,6 +240,159 @@ describe("Zotero.Schema", function() { }); + describe("Repository Check", function () { + describe("Notices", function () { + var win; + var server; + + before(async function () { + win = await loadZoteroPane(); + }); + + beforeEach(function () { + Zotero.HTTP.mock = sinon.FakeXMLHttpRequest; + server = sinon.fakeServer.create(); + server.autoRespond = true; + }); + + afterEach(function () { + Zotero.Prefs.clear('hiddenNotices'); + }); + + after(function () { + win.close(); + Zotero.HTTP.mock = null; + }); + + function createResponseWithMessage(message) { + server.respond(function (req) { + if (req.method != "POST" || !req.url.includes('/repo/updated')) { + return; + } + req.respond( + 200, + { + "Content-Type": "application/xml" + }, + '' + + '1630219842' + + message + + '' + ); + }); + } + + it("should show dialog if repo returns a message", async function () { + createResponseWithMessage( + `This is a warning` + ); + + var promise = waitForDialog(function (dialog) { + var html = dialog.document.documentElement.outerHTML; + assert.include(html, "This is a warning"); + }); + await Zotero.Schema.updateFromRepository(3); + await promise; + + // Don't show id-less message again for a day + var spy = sinon.spy(Zotero, 'debug'); + await Zotero.Schema.updateFromRepository(3); + assert.notEqual(spy.args.findIndex(x => { + return typeof x[0] == 'string' && x[0].startsWith("Not showing hidden"); + }), -1); + spy.restore(); + }); + + it("shouldn't show message with id again for 1 day even if not hidden", async function () { + var id = Zotero.Utilities.randomString(); + createResponseWithMessage( + `This is a warning` + ); + + var promise = waitForDialog(); + await Zotero.Schema.updateFromRepository(3); + await promise; + + // Make sure notice is hidden for 1 day + var hiddenNotices; + var tries = 0; + var ttl = 86400; + while (tries < 100) { + tries++; + hiddenNotices = Zotero.Prefs.get('hiddenNotices'); + if (!hiddenNotices) { + await Zotero.Promise.delay(10); + continue; + } + hiddenNotices = JSON.parse(hiddenNotices); + assert.property(hiddenNotices, id); + assert.approximately(hiddenNotices[id], Math.round(Date.now() / 1000) + ttl, 10); + break; + } + }); + + it("shouldn't show message with id again for 30 days", async function () { + var id = Zotero.Utilities.randomString(); + createResponseWithMessage( + `This is a warning` + ); + + var promise = waitForDialog(function (dialog) { + var doc = dialog.document; + var innerHTML = doc.documentElement.innerHTML; + assert.include(innerHTML, "This is a warning"); + assert.include(innerHTML, Zotero.getString('general.dontShowAgainFor', 30, 30)); + // Check "Don't show again" + doc.getElementById('checkbox').click(); + }); + await Zotero.Schema.updateFromRepository(3); + await promise; + + // Make sure notice is hidden for 30 days + var hiddenNotices; + var tries = 0; + var ttl = 30 * 86400; + while (tries < 100) { + tries++; + hiddenNotices = Zotero.Prefs.get('hiddenNotices'); + if (!hiddenNotices) { + await Zotero.Promise.delay(10); + continue; + } + hiddenNotices = JSON.parse(hiddenNotices); + assert.property(hiddenNotices, id); + assert.approximately(hiddenNotices[id], Math.round(Date.now() / 1000) + ttl, 10); + break; + } + }); + + it("shouldn't show message with id if before expiration", async function () { + var id = Zotero.Utilities.randomString(); + createResponseWithMessage( + `This is a warning` + ); + + // Set expiration for 30 days from now + var ttl = 30 * 86400; + Zotero.Prefs.set( + 'hiddenNotices', + JSON.stringify({ + [id]: Math.round(Date.now() / 1000) + ttl + }) + ); + + // Message should be hidden + var spy = sinon.spy(Zotero, 'debug'); + await Zotero.Schema.updateFromRepository(3); + assert.notEqual(spy.args.findIndex(x => { + return typeof x[0] == 'string' && x[0].startsWith("Not showing hidden"); + }), -1); + spy.restore(); + }); + }); + }); + + describe("#integrityCheck()", function () { before(function* () { yield resetDB({