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({