Shout: Complete Extension UI
authorBen Klang <ben@alkaloid.net>
Sat, 9 Jan 2010 05:12:35 +0000 (00:12 -0500)
committerBen Klang <ben@alkaloid.net>
Sat, 9 Jan 2010 05:13:16 +0000 (00:13 -0500)
* 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
shout/lib/Driver/Ldap.php
shout/templates/extensions/list.inc

index 27b93f9..c38a29a 100644 (file)
@@ -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;
+}
index 6ce248c..76323aa 100644 (file)
@@ -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
      *
index f767065..18cca33 100644 (file)
 
 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 = "<?php echo $registry->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 = "<?php echo $registry->getImageDir() . '/shout.png'; ?>";
+        text = document.createTextNode(" "+s+" ");
+        del = document.createElement('img');
+        del.src = "<?php echo $registry->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 = "<?php echo $registry->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 = "<?php echo $registry->getImageDir() . '/telephone-pole.png'; ?>";
+        text = document.createTextNode(" "+s+" ");
+        del = document.createElement('img');
+        del.src = "<?php echo $registry->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 = "<?php echo $registry->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 = "<?php echo $registry->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('<?php echo Horde::applicationUrl('ajax.php'); ?>',
+    {
+        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("<?php echo _("Number"); ?>");
     option.appendChild(text);
     select.appendChild(option);
 
     option = document.createElement('option');
-    option.name = 'device';
+    option.value = 'device';
     text = document.createTextNode("<?php echo _("Device"); ?>");
     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({
-<?php
-foreach ($extensions as $extension => $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('<?php echo Horde::applicationUrl('ajax.php'); ?>',
+    {
+        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('<?php echo Horde::applicationUrl('ajax.php'); ?>',
+{
+    method: 'post',
+    parameters: $H({'action': 'getDestinations'}),
+    onSuccess: function(r) {
+        destinations = $H(r.responseJSON.response);
+        resetDestInfo(true);
+    }
+});
 
 // -->
 </script>