Big update toward new UI.
authorBen Klang <ben@alkaloid.net>
Mon, 21 Dec 2009 02:19:24 +0000 (02:19 +0000)
committerBen Klang <ben@alkaloid.net>
Mon, 21 Dec 2009 02:19:24 +0000 (02:19 +0000)
Continue improving H4 compliance with Exception handling
More revisions to config.xml to handle differentiated backends
Re-merge all the code from the include dir (extensions) back into single file (extensions.php)

git-svn-id: https://svn.alkaloid.net/gpl/shout/trunk@499 06cd67b6-e706-0410-b29e-9de616bca6e9

14 files changed:
config/conf.xml
extensions/add.php [deleted file]
extensions/delete.php [deleted file]
extensions/edit.php [deleted file]
extensions/list.php [deleted file]
extensions/save.php [deleted file]
index.php
lib/Driver.php
lib/Driver/Ldap.php
lib/Driver/Sql.php [new file with mode: 0644]
lib/Exception.php [new file with mode: 0644]
lib/Shout.php
lib/base.php
templates/extensions/list.inc

index 26eeda0..3175115 100644 (file)
@@ -1,18 +1,32 @@
 <?xml version="1.0"?>
 <!-- $Id$ -->
 <configuration>
+ <configtab name="storage" desc="Storage">
+  <configsection name="storage">
+   <configheader>Context Storage</configheader>
+   <configswitch name="driver" desc="What backend should we use for storing the list of valid contexts/customers?">Sql
+    <case name="Sql" desc="SQL">
+     <configsection name="params">
+      <configsql switchname="driverconfig" />
+      <configstring name="table" desc="Table to hold the list of contexts/customers" required="true">contexts</configstring>
+     </configsection>
+    </case>
+   </configswitch>
+  </configsection>
+ </configtab>
  <configtab name="extensions" desc="Extensions">
   <configsection name="extensions">
    <configheader>Extension Storage</configheader>
-   <configswitch name="driver" desc="What backend should we use for storing Asterisk phone user configuration?">ldap
-    <case name="ldap" desc="LDAP">
+   <configswitch name="driver" desc="What backend should we use for storing Asterisk phone user configuration?">Ldap
+    <case name="Ldap" desc="LDAP">
      <configsection name="params">
-      <configldap switchname="driver" />
+      <configldap switchname="driverconfig" />
      </configsection>
     </case>
-    <case name="sql" desc="SQL">
+    <case name="Sql" desc="SQL">
      <configsection name="params">
-      <configsql switchname="driver" />
+      <configsql switchname="driverconfig" />
+      <configstring name="table" desc="Table to hold the list of extensions" required="true"></configstring>
      </configsection>
     </case>
    </configswitch>
  <configtab name="devices" desc="VoIP Devices">
   <configsection name="devices">
    <configheader>Device Storage</configheader>
-   <configswitch name="driver" desc="What backend should we use for storing Asterisk phone user configuration?">ldap
-    <case name="ldap" desc="LDAP">
+   <configswitch name="driver" desc="What backend should we use for storing Asterisk phone user configuration?">Ldap
+    <case name="Ldap" desc="LDAP">
      <configsection name="params">
-      <configldap switchname="driver" />
+      <configldap switchname="driverconfig" />
      </configsection>
     </case>
-    <case name="sql" desc="SQL">
+    <case name="Sql" desc="SQL">
      <configsection name="params">
-      <configsql switchname="driver" />
+      <configsql switchname="driverconfig" />
+      <configstring name="table" desc="Table to hold the device configuration data" required="true">sip_peers</configstring>
      </configsection>
     </case>
    </configswitch>
