1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 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) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * Creates a filter rules object. 26 * @constructor 27 * @class 28 * This class represents a set of filter rules. The rules are maintained in a {@link AjxVector} 29 * and have an order. Each rule is a {@link ZmFilterRule}. Filter rules can be added and 30 * edited via a {@link ZmFilterRuleDialog}. 31 * 32 * @author Conrad Damon 33 * 34 * @param {String} accountName the name of the account this set of filter rules belongs to 35 * 36 * @extends ZmModel 37 */ 38 ZmFilterRules = function(accountName, outgoing) { 39 40 ZmModel.call(this, ZmEvent.S_FILTER); 41 42 this._vector = new AjxVector(); 43 this._ruleIdHash = {}; 44 this._ruleNameHash = {}; 45 this._initialized = false; 46 this._accountName = accountName; 47 this._outgoing = outgoing; 48 }; 49 50 ZmFilterRules.prototype = new ZmModel; 51 ZmFilterRules.prototype.constructor = ZmFilterRules; 52 53 ZmFilterRules.prototype.toString = 54 function() { 55 return "ZmFilterRules"; 56 }; 57 58 /** 59 * Adds a rule to the list. 60 * 61 * @param {ZmFilterRule} rule the rule to be added 62 * @param {ZmFilterRule} referenceRule the rule after which to add the new rule 63 * @param {AjxCallback} callback the callback 64 */ 65 ZmFilterRules.prototype.addRule = 66 function(rule, referenceRule, callback) { 67 DBG.println(AjxDebug.DBG3, "FILTER RULES: add rule '" + rule.name + "'"); 68 var index = referenceRule ? this._vector.indexOf(referenceRule) : 0; 69 this._insertRule(rule, index); 70 this._saveRules(index, true, callback); 71 }; 72 73 /** 74 * Removes a rule from the list. 75 * 76 * @param {ZmFilterRule} rule the rule to be removed 77 */ 78 ZmFilterRules.prototype.removeRule = 79 function(rule) { 80 if (!rule) { return; } 81 DBG.println(AjxDebug.DBG3, "FILTER RULES: remove rule '" + rule.name + "'"); 82 var index = this.getIndexOfRule(rule); 83 this._vector.removeAt(index); 84 delete this._ruleIdHash[rule.id]; 85 delete this._ruleNameHash[rule.name]; 86 this._deleteMode = true; 87 this._saveRules(index, true); 88 }; 89 90 /** 91 * Moves a rule up in the list. If the rule is the first in the list, it isn't moved. 92 * 93 * @param {ZmFilterRule} rule the rule to be moved 94 */ 95 ZmFilterRules.prototype.moveUp = 96 function(rule) { 97 if (!rule) { return; } 98 DBG.println(AjxDebug.DBG3, "FILTER RULES: move up rule '" + rule.name + "'"); 99 var index = this.getIndexOfRule(rule); 100 if (index == 0) { return; } 101 102 var prevRule = this._vector.removeAt(index - 1); 103 this._insertRule(prevRule, index); 104 this._saveRules(index - 1, true); 105 }; 106 107 /** 108 * Moves a rule down in the list. If the rule is the last in the list, it isn't moved. 109 * 110 * @param {ZmFilterRule} rule the rule to be moved 111 */ 112 ZmFilterRules.prototype.moveDown = 113 function(rule) { 114 if (!rule) { return; } 115 DBG.println(AjxDebug.DBG3, "FILTER RULES: move down rule '" + rule.name + "'"); 116 var index = this.getIndexOfRule(rule); 117 if (index >= (this._vector.size() - 1)) { return; } 118 119 var nextRule = this._vector.removeAt(index + 1); 120 this._insertRule(nextRule, index); 121 this._saveRules(index + 1, true); 122 }; 123 124 /** 125 * Moves a rule to the bottom of the list. If the rule is the last in the list, it isn't moved. 126 * @param rule {ZmFilterRule} rule the rule to be moved 127 * @param skipSave {boolean} true to not save 128 */ 129 ZmFilterRules.prototype.moveToBottom = 130 function(rule, skipSave) { 131 if (!rule) { return; } 132 var index = this.getIndexOfRule(rule); 133 if (index >= (this._vector.size() - 1)) { return; } 134 135 while (index < this._vector.size() -1) { 136 var nextRule = this._vector.removeAt(index+1); 137 this._insertRule(nextRule, index); 138 index++; 139 } 140 if (!skipSave) { 141 this._saveRules(index, true); 142 } 143 }; 144 145 /** 146 * Marks a rule as active/inactive. 147 * 148 * @param {ZmFilterRule} rule the rule to mark active/inactive 149 * @param {Boolean} active if <code>true</code>, the rule is marked active 150 */ 151 ZmFilterRules.prototype.setActive = 152 function(rule, active) { 153 if (!rule) { return; } 154 DBG.println(AjxDebug.DBG3, "FILTER RULES: set active rule '" + rule.name + "', " + active); 155 rule.active = active; 156 this._saveRules(null, false); 157 }; 158 159 // utility methods 160 161 /** 162 * Gets the number of rules in the list. 163 * 164 * @return {int} the number of rules 165 */ 166 ZmFilterRules.prototype.getNumberOfRules = 167 function() { 168 return this._vector.size(); 169 }; 170 171 /** 172 * Gets the active rules in the list. 173 * 174 * @return {AjxVector} the active rules 175 */ 176 ZmFilterRules.prototype.getActiveRules = 177 function() { 178 return this._vector.sub(function(rule){return !rule.active}); 179 }; 180 181 /** 182 * Gets the numeric index of the rule in the list. 183 * 184 * @param {ZmFilterRule} rule a rule 185 * @return {int} the index 186 */ 187 ZmFilterRules.prototype.getIndexOfRule = 188 function(rule) { 189 return this._vector.indexOf(rule); 190 }; 191 192 /** 193 * Gets a rule based on its index. 194 * 195 * @param {int} index the index 196 * @return {ZmFilterRule} the rule 197 */ 198 ZmFilterRules.prototype.getRuleByIndex = 199 function(index) { 200 return this._vector.get(index); 201 }; 202 203 /** 204 * Gets a rule based on its ID. 205 * 206 * @param {String} id the rule ID 207 * @return {ZmFilterRule} the rule 208 */ 209 ZmFilterRules.prototype.getRuleById = 210 function(id) { 211 return this._ruleIdHash[id]; 212 }; 213 214 /** 215 * Gets a rule by name. 216 * 217 * @param {String} name the rule name 218 * @return {ZmFilterRule} the rule 219 */ 220 ZmFilterRules.prototype.getRuleByName = 221 function(name) { 222 return this._ruleNameHash[name]; 223 }; 224 225 ZmFilterRules.prototype.getOutgoing = 226 function(name) { 227 return this._outgoing 228 }; 229 230 /** 231 * Loads the rules from the server. 232 * 233 * @param {Boolean} force if <code>true</code>, get rules from server 234 * @param {AjxCallback} callback the callback 235 */ 236 ZmFilterRules.prototype.loadRules = 237 function(force, callback) { 238 // return cache? 239 if (this._initialized && !force) { 240 if (callback) { 241 callback.run(new ZmCsfeResult(this._vector)); 242 return; 243 } 244 return this._vector; 245 } 246 247 // fetch from server: 248 DBG.println(AjxDebug.DBG3, "FILTER RULES: load rules"); 249 var params = { 250 soapDoc: AjxSoapDoc.create(this._outgoing ? "GetOutgoingFilterRulesRequest" : "GetFilterRulesRequest", "urn:zimbraMail"), 251 asyncMode: true, 252 callback: (new AjxCallback(this, this._handleResponseLoadRules, [callback])), 253 accountName:this._accountName 254 }; 255 appCtxt.getAppController().sendRequest(params); 256 }; 257 258 ZmFilterRules.prototype._handleResponseLoadRules = 259 function(callback, result) { 260 this._vector.removeAll(); 261 this._ruleIdHash = {}; 262 this._ruleNameHash = {}; 263 264 var r = result.getResponse(); 265 var resp = this._outgoing ? r.GetOutgoingFilterRulesResponse : r.GetFilterRulesResponse; 266 var children = resp.filterRules[0].filterRule; 267 if (children) { 268 for (var i = 0; i < children.length; i++) { 269 var ruleNode = children[i]; 270 var rule = new ZmFilterRule(ruleNode.name, ruleNode.active, ruleNode.filterActions[0], ruleNode.filterTests[0]); 271 this._insertRule(rule); 272 } 273 } 274 275 this._initialized = true; 276 277 if (callback) { 278 result.set(this._vector); 279 callback.run(result); 280 } else { 281 return this._vector; 282 } 283 }; 284 285 /** 286 * Public method to save the rules to the server. 287 * 288 * @param {int} index the index of rule to select in list after save 289 * @param {Boolean} notify if <code>true</code>, notify listeners of change event 290 * @param {AjxCallback} callback the callback 291 * 292 * @public 293 */ 294 ZmFilterRules.prototype.saveRules = 295 function(index, notify, callback) { 296 this._saveRules(index, notify, callback); 297 }; 298 299 /** 300 * Saves the rules to the server. 301 * 302 * @param {int} index the index of rule to select in list after save 303 * @param {Boolean} notify if <code>true</code>, notify listeners of change event 304 * @param {AjxCallback} callback the callback 305 * 306 * @private 307 */ 308 ZmFilterRules.prototype._saveRules = 309 function(index, notify, callback) { 310 var requestKey = this._outgoing ? "ModifyOutgoingFilterRulesRequest" : "ModifyFilterRulesRequest"; 311 var jsonObj = {}; 312 jsonObj[requestKey] = {_jsns:"urn:zimbraMail"}; 313 314 var request = jsonObj[requestKey]; 315 316 var rules = this._vector.getArray(); 317 if (rules.length > 0) { 318 request.filterRules = [{filterRule:[]}]; 319 var filterRuleObj = request.filterRules[0].filterRule; 320 321 for (var i = 0; i < rules.length; i++) { 322 var r = rules[i]; 323 var ruleObj = { 324 active: r.active, 325 name: r.name, 326 filterActions: [], 327 filterTests: [] 328 }; 329 ruleObj.filterActions.push(r.actions); 330 ruleObj.filterTests.push(r.conditions); 331 filterRuleObj.push(ruleObj); 332 } 333 } else { 334 request.filterRules = {}; 335 } 336 337 var params = { 338 jsonObj: jsonObj, 339 asyncMode: true, 340 callback: (new AjxCallback(this, this._handleResponseSaveRules, [index, notify, callback])), 341 errorCallback: (new AjxCallback(this, this._handleErrorSaveRules)), 342 accountName: this._accountName 343 }; 344 appCtxt.getAppController().sendRequest(params); 345 }; 346 347 ZmFilterRules.prototype._handleResponseSaveRules = 348 function(index, notify, callback, result) { 349 if (notify) { 350 this._notify(ZmEvent.E_MODIFY, {index: index}); 351 } 352 353 if(this._deleteMode){ 354 appCtxt.setStatusMsg(ZmMsg.filtersDeleted); 355 this._deleteMode = false; 356 } 357 358 else if(appCtxt.getFilterRuleDialog().isEditMode()){ 359 appCtxt.setStatusMsg(ZmMsg.filtersEdited); 360 } 361 else{ 362 appCtxt.setStatusMsg(ZmMsg.filtersSaved); 363 } 364 365 if (callback) { 366 callback.run(result); 367 } 368 }; 369 370 /** 371 * The save failed. Show an error dialog. 372 * @param {AjxException} ex the exception 373 * 374 * @private 375 */ 376 ZmFilterRules.prototype._handleErrorSaveRules = 377 function(ex) { 378 if (ex.code == ZmCsfeException.SVC_PARSE_ERROR || 379 ex.code == ZmCsfeException.SVC_INVALID_REQUEST) 380 { 381 var msgDialog = appCtxt.getMsgDialog(); 382 msgDialog.setMessage([ZmMsg.filterError, " ", AjxStringUtil.htmlEncode(ex.msg)].join(""), DwtMessageDialog.CRITICAL_STYLE); 383 msgDialog.popup(); 384 //only reload rules if the filter rule dialog is not popped up or if a new rule is being added 385 //get index for refreshing list view 386 if (!appCtxt.getFilterRuleDialog() || !appCtxt.getFilterRuleDialog().isPoppedUp() || !appCtxt.getFilterRuleDialog().isEditMode()) { 387 var filterRulesController = ZmPreferencesApp.getFilterRulesController(this._outgoing); 388 var sel = filterRulesController.getListView() ? filterRulesController.getListView().getSelection()[0] : null; 389 var index = sel ? this.getIndexOfRule(sel) - 1: null; //new filter is inserted into vector, subtract 1 to get the selected index 390 var respCallback = new AjxCallback(this, this._handleResponseHandleErrorSaveRules, [index]); 391 this.loadRules(true, respCallback); 392 } 393 return true; 394 } 395 return false; 396 }; 397 398 // XXX: the caller should probably be the one doing this 399 ZmFilterRules.prototype._handleResponseHandleErrorSaveRules = 400 function(index) { 401 var prefController = AjxDispatcher.run("GetPrefController"); 402 var prefsView = prefController.getPrefsView(); 403 var section = ZmPref.getPrefSectionWithPref(ZmSetting.FILTERS); 404 if (section && prefsView && prefsView.getView(section.id)) { 405 var filterRulesController = ZmPreferencesApp.getFilterRulesController(this._outgoing); 406 filterRulesController.resetListView(index); 407 } 408 }; 409 410 /** 411 * Inserts a rule into the internal vector. Adds to the end if no index is given. 412 * 413 * @param {ZmFilterRule} rule the rule to insert 414 * @param {int} index the index at which to insert 415 * 416 * @private 417 */ 418 ZmFilterRules.prototype._insertRule = 419 function(rule, index) { 420 this._vector.add(rule, index); 421 this._ruleIdHash[rule.id] = rule; 422 this._ruleNameHash[rule.name] = rule; 423 }; 424 425 /** 426 * Public method to insert rule into internval vectors. Adds to the end if no index is given. 427 * 428 * @param {ZmFilterRule} rule the rule to insert 429 * @param {int} index the index at which to insert 430 * 431 * @public 432 */ 433 ZmFilterRules.prototype.insertRule = 434 function(rule, index) { 435 this._insertRule(rule, index); 436 };