summaryrefslogtreecommitdiffstats
path: root/mailnews/base/util/ABQueryUtils.jsm
blob: d4b694033025f96aca6d0db7c34a8e23fba0a3f4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/* 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/. */

/**
 * This file contains helper methods for dealing with addressbook search URIs.
 */

this.EXPORTED_SYMBOLS = ["getSearchTokens", "getModelQuery",
                         "modelQueryHasUserValue", "generateQueryURI",
                         "encodeABTermValue"];
Components.utils.import("resource://gre/modules/Services.jsm");

/**
 * Parse the multiword search string to extract individual search terms
 * (separated on the basis of spaces) or quoted exact phrases to search
 * against multiple fields of the addressbook cards.
 *
 * @param aSearchString The full search string entered by the user.
 *
 * @return an array of separated search terms from the full search string.
 */
function getSearchTokens(aSearchString) {
  let searchString = aSearchString.trim();
  if (searchString == "")
    return [];

  let quotedTerms = [];

  // Split up multiple search words to create a *foo* and *bar* search against
  // search fields, using the OR-search template from modelQuery for each word.
  // If the search query has quoted terms as "foo bar", extract them as is.
  let startIndex;
  while ((startIndex = searchString.indexOf('"')) != -1) {
    let endIndex = searchString.indexOf('"', startIndex + 1);
    if (endIndex == -1)
      endIndex = searchString.length;

    quotedTerms.push(searchString.substring(startIndex + 1, endIndex));
    let query = searchString.substring(0, startIndex);
    if (endIndex < searchString.length)
      query += searchString.substr(endIndex + 1);

    searchString = query.trim();
  }

  let searchWords = [];
  if (searchString.length != 0) {
    searchWords = quotedTerms.concat(searchString.split(/\s+/));
  } else {
    searchWords = quotedTerms;
  }

  return searchWords;
}

/**
 * For AB quicksearch or recipient autocomplete, get the normal or phonetic model
 * query URL part from prefs, allowing users to customize these searches.
 * @param aBasePrefName  the full pref name of default, non-phonetic model query,
 *                       e.g. mail.addr_book.quicksearchquery.format
 *                       If phonetic search is used, corresponding pref must exist:
 *                       e.g. mail.addr_book.quicksearchquery.format.phonetic
 * @return               depending on mail.addr_book.show_phonetic_fields pref,
 *                       the value of aBasePrefName or aBasePrefName + ".phonetic"
 */
function getModelQuery(aBasePrefName) {
  let modelQuery = "";
  if (Services.prefs.getComplexValue("mail.addr_book.show_phonetic_fields",
      Components.interfaces.nsIPrefLocalizedString).data == "true") {
    modelQuery = Services.prefs.getCharPref(aBasePrefName + ".phonetic");
  } else {
    modelQuery = Services.prefs.getCharPref(aBasePrefName);
  }
  // remove leading "?" to migrate existing customized values for mail.addr_book.quicksearchquery.format
  // todo: could this be done in a once-off migration at install time to avoid repetitive calls?
  if (modelQuery.startsWith("?"))
    modelQuery = modelQuery.slice(1);
  return modelQuery;
}

/**
 * Check if the currently used pref with the model query was customized by user.
 * @param aBasePrefName  the full pref name of default, non-phonetic model query,
 *                       e.g. mail.addr_book.quicksearchquery.format
 *                       If phonetic search is used, corresponding pref must exist:
 *                       e.g. mail.addr_book.quicksearchquery.format.phonetic
 * @return               true or false
 */
function modelQueryHasUserValue(aBasePrefName) {
  if (Services.prefs.getComplexValue("mail.addr_book.show_phonetic_fields",
      Components.interfaces.nsIPrefLocalizedString).data == "true")
    return Services.prefs.prefHasUserValue(aBasePrefName + ".phonetic");
  return Services.prefs.prefHasUserValue(aBasePrefName);
}

/*
 * Given a database model query and a list of search tokens,
 * return query URI.
 *
 * @param aModelQuery database model query
 * @param aSearchWords an array of search tokens.
 *
 * @return query URI.
 */
function generateQueryURI(aModelQuery, aSearchWords) {
  // If there are no search tokens, we simply return an empty string.
  if (!aSearchWords || aSearchWords.length == 0)
    return "";

  let queryURI = "";
  aSearchWords.forEach(searchWord =>
    queryURI += aModelQuery.replace(/@V/g, encodeABTermValue(searchWord)));

  // queryURI has all the (or(...)) searches, link them up with (and(...)).
  queryURI = "?(and" + queryURI + ")";

  return queryURI;
}


/**
 * Encode the string passed as value into an addressbook search term.
 * The '(' and ')' characters are special for the addressbook
 * search query language, but are not escaped in encodeURIComponent()
 * so must be done manually on top of it.
 */
function encodeABTermValue(aString) {
  return encodeURIComponent(aString).replace(/\(/g, "%28").replace(/\)/g, "%29");
}