diff --git a/extensions/add.php b/extensions/add.php
deleted file mode 100644 (file)
index 16f3f3b..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-/**
- * $Id$
- *
- * Copyright 2005 Ben Klang <ben@alkaloid.net>
- *
- * See the enclosed file LICENSE for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.php.
- */
-@define('SHOUT_BASE', dirname(__FILE__) . '/..');
-
-require SHOUT_BASE . '/users/edit.php';
\ No newline at end of file
diff --git a/extensions/delete.php b/extensions/delete.php
deleted file mode 100644 (file)
index 4400c5e..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-<?php
-/**
- * $Id$
- *
- * Copyright 2005-2006 Ben Klang <ben@alkaloid.net>
- *
- * See the enclosed file LICENSE for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.php.
- *
- * @package shout
- */
-@define('SHOUT_BASE', dirname(__FILE__) . '/..');
-//require_once 'Horde/Variables.php';
-
-$context = Horde_Util::getFormData('context');
-$extension = Horde_Util::getFormData('extension');
-
-$res = $shout->deleteUser($context, $extension);
-
-if (!$res) {
-    echo "Failed!";
-    print_r($res);
-}
-$notification->push("User Deleted.");
-$notification->notify();
-require SHOUT_TEMPLATES . '/common-footer.inc';
\ No newline at end of file
diff --git a/extensions/edit.php b/extensions/edit.php
deleted file mode 100644 (file)
index 3c3fe1e..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-<?php
-/**
- * $Id$
- *
- * Copyright 2005-2006 Ben Klang <ben@alkaloid.net>
- *
- * See the enclosed file LICENSE for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.php.
- *
- * @package shout
- */
-if (!isset($SHOUT_RUNNING) || !$SHOUT_RUNNING) {
-    header('Location: /');
-    exit();
-}
-
-require_once SHOUT_BASE . '/lib/User.php';
-require_once 'Horde/Variables.php';
-
-$RENDERER = &new Horde_Form_Renderer();
-
-$empty = '';
-$beendone = 0;
-$wereerrors = 0;
-
-$vars = &Variables::getDefaultVariables($empty);
-
-$FormName = 'UserDetailsForm';
-$Form = &Horde_Form::singleton($FormName, $vars);
-if (is_a($Form, 'PEAR_Error')) {
-    $notification->push($Form);
-} else {
-    $FormValid = $Form->validate($vars, true);
-    if (is_a($FormValid, 'PEAR_Error')) {
-        $notification->push($FormValid);
-    } else {
-        $Form->fillUserForm(&$vars, $extension);
-    }
-}
-
-
-$notification->notify();
-
-if (!$FormValid || !$Form->isSubmitted()) {
-    # Display the form for editing
-    $Form->open($RENDERER, $vars, 'index.php', 'post');
-    $Form->preserveVarByPost(&$vars, 'extension');
-    $Form->preserveVarByPost(&$vars, 'context');
-    $Form->preserveVarByPost(&$vars, 'section');
-    $RENDERER->beginActive($Form->getTitle());
-    $RENDERER->renderFormActive($Form, $vars);
-    $RENDERER->submit();
-    $RENDERER->end();
-    $Form->close($RENDERER);
-} else {
-    # Process the Valid and Submitted form
-$notification->push("How did we get HERE?!", 'horde.error');
-$notification->notify();
-//     $info = array();
-//     $Form->getInfo($vars, $info);
-//
-//     $name = $info['name'];
-//     $extension = $info['extension'];
-//     $newextension = $info['newextension'];
-//     $email = $info['email'];
-//     $pin = $info['pin'];
-//
-//
-//     $limits = $shout->getLimits($context, $extension);
-//
-//     $userdetails = array("newextension" => $newextension,
-//                 "name" => $name,
-//                 "pin" => $pin,
-//                 "email" => $email);
-//
-//     $i = 1;
-//     $userdetails['telephonenumbers'] = array();
-//     while ($i <= $limits['telephonenumbersmax']) {
-//         $tmp = $info['telephone'.$i];
-//         if (!empty($tmp)) {
-//             $userdetails['telephonenumbers'][] = $tmp;
-//         }
-//         $i++;
-//     }
-//
-//     $userdetails['dialopts'] = array();
-//     if ($info['moh']) {
-//         $userdetails['dialopts'][] = 'm';
-//     }
-//     if ($info['transfer']) {
-//         $userdetails['dialopts'][] = 't';
-//     }
-}
\ No newline at end of file
diff --git a/extensions/list.php b/extensions/list.php
deleted file mode 100644 (file)
index 45cfc22..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-<?php
-/**
- * $Id$
- *
- * Copyright 2005-2006 Ben Klang <ben@alkaloid.net>
- *
- * 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 shout
- */
-
-if (!isset($SHOUT_RUNNING) || !$SHOUT_RUNNING) {
-    header('Location: /');
-    exit();
-}
-
-$users = &$shout->getUsers($context);
diff --git a/extensions/save.php b/extensions/save.php
deleted file mode 100644 (file)
index e5a9355..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-<?php
-/**
- * $Id$
- *
- * Copyright 2005-2006 Ben Klang <ben@alkaloid.net>
- *
- * See the enclosed file LICENSE for license information (GPL). If you
- * did not receive this file, see http://www.horde.org/licenses/gpl.php.
- *
- * @package shout
- */
-if (!isset($SHOUT_RUNNING) || !$SHOUT_RUNNING) {
-    header('Location: /');
-    exit();
-}
-
-require_once SHOUT_BASE . '/lib/User.php';
-require_once 'Horde/Variables.php';
-
-$RENDERER = &new Horde_Form_Renderer();
-
-$vars = &Variables::getDefaultVariables();
-$FormName = $vars->get('formname');
-
-$Form = &Horde_Form::singleton($FormName, $vars);
-
-$FormValid = $Form->validate($vars, true);
-
-if (!$FormValid || !$Form->isSubmitted()) {
-    require SHOUT_BASE . '/usermgr/edit.php';
-} else {
-    # Form is Valid and Submitted
-    $extension = $vars->get('extension');
-
-    $limits = $shout->getLimits($context, $extension);
-
-    # FIXME: Input Validation (Text::??)
-    $userdetails = array(
-        "newextension" => $vars->get('newextension'),
-        "name" => $vars->get('name'),
-        "mailboxpin" => $vars->get('mailboxpin'),
-        "email" => $vars->get('email'),
-        "uid" => $vars->get('uid'),
-    );
-
-    $userdetails['telephonenumber'] = array();
-    $telephonenumber = $vars->get("telephonenumber");
-    if (!empty($telephonenumber) && is_array($telephonenumber)) {
-        $i = 1;
-        while ($i <= $limits['telephonenumbersmax']) {
-            if (!empty($telephonenumber[$i])) {
-                $userdetails['telephonenumber'][] = $telephonenumber[$i++];
-            } else {
-                $i++;
-            }
-        }
-    }
-
-    $userdetails['dialopts'] = array();
-
-    if ($vars->get('moh')) {
-        $userdetails['dialopts'][] = 'm';
-    }
-    if ($vars->get('transfer')) {
-        $userdetails['dialopts'][] = 't';
-    }
-    if ($vars->get('eca')) {
-        $userdetails['dialopts'][] = 'e';
-    }
-    $res = $shout->saveUser($context, $extension, $userdetails);
-    if (is_a($res, 'PEAR_Error')) {
-        $notification->push($res);
-    } else {
-        $notification->push('User information updated.  '.
-            'Changes will take effect within 10 minutes',
-            'horde.success');
-    }
-
-    $notification->notify();
-}
index 74f7aea..4097661 100644 (file)
--- a/index.php
+++ b/index.php
  */
 
 @define('SHOUT_BASE', dirname(__FILE__));
