From 1268ac94441553bca0907fef522a94a4969d7cf7 Mon Sep 17 00:00:00 2001 From: Michael M Slusarz Date: Sat, 17 Jan 2009 16:47:29 -0700 Subject: [PATCH] Update autocomplete code. --- kronolith/js/autocomplete.js | 1 + kronolith/js/src/autocomplete.js | 465 +++++++++++++++++++++++++++ kronolith/lib/Imple.php | 2 - kronolith/lib/Imple/ContactAutoCompleter.php | 219 ++++--------- 4 files changed, 530 insertions(+), 157 deletions(-) create mode 100644 kronolith/js/autocomplete.js create mode 100644 kronolith/js/src/autocomplete.js diff --git a/kronolith/js/autocomplete.js b/kronolith/js/autocomplete.js new file mode 100644 index 000000000..9e3ea50e7 --- /dev/null +++ b/kronolith/js/autocomplete.js @@ -0,0 +1 @@ +var Autocompleter={};Autocompleter.Base=Class.create({baseInitialize:function(b,c,a){this.element=$(b);this.update=$(c).hide();this.active=this.changed=this.hasFocus=false;this.entryCount=this.index=0;this.observer=null;this.oldval=$F(this.element);this.options=Object.extend({paramName:this.element.name,tokens:[],frequency:0.4,minChars:1,onHide:this._onHide.bind(this),onShow:this._onShow.bind(this)},(this._setOptions?this._setOptions(a):(a||{})));if(!this.options.tokens.include("\n")){this.options.tokens.push("\n")}this.element.writeAttribute("autocomplete","off").observe("blur",this._onBlur.bindAsEventListener(this)).observe(Prototype.Browser.Gecko?"keypress":"keydown",this._onKeyPress.bindAsEventListener(this))},_onShow:function(a,e){var d,b=e.getStyle("position");if(!b||b=="absolute"){d=(Prototype.Browser.IE)?a.cumulativeScrollOffset():[0];e.setStyle({position:"absolute"}).clonePosition(a,{setHeight:false,offsetTop:a.offsetHeight,offsetLeft:d[0]})}new Effect.Appear(e,{duration:0.15})},_onHide:function(a,b){new Effect.Fade(b,{duration:0.15})},show:function(){if(!this.update.visible()){this.options.onShow(this.element,this.update)}if(Prototype.Browser.IE&&!this.iefix&&this.update.getStyle("position")=="absolute"){this.iefix=new Element("IFRAME",{src:"javascript:false;",frameborder:0,scrolling:"no"}).setStyle({position:"absolute",filter:"progid:DXImageTransform.Microsoft.Alpha(opactiy=0)",zIndex:1}).hide();this.update.setStyle({zIndex:2}).insert({after:this.iefix})}if(this.iefix){this._fixIEOverlapping.bind(this).delay(0.05)}},_fixIEOverlapping:function(){this.iefix.clonePosition(this.update).show()},hide:function(){this.stopIndicator();if(this.update.visible()){this.options.onHide(this.element,this.update);if(this.iefix){this.iefix.hide()}}},startIndicator:function(){if(this.options.indicator){$(this.options.indicator).show()}},stopIndicator:function(){if(this.options.indicator){$(this.options.indicator).hide()}},_onKeyPress:function(a){if(this.active){switch(a.keyCode){case Event.KEY_TAB:case Event.KEY_RETURN:this.selectEntry();a.stop();return;case Event.KEY_ESC:this.hide();this.active=false;a.stop();return;case Event.KEY_LEFT:case Event.KEY_RIGHT:return;case Event.KEY_UP:case Event.KEY_DOWN:if(a.keyCode==Event.KEY_UP){this.markPrevious()}else{this.markNext()}this.render();a.stop();return}}else{switch(a.keyCode){case 0:if(!Prototype.Browser.WebKit){break}case Event.KEY_TAB:case Event.KEY_RETURN:return}}this.changed=this.hasFocus=true;if(this.observer){clearTimeout(this.observer)}this.observer=this.onObserverEvent.bind(this).delay(this.options.frequency)},_onHover:function(c){var b=c.findElement("LI"),a=b.readAttribute("acIndex");if(this.index!=a){this.index=a;this.render()}c.stop()},_onClick:function(a){this.index=a.findElement("LI").readAttribute("acIndex");this.selectEntry()},_onBlur:function(a){this.hide.bind(this).delay(0.25);this.active=this.hasFocus=false},render:function(){var a=0;if(this.entryCount){this.update.down().childElements().each(function(b){[b].invoke(this.index==a++?"addClassName":"removeClassName","selected")},this);if(this.hasFocus){this.show();this.active=true}}else{this.active=false;this.hide()}},markPrevious:function(){if(this.index){--this.index}else{this.index=this.entryCount-1}this.getEntry(this.index).scrollIntoView(true)},markNext:function(){if(this.index#{1}")))});this.update.update(c);this.entryCount=e.size();c.childElements().each(this.addObservers.bind(this));this.stopIndicator();this.index=0;if(this.entryCount==1&&this.options.autoSelect){this.selectEntry()}else{this.render()}}},addObservers:function(a){$(a).observe("mouseover",this._onHover.bindAsEventListener(this)).observe("click",this._onClick.bindAsEventListener(this))},onObserverEvent:function(){this.changed=false;if(this.getToken().length>=this.options.minChars){this.getUpdatedChoices()}else{this.active=false;this.hide()}this.oldval=$F(this.element)},getToken:function(){var a=this.getTokenBounds();return $F(this.element).substring(a[0],a[1]).strip()},getTokenBounds:function(){var j,e,f,c,d,g,m=this.options.tokens,k=$F(this.element),h=k.length,b=-1,a=Math.min(h,this.oldval.length);if(k.strip().empty()){return[-1,0]}j=a;for(e=0;eb){b=g}g=k.indexOf(m[f],j+d);if(g!=-1&&g=d.partialChars&&d.partialSearch&&(d.fullSearch||/\s/.test(e.substr(f-1,1)))))){++a;return true}return false},this)}}); \ No newline at end of file diff --git a/kronolith/js/src/autocomplete.js b/kronolith/js/src/autocomplete.js new file mode 100644 index 000000000..dfc925c80 --- /dev/null +++ b/kronolith/js/src/autocomplete.js @@ -0,0 +1,465 @@ +/** + * autocomplete.js - A javascript library which implements autocomplete. + * Requires prototype.js v1.6.0.2+ and scriptaculous v1.8.0+ (effects.js) + * + * Adapted from script.aculo.us controls.js v1.8.0 + * (c) 2005-2007 Thomas Fuchs, Ivan Krstic, and Jon Tirsen + * Contributors: Richard Livsey, Rahul Bhargava, Rob Wills + * http://script.aculo.us/ + * + * The original script was freely distributable under the terms of an + * MIT-style license. + * + * Copyright 2007-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (GPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/gpl.html. + */ + +var Autocompleter = {}; +Autocompleter.Base = Class.create({ + baseInitialize: function(element, update, options) + { + this.element = $(element); + this.update = $(update).hide(); + this.active = this.changed = this.hasFocus = false; + this.entryCount = this.index = 0; + this.observer = null; + this.oldval = $F(this.element); + + this.options = Object.extend({ + paramName: this.element.name, + tokens: [], + frequency: 0.4, + minChars: 1, + onHide: this._onHide.bind(this), + onShow: this._onShow.bind(this) + }, (this._setOptions ? this._setOptions(options) : (options || {}))); + + // Force carriage returns as token delimiters anyway + if (!this.options.tokens.include('\n')) { + this.options.tokens.push('\n'); + } + + this.element.writeAttribute('autocomplete', 'off').observe("blur", this._onBlur.bindAsEventListener(this)).observe(Prototype.Browser.Gecko ? "keypress" : "keydown", this._onKeyPress.bindAsEventListener(this)); + }, + + _onShow: function(elt, update) + { + var c, p = update.getStyle('position'); + if (!p || p == 'absolute') { + // Temporary fix for Bug #7074 - Fixed as of prototypejs 1.6.0.3 + c = (Prototype.Browser.IE) ? elt.cumulativeScrollOffset() : [ 0 ]; + update.setStyle({ position: 'absolute' }).clonePosition(elt, { + setHeight: false, + offsetTop: elt.offsetHeight, + offsetLeft: c[0] + }); + } + new Effect.Appear(update, { duration: 0.15 }); + }, + + _onHide: function(elt, update) + { + new Effect.Fade(update, { duration: 0.15 }); + }, + + show: function() + { + if (!this.update.visible()) { + this.options.onShow(this.element, this.update); + } + + if (Prototype.Browser.IE && + !this.iefix && + this.update.getStyle('position') == 'absolute') { + this.iefix = new Element('IFRAME', { src: 'javascript:false;', frameborder: 0, scrolling: 'no' }).setStyle({ position: 'absolute', filter: 'progid:DXImageTransform.Microsoft.Alpha(opactiy=0)', zIndex: 1 }).hide(); + this.update.setStyle({ zIndex: 2 }).insert({ after: this.iefix }); + } + + if (this.iefix) { + this._fixIEOverlapping.bind(this).delay(0.05); + } + }, + + _fixIEOverlapping: function() + { + this.iefix.clonePosition(this.update).show(); + }, + + hide: function() + { + this.stopIndicator(); + if (this.update.visible()) { + this.options.onHide(this.element, this.update); + if (this.iefix) { + this.iefix.hide(); + } + } + }, + + startIndicator: function() + { + if (this.options.indicator) { + $(this.options.indicator).show(); + } + }, + + stopIndicator: function() + { + if (this.options.indicator) { + $(this.options.indicator).hide(); + } + }, + + _onKeyPress: function(e) + { + if (this.active) { + switch (e.keyCode) { + case Event.KEY_TAB: + case Event.KEY_RETURN: + this.selectEntry(); + e.stop(); + return; + + case Event.KEY_ESC: + this.hide(); + this.active = false; + e.stop(); + return; + + case Event.KEY_LEFT: + case Event.KEY_RIGHT: + return; + + case Event.KEY_UP: + case Event.KEY_DOWN: + if (e.keyCode == Event.KEY_UP) { + this.markPrevious(); + } else { + this.markNext(); + } + this.render(); + e.stop(); + return; + } + } else { + switch (e.keyCode) { + case 0: + if (!Prototype.Browser.WebKit) { + break; + } + // Fall through to below case + // + case Event.KEY_TAB: + case Event.KEY_RETURN: + return; + } + } + + this.changed = this.hasFocus = true; + + if (this.observer) { + clearTimeout(this.observer); + } + this.observer = this.onObserverEvent.bind(this).delay(this.options.frequency); + }, + + _onHover: function(e) + { + var elt = e.findElement('LI'), + index = elt.readAttribute('acIndex'); + if (this.index != index) { + this.index = index; + this.render(); + } + e.stop(); + }, + + _onClick: function(e) + { + this.index = e.findElement('LI').readAttribute('acIndex'); + this.selectEntry(); + }, + + _onBlur: function(e) + { + // Needed to make click events work + this.hide.bind(this).delay(0.25); + this.active = this.hasFocus = false; + }, + + render: function() + { + var i = 0; + + if (this.entryCount) { + this.update.down().childElements().each(function(e) { + [ e ].invoke(this.index == i++ ? 'addClassName' : 'removeClassName', 'selected'); + }, this); + if (this.hasFocus) { + this.show(); + this.active = true; + } + } else { + this.active = false; + this.hide(); + } + }, + + markPrevious: function() + { + if (this.index) { + --this.index; + } else { + this.index = this.entryCount - 1; + } + this.getEntry(this.index).scrollIntoView(true); + }, + + markNext: function() + { + if (this.index < this.entryCount - 1) { + ++this.index; + } else { + this.index = 0; + } + this.getEntry(this.index).scrollIntoView(false); + }, + + getEntry: function(index) + { + return this.update.down().childElements()[index]; + }, + + selectEntry: function() + { + this.active = false; + this.updateElement(this.getEntry(this.index)); + this.hide(); + }, + + updateElement: function(elt) + { + var bounds, newValue, nodes, whitespace, v, + o = this.options, + value = ''; + + if (o.updateElement) { + o.updateElement(elt); + return; + } + + if (o.select) { + nodes = $(elt).select('.' + o.select) || []; + if (nodes.size()) { + value = nodes[0].collectTextNodes(o.select); + } + } else { + value = elt.collectTextNodesIgnoreClass('informal'); + } + + bounds = this.getTokenBounds(); + if (bounds[0] != -1) { + v = $F(this.element); + newValue = v.substr(0, bounds[0]); + whitespace = v.substr(bounds[0]).match(/^\s+/); + if (whitespace) { + newValue += whitespace[0]; + } + this.element.setValue(newValue + value + v.substr(bounds[1])); + } else { + this.element.setValue(value); + } + this.element.focus(); + + if (o.afterUpdateElement) { + o.afterUpdateElement(this.element, elt); + } + + this.oldval = $F(this.element); + }, + + updateChoices: function(choices) + { + var li, re, ul, + i = 0; + + if (!this.changed && this.hasFocus) { + li = new Element('LI'); + ul = new Element('UL'); + re = new RegExp("(" + this.getToken() + ")", "i"); + + choices.each(function(n) { + ul.insert(li.cloneNode(false).writeAttribute('acIndex', i++).update(n.gsub(re, '#{1}'))); + }); + + this.update.update(ul); + this.entryCount = choices.size(); + ul.childElements().each(this.addObservers.bind(this)); + + this.stopIndicator(); + this.index = 0; + + if (this.entryCount == 1 && this.options.autoSelect) { + this.selectEntry(); + } else { + this.render(); + } + } + }, + + addObservers: function(elt) + { + $(elt).observe("mouseover", this._onHover.bindAsEventListener(this)).observe("click", this._onClick.bindAsEventListener(this)); + }, + + onObserverEvent: function() + { + this.changed = false; + if (this.getToken().length >= this.options.minChars) { + this.getUpdatedChoices(); + } else { + this.active = false; + this.hide(); + } + this.oldval = $F(this.element); + }, + + getToken: function() + { + var bounds = this.getTokenBounds(); + return $F(this.element).substring(bounds[0], bounds[1]).strip(); + }, + + getTokenBounds: function() + { + var diff, i, index, l, offset, tp, + t = this.options.tokens, + value = $F(this.element), + nextTokenPos = value.length, + prevTokenPos = -1, + boundary = Math.min(nextTokenPos, this.oldval.length); + + if (value.strip().empty()) { + return [ -1, 0 ]; + } + + diff = boundary; + for (i = 0; i < boundary; ++i) { + if (value[i] != this.oldval[i]) { + diff = i; + break; + } + } + + offset = (diff == this.oldval.length ? 1 : 0); + + for (index = 0, l = t.length; index < l; ++index) { + tp = value.lastIndexOf(t[index], diff + offset - 1); + if (tp > prevTokenPos) { + prevTokenPos = tp; + } + tp = value.indexOf(t[index], diff + offset); + if (tp != -1 && tp < nextTokenPos) { + nextTokenPos = tp; + } + } + return [ prevTokenPos + 1, nextTokenPos ]; + } +}); + +Ajax.Autocompleter = Class.create(Autocompleter.Base, { + initialize: function(element, update, url, options) + { + this.baseInitialize(element, update, options); + this.options = Object.extend(this.options, { + asynchronous: true, + onComplete: this._onComplete.bind(this), + defaultParams: $H(this.options.parameters) + }); + this.url = url; + this.cache = $H(); + }, + + getUpdatedChoices: function() + { + var p, + o = this.options, + t = this.getToken(), + c = this.cache.get(t); + + if (c) { + this.updateChoices(c); + } else { + p = Object.clone(o.defaultParams); + this.startIndicator(); + p.set(o.paramName, t); + o.parameters = p.toQueryString(); + new Ajax.Request(this.url, o); + } + }, + + _onComplete: function(request) + { + this.updateChoices(this.cache.set(this.getToken(), request.responseJSON)); + } +}); + +Autocompleter.Local = Class.create(Autocompleter.Base, { + initialize: function(element, update, arr, options) + { + this.baseInitialize(element, update, options); + this.options.arr = arr; + }, + + getUpdatedChoices: function() + { + this.updateChoices(this._selector()); + }, + + _setOptions: function(options) + { + return Object.extend({ + choices: 10, + partialSearch: true, + partialChars: 2, + ignoreCase: true, + fullSearch: false + }, options || {}); + }, + + _selector: function() + { + var entry = this.getToken(), + entry_len = entry.length, + i = 0, + o = this.options; + + if (o.ignoreCase) { + entry = entry.toLowerCase(); + } + + return o.arr.findAll(function(t) { + if (i == o.choices) { + throw $break; + } + + if (o.ignoreCase) { + t = t.toLowerCase(); + } + t = t.unescapeHTML(); + + var pos = t.indexOf(entry); + if (pos != -1 && + ((pos == 0 && t.length != entry_len) || + (entry_len >= o.partialChars && + o.partialSearch && + (o.fullSearch || /\s/.test(t.substr(pos - 1, 1)))))) { + ++i; + return true; + } + return false; + }, this); + } +}); diff --git a/kronolith/lib/Imple.php b/kronolith/lib/Imple.php index bdcff3983..87ee3289b 100644 --- a/kronolith/lib/Imple.php +++ b/kronolith/lib/Imple.php @@ -65,9 +65,7 @@ class Imple { function attach() { Horde::addScriptFile('prototype.js', 'horde', true); - Horde::addScriptFile('builder.js', 'horde', true); Horde::addScriptFile('effects.js', 'horde', true); - Horde::addScriptFile('controls.js', 'horde', true); } /** diff --git a/kronolith/lib/Imple/ContactAutoCompleter.php b/kronolith/lib/Imple/ContactAutoCompleter.php index 2e34a8674..49967eb32 100644 --- a/kronolith/lib/Imple/ContactAutoCompleter.php +++ b/kronolith/lib/Imple/ContactAutoCompleter.php @@ -1,31 +1,15 @@ * @package Kronolith */ -class Imple_ContactAutoCompleter extends Imple { - - /** - * onShow javascript code. - * - * @var string - */ - var $_onshow = ', onShow: function(e, u){ if(!u.style.position || u.style.position==\'absolute\') { u.style.position = \'absolute\'; Position.clone(e, u, { setHeight: false, offsetTop: e.offsetHeight }); } Effect.Appear(u,{duration:0.15, beforeSetup:function(effect) { effect.element.setOpacity(effect.options.from); effect.element.show(); u.style.height = Math.min(u.offsetHeight, ((window.innerHeight ? window.innerHeight : document.body.clientHeight) - Position.page(e)[1] - e.offsetHeight - 10)) + \'px\'; u.style.overflow = \'auto\'; } }); }'; - +class Imple_ContactAutoCompleter extends Imple +{ /** * Constructor. * @@ -35,7 +19,7 @@ class Imple_ContactAutoCompleter extends Imple { * 'resultsId' => TODO (optional) * */ - function Imple_ContactAutoCompleter($params) + public function __construct($params) { if (empty($params['triggerId'])) { $params['triggerId'] = $this->_randomid(); @@ -44,17 +28,32 @@ class Imple_ContactAutoCompleter extends Imple { $params['resultsId'] = $params['triggerId'] . '_results'; } - parent::Imple($params); + parent::__construct($params); } /** * Attach the Imple object to a javascript event. */ - function attach() + public function attach() { parent::attach(); - $url = Horde::url($GLOBALS['registry']->get('webroot', 'kronolith') . '/imple.php?imple=ContactAutoCompleter/input=' . rawurlencode($this->_params['triggerId']), true); - Kronolith::addInlineScript('Event.observe(window, "load", function() { new Ajax.Autocompleter("' . $this->_params['triggerId'] . '", "' . $this->_params['resultsId'] . '", "' . $url . '", { tokens: ",", indicator: "' . $this->_params['triggerId'] . '_loading_img"' . $this->_onshow . ', afterUpdateElement: function(f, t) { if (f.value.lastIndexOf(";") != (f.value.length - 1)) { f.value += ", "; } } }); });'); + Horde::addScriptFile('autocomplete.js', 'kronolith', true); + + $params = array( + '"' . $this->_params['triggerId'] . '"', + '"' . $this->_params['resultsId'] . '"', + '"' . Horde::url($GLOBALS['registry']->get('webroot', 'kronolith') . '/imple.php?imple=ContactAutoCompleter/input=' . rawurlencode($this->_params['triggerId']), true) . '"' + ); + + $js_params = array( + 'tokens: [",", ";"]', + 'indicator: "' . $this->_params['triggerId'] . '_loading_img"', + 'afterUpdateElement: function(f, t) { if (!f.value.endsWith(";")) { f.value += ","; } f.value += " "; }' + ); + + $params[] = '{' . implode(',', $js_params) . '}'; + + Kronolith::addInlineScript('new Ajax.Autocompleter(' . implode(',', $params) . ')'); } /** @@ -64,63 +63,52 @@ class Imple_ContactAutoCompleter extends Imple { * * @return string TODO */ - function handle($args) + public function handle($args) { // Avoid errors if 'input' isn't set and short-circuit empty searches. if (empty($args['input']) || !($input = Util::getFormData($args['input']))) { - return '
    '; - } - - $results = $this->expandAddresses($input, true); - if (is_a($results, 'PEAR_Error')) { - // TODO: error handling - return '
      '; + return array(); } - if (is_array($results)) { - $results = $results[0]; - array_shift($results); - } else { - $results = array($results); - } - - $html = '
        '; - $input = htmlspecialchars($input); - $input_regex = '/(' . preg_quote($input, '/') . ')/i'; - foreach ($results as $result) { - $html .= '
      • ' . str_replace(array('<strong>', '</strong>'), - array('', ''), - htmlspecialchars(preg_replace($input_regex, '$1', $result))) . '
      • '; - } - return $html . '
      '; + return array_map('htmlspecialchars', $this->_expandAddresses($input)); } /** - * Uses the Registry to expand names and returning error information for - * any address that is either not valid or fails to expand. + * Uses the Registry to expand names and return error information for + * any address that is either not valid or fails to expand. This function + * will not search if the address string is empty. * * @param string $addrString The name(s) or address(es) to expand. - * @param boolean $full If true generate a full, rfc822-valid address - * list. * - * @return mixed Either a string containing all expanded addresses or an - * array containing all matching address or an error - * object. + * @return array All matching addresses. */ - function expandAddresses($addrString, $full = false) + protected function _expandAddresses($addrString) { - if (!preg_match('|[^\s]|', $addrString)) { - return ''; - } - - $search_fields = array(); + return preg_match('|[^\s]|', $addrString) + ? $this->_getAddressList(reset(array_filter(array_map('trim', Horde_Mime_Address::explode($addrString, ',;'))))) + : ''; + } + /** + * Uses the Registry to expand names and return error information for + * any address that is either not valid or fails to expand. + * + * This method can be called statically, i.e.: + * $ret = IMP_Compose::expandAddresses(); + * + * @param string $search The term to search by. + * + * @return array All matching addresses. + */ + static public function getAddressList($search = '') + { $src = explode("\t", $GLOBALS['prefs']->getValue('search_sources')); if ((count($src) == 1) && empty($src[0])) { $src = array(); } + $fields = array(); if (($val = $GLOBALS['prefs']->getValue('search_fields'))) { $field_arr = explode("\n", $val); foreach ($field_arr as $field) { @@ -129,114 +117,35 @@ class Imple_ContactAutoCompleter extends Imple { $tmp = explode("\t", $field); if (count($tmp) > 1) { $source = array_splice($tmp, 0, 1); - $search_fields[$source[0]] = $tmp; + $fields[$source[0]] = $tmp; } } } } - $arr = array_filter(array_map('trim', MIME::rfc822Explode($addrString, ','))); - - $results = $GLOBALS['registry']->call('contacts/search', array($arr, $src, $search_fields, true)); - if (is_a($results, 'PEAR_Error')) { - return $results; - } - - /* Remove any results with empty email addresses. */ - foreach (array_keys($results) as $key) { - for ($i = 0, $subTotal = count($results[$key]); $i < $subTotal; ++$i) { - if (empty($results[$key][$i]['email'])) { - unset($results[$key][$i]); - } - } + $res = $GLOBALS['registry']->call('contacts/search', array($search, $src, $fields, true)); + if (is_a($res, 'PEAR_Error') || !count($res)) { + Horde::logMessage($res, __FILE__, __LINE__, PEAR_LOG_ERR); + return array(); } - $ambiguous = $error = false; - $missing = array(); - $vars = null; - - require_once 'Mail/RFC822.php'; - $parser = new Mail_RFC822(null, '@INVALID'); - - foreach ($arr as $i => $tmp) { - $address = MIME::encodeAddress($tmp, null, ''); - if (!is_a($address, 'PEAR_Error') && - ($parser->validateMailbox($address) || - $parser->_isGroup($address))) { - // noop - } elseif (!isset($results[$tmp]) || !count($results[$tmp])) { - /* Handle the missing/invalid case - we should return error - * info on each address that couldn't be - * expanded/validated. */ - $error = true; - if (!$ambiguous) { - $arr[$i] = PEAR::raiseError(null, null, null, null, $arr[$i]); - $missing[$i] = $arr[$i]; - } - } else { - $res = $results[$tmp]; - if (count($res) == 1) { - if ($full) { - if (strpos($res[0]['email'], ',') !== false) { - if ($vars === null) { - $vars = get_class_vars('MIME'); - } - $arr[$i] = MIME::_rfc822Encode($res[0]['name'], $vars['rfc822_filter'] . '.') . ': ' . $res[0]['email'] . ';'; - } else { - list($mbox, $host) = explode('@', $res[0]['email']); - $arr[$i] = MIME::rfc822WriteAddress($mbox, $host, $res[0]['name']); - } - } else { - $arr[$i] = $res[0]['email']; - } + /* The first key of the result will be the search term. The matching + * entries are stored underneath this key. */ + $search = array(); + foreach (reset($res) as $val) { + if (!empty($val['email'])) { + if (strpos($val['email'], ',') !== false) { + $search[] = Horde_Mime_Address::encode($val['name'], 'personal') . ': ' . $val['email'] . ';'; } else { - /* Handle the multiple case - we return an array - * with all found addresses. */ - $arr[$i] = array($arr[$i]); - foreach ($res as $one_res) { - if (empty($one_res['email'])) { - continue; - } - if ($full) { - if (strpos($one_res['email'], ',') !== false) { - if ($vars === null) { - $vars = get_class_vars('MIME'); - } - $arr[$i][] = MIME::_rfc822Encode($one_res['name'], $vars['rfc822_filter'] . '.') . ': ' . $one_res['email'] . ';'; - } else { - $mbox_host = explode('@', $one_res['email']); - if (isset($mbox_host[1])) { - $arr[$i][] = MIME::rfc822WriteAddress($mbox_host[0], $mbox_host[1], $one_res['name']); - } - } - } else { - $arr[$i][] = $one_res['email']; - } + $mbox_host = explode('@', $val['email']); + if (isset($mbox_host[1])) { + $search[] = Horde_Mime_Address::writeAddress($mbox_host[0], $mbox_host[1], $val['name']); } - $ambiguous = true; } } } - if ($ambiguous) { - foreach ($missing as $i => $addr) { - $arr[$i] = $addr->getUserInfo(); - } - return $arr; - } elseif ($error) { - return PEAR::raiseError(_("Please resolve ambiguous or invalid addresses."), null, null, null, $arr); - } else { - $list = ''; - foreach ($arr as $elm) { - if (substr($list, -1) == ';') { - $list .= ' '; - } elseif (!empty($list)) { - $list .= ', '; - } - $list .= $elm; - } - return $list; - } + return $search; } } -- 2.11.0