--- /dev/null
+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<this.entryCount-1){++this.index}else{this.index=0}this.getEntry(this.index).scrollIntoView(false)},getEntry:function(a){return this.update.down().childElements()[a]},selectEntry:function(){this.active=false;this.updateElement(this.getEntry(this.index));this.hide()},updateElement:function(d){var e,g,b,c,a,h=this.options,f="";if(h.updateElement){h.updateElement(d);return}if(h.select){b=$(d).select("."+h.select)||[];if(b.size()){f=b[0].collectTextNodes(h.select)}}else{f=d.collectTextNodesIgnoreClass("informal")}e=this.getTokenBounds();if(e[0]!=-1){a=$F(this.element);g=a.substr(0,e[0]);c=a.substr(e[0]).match(/^\s+/);if(c){g+=c[0]}this.element.setValue(g+f+a.substr(e[1]))}else{this.element.setValue(f)}this.element.focus();if(h.afterUpdateElement){h.afterUpdateElement(this.element,d)}this.oldval=$F(this.element)},updateChoices:function(e){var a,d,c,b=0;if(!this.changed&&this.hasFocus){a=new Element("LI");c=new Element("UL");d=new RegExp("("+this.getToken()+")","i");e.each(function(f){c.insert(a.cloneNode(false).writeAttribute("acIndex",b++).update(f.gsub(d,"<strong>#{1}</strong>")))});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;e<a;++e){if(k[e]!=this.oldval[e]){j=e;break}}d=(j==this.oldval.length?1:0);for(f=0,c=m.length;f<c;++f){g=k.lastIndexOf(m[f],j+d-1);if(g>b){b=g}g=k.indexOf(m[f],j+d);if(g!=-1&&g<h){h=g}}return[b+1,h]}});Ajax.Autocompleter=Class.create(Autocompleter.Base,{initialize:function(c,d,b,a){this.baseInitialize(c,d,a);this.options=Object.extend(this.options,{asynchronous:true,onComplete:this._onComplete.bind(this),defaultParams:$H(this.options.parameters)});this.url=b;this.cache=$H()},getUpdatedChoices:function(){var b,d=this.options,a=this.getToken(),e=this.cache.get(a);if(e){this.updateChoices(e)}else{b=Object.clone(d.defaultParams);this.startIndicator();b.set(d.paramName,a);d.parameters=b.toQueryString();new Ajax.Request(this.url,d)}},_onComplete:function(a){this.updateChoices(this.cache.set(this.getToken(),a.responseJSON))}});Autocompleter.Local=Class.create(Autocompleter.Base,{initialize:function(c,d,a,b){this.baseInitialize(c,d,b);this.options.arr=a},getUpdatedChoices:function(){this.updateChoices(this._selector())},_setOptions:function(a){return Object.extend({choices:10,partialSearch:true,partialChars:2,ignoreCase:true,fullSearch:false},a||{})},_selector:function(){var b=this.getToken(),c=b.length,a=0,d=this.options;if(d.ignoreCase){b=b.toLowerCase()}return d.arr.findAll(function(e){if(a==d.choices){throw $break}if(d.ignoreCase){e=e.toLowerCase()}e=e.unescapeHTML();var f=e.indexOf(b);if(f!=-1&&((f==0&&e.length!=c)||(c>=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
--- /dev/null
+/**
+ * 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, '<strong>#{1}</strong>')));
+ });
+
+ 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);
+ }
+});
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);
}
/**
<?php
/**
- * $Horde: kronolith/lib/Imple/ContactAutoCompleter.php,v 1.5 2009/01/06 18:01:01 jan Exp $
- *
* Copyright 2005-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.
*
- * @package Kronolith
- */
-
-/** Horde_MIME */
-require_once 'Horde/MIME.php';
-
-/**
* @author Michael Slusarz <slusarz@horde.org>
* @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.
*
* 'resultsId' => TODO (optional)
* </pre>
*/
- function Imple_ContactAutoCompleter($params)
+ public function __construct($params)
{
if (empty($params['triggerId'])) {
$params['triggerId'] = $this->_randomid();
$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) . ')');
}
/**
*
* @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 '<ul></ul>';
- }
-
- $results = $this->expandAddresses($input, true);
- if (is_a($results, 'PEAR_Error')) {
- // TODO: error handling
- return '<ul></ul>';
+ return array();
}
- if (is_array($results)) {
- $results = $results[0];
- array_shift($results);
- } else {
- $results = array($results);
- }
-
- $html = '<ul>';
- $input = htmlspecialchars($input);
- $input_regex = '/(' . preg_quote($input, '/') . ')/i';
- foreach ($results as $result) {
- $html .= '<li>' . str_replace(array('<strong>', '</strong>'),
- array('<strong>', '</strong>'),
- htmlspecialchars(preg_replace($input_regex, '<strong>$1</strong>', $result))) . '</li>';
- }
- return $html . '</ul>';
+ 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) {
$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;
}
}