-$shout_configured = (is_readable(SHOUT_BASE . '/config/conf.php') &&
-                     is_readable(SHOUT_BASE . '/config/applist.xml') &&
-                     is_readable(SHOUT_BASE . '/config/defines.php'));
+$shout_configured = (is_readable(SHOUT_BASE . '/config/conf.php'));
 
 if (!$shout_configured) {
 require SHOUT_BASE . '/../lib/Test.php';
     Horde_Test::configFilesMissing('Shout', SHOUT_BASE,
-        array('conf.php', 'applist.xml', 'defines.php'));
+        array('conf.php'));
 }
 
 require_once SHOUT_BASE . '/lib/base.php';
-header('Location: ' . Horde::applicationUrl('usermgr.php'));
+header('Location: ' . Horde::applicationUrl('extensions.php'));
index f06fc89..d5278e0 100644 (file)
@@ -129,69 +129,32 @@ class Shout_Driver {
      * @return mixed  The newly created concrete Shout_Driver instance, or
      *                false on an error.
      */
-    function &factory($driver = null, $params = null)
+    function &factory($class, $driver = null, $params = null)
     {
         if (is_null($driver)) {
-            $driver = $GLOBALS['conf']['storage']['driver'];
+            $driver = $GLOBALS['conf'][$class]['driver'];
         }
 
         $driver = basename($driver);
 
         if (is_null($params)) {
-            $params = Horde::getDriverConfig('storage', $driver);
+            if ($GLOBALS['conf'][$class]['params']['driverconfig'] == 'horde') {
+                $params = array_merge($GLOBALS['conf'][$class]['params'],
+                                      Horde::getDriverConfig('storage', $driver));
+            } else {
+                $params = $GLOBALS['conf'][$class]['params'];
+            }
         }
 
+        $params['class'] = $class;
+
         require_once dirname(__FILE__) . '/Driver/' . $driver . '.php';
         $class = 'Shout_Driver_' . $driver;
         if (class_exists($class)) {
-            $shout = new $class($params);
-            return $shout;
+            return new $class($params);
         } else {
             return false;
         }
     }
 
-    /**
-     * Attempts to return a reference to a concrete Shout_Driver
-     * instance based on $driver. It will only create a new instance
-     * if no Shout_Driver instance with the same parameters currently
-     * exists.
-     *
-     * This method must be invoked as: $var = &Shout_Driver::singleton()
-     *
-     * @param string $driver  The type of concrete Shout_Driver subclass
-     *                        to return.  The is based on the storage
-     *                        driver ($driver).  The code is dynamically
-     *                        included.
-     * @param array $params   (optional) A hash containing any additional
-     *                        configuration or connection parameters a
-     *                        subclass might need.
-     *
-     * @return mixed  The created concrete Shout_Driver instance, or false
-     *                on error.
-     */
-    function &singleton($driver = null, $params = null)
-    {
-        static $instances;
-
-        if (is_null($driver)) {
-            $driver = $GLOBALS['conf']['storage']['driver'];
-        }
-
-        if (is_null($params)) {
-            $params = Horde::getDriverConfig('storage', $driver);
-        }
-
-        if (!isset($instances)) {
-            $instances = array();
-        }
-
-        $signature = serialize(array($driver, $params));
-        if (!isset($instances[$signature])) {
-            $instances[$signature] = &Shout_Driver::factory($driver, $params);
-        }
-
-        return $instances[$signature];
-    }
 }
-// }}}
index 8dfe2a8..6fd1640 100644 (file)
@@ -1,9 +1,6 @@
 <?php
 
