diff options
Diffstat (limited to 'comm/mailnews/addrbook/modules/AddrBookMailingList.jsm')
-rw-r--r-- | comm/mailnews/addrbook/modules/AddrBookMailingList.jsm | 420 |
1 files changed, 420 insertions, 0 deletions
diff --git a/comm/mailnews/addrbook/modules/AddrBookMailingList.jsm b/comm/mailnews/addrbook/modules/AddrBookMailingList.jsm new file mode 100644 index 0000000000..31d16e93aa --- /dev/null +++ b/comm/mailnews/addrbook/modules/AddrBookMailingList.jsm @@ -0,0 +1,420 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at http://mozilla.org/MPL/2.0/. */ + +const EXPORTED_SYMBOLS = ["AddrBookMailingList"]; + +/* Prototype for mailing lists. A mailing list can appear as nsIAbDirectory + * or as nsIAbCard. Here we keep all relevant information in the class itself + * and fulfill each interface on demand. This will make more sense and be + * a lot neater once we stop using two XPCOM interfaces for one job. */ + +function AddrBookMailingList(uid, parent, name, nickName, description) { + this._uid = uid; + this._parent = parent; + this._name = name; + this._nickName = nickName; + this._description = description; +} +AddrBookMailingList.prototype = { + get asDirectory() { + let self = this; + return { + QueryInterface: ChromeUtils.generateQI(["nsIAbDirectory"]), + classID: Components.ID("{e96ee804-0bd3-472f-81a6-8a9d65277ad3}"), + + get readOnly() { + return self._parent._readOnly; + }, + get isRemote() { + return self._parent.isRemote; + }, + get isSecure() { + return self._parent.isSecure; + }, + get propertiesChromeURI() { + return "chrome://messenger/content/addressbook/abAddressBookNameDialog.xhtml"; + }, + get UID() { + return self._uid; + }, + get URI() { + return `${self._parent.URI}/${self._uid}`; + }, + get dirName() { + return self._name; + }, + set dirName(value) { + self._name = value; + }, + get listNickName() { + return self._nickName; + }, + set listNickName(value) { + self._nickName = value; + }, + get description() { + return self._description; + }, + set description(value) { + self._description = value; + }, + get isMailList() { + return true; + }, + get childNodes() { + return []; + }, + get childCards() { + let selectStatement = self._parent._dbConnection.createStatement( + "SELECT card FROM list_cards WHERE list = :list ORDER BY oid" + ); + selectStatement.params.list = self._uid; + let results = []; + while (selectStatement.executeStep()) { + results.push(self._parent.getCard(selectStatement.row.card)); + } + selectStatement.finalize(); + return results; + }, + get supportsMailingLists() { + return false; + }, + + search(query, string, listener) { + if (!listener) { + return; + } + if (!query) { + listener.onSearchFinished(Cr.NS_ERROR_FAILURE, true, null, ""); + return; + } + if (query[0] == "?") { + query = query.substring(1); + } + + let results = this.childCards; + + // Process the query string into a tree of conditions to match. + let lispRegexp = /^\((and|or|not|([^\)]*)(\)+))/; + let index = 0; + let rootQuery = { children: [], op: "or" }; + let currentQuery = rootQuery; + + while (true) { + let match = lispRegexp.exec(query.substring(index)); + if (!match) { + break; + } + index += match[0].length; + + if (["and", "or", "not"].includes(match[1])) { + // For the opening bracket, step down a level. + let child = { + parent: currentQuery, + children: [], + op: match[1], + }; + currentQuery.children.push(child); + currentQuery = child; + } else { + let [name, condition, value] = match[2].split(","); + currentQuery.children.push({ + name, + condition, + value: decodeURIComponent(value).toLowerCase(), + }); + + // For each closing bracket except the first, step up a level. + for (let i = match[3].length - 1; i > 0; i--) { + currentQuery = currentQuery.parent; + } + } + } + + results = results.filter(card => { + let properties = card._properties; + let matches = b => { + if ("condition" in b) { + let { name, condition, value } = b; + if (name == "IsMailList" && condition == "=") { + return value == "true"; + } + + if (!properties.has(name)) { + return condition == "!ex"; + } + if (condition == "ex") { + return true; + } + + let cardValue = properties.get(name).toLowerCase(); + switch (condition) { + case "=": + return cardValue == value; + case "!=": + return cardValue != value; + case "lt": + return cardValue < value; + case "gt": + return cardValue > value; + case "bw": + return cardValue.startsWith(value); + case "ew": + return cardValue.endsWith(value); + case "c": + return cardValue.includes(value); + case "!c": + return !cardValue.includes(value); + case "~=": + case "regex": + default: + return false; + } + } + if (b.op == "or") { + return b.children.some(bb => matches(bb)); + } + if (b.op == "and") { + return b.children.every(bb => matches(bb)); + } + if (b.op == "not") { + return !matches(b.children[0]); + } + return false; + }; + + return matches(rootQuery); + }, this); + + for (let card of results) { + listener.onSearchFoundCard(card); + } + listener.onSearchFinished(Cr.NS_OK, true, null, ""); + }, + addCard(card) { + if (this.readOnly) { + throw new Components.Exception( + "Directory is read-only", + Cr.NS_ERROR_FAILURE + ); + } + + if (!card.primaryEmail) { + return card; + } + if (!self._parent.hasCard(card)) { + card = self._parent.addCard(card); + } + let insertStatement = self._parent._dbConnection.createStatement( + "REPLACE INTO list_cards (list, card) VALUES (:list, :card)" + ); + insertStatement.params.list = self._uid; + insertStatement.params.card = card.UID; + insertStatement.execute(); + Services.obs.notifyObservers( + card, + "addrbook-list-member-added", + self._uid + ); + insertStatement.finalize(); + return card; + }, + deleteCards(cards) { + if (this.readOnly) { + throw new Components.Exception( + "Directory is read-only", + Cr.NS_ERROR_FAILURE + ); + } + + let deleteCardStatement = self._parent._dbConnection.createStatement( + "DELETE FROM list_cards WHERE list = :list AND card = :card" + ); + for (let card of cards) { + deleteCardStatement.params.list = self._uid; + deleteCardStatement.params.card = card.UID; + deleteCardStatement.execute(); + if (self._parent._dbConnection.affectedRows) { + Services.obs.notifyObservers( + card, + "addrbook-list-member-removed", + self._uid + ); + } + deleteCardStatement.reset(); + } + deleteCardStatement.finalize(); + }, + dropCard(card, needToCopyCard) { + if (this.readOnly) { + throw new Components.Exception( + "Directory is read-only", + Cr.NS_ERROR_FAILURE + ); + } + + if (needToCopyCard) { + card = self._parent.dropCard(card, true); + } + this.addCard(card); + Services.obs.notifyObservers( + card, + "addrbook-list-member-added", + self._uid + ); + }, + editMailListToDatabase(listCard) { + if (this.readOnly) { + throw new Components.Exception( + "Directory is read-only", + Cr.NS_ERROR_FAILURE + ); + } + + // Check if the new name is empty. + if (!self._name) { + throw new Components.Exception( + "Invalid mailing list name", + Cr.NS_ERROR_ILLEGAL_VALUE + ); + } + + // Check if the new name contains 2 spaces. + if (self._name.match(" ")) { + throw new Components.Exception( + "Invalid mailing list name", + Cr.NS_ERROR_ILLEGAL_VALUE + ); + } + + // Check if the new name contains the following special characters. + for (let char of ',;"<>') { + if (self._name.includes(char)) { + throw new Components.Exception( + "Invalid mailing list name", + Cr.NS_ERROR_ILLEGAL_VALUE + ); + } + } + + self._parent.saveList(self); + Services.obs.notifyObservers( + this, + "addrbook-list-updated", + self._parent.UID + ); + }, + hasMailListWithName(name) { + return false; + }, + getMailListFromName(name) { + return null; + }, + }; + }, + get asCard() { + let self = this; + return { + QueryInterface: ChromeUtils.generateQI(["nsIAbCard"]), + classID: Components.ID("{1143991d-31cd-4ea6-9c97-c587d990d724}"), + + get UID() { + return self._uid; + }, + get isMailList() { + return true; + }, + get mailListURI() { + return `${self._parent.URI}/${self._uid}`; + }, + + get directoryUID() { + return self._parent.UID; + }, + get firstName() { + return ""; + }, + get lastName() { + return self._name; + }, + get displayName() { + return self._name; + }, + set displayName(value) { + self._name = value; + }, + get primaryEmail() { + return ""; + }, + get emailAddresses() { + // NOT the members of this list. + return []; + }, + + generateName(generateFormat) { + return self._name; + }, + getProperty(name, defaultValue) { + switch (name) { + case "NickName": + return self._nickName; + case "Notes": + return self._description; + } + return defaultValue; + }, + setProperty(name, value) { + switch (name) { + case "NickName": + self._nickName = value; + break; + case "Notes": + self._description = value; + break; + } + }, + equals(card) { + return self._uid == card.UID; + }, + hasEmailAddress(emailAddress) { + return false; + }, + get properties() { + const entries = [ + ["DisplayName", this.displayName], + ["NickName", this.getProperty("NickName", "")], + ["Notes", this.getProperty("Notes", "")], + ]; + let props = []; + for (const [name, value] of entries) { + props.push({ + get name() { + return name; + }, + get value() { + return value; + }, + QueryInterface: ChromeUtils.generateQI(["nsIProperty"]), + }); + } + return props; + }, + get supportsVCard() { + return false; + }, + get vCardProperties() { + return null; + }, + translateTo(type) { + // Get nsAbCardProperty to do the work, the code is in C++ anyway. + let cardCopy = Cc[ + "@mozilla.org/addressbook/cardproperty;1" + ].createInstance(Ci.nsIAbCard); + cardCopy.UID = this.UID; + cardCopy.copy(this); + return cardCopy.translateTo(type); + }, + }; + }, +}; |