1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2013, 2014, 2016 Synacor, Inc. 5 * 6 * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at: https://www.zimbra.com/license 9 * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15 10 * have been added to cover use of software over a computer network and provide for limited attribution 11 * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B. 12 * 13 * Software distributed under the License is distributed on an "AS IS" basis, 14 * WITHOUT WARRANTY OF ANY KIND, either express or implied. 15 * See the License for the specific language governing rights and limitations under the License. 16 * The Original Code is Zimbra Open Source Web Client. 17 * The Initial Developer of the Original Code is Zimbra, Inc. All rights to the Original Code were 18 * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015. 19 * 20 * All portions of the code are Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * @overview 26 * This file contains the search result class. 27 */ 28 29 /** 30 * Creates the search result 31 * @class 32 * This class represents a search result. 33 * 34 * @param {ZmSearch} search the search 35 */ 36 ZmSearchResult = function(search) { 37 if (!search) { return; } 38 this._results = {}; 39 this.search = search; 40 this.type = search.searchFor; 41 }; 42 43 ZmSearchResult.prototype.isZmSearchResult = true; 44 ZmSearchResult.prototype.toString = function() { return "ZmSearchResult"; }; 45 46 /** 47 * Gets the results. 48 * 49 * @param {constant} type the type 50 * @return {Array} an array of results 51 */ 52 ZmSearchResult.prototype.getResults = 53 function(type) { 54 55 type = type || this.type; 56 if (!this._results) { 57 // probably got an exception - return an empty list 58 return this._getResultsList(type); 59 } else if (this.search.idsOnly) { 60 return this._results; 61 } else { 62 // if we don't have results for the requested type, the search was probably for the wrong type 63 return this._results[type] ? this._results[type] : type && this._getResultsList(type); 64 } 65 }; 66 67 /** 68 * Gets the attribute. 69 * 70 * @param {String} name the attribute name 71 * @return {Object} the attribute 72 */ 73 ZmSearchResult.prototype.getAttribute = 74 function(name) { 75 return this._respEl ? this._respEl[name] : null; 76 }; 77 78 /** 79 * Sets the response. 80 * 81 * @private 82 */ 83 ZmSearchResult.prototype.set = 84 function(respEl) { 85 86 if (!this.search) { return; } 87 88 this._respEl = respEl; 89 90 // <match> objects are returned for autocomplete search, not items; let caller handle them 91 if (this.search.isAutocompleteSearch) { return; } 92 93 var foundType = {}; 94 var numTypes = 0; 95 var currentType, defaultType; 96 var isGalSearch = this.search.isGalSearch; 97 98 var _st = new Date(); 99 var count = 0; 100 if (isGalSearch || this.search.isCalResSearch) { 101 // process JS eval result for SearchGalRequest 102 currentType = defaultType = isGalSearch ? ZmItem.CONTACT : ZmItem.RESOURCE; 103 var data = isGalSearch ? respEl.cn : respEl.calresource; 104 if (data) { 105 if (!this._results[currentType]) { 106 // create list as needed - may invoke package load 107 this._results[currentType] = this._getResultsList(currentType); 108 } 109 for (var j = 0; j < data.length; j++) { 110 this._results[currentType].addFromDom(data[j]); 111 } 112 113 // manually sort gal results since server won't do it for us :( 114 if (isGalSearch) { 115 this._results[currentType].getArray().sort(ZmSearchResult._sortGalResults) 116 } 117 count = data.length; 118 } 119 } else if (this.search.idsOnly) { 120 this._results = respEl.hit || []; 121 return; 122 } else { 123 // process JS eval result for SearchResponse 124 var types = this.search.types.getArray(); 125 defaultType = types[0]; 126 127 // bug fix #44232 - resolve default type if none provided 128 if (!defaultType) { 129 var allTypes = AjxUtil.values(ZmList.NODE); 130 for (var i = 0; i < allTypes.length; i++) { 131 var t = allTypes[i]; 132 if (respEl[t]) { 133 defaultType = ZmList.ITEM_TYPE[t]; 134 if (types && types.length == 0) { 135 types = [defaultType]; 136 } 137 break; 138 } 139 } 140 } 141 142 if (!defaultType) { 143 var curApp = appCtxt.getCurrentAppName(); 144 var types = ZmApp.SEARCH_TYPES[curApp]; 145 defaultType = types && types.length && types[0]; 146 } 147 148 for (var i = 0; i < types.length; i++) { 149 var type = types[i]; 150 var data = respEl[ZmList.NODE[type]]; 151 152 // A chat isa message. Futz with the types to deal with this. 153 // (Eventually we'll avoid this problem by displying chat history in im app.) 154 if (!data && (type == ZmItem.MSG)) { 155 data = respEl["chat"]; 156 } 157 // Likewise, a Resource is a Contact. Futz with the types to deal with this. 158 if (!data && (type == ZmItem.RESOURCE)) { 159 data = respEl[ZmList.NODE[ZmItem.CONTACT]]; 160 } 161 162 // do a bunch of sanity checks 163 if (data && data.length) { 164 count += data.length; 165 if (!this._results[type]) { 166 // create list as needed - may invoke package load 167 this._results[type] = this._getResultsList(type); 168 } 169 for (var j = 0; j < data.length; j++) { 170 var item = data[j]; 171 item._type = type; 172 this._results[type].addFromDom(item); 173 } 174 175 if (!foundType[type]) { 176 foundType[type] = true; 177 numTypes++; 178 currentType = type; 179 } 180 } 181 } 182 } 183 if (!count && defaultType) { 184 this._results[defaultType] = this._getResultsList(defaultType); 185 } 186 if ((isGalSearch || this.search.isGalAutocompleteSearch) && this._results[ZmItem.CONTACT]) { 187 this._results[ZmItem.CONTACT].setIsGal(true); 188 } 189 if (this.search.isGalAutocompleteSearch) { 190 this.isTokenized = (this._respEl.tokenizeKey != null); 191 } 192 193 var _en = new Date(); 194 DBG.println(AjxDebug.DBG1, "TOTAL PARSE TIME for " + count + " NODES: " + (_en.getTime() - _st.getTime())); 195 196 currentType = currentType || defaultType; 197 if (numTypes <= 1) { 198 this.type = currentType; 199 } 200 201 return this.type; 202 }; 203 204 /** 205 * @private 206 */ 207 ZmSearchResult._sortGalResults = 208 function(a, b) { 209 var af = a.getFileAs && a.getFileAs().toLowerCase(); 210 var bf = b.getFileAs && b.getFileAs().toLowerCase(); 211 return af < bf ? -1 : (af > bf ? 1 : 0); 212 }; 213 214 ZmSearchResult.prototype._getResultsList = 215 function(type) { 216 217 if (type && typeof(ZmItem.RESULTS_LIST[type]) === "function") { 218 return ZmItem.RESULTS_LIST[type](this.search); 219 } else { 220 DBG.println( 221 AjxDebug.DBG1, 222 AjxMessageFormat.format( 223 "!type || ZmItem.RESULTS_LIST[type] !== function. Active app: {0}, type: {1}, searchFor: {2}.", 224 [appCtxt.getCurrentAppName(), type, this.search.searchFor] 225 ) 226 ); 227 return new ZmList(type, this.search); 228 } 229 230 }; 231 232