-# Make Shout methods available
-require_once SHOUT_BASE . '/lib/Shout.php';
-
-class Shout_Driver_ldap extends Shout_Driver
+class Shout_Driver_Ldap extends Shout_Driver
 {
     var $_ldapKey;  // Index used for storing objects
     var $_appKey;   // Index used for moving info to/from the app
@@ -12,14 +9,14 @@ class Shout_Driver_ldap extends Shout_Driver
      * Handle for the current database connection.
      * @var object LDAP $_LDAP
      */
-    var $_LDAP;
+    private $_LDAP;
 
     /**
      * Boolean indicating whether or not we're connected to the LDAP
      * server.
      * @var boolean $_connected
      */
-    var $_connected = false;
+    private $_connected = false;
 
 
     /**
@@ -27,9 +24,9 @@ class Shout_Driver_ldap extends Shout_Driver
     *
     * @param array  $params    A hash containing connection parameters.
     */
-    function Shout_Driver_ldap($params = array())
+    function __construct($params = array())
     {
-        parent::Shout_Driver($params);
+        parent::__construct($params);
         $this->_connect();
 
         /* These next lines will translate between indexes used in the
@@ -71,165 +68,13 @@ class Shout_Driver_ldap extends Shout_Driver
     }
 
     /**
-    * Get a list of contexts from the backend
-    *
-    * @param string $filter Search filter
-    *
-    * @return array Contexts valid for this system
-    *
-    * @access private
-    */
-    function &getContexts($searchfilters = SHOUT_CONTEXT_ALL,
-                         $filterperms = null)
-    {
-        static $entries = array();
-        if (isset($entries[$searchfilters])) {
-            return $entries[$searchfilters];
-        }
-
-        if ($filterperms === null) {
-            $filterperms = PERMS_SHOW|PERMS_READ;
-        }
-
-        # TODO Add caching mechanism here.  Possibly cache results per
-        # filter $this->contexts['customer'] and return either data
-        # or possibly a reference to that data
-
-        # Determine which combination of contexts need to be returned
-        if ($searchfilters == SHOUT_CONTEXT_ALL) {
-            $searchfilter="(objectClass=asteriskObject)";
-        } else {
-            $searchfilter = "(&";
-            # FIXME Change this to non-V-Office specific objectClass
-            if ($searchfilters & SHOUT_CONTEXT_CUSTOMERS) {
-                # FIXME what does this objectClass really do for us?
-                $searchfilter.="(objectClass=vofficeCustomer)";
-            }
-            if ($searchfilters & SHOUT_CONTEXT_EXTENSIONS) {
-                $searchfilter.="(objectClass=asteriskExtensions)";
-            }
-            if ($searchfilters & SHOUT_CONTEXT_MOH) {
-                $searchfilter.="(objectClass=asteriskMusicOnHold)";
-            }
-            if ($searchfilters & SHOUT_CONTEXT_CONFERENCE) {
-                $searchfilter.="(objectClass=asteriskMeetMe)";
-            }
-            $searchfilter .= ")";
-        }
-
-        $attributes = array(SHOUT_ACCOUNT_ID_ATTRIBUTE, 'objectClass', 'context');
-
-        # Collect all the possible contexts from the backend
-        $res = @ldap_search($this->_LDAP,
-            SHOUT_ASTERISK_BRANCH.','.$this->_params['basedn'],
-            "$searchfilter");
-            #array('context', 'associatedDomain'));
-        if (!$res) {
-            return PEAR::raiseError("Unable to locate any contexts " .
-            "underneath ".SHOUT_ASTERISK_BRANCH.",".$this->_params['basedn'] .
-            " matching those search filters" . ldap_error($this->_LDAP));
-        }
-
-        $res = ldap_get_entries($this->_LDAP, $res);
-        $i = 0;
-        $entries[$searchfilters] = array();
-        while ($i < $res['count']) {
-            $context = $res[$i]['context'][0];
-            $type = SHOUT_CONTEXT_NONE;
-            foreach ($res[$i][strtolower('objectClass')] as $objectClass) {
-                switch ($objectClass) {
-                    case SHOUT_CONTEXT_CUSTOMERS_OBJECTCLASS:
-                        # FIXME What does this objectClass really get us?
-                        $type = $type | SHOUT_CONTEXT_CUSTOMERS;
-                        break;
-                    case SHOUT_CONTEXT_EXTENSIONS_OBJECTCLASS:
-                        $type = $type | SHOUT_CONTEXT_EXTENSIONS;
-                        break;
-                    case SHOUT_CONTEXT_MOH_OBJECTCLASS:
-                        $type = $type | SHOUT_CONTEXT_MOH;
-                        break;
-                    case SHOUT_CONTEXT_CONFERENCE_OBJECTCLASS:
-                        $type = $type | SHOUT_CONTEXT_CONFERENCE;
-                        break;
-                    case SHOUT_CONTEXT_VOICEMAIL_OBJECTCLASS:
-                        $type = $type | SHOUT_CONTEXT_VOICEMAIL;
-                        break;
-                }
-            }
-            if (Shout::checkRights("shout:contexts:$context", $filterperms)) {
-                $entries[$searchfilters][$context] =
-                    array(
-                        'custid' => SHOUT_ACCOUNT_ID_ATTRIBUTE,
-                        'type' => $type,
-                    );
-            }
-            $i++;
-        }
-        # return the array
-        return $entries[$searchfilters];
-    }
-
-    /**
-     * For the given context and type, make sure the context has the
-     * appropriate properties, that it is effectively of that "type"
-     *
-     * @param string $context the context to check type for
-     *
-     * @param string $type the type to verify the context is of
-     *
-     * @return boolean true of the context is of type, false if not
-     *
-     * @access public
-     */
-    function checkContextType($context, $type) {
-        switch ($type) {
-            case "users":
-                $searchfilter = '(objectClass='.SHOUT_CONTEXT_VOICEMAIL_OBJECTCLASS.')';
-                break;
-            case "dialplan":
-                $searchfilter = '(objectClass='.SHOUT_CONTEXT_EXTENSIONS_OBJECTCLASS.')';
-                break;
-            case "moh":
-                $searchfilter='(objectClass='.SHOUT_CONTEXT_MOH_OBJECTCLASS.')';
-                break;
-            case "conference":
-                $searchfilter='(objectClass='.SHOUT_CONTEXT_CONFERENCE_OBJECTCLASS.')';
-                break;
-            case "all":
-            default:
-                $searchfilter="";
-                break;
-        }
-
-        $res = @ldap_search($this->_LDAP,
-            SHOUT_ASTERISK_BRANCH.','.$this->_params['basedn'],
-            "(&(objectClass=asteriskObject)$searchfilter(context=$context))",
-            array("context"));
-        if (!$res) {
-            return PEAR::raiseError("Unable to search directory for context
-type");
-        }
-
-        $res = ldap_get_entries($this->_LDAP, $res);
-        if (!$res) {
-            return PEAR::raiseError("Unable to get results from LDAP query");
-        }
-
-        if ($res['count'] == 1) {
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    /**
      * Get a list of users valid for the contexts
      *
-     * @param string $context Context on which to search
+     * @param string $context  Context in which to search
      *
      * @return array User information indexed by voice mailbox number
      */
-    function getUsers($context)
+    public function getExtensions($context)
     {
 
         static $entries = array();
@@ -237,84 +82,61 @@ type");
             return $entries[$context];
         }
 
-        $basedn = SHOUT_USERS_BRANCH.','.$this->_params['basedn'];
+        $this->_params['basedn'];
 
         $filter  = '(&';
-        $filter .= '(objectClass='.SHOUT_USER_OBJECTCLASS.')';
-        $filter .= '(context='.$context.')';
+        $filter .= '(objectClass=AsteriskVoiceMail)';
+        $filter .= '(AstContext='.$context.')';
         $filter .= ')';
 
         $attributes = array(
-            'voiceMailbox',
-            'asteriskUserDialOptions',
-            'asteriskVoiceMailboxOptions',
-            'voiceMailboxPin',
             'cn',
-            'telephoneNumber',
-            'asteriskUserDialTimeout',
             'mail',
-            'asteriskPager',
+            'AstVoicemailMailbox',
+            'AstVoicemailPassword',
+            'AstVoicemailOptions',
+            'AstVoicemailPager'
         );
 
-        $search = @ldap_search($this->_LDAP, $basedn, $filter, $attributes);
-
-        if (!$search) {
-            return PEAR::raiseError("Unable to search directory: " .
-                ldap_error($this->_LDAP));
+        $search = ldap_search($this->_LDAP, $this->_params['basedn'], $filter, $attributes);
+        if ($search === false) {
+            throw new Shout_Exception("Unable to search directory: " .
+                ldap_error($this->_LDAP), ldap_errno($this->_LDAP));
         }
 
         $res = ldap_get_entries($this->_LDAP, $search);
-        #
-        # ATTRIBUTES RETURNED FROM ldap_get_entries ARE ALL LOWER CASE!!
-        #
+        if ($res === false) {
+            throw new Shout_Exception("Unable to fetch results from directory: " .
+                ldap_error($this->_LDAP), ldap_errno($this->_LDAP));
+        }
+
+        // ATTRIBUTES RETURNED FROM ldap_get_entries ARE ALL LOWER CASE!!
+        // It's a PHP thing.
         $entries[$context] = array();
         $i = 0;
         while ($i < $res['count']) {
-            $extension = $res[$i]['voicemailbox'][0];
-            $uid = md5($res[$i]['dn']);
+            list($extension) = explode('@', $res[$i]['AstVoicemailMailbox'][0]);
             $entries[$context][$extension] = array();
-            $entries[$context][$extension]['uid'] = $uid;
-
-            $j = 0;
-            $entries[$context][$extension]['dialopts'] = array();
-            while ($j < @$res[$i]['asteriskuserdialoptions']['count']) {
-                $entries[$context][$extension]['dialopts'][] =
-                    $res[$i]['asteriskuserdialoptions'][$j];
-                $j++;
-            }
 
             $j = 0;
             $entries[$context][$extension]['mailboxopts'] = array();
-            while ($j < @$res[$i]['asteriskvoicemailboxoptions']['count']) {
+            while ($j < @$res[$i]['astvoicemailoptions']['count']) {
                 $entries[$context][$extension]['mailboxopts'][] =
-                    $res[$i]['asteriskvoicemailboxoptions'][$j];
+                    $res[$i]['astvoicemailoptions'][$j];
                 $j++;
             }
 
             $entries[$context][$extension]['mailboxpin'] =
-                $res[$i]['voicemailboxpin'][0];
+                $res[$i]['astvoicemailpassword'][0];
 
-            @$entries[$context][$extension]['name'] =
+            $entries[$context][$extension]['name'] =
                 $res[$i]['cn'][0];
 
-            $j = 0;
-            $entries[$context][$extension]['telephonenumber'] = array();
-            while ($j < @$res[$i]['telephonenumber']['count']) {
-                // Start with 1 for telephone numbers for user convenience
-                $entries[$context][$extension]['telephonenumber'][$j+1] =
-                    $res[$i]['telephonenumber'][$j];
-                $j++;
-            }
-
-            # FIXME Do some sanity checking here.  Also set a default?
-            @$entries[$context][$extension]['dialtimeout'] =
-                $res[$i]['asteriskuserdialtimeout'][0];
-
             @$entries[$context][$extension]['email'] =
                 $res[$i]['mail'][0];
 
             @$entries[$context][$extension]['pageremail'] =
-                $res[$i]['asteriskpager'][0];
+                $res[$i]['astvoicemailpager'][0];
 
             $i++;
 
@@ -326,51 +148,13 @@ type");
     }
 
     /**
-     * Returns the name of the user's default context
-     *
-     * @return string User's default context
-     */
-    function getHomeContext()
-    {
-        # FIXME Cache this lookup?
-
-        $basedn = SHOUT_USERS_BRANCH.','.$this->_params['basedn'];
-        $filter  = '(&';
-        $filter .= '(mail='.Auth::getAuth().')';
-        $filter .= '(objectClass='.SHOUT_USER_OBJECTCLASS.')';
-        $filter .= ')';
-        $attributes = array('context');
-
-        $res = @ldap_search($this->_LDAP, $basedn, $filter, $attributes);
-        if (!$res) {
-            return PEAR::raiseError("Unable to locate any customers " .
-            "underneath ".SHOUT_ASTERISK_BRANCH.",".$this->_params['basedn'] .
-            " matching those search filters");
-            # FIXME Better error string above
-        }
-
-        $res = ldap_get_entries($this->_LDAP, $res);
-
-        # Assume the user only has one context.  The schema enforces this
-        # FIXME: Handle cases where the managing user isn't a valid telephone
-        # system user
-        # FIXME: Do we want to warn?  If so, how?  This PEAR::Error shows up
-        # in unfavorable places (ie. perms screen)
-        if ($res['count'] != 1) {
-            //return PEAR::raiseError(_("Unable to determine default context"));
-            return '';
-        }
-        return $res[0]['context'][0];
-    }
-
-    /**
      * Get a context's properties
      *
      * @param string $context Context to get properties for
      *
      * @return integer Bitfield of properties valid for this context
      */
-    function getContextProperties($context)
+    public function getContextProperties($context)
     {
 
         $res = @ldap_search($this->_LDAP,
@@ -424,7 +208,7 @@ for $context"));
      * @return array Multi-dimensional associative array of extensions data
      *
      */
-    function &getDialplan($context, $preprocess = false)
+    public function getDialplan($context, $preprocess = false)
     {
         # FIXME Implement preprocess functionality.  Don't forget to cache!
         static $dialplans = array();
@@ -556,7 +340,7 @@ for $context"));
      * @return array Array with elements indicating various limits
      */
      # FIXME Figure out how this fits into Shout/Congregation better
-    function &getLimits($context = null, $extension = null)
+    public function getLimits($context = null, $extension = null)
     {
 
         $limits = array('telephonenumbersmax',
@@ -684,7 +468,7 @@ for $context"));
      *
      * @return TRUE on success, PEAR::Error object on error
      */
-    function saveUser($context, $extension, $userdetails)
+    public function saveUser($context, $extension, $userdetails)
     {
         $ldapKey = &$this->_ldapKey;
         $appKey = &$this->_appKey;
@@ -852,7 +636,7 @@ for $context"));
      *
      * @return boolean True on success, PEAR::Error object on error
      */
-    function deleteUser($context, $extension)
+    public function deleteUser($context, $extension)
     {
         $ldapKey = &$this->_ldapKey;
         $appKey = &$this->_appKey;
@@ -881,7 +665,7 @@ for $context"));
 
 
     /* Needed because uksort can't take a classed function as its callback arg */
-    function _sortexten($e1, $e2)
+    protected function _sortexten($e1, $e2)
     {
         print "$e1 and $e2\n";
         $ret =  Shout::extensort($e1, $e2);
@@ -896,37 +680,75 @@ for $context"));
      *
      * @access private
      */
-    function _connect()
+    protected function _connect()
     {
-        if (!$this->_connected) {
-            # FIXME What else is needed for this assert?
-            Horde::assertDriverConfig($this->_params, 'storage',
-                array('hostspec', 'basedn', 'binddn', 'password'));
-
-            # FIXME Add other sane defaults here (mostly objectClass related)
-            if (!isset($this->_params['userObjectclass'])) {
-                $this->_params['userObjectclass'] = 'asteriskUser';
+        if ($this->_connected) {
+            return;
+        }
+
+        if (!Horde_Util::extensionExists('ldap')) {
+            throw new Horde_Exception('Required LDAP extension not found.');
+        }
+
+        Horde::assertDriverConfig($this->_params, $this->_params['class'],
+            array('hostspec', 'basedn', 'writedn'));
+
+        /* Open an unbound connection to the LDAP server */
+        $conn = ldap_connect($this->_params['hostspec'], $this->_params['port']);
+        if (!$conn) {
+             Horde::logMessage(
+                sprintf('Failed to open an LDAP connection to %s.',
+                        $this->_params['hostspec']),
+                __FILE__, __LINE__, PEAR_LOG_ERR);
+            throw new Horde_Exception('Internal LDAP error. Details have been logged for the administrator.');
+        }
+
+        /* Set hte LDAP protocol version. */
+        if (isset($this->_params['version'])) {
+            $result = ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION,
+                                       $this->_params['version']);
+            if ($result === false) {
+                Horde::logMessage(
+                    sprintf('Set LDAP protocol version to %d failed: [%d] %s',
+                            $this->_params['version'],
+                            ldap_errno($conn),
+                            ldap_error($conn)),
+                    __FILE__, __LINE__, PEAR_LOG_WARNING);
+                throw new Horde_Exception('Internal LDAP error. Details have been logged for the administrator.', ldap_errno($conn));
             }
+        }
 
-            $this->_LDAP = ldap_connect($this->_params['hostspec'], 389); #FIXME
-            if (!$this->_LDAP) {
-                Horde::fatal("Unable to connect to LDAP server $hostname on
-$port", __FILE__, __LINE__); #FIXME: $port
-            }
-            $res = ldap_set_option($this->_LDAP, LDAP_OPT_PROTOCOL_VERSION,
-$this->_params['version']);
-            if (!$res) {
-                return PEAR::raiseError("Unable to set LDAP protocol version");
-            }
-            $res = ldap_bind($this->_LDAP, $this->_params['binddn'],
-$this->_params['password']);
-            if (!$res) {
-                return PEAR::raiseError("Unable to bind to the LDAP server.
-Check authentication credentials.");
+        /* Start TLS if we're using it. */
+        if (!empty($this->_params['tls'])) {
+            if (!@ldap_start_tls($conn)) {
+                Horde::logMessage(
+                    sprintf('STARTTLS failed: [%d] %s',
+                            @ldap_errno($this->_ds),
+                            @ldap_error($this->_ds)),
+                    __FILE__, __LINE__, PEAR_LOG_ERR);
             }
+        }
 
-            $this->_connected = true;
+        /* If necessary, bind to the LDAP server as the user with search
+         * permissions. */
+        if (!empty($this->_params['searchdn'])) {
+            $bind = ldap_bind($conn, $this->_params['searchdn'],
+                              $this->_params['searchpw']);
+            if ($bind === false) {
+                Horde::logMessage(
+                    sprintf('Bind to server %s:%d with DN %s failed: [%d] %s',
+                            $this->_params['hostspec'],
+                            $this->_params['port'],
+                            $this->_params['searchdn'],
+                            @ldap_errno($conn),
+                            @ldap_error($conn)),
+                    __FILE__, __LINE__, PEAR_LOG_ERR);
+                throw new Horde_Exception('Internal LDAP error. Details have been logged for the administrator.', ldap_errno($conn));
+            }
         }
-        return true;
+
+        /* Store the connection handle at the instance level. */
+        $this->_LDAP = $conn;
     }
+
 }
diff --git a/lib/Driver/Sql.php b/lib/Driver/Sql.php
new file mode 100644 (file)
index 0000000..12166ac
--- /dev/null
@@ -0,0 +1,222 @@
+<?php
+
+class Shout_Driver_Sql extends Shout_Driver
+{
+    /**
+     * Handle for the current database connection.
+     * @var object $_db
+     */
+    protected $_db = null;
+
+    /**
+     * Boolean indicating whether or not we're connected to the LDAP
+     * server.
+     * @var boolean $_connected
+     */
+    protected $_connected = false;
+
+
+    /**
+    * Constructs a new Shout LDAP driver object.
+    *
+    * @param array  $params    A hash containing connection parameters.
+    */
+    function __construct($params = array())
+    {
+        parent::__construct($params);
+        $this->_connect();
+    }
+
+    public function getContexts()
+    {
+        $this->_connect();
+
+        $sql = 'SELECT context FROM %s';
+        $sql = sprintf($sql, $this->_params['table']);
+        $vars = array();
+
+        $result = $this->_db->query($sql, $vars);
+        if ($result instanceof PEAR_Error) {
+            throw Shout_Exception($result);
+        }
+
+        $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
+        if ($row instanceof PEAR_Error) {
+            throw Shout_Exception($row);
+        }
+
+        $contexts = array();
+        while ($row && !($row instanceof PEAR_Error)) {
+            /* Add this new foo to the $_foo list. */
+            $contexts[] = $row['context'];
+
+            /* Advance to the new row in the result set. */
+            $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
+        }
+
+         $result->free();
+         return $contexts;
+    }
+
+    /**
+     * Get a list of devices for a given context
+     *
+     * @param string $context    Context in which to search for devices
+     * @param string $extension  Extension in which to search for devices
+     *
+     * @return array  Array of devices within this context with their information
+     *
+     * @access private
+     */
+    public function getDevices($context, $extension)
+    {
+        $sql = 'SELECT id, name, callerid, context, host, permit, nat, ' .
+               'secret, disallow, allow FROM %s WHERE mailbox = ?';
+        $sql = sprintf($sql, $this->_params['table']);
+        $args = array($extension . '@' . $context);
+
+        $result = $this->_db->query($sql, $args);
+        if (is_a($result instanceof PEAR_Error)) {
+            throw Shout_Exception($result);
+        }
+
+        $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
+        if ($row instanceof PEAR_Error) {
+            throw Shout_Exception($row);
+        }
+
+        $devices = array();
+        while ($row && !($row instanceof PEAR_Error)) {
+            /* Add this new foo to the $_foo list. */
+            $devices[] = $row;
+
+            /* Advance to the new row in the result set. */
+            $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
+        }
+
+        $result->free();
+
+    }
+
+    /**
+     * Get a list of users valid for the contexts
+     *
+     * @param string $context Context on which to search
+     *
+     * @return array User information indexed by voice mailbox number
+     */
+    public function getExtensions($context)
+    {
+        throw new Shout_Exception("Not implemented yet.");
+    }
+
+    /**
+     * Save a user to the LDAP tree
+     *
+     * @param string $context Context to which the user should be added
+     *
+     * @param string $extension Extension to be saved
+     *
+     * @param array $userdetails Phone numbers, PIN, options, etc to be saved
+     *
+     * @return TRUE on success, PEAR::Error object on error
+     */
+    public function saveExtension($context, $extension, $userdetails)
+    {
+        throw new Shout_Exception("Not implemented.");
+    }
+
+    /**
+     * Deletes a user from the LDAP tree
+     *
+     * @param string $context Context to delete the user from
+     * @param string $extension Extension of the user to be deleted
+     *
+     * @return boolean True on success, PEAR::Error object on error
+     */
+    public function deleteExtension($context, $extension)
+    {
+        throw new Shout_Exception("Not implemented.");
+    }
+
+    /**
+     * Attempts to open a persistent connection to the SQL server.
+     *
+     * @throws Horde_Exception
+     */
+    protected function _connect()
+    {
+        if ($this->_connected) {
+            return;
+        }
+
+        Horde::assertDriverConfig($this->_params, $this->_params['class'],
+                                  array('phptype', 'charset', 'table'));
+
+        if (!isset($this->_params['database'])) {
+            $this->_params['database'] = '';
+        }
+        if (!isset($this->_params['username'])) {
+            $this->_params['username'] = '';
+        }
+        if (!isset($this->_params['hostspec'])) {
+            $this->_params['hostspec'] = '';
+        }
+
+        /* Connect to the SQL server using the supplied parameters. */
+        $this->_write_db = DB::connect($this->_params,
+                                       array('persistent' => !empty($this->_params['persistent'])));
+        if ($this->_write_db instanceof PEAR_Error) {
+            throw Horde_Exception($this->_write_db);
+        }
+
+        // Set DB portability options.
+        switch ($this->_write_db->phptype) {
+        case 'mssql':
+            $this->_write_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS | DB_PORTABILITY_RTRIM);
+            break;
+
+        default:
+            $this->_write_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS);
+        }
+
+        /* Check if we need to set up the read DB connection seperately. */
+        if (!empty($this->_params['splitread'])) {
+            $params = array_merge($this->_params, $this->_params['read']);
+            $this->_db = DB::connect($params,
+                                     array('persistent' => !empty($params['persistent'])));
+            if ($this->_db instanceof PEAR_Error) {
+                throw Horde_Exception($this->_db);
+            }
+
+            // Set DB portability options.
+            switch ($this->_db->phptype) {
+            case 'mssql':
+                $this->_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS | DB_PORTABILITY_RTRIM);
+                break;
+
+            default:
+                $this->_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS);
+            }
+
+        } else {
+            /* Default to the same DB handle for the writer too. */
+            $this->_db = $this->_write_db;
+        }
+
+        $this->_connected = true;
+    }
+
+    /**
+     * Disconnects from the SQL server and cleans up the connection.
+     */
+    protected function _disconnect()
+    {
+        if ($this->_connected) {
+            $this->_connected = false;
+            $this->_db->disconnect();
+            $this->_write_db->disconnect();
+        }
+    }
+    
+}
diff --git a/lib/Exception.php b/lib/Exception.php
new file mode 100644 (file)
index 0000000..301bcf7
--- /dev/null
@@ -0,0 +1,4 @@
+<?php
+class Shout_Exception extends Horde_Exception
+{
+}
\ No newline at end of file
index e42142b..6d7e9bb 100644 (file)
@@ -100,44 +100,30 @@ class Shout
 
         $tabs = new Horde_UI_Tabs('section', $vars);
 
-        if (Shout::checkRights($permprefix . ':users', null, 1) &&
-            $shout->checkContextType($context, 'users')) {
-
-            $url = Horde::applicationUrl('usermgr.php');
+        if (Shout::checkRights($permprefix . ':extensions', null, 1)) {
+            $url = Horde::applicationUrl('extensions.php');
             $url = Horde_Util::addParameter($url, 'context', $context);
-            $tabs->addTab(_("_User Manager"), $url, 'usermgr');
+            $tabs->addTab(_("_Extensions"), $url, 'usermgr');
         }
 
-        if (Shout::checkRights($permprefix . ':dialplan', null, 1) &&
-            $shout->checkContextType($context, 'dialplan')) {
-
+        if (Shout::checkRights($permprefix . ':dialplan', null, 1)) {
             $url = Horde::applicationUrl('dialplan.php');
             $url = Horde_Util::addParameter($url, 'context', $context);
-            $tabs->addTab(_("_Dial Plan"), $url, 'dialplan');
+            $tabs->addTab(_("_Automated Attendant"), $url, 'dialplan');
         }
 
-        if (Shout::checkRights($permprefix . ':conference', null, 1) &&
-            $shout->checkContextType($context, 'conference')) {
-
+        if (Shout::checkRights($permprefix . ':conference', null, 1)) {
             $url = Horde::applicationUrl('conference.php');
             $url = Horde_Util::addParameter($url, 'context', $context);
             $tabs->addTab(_("_Conference Rooms"), $url, 'conference');
         }
 
-       if (Shout::checkRights($permprefix . ':moh', null, 1) &&
-            $shout->checkContextType($context, "moh")) {
-
+       if (Shout::checkRights($permprefix . ':moh', null, 1)) {
             $url = Horde::applicationUrl('moh.php');
             $url = Horde_Util::addParameter($url, 'context', $context);
             $tabs->addTab(_("_Music on Hold"), $url, 'moh');
         }
 
-        if ($perms->hasPermission('shout:superadmin', Horde_Auth::getAuth(), PERMS_SHOW|PERMS_READ)) {
-            $url = Horde::applicationUrl('security.php');
-            $url = Horde_Util::addParameter($url, 'context', $context);
-            $tabs->addTab(_("_Security"), $url, 'security');
-        }
-
         return $tabs;
     }
 
@@ -189,12 +175,4 @@ $ret = ($test & $permmask) == $permmask;
 print "Shout::checkRights() returning $ret";
         return ($test & $permmask) == $permmask;
     }
-
-    static public function getContextTypes()
-    {
-        return array(SHOUT_CONTEXT_CUSTOMERS => _("Customers"),
-                     SHOUT_CONTEXT_EXTENSIONS => _("Dialplan"),
-                     SHOUT_CONTEXT_MOH => _("Music On Hold"),
-                     SHOUT_CONTEXT_CONFERENCE => _("Conference Calls"));
-    }
 }
index b46161d..4ec1df0 100644 (file)
@@ -38,7 +38,6 @@ $conf = &$GLOBALS['conf'];
 // Ensure Shout is properly configured before use
 $shout_configured = (@is_readable(SHOUT_BASE . '/config/conf.php'));
 if (!$shout_configured) {
-    require SHOUT_BASE . '/../lib/Test.php';
     Horde_Test::configFilesMissing('Shout', SHOUT_BASE, array('conf.php'));
 }
 
@@ -60,16 +59,15 @@ $notification->attach('status');
 //// UI classes.
 //require_once 'Horde/UI/Tabs.php';
 
-$shout = Shout_Driver::singleton();
-
-//// Horde libraries.
-//require_once 'Horde/Help.php';
+$shout_storage = Shout_Driver::factory('storage');
+$shout_extensions = Shout_Driver::factory('extensions');
+$shout_devices = Shout_Driver::factory('devices');
 
 $context = Horde_Util::getFormData('context');
 $section = Horde_Util::getFormData('section');
 
 try {
-    $contexts = $shout->getContexts();
+    $contexts = $shout_storage->getContexts();
 } catch (Shout_Exception $e) {
     $notification->push($e);
     $contexts = false;
@@ -78,12 +76,12 @@ try {
 if (count($contexts) == 1) {
     // Default to the user's only context
     $context = $contexts[0];
-} elseif (!$context) {
-    try {
-        // Attempt to locate the user's "home" context
-        $context = $shout->getHomeContext();
-    } catch (Shout_Exception $e) {
-        $notification->push($e);
-    }
+} elseif (!empty($context) && !in_array($context, $contexts)) {
+    $notification->push('You do not have permission to access that context.', 'horde.error');
+    $context = false;
+} elseif (!empty($context)) {
+    $notification->push("Please select a context to continue.", 'horde.info');
     $context = false;
-}
\ No newline at end of file
+}
+
+$_SESSION['shout']['context'] = $context;
\ No newline at end of file
index dc0d89d..cddc8ff 100644 (file)
@@ -11,7 +11,7 @@
         </tr>
         <?php
             $line = 0;
-            foreach ($users as $extension => $user) {
+            foreach ($extensions as $extension => $user) {
                 $rowcolor = $line % 2;
                 $line++;
                 $url = Horde::applicationUrl("index.php");