From 06ab6f8e031729f995cd3e9212d3050ccd0a943b Mon Sep 17 00:00:00 2001 From: Ben Klang Date: Sat, 9 Jan 2010 00:12:35 -0500 Subject: [PATCH] Shout: Complete Extension UI * Fully Ajax-ify the add/remove destination functions * Implement get/add/delete destination in LDAP driver * Implement get/add/delete destinations in UI * Todo: Enable ability to specify devices in UI and link to valid device list My undying gratitude again to the Prototype.js people. --- shout/ajax.php | 51 ++++++++++++- shout/lib/Driver/Ldap.php | 70 +++++++++++++++++- shout/templates/extensions/list.inc | 139 +++++++++++++++++++++++++----------- 3 files changed, 214 insertions(+), 46 deletions(-) diff --git a/shout/ajax.php b/shout/ajax.php index 27b93f9ba..c38a29a77 100644 --- a/shout/ajax.php +++ b/shout/ajax.php @@ -16,7 +16,8 @@ // Need to load Horde_Util:: to give us access to Horde_Util::getPathInfo(). require_once dirname(__FILE__) . '/lib/Application.php'; -$action = basename(Horde_Util::getPathInfo()); +$action = Horde_Util::getFormData('action'); + if (empty($action)) { // This is the only case where we really don't return anything, since // the frontend can be presumed not to make this request on purpose. @@ -25,7 +26,7 @@ if (empty($action)) { } try { - new Shout_Application(); + $shout = new Shout_Application(array('init' => true)); } catch (Horde_Exception $e) { /* Handle session timeouts when they come from an AJAX request. */ if (($e->getCode() == Horde_Registry::AUTH_FAILURE) && @@ -42,3 +43,49 @@ try { Horde_Auth::authenticateFailure('shout', $e); } +$context = $_SESSION['shout']['context']; + +switch($action) { +case 'addDestination': + try { + // FIXME: Use Form? + $exten = Horde_Util::getFormData('extension'); + $type = Horde_Util::getFormData('type'); + $dest = Horde_Util::getFormData('destination'); + $shout->extensions->addDestination($context, $exten, $type, $dest); + + $extensions = $shout->extensions->getExtensions($context); + Horde::sendHTTPResponse(Horde::prepareResponse($extensions), 'json'); + } catch (Exception $e) { + Horde::logMessage($e->getMessage(), __FILE__, __LINE__, PEAR_LOG_ERR); + //FIXME: Create a way to notify the user of the failure. + } + + break; + +case 'deleteDestination': + try { + // FIXME: Use Form? + $exten = Horde_Util::getFormData('extension'); + $type = Horde_Util::getFormData('type'); + $dest = Horde_Util::getFormData('destination'); + $shout->extensions->deleteDestination($context, $exten, $type, $dest); + + $extensions = $shout->extensions->getExtensions($context); + Horde::sendHTTPResponse(Horde::prepareResponse($extensions), 'json'); + } catch (Exception $e) { + Horde::logMessage($e->getMessage(), __FILE__, __LINE__, PEAR_LOG_ERR); + //FIXME: Create a way to notify the user of the failure. + } + break; + +case 'getDestinations': + try { + $extensions = $shout->extensions->getExtensions($context); + Horde::sendHTTPResponse(Horde::prepareResponse($extensions), 'json'); + } catch (Exception $e) { + Horde::logMessage($e->getMessage(), __FILE__, __LINE__, PEAR_LOG_ERR); + //FIXME: Create a way to notify the user of the failure. + } + break; +} diff --git a/shout/lib/Driver/Ldap.php b/shout/lib/Driver/Ldap.php index 6ce248cd5..76323aa51 100644 --- a/shout/lib/Driver/Ldap.php +++ b/shout/lib/Driver/Ldap.php @@ -153,9 +153,36 @@ class Shout_Driver_Ldap extends Shout_Driver } /** + * Add a new destination valid for this extension. + * A destination is either a telephone number or a VoIP device. + * + * @param string $context Context for the extension + * @param string $extension Extension for which to return destinations + * @param string $type Destination type ("device" or "number") + * @param string $destination The destination itself + * + * @return boolean True on success. + */ + function addDestination($context, $extension, $type, $destination) + { + // FIXME: Permissions check + $dn = $this->_getExtensionDn($context, $extension); + $attr = $this->_getDestAttr($type, $destination); + + $res = ldap_mod_add($this->_LDAP, $dn, $attr); + if ($res === false) { + $msg = sprintf('Error while modifying the LDAP entry. Code %s; Message "%s"', + ldap_errno($this->_LDAP), ldap_error($this->_LDAP)); + Horde::logMessage($msg, __FILE__, __LINE__, PEAR_LOG_ERR); + throw new Shout_Exception(_("Internal error modifing the directory. Details have been logged for the administrator.")); + } + + return true; + } + + /** * Get a list of destinations valid for this extension. - * A destination is either a telephone number, a VoIP device or an - * Instant Messaging address (a special case of VoIP). + * A destination is either a telephone number or a VoIP device. * * @param string $context Context for the extension * @param string $extension Extension for which to return destinations @@ -198,6 +225,45 @@ class Shout_Driver_Ldap extends Shout_Driver 'devices' => $res['astextensions']); } + function deleteDestination($context, $extension, $type, $destination) + { + $dn = $this->_getExtensionDn($context, $extension); + $attr = $this->_getDestAttr($type, $destination); + + $res = ldap_mod_del($this->_LDAP, $dn, $attr); + if ($res === false) { + $msg = sprintf('Error while modifying the LDAP entry. Code %s; Message "%s"', + ldap_errno($this->_LDAP), ldap_error($this->_LDAP)); + Horde::logMessage($msg, __FILE__, __LINE__, PEAR_LOG_ERR); + throw new Shout_Exception(_("Internal error modifing the directory. Details have been logged for the administrator.")); + } + + return true; + } + + protected function _getDestAttr($type, $destination) + { + switch ($type) { + case 'number': + // FIXME: Strip this number down to just digits + // FIXME: Add check for non-international numbers? + $attr = array('telephoneNumber' => $destination); + break; + + case 'device': + // FIXME: Check that the device is valid and associated with this + // context. + $attr = array('AstExtension' => $destination); + break; + + default: + throw new Shout_Exception(_("Invalid destination type specified.")); + break; + } + + return $attr; + } + /** * Save an extension to the LDAP tree * diff --git a/shout/templates/extensions/list.inc b/shout/templates/extensions/list.inc index f767065dd..18cca33b3 100644 --- a/shout/templates/extensions/list.inc +++ b/shout/templates/extensions/list.inc @@ -72,8 +72,20 @@ var destinations = new Array(); -function resetDestInfo(exten) +function resetDestInfo(collapse) { + destinations.each(function(item) { + exten = item.key; + _resetExtenDest(exten); + if (collapse) { + contract(exten); + } + }); +} + +function _resetExtenDest(exten) +{ + while ((e = $('destX'+exten+'info').childNodes[0]) != null) { $('destX'+exten+'info').removeChild(e); } @@ -89,24 +101,34 @@ function resetDestInfo(exten) } dest['devices'].each(function(s) { - e = document.createElement('img'); - e.src = "getImageDir() . '/shout.png'; ?>"; - t = document.createTextNode(" "+s); - b = document.createElement('br'); - $('destX'+exten+'info').appendChild(e); - $('destX'+exten+'info').appendChild(t); - $('destX'+exten+'info').appendChild(b); + img = document.createElement('img'); + img.src = "getImageDir() . '/shout.png'; ?>"; + text = document.createTextNode(" "+s+" "); + del = document.createElement('img'); + del.src = "getImageDir('horde') . '/delete-small.png'; ?>" + del.style.cursor = 'pointer'; + del.setAttribute('onclick', 'delDest("'+exten+'", "device", "'+s+'")'); + br = document.createElement('br'); + $('destX'+exten+'info').appendChild(img); + $('destX'+exten+'info').appendChild(text); + $('destX'+exten+'info').appendChild(del); + $('destX'+exten+'info').appendChild(br); }); dest['numbers'].each(function(s) { - e = document.createElement('img'); - e.src = "getImageDir() . '/telephone-pole.png'; ?>"; - t = document.createTextNode(" "+s); - b = document.createElement('br'); - $('destX'+exten+'info').appendChild(e); - $('destX'+exten+'info').appendChild(t); - $('destX'+exten+'info').appendChild(b); + img = document.createElement('img'); + img.src = "getImageDir() . '/telephone-pole.png'; ?>"; + text = document.createTextNode(" "+s+" "); + del = document.createElement('img'); + del.src = "getImageDir('horde') . '/delete-small.png'; ?>" + del.style.cursor = 'pointer'; + del.setAttribute('onclick', 'delDest("'+exten+'", "number", "'+s+'")'); + br = document.createElement('br'); + $('destX'+exten+'info').appendChild(img); + $('destX'+exten+'info').appendChild(text); + $('destX'+exten+'info').appendChild(del); + $('destX'+exten+'info').appendChild(br); }); form = document.createElement('form'); @@ -158,51 +180,72 @@ function contract(exten) function processForm(event) { Event.stop(event); - spinner = document.createElement('img'); - spinner.src = "getImageDir('horde') . '/loading.gif'; ?>" form = event.target; Element.extend(form); exten = form.getInputs('hidden', 'extension').first().value; - resetDestInfo(exten); + spinner = document.createElement('img'); + spinner.src = "getImageDir('horde') . '/loading.gif'; ?>" - $('destX'+exten+'addDest').show(); + $('destX'+exten+'submit').hide(); + form.insertBefore(spinner, $('destX'+exten+'submit')) + // FIXME: Better error handling + new Ajax.Request('', + { + method: 'post', + parameters: form.serialize(true), + onSuccess: function(r) { + //alert(json ? Object.inspect(json) : "no JSON object") + destinations = $H(r.responseJSON.response); + resetDestInfo(); + }, + onFailure: function(){ alert('Something went wrong...') } + }); } function addDest(exten) { + // Hide the link to create the form below. + // We only want one active at a time. $('destX'+exten+'addDest').hide(); + + hidden = document.createElement('input'); + hidden.type = 'hidden'; + hidden.name = 'action'; + hidden.value = 'addDestination'; + select = document.createElement('select'); select.name = 'type'; option = document.createElement('option'); - option.name = 'number'; + option.value = 'number'; text = document.createTextNode(""); option.appendChild(text); select.appendChild(option); option = document.createElement('option'); - option.name = 'device'; + option.value = 'device'; text = document.createTextNode(""); option.appendChild(text); select.appendChild(option); input = document.createElement('input'); input.name = 'destination'; - input.id = 'destination'; input.type = "text"; input.size = 12; input.maxlength = 15; save = document.createElement("input"); - save.name = "action"; + save.name = "submit"; save.value = "Save"; save.type = "submit"; + save.id = 'destX'+exten+'submit'; br = document.createElement('br'); + $('destX'+exten+'form').appendChild(hidden); $('destX'+exten+'form').appendChild(select); $('destX'+exten+'form').appendChild(input); $('destX'+exten+'form').appendChild(save); @@ -211,28 +254,40 @@ function addDest(exten) Event.observe($('destX'+exten+'form'), 'submit', function(event) {processForm(event);}); } -destinations = $H({ - $info) +function delDest(exten, type, dest) { - echo "'${extension}': {\n"; - if (count($info['devices'])) { - echo ' \'devices\': ["' . implode('","', $info['devices']) . '"],'; - } - if (count($info['numbers'])) { - echo ' \'numbers\': ["' . implode('","', $info['numbers']) . '"],'; - } - echo "},\n"; + params = $H({ + 'extension': exten, + 'type': type, + 'destination': dest, + 'action': 'deleteDestination' + }); + + // FIXME: Better error handling + new Ajax.Request('', + { + method: 'post', + parameters: params, + onSuccess: function(r) { + //alert(json ? Object.inspect(json) : "no JSON object") + destinations = $H(r.responseJSON.response); + resetDestInfo(); + }, + onFailure: function(){ alert('Something went wrong...') } + }); } -?> -}); -// Initialize the data. -destinations.each(function(item) { - exten = item.key; - resetDestInfo(exten); - contract(exten); -}) +destinations = $H(); + +new Ajax.Request('', +{ + method: 'post', + parameters: $H({'action': 'getDestinations'}), + onSuccess: function(r) { + destinations = $H(r.responseJSON.response); + resetDestInfo(true); + } +}); // --> -- 2.11.0