<?php
/**
 * @category    Atmail
 * @package     Models
 * @subpackage  API
 * @copyright   Copyright (c) 2009-2011 ATMAIL. All rights reserved
 * @license     See http://atmail.com/license.php for license agreement
 * @author      Ben Duncan ben@staff.atmail.com
 * @author      Atmail (http://atmail.com)
 */

function deleteDirectory($dir)
{
    if($dir == '' || $dir == 'tmp') return;

    if( !file_exists($dir) )
        return true;
    if( !is_dir($dir) || is_link($dir) )
        return unlink($dir);
    foreach( scandir($dir) as $item )
    {
        if( $item == '.' || $item == '..')
            continue;
        if( !deleteDirectory($dir . "/" . $item) )
        {
            @chmod($dir . "/" . $item, 0777);
            if( !deleteDirectory($dir . "/" . $item) )
                return false;

        }

    }
    return @rmdir($dir);
}

class api
{
    private $directApi;
    private $dbTables;

    public function __construct(Array $args=array())
    {
        require_once('contacts.php');
        require_once('users.php');

        $this->dbTables = new dbTables();
        $this->dbAdapter = Zend_Registry::get('dbAdapter');
        $this->config = Zend_Registry::get('config');

        if( !empty($args['Username']) )
        {
            $this->Username = $args['Username'];

        }
        elseif( isset($_SERVER["PHP_AUTH_USER"]) )
        {
            $this->Username = $_SERVER["PHP_AUTH_USER"];
        }
        else
        {
            $this->Username = null;

        }
        $password_md5 = 0;
        $admin_config = $this->config->admin;
        if(isset($admin_config['password']) && strtoupper($admin_config['password']) == Atmail_Enum::PASSWORD_MD5)
        {
            $password_md5 = 1;
        }

        if( !empty($args['Password']) )
        {
            if($password_md5)
            {
                $this->Password = md5($args['Password']); //for security (not needed remotely as plain text) store passwords md5 encrypted
            }
            else
            {
                $this->Password = $args['Password']; // legacy
            }
        }
        else
        {
            if( $password_md5 && isset($_SERVER["PHP_AUTH_PW"]) )
            {
                $this->Password = md5($_SERVER["PHP_AUTH_PW"]); //for security (not needed remotely as plain text) store passwords md5 encrypted

            }
            elseif( isset($_SERVER["PHP_AUTH_PW"]) )
            {
                $this->Password = $_SERVER["PHP_AUTH_PW"]; // legacy

            }
            else
            {
                $this->Password = null;

            }
        }

        //resolve user from auth username and make available as class var for use in this hit (stateless)
        $this->dbAdapter = Zend_Registry::get('dbAdapter');
        //$this->log = Zend_Registry::get('log');

        $this->directApi = 0;
        if(isset($args['directApi']) && $args['directApi'] == 1)
        {
            $this->directApi = 1;
        }
        $this->adminInfo = $this->getCurrentAdminData();

        //TODO: abstract this into a reusable model
        $subadminGroupsResponse = $this->dbAdapter->fetchAll(
            $this->dbAdapter->select()
                            ->from( $this->dbTables->AdminGroup, array('Ugroup') )
                            ->where("Username = " . $this->dbAdapter->quote($this->Username) )
        );

        $this->subadminGroups = array();
        foreach( $subadminGroupsResponse as $row )
        {
            if( isset($row['Ugroup']) )//N.B. '' marker for no group restrictions
            {
                $this->subadminGroups[] = $row['Ugroup'];

            }

        }
        if( $this->config->global['demo'] == 1 )
            $this->subadminDomains = array('demo.atmail.com');
        else
        {
            $subadminDomainsResponse = $this->dbAdapter->fetchAll( $this->dbAdapter->select()
                                                                                   ->from( $this->dbTables->AdminGroup, array('Domain') )
                                                                                   ->where("UserName = " . $this->dbAdapter->quote($this->Username) )
                                                                 );
            $this->subadminDomains = array();
            foreach( $subadminDomainsResponse as $row )
                if( isset($row['Domain']) && $row['Domain'] != '' )
                    $this->subadminDomains[] = $row['Domain'];
        }

        $this->groupsObject = new groups();
        $this->groups = $this->groupsObject->getList();
        $this->domains = domains::getList();

    }

    public function index()
    {
        return array( 'response' => array('message' => 'This API does not have a default method. Refer to documentation.'), 'status' => 'failed' );

    }

    public function authenticate()
    {
        //redundant. ACL handled by parent
        if( $this->adminInfo == null )
            return $this->authenticationFailed();
        //api protected by application level ACL so only have to respond with success and optionally store auth for internal use
        return array( 'response' => array('message' => "Authentication success") );
    }

    public function authenticationFailed()
    {
        return array( 'response' => array('message' => 'Authentication failed'), 'status' => 'failed' );
    }

    public function error($message)
    {
        return array( 'response' => array('message' => $message), 'status' => 'failed' );
    }

    /**
     * Domain functions
     */
    public function domainCreate( $name )
    {
        $this->_pluginCall('preDomainCreate', array("domain" => $name, "apiObj" => $this));

        if( $this->adminInfo['UMasterAdmin'] != true )
        {
            return array( 'response' => array('message' => 'Permission denied adding domain ' . $name), 'status' => 'failed' );
        }

        if( in_array($name, $this->domains) )
        {
            return array( 'response' => array('message' => $name . " already exists"), 'status' => 'failed' );
        }
        else
        {
            $numberOfAffectedRows = $this->dbAdapter->insert("Domains", array('Hostname' => $name, 'Enable' => '1') );

            $this->_pluginCall('postDomainCreate', array("numberOfAffectedRows" => $numberOfAffectedRows, "apiObj" => $this));

            if( $numberOfAffectedRows == 1 )
            {
                return array('response' => array('message' => "Domain added"), 'status' =>'success');
            }
            else
            {
                return array('response' => array('message' => "Failed adding domain. Failed adding group " . $name), 'status' => 'failed' );
            }
        }
    }

    public function domainDelete( $name )
    {
        $resp = $this->_pluginCall('preDomainDelete', array("domainName" => $name, "apiObj" => $this));

        if( $this->adminInfo['UMasterAdmin'] != true )
        {
            return array( 'response' => array('message' => 'Permission denied deleting domain ' . $name), 'status' => 'failed' );
        }

        if( !in_array($name, $this->domains) )
        {
            return array( 'response' => array('message' => $name . " does not exist"), 'status' => 'failed' );
        }
        else
        {
            if ($resp !== false)
            {
                $success = domains::delete( $name );
                $this->_pluginCall('postDomainDelete', array("success" => $success, "apiObj" => $this));
            }

            if( $success )
            {
                return array('response' => array('message' => "$name deleted") );
            }
            else
            {
                return array( 'response' => array('message' => $name . " was found, but unable to delete"), 'status' => 'failed' );
            }
        }
    }

    public function domainList($optionalArgs = null)
    {
        $this->_pluginCall('preDomainList', $this);

        if( $this->directApi || $this->adminInfo['UMasterAdmin'] == 1 && empty($this->config->global['demo']) )
        {
            $arr = $this->dbAdapter->select();
            if(isset($optionalArgs['countOnly']))
            {
                $arr = $arr->from($this->dbTables->Domains, 'count(*) as count');
            }
            else
            {
                $arr = $arr->from($this->dbTables->Domains, new Zend_Db_Expr('Hostname as DomainName'))->order('DomainName ASC');
            }
            if(isset($optionalArgs['page']) && isset($optionalArgs['pageSize']))
            {
                $arr = $arr->limitPage($optionalArgs['page'], $optionalArgs['pageSize']);
            }

            $arr = $arr->query()->fetchAll();
        }
        elseif( isset($this->adminInfo['UMasterAdmin']) && isset($this->adminInfo['Username']) )
        {
            $arr = $this->dbAdapter->select();
            if(isset($optionalArgs['countOnly']))
            {
                $arr = $arr->from($this->dbTables->AdminGroup, 'count(*) as count')->where('Username = ?', array($this->adminInfo['Username']));
            }
            else
            {
                $arr = $arr->from($this->dbTables->AdminGroup, new Zend_Db_Expr('Domain as DomainName'))->where('Username = ?', array($this->adminInfo['Username']))->order('DomainName ASC');
            }
            if(isset($optionalArgs['page']) && isset($optionalArgs['pageSize']))
            {
                $arr = $arr->limitPage($optionalArgs['page'], $optionalArgs['pageSize']);
            }

            $arr = $arr->query()->fetchAll();
        }
        else
        {
            return array( 'response' => array('message' => 'Permission denied'), 'status' => 'failed' );
        }

        if(!isset($optionalArgs['countOnly']))
        {
            $hostnames = array();
            $i=0;
            foreach($arr as $hostname)
            {
                if($hostname['DomainName'] != '')
                {
                    $hostnames[$i]['DomainName'] = $hostname['DomainName'];
                    $total = $this->dbAdapter->select()->from($this->dbTables->Users, new Zend_Db_Expr('count(id) as totalUsers'))->where("Users.Account like concat('%@', " . $this->dbAdapter->quote($hostname['DomainName']) . ")")->query()->fetchAll();
                    $hostnames[$i]['totalUsers'] = $total[0]['totalUsers'];
                    $enabled = $this->dbAdapter->fetchCol("select Enable from Domains where Hostname = ?", array($hostname['DomainName']));
                    $hostnames[$i]['enabled'] = $enabled[0];
                    $i++;
                }
            }
        }
        else
        {
            $hostnames = array('status' => 'success', 'count' => $arr[0]['count']);
        }

        $this->_pluginCall('postDomainList', array("domains" => &$hostnames, "apiObj" => $this));

        return $hostnames;

    }

    public function domainCheck($name)
    {
        $this->_pluginCall('preDomainCheck', array("domainName" => &$name, "apiObj" => $this));

        if( $this->adminInfo['UMasterAdmin'] != true )
            return array( 'response' => array('message' => 'Permission denied checking domain ' . $name), 'status' => 'failed' );

        $matches = $this->dbAdapter->select('Hostname')->from($this->dbTables->Domains)->where('Hostname = ?', array($name))->query()->fetchAll();

        $this->_pluginCall('postDomainCheck', array("domainName" => &$name, "matches" => &$matches, "apiObj" => $this));

        if( count($matches) == 0)
            return array('response' => array('message' => "$name does not exist"), 'status' => 'failed' );
        else
            return array('response' => array('message' => "$name exists"), 'status' => 'success' );

    }

/**
 * Group functions
 */
    public function groupCreate( $args = array() )
    {
        if( $this->adminInfo['UMasterAdmin'] != true )
        {
            return array( 'response' => array('message' => 'Permission denied adding group'), 'status' => 'failed' );
        }

        if( !isset($args['GroupName']) )
        {
            $args['GroupName'] = $args['groupName'];
        }

        $defaultGroupValuesResponse = $this->groupGet( 'default' );
        $defaultGroupValues = $defaultGroupValuesResponse['response']['results'];
        //merge supplied args over default
        $newGroup = array_merge($defaultGroupValues, $args);

        $result = groups::create( $newGroup );
        if( $result['status'] == 'success' )
        {
            return array( 'response' => array('message' => 'Created group ' . $args['groupName']), 'status' => 'success' );
        }
        else
        {
            return $result;
        }

    }

    public function groupGet( $groupName )
    {
        if( !$this->directApi && $this->adminInfo['UMasterAdmin'] != true )
        {
            return array( 'response' => array('message' => 'Permission denied getting group data ' . $groupName), 'status' => 'failed' );

        }

        $exists = groups::exists( $groupName );
        if( !$exists )
        {
            return array('response' => array('message' => $groupName . " does not exist"), 'status' => 'failed' );
        }
        else
        {
            $results = groups::get( $groupName );
            return array('response' => array('results' => $results), 'status' => 'success' );

        }

    }

    public function groupExists( $groupName )
    {
        if(  !($this->adminInfo['UAdd'] || $this->adminInfo['UMasterAdmin']) )
        {
            return array( 'response' => array('message' => 'Permission denied checking existence of group'), 'status' => 'failed' );
        }

        $exists = groups::exists( $groupName );
        if( !$exists )
        {
            return array( 'response' => array('message' => "Group does not exist", 'exists' => false), 'status' => 'success' );
        }
        else
        {
            return array( 'response' => array('message' => "Group exists", 'exists' => true), 'status' => 'success' );
        }
    }

    public function domainGroupsList()
    {
        if( !($this->adminInfo['UAdd'] || $this->adminInfo['UMasterAdmin']) )
        {
            return array( 'response' => array('message' => 'Permission denied checking existence of group'), 'status' => 'failed' );
        }

        $list = groups::getList( $namesOnly = false );
        if( !is_array($list) )
        {
            return array( 'response' => array('message' => "Error finding Groups"), 'status' => 'failed', 'success' => false );
        }
        else
        {
            return array( 'response' => array('message' => "Groups exist", 'results' => $list), 'status' => 'success', 'success' => true );
        }

    }

    public function groupsList( $type = 'all' )
    {
        if( $this->adminInfo['UMasterAdmin'] == 1 && empty($this->config->global['demo']))
        { //masteradmin

            $list = $this->groups;

        }
        elseif( isset($this->adminInfo['UMasterAdmin']) && isset($this->adminInfo['Username']) )
        {
            //subadmin
            $list = $this->dbAdapter->select()->from( $this->dbTables->AdminGroup, array('Ugroup') )
                                              ->where('Username = ?', array($this->adminInfo['Username']))
                                              ->where('Ugroup IS NOT NULL')
                                              ->query()->fetchAll();
            foreach( $list as $k => $v )
            {
                $list[$k] = $v['Ugroup']; //unpack
            }
            //add the default group onto the begidnning - is a member of admin and subadmin user groups
            array_unshift($list, 'default');

        }
        else
        {
            return array( 'response' => array('message' => 'Permission denied'), 'status' => 'failed' );
        }

        if( $type == 'user' )
        {
            //cull domain matches from list
            $processedGroups = array();
            foreach( $list as $group )
            {
                if( in_array($group, $this->domains) )
                {
                    continue;
                }
                else
                {
                    $processedGroups[] = $group;
                }
            }
            return array( 'response' => array('results' => $processedGroups), 'status' => 'success' );

        }
        elseif( $type == 'domain' )
        {
            //cull domain matches from list
            $processedGroups = array();
            foreach( $list as $group )
            {
                if( in_array($group, $this->domains) )
                {
                    $processedGroups[] = $group;
                }
            }
            return array( 'response' => array('results' => $processedGroups), 'status' => 'success' );
        }
        else
        {
            return array( 'response' => array('results' => $list), 'status' => 'success' );
        }
    }

    public function groupTotal( $groupName )
    {
        $exists = groups::exists( $groupName );
        if( !$exists )
        {
            return array( 'response' => array('message' => "Group does not exist", 'exists' => false), 'status' => 'success' );
        }
        else
        {
            $groupTotalRows = $this->dbAdapter->fetchAll( $this->dbAdapter->select()->from($this->dbTables->Users, new Zend_Db_Expr('count(id) as total'))->where("Ugroup = ?", $groupName) );
            return array( 'response' => array('total' => $groupTotalRows[0]['total']), 'status' => 'success' );
        }
    }

    public function groupsListTotals( $type = 'user')
    {
        if( $this->adminInfo['UMasterAdmin'] == 1 && empty($this->config->global['demo']))
        { //masteradmin

            $list = array();

            foreach( $this->groups AS $v )
            {
               $list[] = array('GroupName' => $v);
            }
        }
        //subAdmins not yet allowed to create groups, so should only see allowed domains and all groups
        elseif( isset($this->adminInfo['UMasterAdmin']) && isset($this->adminInfo['Username']) )
        {
            //subadmin
            $list = $this->dbAdapter->select()->from($this->dbTables->AdminGroup)->where('Username = ?', array($this->adminInfo['Username']))->query()->fetchAll();

            //merge Ugroup and Domain into one list (currently Subadmins not allowed to have own Ugroups related to domains)
            foreach( $list as $k => $v )
            {
                $list[$k]['GroupName'] = $v['Domain'];
                //overwrite with Ugroup if set
                if( $v['Ugroup'] != null )
                {
                    $list[$k]['GroupName'] = $v['Ugroup'];
                }
            }
        }
        else
        {
            return array( 'response' => array('message' => 'Permission denied'), 'status' => 'failed' );
        }
        //if $type == 'user' then cull all domain groups out of the results to make them more logical
        if( $type == 'user' )
        {
            $newList = array();
            foreach( $list as $group )
            {
                if( !in_array($group['GroupName'], $this->domains) )
                {
                    $newList[] = $group;
                }
            }
            $list = $newList;
        }

        //at this point $list contains complete group list (domain and user) or (domain list)
        // if type = user then we searching users for matches in Users.Ugroup
        //if type = domain then we searching for users with matches in Account
        //merge total users for each group
        foreach($list as $k => $group)
        {
            $groupTotalResult = self::groupTotal( $group['GroupName'] );
            if( isset($groupTotalResult['status']) && $groupTotalResult['status'] == 'success' )
            {
                $list[$k]['totalUsers'] = $groupTotalResult['response']['total'];
            }
        }

        return array( 'response' => array('results' => $list), 'status' => 'success' );

    }

    public function groupUpdate( $groupName, $data )
    {
        if( $this->adminInfo['UMasterAdmin'] != true )
            return array( 'response' => array('message' => 'Permission denied updating group'), 'status' => 'failed' );

        $result = groups::update( $groupName, $data );

        if( $result['status'] == 'success' )
        {
            //now that group is updated, we need to sync dependant fields that are handled seperately.
            if( isset($data['Calendar']) )
                $this->groupUserUpdateCachedData( $groupName );

            return array( 'response' => array('message' => 'Updated group'), 'status' => 'success' );

        }
        else
            return $result;

    }

    public function groupDelete( $groupName )
    {
        if( $this->adminInfo['UMasterAdmin'] != true )
        {
            return array( 'response' => array('message' => 'Permission denied deleting group'), 'status' => 'failed' );

        }

        $result = groups::delete( $groupName );

        if( $result['status'] == 'success')
        {
            //group successfully deleted so now update all user groups that had this deleted group
            $groupUsers = users::getGroupUsers( $groupName );
            if( count($groupUsers) > 0 )
            {
                //if find users belonging to old group, then get domain, search for domain group. if found set Ugroup to domain group else default
                $Account = $groupUsers[0]['Account'];
                $AccountParts = explode('@', $Account);
                if( count($AccountParts) == 2)
                {
                    //try find domain group and save all users to Ugroup = domain, else default group
                    $existsResult = $this->groupExists( $AccountParts[1] );
                    if( $existsResult['response']['exists'] == true )
                    {
                        foreach( $groupUsers as $user )
                        {

                            $result = users::set( $user['Account'], array('Ugroup' => $AccountParts[1]) );

                        }

                    }
                    else
                    {

                        foreach( $groupUsers as $user )
                        {

                            $result = users::set( $user['Account'], array('Ugroup' => 'default') );

                        }

                    }

                }
                else
                {

                    return array( 'response' => array('message' => 'Found bad domain part in user Account while deleting group users'), 'status' => 'failed' );

                }

            }

        }
        else
        {

            return array( 'response' => array('message' => 'Unable to find group'), 'status' => 'failed' );

        }
        return array( 'status' => 'success' );

    }

    public function groupUserAdd( $groupName, $Account )
    {

        if( $this->adminInfo['UMasterAdmin'] != true )
            return array( 'response' => array('message' => 'Permission denied updating group'), 'status' => 'failed' );

        $existsResult = $this->groupExists( $groupName );
        if( $existsResult['response']['exists'] == false )
            return $existsResult;

        return $this->userUpdate( array('name' => $Account, 'Ugroup' => $groupName) );

    }

    public function groupUsersAdd( $groupName, $Accounts )
    {

        $dbAdapter = Zend_Registry::get('dbAdapter');
        $dbTables = new dbTables();

        if( $this->adminInfo['UMasterAdmin'] != true )
            return array( 'response' => array('message' => 'Permission denied updating group'), 'status' => 'failed' );

        $existsResult = $this->groupExists( $groupName );
        if( $existsResult['response']['exists'] == false )
            return $existsResult;

        foreach( $Accounts as $k => $v )
        {

            $userUpdateResult = $this->userUpdate( array('name' => $v, 'Ugroup' => $groupName) );
            if( $userUpdateResult['status'] == 'failed' )
                return $userUpdateResult;

        }
        return array( 'status' => 'success' );

    }

    public function groupUserDelete( $groupName, $Account )
    {

        if( $this->adminInfo['UMasterAdmin'] != true )
        {

            return array( 'response' => array('message' => 'Permission denied removing user from group'), 'status' => 'failed' );

        }

        $AccountParts = explode('@', $Account);
        if( count($AccountParts) == 2)
        {

            //try find domain group and save user to domain group, else default group
            $existsResult = $this->groupExists( $AccountParts[1] );
            if( $existsResult['response']['exists'] == true )
            {

                $newUgroup = $AccountParts[1];

            }
            else
            {

                $newUgroup = 'default';

            }

            $this->groupUserAdd( $newUgroup, $Account );

            return array( 'status' => 'success' );

        }

    }

    public function groupUsersDelete( $groupName, $Accounts )
    {

        if( $this->adminInfo['UMasterAdmin'] != true )
            return array( 'response' => array('message' => 'Permission denied removing user from group', 'errorCode' => 1), 'status' => 'failed' );

        //first check if domain group or default PushSupport=1 and how many users belong to group from PushSupport=0 then calculate if this will push over Push Seats avaialble
        $groupDefaultInfo = groups::get( 'default' );
        $groupCurrentInfo = groups::get( $groupName );
        if( $groupCurrentInfo === false )
            throw new Exception( 'Fatal error: group does not exist' );

        foreach( $Accounts as $Account )
        {

            $AccountParts = explode('@', $Account);
            if( count($AccountParts) == 2)
            {

                //try find domain group and save user to domain group, else default group
                $existsResult = $this->groupExists( $AccountParts[1] );
                if( $existsResult['response']['exists'] == true )
                    $newUgroup = $AccountParts[1];
                else
                    $newUgroup = 'default';

                $result = $this->groupUserAdd( $newUgroup, $Account );
                if( $result['status'] != 'success' )
                    return $result;

            }

        }
        return array( 'status' => 'success' );

    }

// user functions

    public function userCreate( $args = array() )
    {

        $this->_pluginCall('preUserCreate', array("args" => &$args, "apiObj" => $this));
        if (isset($this->_pluginBlockUserCreation)) {
            return array( 'response' => array('message' => $this->_pluginBlockUserCreation), 'status' => 'failed' );
        }

        //check for minimum argument requirements
        if( !array_key_exists('name', $args) )
        {

            return array( 'response' => array('message' => 'required name argument missing'), 'status' => 'failed' );

        }

        // sanity check incoming quota
        if(!array_key_exists('UserQuota',$args))
        {
            $args['UserQuota'] = 0;
        }
        if($args['UserQuota'] < 0)
        {
            $args['UserQuota'] = 0;
        }

        //validate if ACL authorized
        //RULES:
        // subadmin must be allowed to add users UAdd
        // number of existing subadmin users must be < NumUsers
        // new user domain must be in subadmin allowed list

        // subadmin must be allowed to add users UAdd
        require_once('Mail/RFC822.php');
        $rfc822 = new Mail_RFC822;
        $emailObject = $rfc822->parseAddressList(stripslashes($args['name']), null, false);
        if(gettype($emailObject) != "array")
        {
            return array( 'response' => array('message' => 'Invalid user address supplied'), 'status' => 'failed' );
        }

        // Create SIP Account if enabled
        $this->_globalConfig = $this->config->global;
        if( array_key_exists('serviceProviders', $this->_globalConfig) && $this->_globalConfig['serviceProviders'] == true )
            $this->serviceProvidersEnabled = true;
        else
            $this->serviceProvidersEnabled = false;

        if( $this->serviceProvidersEnabled && array_key_exists('SIPEnabled', $this->_globalConfig) && $this->_globalConfig['SIPEnabled'] == true )
            $this->SIPEnabled = true;
        else
            $this->SIPEnabled = false;
        if( $this->SIPEnabled && $this->_globalConfig['SIPAutoCreateUser'] == true )
        {
            //if demo server then also create the account on the SIP server
            if( $this->_globalConfig['demo'] == 1 ) //trigger creation of SIP Server account
            {
                //for demo accounts, cycle 8005-8995
                //first select max number, and if it is 8999 then delete lowest DateAdded and use its extension number
                $q = "SELECT MAX(PhoneNumber) AS m FROM ServiceAccounts WHERE PhoneNumber != 'null' AND PhoneNumber != 'Unknown' AND PhoneNumber IS NOT NULL AND PhoneNumber >= 8000 AND PhoneNumber < 9000";
                $result = $this->dbAdapter->query($q)->fetchAll();
                //Zend_Registry::get('log')->debug( "\n\n" . print_r($result,true) . "\n" . __METHOD__ . ' #' . __LINE__ . ": \$result: \n\n");
                if( $result[0]['m'] > 8995 )
                {

                    $q = "SELECT id, MIN(DateAdded) as m, PhoneNumber FROM ServiceAccounts WHERE PhoneNumber != 'null' AND PhoneNumber != 'Unknown' AND PhoneNumber IS NOT NULL AND PhoneNumber >= 8000 AND PhoneNumber < 9000";
                    $result = $this->dbAdapter->query($q)->fetchAll();
                    $numberBeingAssigned = $result[0]['m'];
                    $q = "UPDATE ServiceAccounts SET PhoneNumber = NULL WHERE PhoneNumber = '" . $numberBeingAssigned . "'";
                    $result = $this->dbAdapter->query($q);

                }
                else
                {
                    $numberBeingAssigned = (Integer)$result[0]['m'] + 1;
                    if( $numberBeingAssigned < 8005 )
                        $numberBeingAssigned = 8005;
                }
                $args['PhoneNumber'] = $numberBeingAssigned;

                $SIPServerAddress = $this->_globalConfig['SIPServerAddress'];
                $account = $args['name'];
                $args['Password'] = 'demo';
                $password = $args['Password'];
                $username = substr($account, 0, strpos($account, '@'));
                $domain = 'demo.atmail.com';//$emailObject[0]->host;
                $AuthorizationName = $username . '@' . $domain;

                if( !empty($SIPServerAddress) )
                {

                    $SIPServerAddress = $this->_globalConfig['SIPServerAddress'];
                    $account = $args['name'];
                    $password = $args['Password'];
                    $username = substr($account, 0, strpos($account, '@'));
                    $domain = $emailObject[0]->host;
                    $AuthorizationName = $username . '@' . $domain;
                    $q = "http://demo.atmail.com/ast/manageSIPUsers.php?action=create&name=$username&domain=$domain&Password=$password&UserFirstName=$username&UserLastName=Demo%20Account&cidn=$numberBeingAssigned";
                    //Zend_Registry::get('log')->debug( "\n\n" . print_r($q,true) . "\n" . __METHOD__ . ' #' . __LINE__ . ": \$q: \n\n");
                    $response = file_get_contents($q);
                    //Zend_Registry::get('log')->debug( "\n\n" . print_r($response,true) . "\n" . __METHOD__ . ' #' . __LINE__ . ": \$response: \n\n");

                }

            }

            $this->userCreateSIP( $args, $emailObject ); //creates SIP client account
        }

        $domain = $emailObject[0]->host;

        if( $this->directApi || $this->adminInfo['UMasterAdmin'] != true )
        {
            if( !$this->directApi )
            {

                if( $this->adminInfo['UAdd'] != 1 )
                    return array( 'response' => array('message' => 'Not allowed to add users'), 'status' => 'failed' );

                // Calculate the number of user accounts the Subadmin currently has
                if( count($this->getDomainAndGroupUsers()) >= $this->adminInfo['NumUsers'] )
                {

                    return array( 'response' => array('message' => 'No user credits available'), 'status' => 'failed' );

                }

                // Check the disk quota has not exceeded
                if( $this->adminInfo['NumQuota'] > 0)
                {
                    // unlimited quota is not allowed for subadmins with quota.
                    if($args['UserQuota'] == 0)
                    {
                        return array( 'response' => array('message' => 'You do not have permission to create users with unlimited quota.'), 'status' => 'failed' );
                    }

                    // Add the current quota, plus the new amount for the user
                    $currentQuota = $this->getUsedQuota(array(Atmail_Quota::TYPE => Atmail_Quota::TYPE_SUBADMIN, Atmail_Quota::DATA => $this->subadminDomains));
                    $requestedQuota = round($currentQuota + $args['UserQuota']);

                    if( $requestedQuota > $this->adminInfo['NumQuota'])
                    {

                        return array( 'response' => array('message' => 'Unable to add user. Requested disk space quota of ' . $args['UserQuota'] . 'MB exceeds available ' . round($this->adminInfo['NumQuota'] - $currentQuota) . 'MB quota.'), 'status' => 'failed' );

                    }
                }
            }

            // new user domain must be in subadmin allowed list
            if( !array_key_exists('Ugroup', $args) )
            {
                if( array_key_exists('group', $args) )
                {
                    $args['Ugroup'] = $args['group'];
                }
                else
                {
                    $args['Ugroup'] = '';
                }
            }
            if( $args['Ugroup'] == '' )
            {
                $groupExistsResponse = $this->groupExists( $domain );
                if( isset( $groupExistsResponse['response'] ) &&  isset($groupExistsResponse['response']['exists']) && $groupExistsResponse['response']['exists'] )
                {
                    $args['Ugroup'] = $domain;
                }
                else
                {
                    $args['Ugroup'] = 'default';
                }
            }

            // If group not defined, add to default
            if(empty($args['Ugroup']))
            {
                $args['Ugroup'] = 'default';
            }

            // TODO: condense below to origional specification
            if( !$this->directApi && !$this->groupAllowed($args['Ugroup']) )
            {

                return array( 'response' => array('message' => 'Permission denied adding to group ' . $args['Ugroup']), 'status' => 'failed' );

            }

            if( !$this->directApi && !$this->domainAllowed($domain) )
            {

                return array( 'response' => array('message' => 'Permission denied adding to domain ' . $domain), 'status' => 'failed' );

            }

        }

        // Lowercase the username
        $args['name'] = strtolower($args['name']);

        $existingUser = $this->dbAdapter
                             ->select()
                             ->from($this->dbTables->Users)
                             ->where('Account = ' . $this->dbAdapter->quote($args['name']))
                             ->query()
                             ->fetchAll();
        if( count($existingUser) > 0 )
        {
            return array( 'response' => array('message' => $args['name'] . " already exists"), 'status' => 'failed', 'datecreated' => $existingUser['0']['DateCreate'] );
        }

        //Prepare all data for users::add($name, $userArray)
        $userArray = array( 'Users' => array(), 'UserSession' => array(), 'UserSettings' => array() );
        $userArray['Users'] = array('Account' => $args['name']);
        $userArray['UserSession'] = array('Account' => $args['name']);
        $userArray['UserSettings'] = array('Account' => $args['name']);

        //UserStatus can now not be NULL so if not set then we need to set it to enabled (0)
        if( !array_key_exists('UserStatus', $args) )
        {

            $args['UserStatus'] = '0';
        }

        //STEP1: set up Users data
        $allowedInputUserFields = array('PasswordQuestion', 'Ugroup', 'UserStatus', 'MailDir', 'Forward', 'AutoReply', 'UserQuota');
        foreach( $allowedInputUserFields as $k)
        {

            if( array_key_exists($k, $args) )
            {

                $userArray['Users'][$k] = $args[$k];

            }

        }

        // If a blank or no UserQuota defined, use the system default
        if(
            !array_key_exists('UserQuota', $userArray['Users'])
            || (array_key_exists('UserQuota', $userArray['Users']) && $userArray['Users']['UserQuota'] == '')
        )
        {
            $userArray['Users']['UserQuota'] = $this->config->defaultUserSettings['UserQuota'];
        }

        //only create users for local accounts, not external
        if( isset($args['isExternalUser']) && $args['isExternalUser'] == '1' )
        {

            $userArray['Users']['MailDir'] = '';

        }
        else
        {

            $userArray['Users']['MailDir'] = users::generateMailDir($args['name']);
            users::generateDefaultFolders($userArray['Users']['MailDir']);

        }

        $userArray['Users']['DateCreate'] = new Zend_Db_Expr("NOW()");

        // Step 2: set up UserSession data
        $allowedInputUserFields = array('ChangePass', 'lifetime');
        foreach( $allowedInputUserFields as $k)
        {

            if( array_key_exists($k, $args) )
            {

                $userArray['UserSession'][$k] = $args[$k];

            }

        }

        if( array_key_exists('password', $args) )
        {
            $args['Password'] = $args['password'];
        }

        if( array_key_exists('Password', $args) )
        {
            $cryptType = $this->config->global['userPasswordEncryptionType'];
            if( isset($args['isExternalUser']) && $args['isExternalUser'])
            {
                $cryptType = !isset($this->config->global['externalUserPasswordEncryptionType']) ? Atmail_Enum::PASSWORD_PLAIN : $this->config->global['externalUserPasswordEncryptionType'];
            }

            //crypt Password field if crypt enabled
            switch($cryptType)
            {
                case Atmail_Enum::PASSWORD_MD5_CRYPT :
                {
                    try
                    {
                        $userArray['UserSession']['Password'] = crypt($args['Password'], "$1$".substr(md5(microtime()),rand(0,26),8)."$");
                    }
                    catch (Exception $e)
                    {
                        $userArray['UserSession']['Password'] = crypt($args['Password']);
                    }
                }
                break;
                case Atmail_Enum::PASSWORD_MD5 :
                {
                    $userArray['UserSession']['Password'] = md5($args['Password']);
                }
                break;
                case Atmail_Enum::PASSWORD_ENCRYPTED :
                {
                    $userArray['UserSession']['Password'] = Atmail_Password::hexaes_encrypt($args['Password']);
                }
                break;
                case Atmail_Enum::PASSWORD_PLAIN :
                {
                    $userArray['UserSession']['Password'] = $args['Password'];
                } break;
            }
        }

        foreach(array('DisplayImages', 'Language', 'MsgNum', 'TimeFormat', 'DateFormat', 'ViewThreads', 'TimeZone', 'CalDavUrl', 'DateDisplay', 'DefaultView', 'CalDavView', 'CalDavType', 'cssStyleTheme', 'ThreadLimit', 'SaveDrafts', 'AutoReload', 'DisplayEmailImages', 'DeleteTrashOnLogout') as $name)
        {
            $userArray['UserSettings'][$name] = $this->config->defaultUserSettings[$name];
        }

        // Users language, can be overwritten, if specified
        if(!empty($args['Language']))
            $userArray['UserSettings']['Language'] = $args['Language'];

        $allowedInputUserFields = array('MailServer',);
        foreach( $allowedInputUserFields as $k)
        {

            if( array_key_exists($k, $args) )
                $userArray['UserSettings'][$k] = $args[$k];

        }
        if( !empty($args['firstName']) )
        {

            $args['UserFirstName'] = $args['firstName'];

        }
        if( !empty($args['lastName']) )
        {

            $args['UserLastName'] = $args['lastName'];

        }

        if(!empty($args['UserFirstName']) && !empty($args['UserLastName']))
        $userArray['UserSettings']['RealName'] = $args['UserFirstName'] . ' ' . $args['UserLastName'];
        else if(!empty($args['UserFirstName']))
        $userArray['UserSettings']['RealName'] = $args['UserFirstName'];

        // If user is logging in for the first time usig IMAPS
        // then set UseSSL = 1
        if (isset($args['ssl'])) {
            $userArray['UserSettings']['UseSSL'] = 1;
        }

        users::add($args['name'], $userArray );

        // Step 3: Update the users personal information in the global address-book ( Name, Address, etc )
        // Create a contact object with allowed fields
        $Abook['Global'] = '1';
        $Abook['UserEmail'] = $args['name'];

        $contacts = new contacts(array('Account' => $args['name']));
        foreach($contacts->allowedFields as $field)
        {
            if( isset($args[$field]) )
            {
                $Abook[$field] = $args[$field];
            }
        }

        if(empty($args['numberFieldName']))
            $args['numberFieldName'] = array();

        // Decide which phone fields are active
        //contact number fields
        $contactNumberFields = array('UserHomePhone', 'UserHomeMobile', 'UserHomeFax','UserWorkPhone', 'UserWorkMobile', 'UserWorkFax');
        $contactNumberFieldNameC = count( $args['numberFieldName'] );
        //create zeroed fields first so that deleted fields do actually get zeroed on the db
        foreach( $contactNumberFields as $k)
        {
            $Abook[$k] = '';
        }
        for( $a = 0 ; $a < $contactNumberFieldNameC ; $a++ )
        {
            if( strlen($args['numberValue'][$a]) > 0 )
                $Abook[$args['numberFieldName'][$a]] = $args['numberValue'][$a];
        }

        // Insert the entry into the GAL
        $contacts->addContact($Abook);

        //now get Users.id
        $userArray = users::getAllUserData($args['name']);
        $id = $userArray['Users']['id'];

        // Next, setup the users default folders for the files tab
        $this->fileAdapter = Atmail_Files_Factory::instance($args['name']);
        foreach( array('Documents', 'Pictures', 'Music', 'Attachments') as $dir )   {
            $this->fileAdapter->createDirectory($dir);
        }

        //Trim password out of welcome message if encrypted
        if( $this->config->global['userPasswordEncryptionType'] != Atmail_Enum::PASSWORD_PLAIN )
            unset($userArray['UserSession']['Password']);
        // Step 4 Send the users welcome message
        if( (!isset($args['skipWelcome']) || $args['skipWelcome'] == false) && (!isset($args['isExternalUser']) || $args['isExternalUser'] == false) )
            users::sendWelcomeEmail($userArray);

        return array('response' => array('message' => $args['name'] . ' added', 'id' => $id) );

    }

    public function userCreateSIP( $args = array(), $emailObject ) //create the SIP client account (does not create the SIP server account as in demo mode where we also must create the user accounton the SIP server)
    {

        $account = $args['name'];
        $password = $args['Password'];
        $username = substr($account, 0, strpos($account, '@'));
        $domain = $emailObject[0]->host;
        $AuthorizationName = $username;// Asterisk uses short, ims uses long . '@' . $domain;

        if( empty($args['PhoneNumber']) || !isset($args['PhoneNumber']) || !array_key_exists('PhoneNumber', $args) || $args['PhoneNumber'] == '' )
            $args['PhoneNumber'] = 'Unknown';

        $sql = "select id from `ServiceAccounts` where `Account`='" . $account . "'";
        $result = $this->dbAdapter->fetchAll($sql);
        if( count($result) > 0 )
        {
            return;
        }

        $sql = "INSERT INTO `ServiceAccounts` (
            `Account` ,
            `Type` ,
            `Provider` ,
            `PhoneNumber` ,
            `DateAdded` ,
            `SIP` ,
            `SMS` ,
            `Voicemail` ,
            `id` ,
            `UserId` ,
            `Domain` ,
            `AuthorizationName` ,
            `Password` ,
            `DisplayName` ,
            `SIPServer` ,
            `Data`
        )
        VALUES (
            '" . $account . "',
            'SIP',
            'Atmail',
            '" . $args['PhoneNumber'] . "',
            Now(),
            '1',
            '0',
            '0',
            NULL ,
            '" . $username . "',
            '" . $domain . "',
            '" . $AuthorizationName . "',
            '" . $password . "',
            '" . $username . "',
            '" . $this->config->global['SIPServerAddress'] . "',
            ''
        )";
        $this->dbAdapter->query($sql);

    }

    public function userSignup( $args = array() )
    {

        $this->_pluginCall('preUserSignup', array("args" => &$args, "apiObj" => $this));

        //check for minimum argument requirements
        if( !array_key_exists('hostname', $args) )
        {

            return array( 'response' => array('message' => 'required hostname argument missing'), 'status' => 'failed' );

        }

        //additional business rule of user signup: only allow signup to local domains;
        if( !in_array($args['hostname'], $this->domains) )
        {

            return array( 'response' => array('message' => 'Signup failed'), 'status' => 'failed' );

        }

        return $this->userCreate( $args );

    }

    public function userView($name='', $id='')
    {

        if( ($name == '' && $id == '') || ($name != '' && $id != '') )
            return array( 'response' => array('message' => 'Specify name or id argument'), 'status' => 'failed' );

        // format group and domain filter
        $groupWhereClause = '';
        $domainWhereClause = '';

        if( !$this->directApi && ( $this->adminInfo['UMasterAdmin'] != true || !empty($this->config->global['demo']) ))
        {

            $foundAGroupItem = false;
            foreach( $this->subadminGroups as $group )
            {

                if( $group != '' )
                {

                    if($foundAGroupItem)
                    {

                        $groupWhereClause .= " OR ";

                    }
                    $groupWhereClause .= "Ugroup = " . $this->dbAdapter->quote( $group );
                    $foundAGroupItem = true;

                }

            }
            if( $foundAGroupItem)
            {

                $groupWhereClause = " AND (" . $groupWhereClause . ")";

            }

            $foundADomainItem = false;
            foreach( $this->subadminDomains as $domain )
            {
                if( $foundADomainItem )
                {

                    $domainWhereClause .= " OR ";

                }
                $domainWhereClause .= "Users.Account LIKE " . $this->dbAdapter->quote("%@" . $domain);
                $foundADomainItem = true;

            }
            if( $foundADomainItem )
            {

                $domainWhereClause = " AND (" . $domainWhereClause . ")";

            }
            else
            {

                return array( 'response' => array('message' => "No domain permissions"), 'status' => 'failed' );

            }

        }

        if( empty($name) )
        {

            $idWhereClause = " AND Users.id=" . $this->dbAdapter->quote($id, "INTEGER");

        }
        else
        {

            $idWhereClause = " AND Users.Account = " . $this->dbAdapter->quote($name, "STRING");

        }

        // TODO: SOMEHOW we need to convert this to zenddb proper
        // currently, the left join will add a "AS `t`" which screws the rest of the statement.
        // Zend_db -> sigh.
        $q = "SELECT DISTINCT Users.PasswordQuestion, Users.Ugroup, Users.UserStatus, Users.MailDir, Users.Forward, Users.AutoReply, Users.UserQuota, Users.id AS userId, Abook.*, UserSession.Password, UserSettings.MailServer
                                            FROM Users
                                            LEFT JOIN Abook ON Users.Account=Abook.Account
                                            LEFT JOIN UserSession on Users.Account = UserSession.Account
                                            LEFT JOIN UserSettings on Users.Account = UserSettings.Account
                                            WHERE Abook.Global='1'$groupWhereClause $domainWhereClause $idWhereClause";

        $users = $this->dbAdapter->fetchAll($q);

        //TODO: join on Abook data Global = 1 (User self Abook records)
        if( count($users) > 0 )
        {
            $users[0]['UsedQuota'] = $this->getUsedQuota(array(Atmail_Quota::TYPE => Atmail_Quota::TYPE_MAILDIR, Atmail_Quota_Maildir::MAILDIR => $users[0]['MailDir']));
            $users[0]['status'] = 'success';
            return $users[0];
        } else
            return array( 'response' => array('message' => "$name$id does not exist"), 'status' => 'failed' );

    }

    public function userList($domain = false, $start=null, $volume=null, $searchQuery=null, $group = false, $fields = false)
    {
        /*
        ACL RULES:
        subadmin must be allowed to view domain if set
        */
        //return 'ddd' . $domain;

        $domainFilterWhereClause = '';
        if( $domain != false && !$this->domainAllowed($domain) && $this->adminInfo['UMasterAdmin'] != true )
        {

            return array( 'response' => array('message' => 'Permission denied'), 'status' => 'failed' );

        }
        elseif ($domain == 'external' && $this->adminInfo['UMasterAdmin'] == true)
        {

            $stmt = $this->dbAdapter->query("select Hostname from Domains");
            $where = array();
            while ($dom = $stmt->fetch())
            {
                $where[] = "Users.Account not like " . $this->dbAdapter->quote("%@" . $dom['Hostname']);
            }

            if( count($where) > 0 )
            {
                $domainFilterWhereClause = ' AND ' . join(' and ', $where);
            }
        }
        else if( $domain != false )
        {
            $domainFilterWhereClause = " AND Users.Account LIKE " . $this->dbAdapter->quote('%@' . $domain);
        }
        else if( $domain == false && $group !== false)
        {

            $domainFilterWhereClause = $this->dbAdapter->quoteInto(" AND Users.Ugroup = ?", $group);

        }
        $domainRestrictionWhereClause = "";
        if( count($this->subadminDomains) == 0 && $this->adminInfo['UMasterAdmin'] != true )
        {

            return array();
            //return array( 'response' => array('message' => 'No domains authorized'), 'status' => 'failed' );

        // If not the Master Webadmin and demo mode is off
        }
        else if($this->adminInfo['UMasterAdmin'] != true || !empty($this->config->global['demo']))
        {

            $foundADomainItem = false;
            foreach( $this->subadminDomains as $v )
            {
                if( $foundADomainItem )
                    $domainRestrictionWhereClause .= " OR";
                $domainRestrictionWhereClause .= " Users.Account LIKE " . $this->dbAdapter->quote('%@' . $v);
                $foundADomainItem = true;
            }
            if( $foundADomainItem )
                $domainRestrictionWhereClause = " AND (" . $domainRestrictionWhereClause . ")";

        }

        $limit = '';
        if (is_numeric($volume))
        {

            $limit = (is_numeric($start))? "LIMIT $start, $volume" : "LIMIT $volume";

        }

        $searchQueryWhereClause = "";
        $sortOrder = 'ORDER BY Abook.Account ASC';
        if ( !empty($searchQuery) )
        {

            if( substr($searchQuery,0,10) == "lastLogin=" )
                $searchQueryWhereClause = " AND ( UserSession.LastLogin < DATE_SUB(NOW(),INTERVAL " . $this->dbAdapter->quote( substr($searchQuery,10) ) . " DAY) )";
            elseif( mb_strpos($searchQuery, ':', 0, 'UTF-8') !== false )
            {

                //if : exists in search terms then explode into filter terms.
                //name:4444 surname:5555 sort:group order:DESC
                $mapAllowedHumanToInternal = array(
                    'name' => 'UserFirstName',
                    'surname' => 'UserLastName',
                    'lastname' => 'UserLastName',
                    'lastlogin' => 'LastLogin',
                    'company' => 'UserWorkCompany',
                    'department' => 'UserWorkDept',
                    'country' => 'UserHomeCountry',
                    'email' => 'Users.Account',
                    'account' => 'Users.Account',
                    'group' => 'UGroup',
                    'sort' => 'sort',
                    'order' => 'order'
                );
                //try split the query by tokens else search to, from, subject.
                $tokensPos = array();
                //initialize tokenPos
                foreach( $mapAllowedHumanToInternal as $fieldHuman => $fieldInternal )
                    $tokensPos[$fieldHuman] = array('start' => -1, 'end' => -1, 'value' => null, 'fieldInternel' => $fieldInternal);

                $searchQuery = strtolower($searchQuery);
                //N.B. must search including : else will badly match order:fields
                foreach( $tokensPos as $fieldHuman => &$vector )
                    if( $fieldHuman !== false && ($pos = mb_strpos($searchQuery, $fieldHuman . ':', 0, 'UTF-8')) !== false )
                        $vector['start'] = $pos;

                //if the lowest pos > -1 is not 0 then we have non token search as well
                $strlenSearch = mb_strlen($searchQuery);

                //now go through each one and build up the boundaries, trim and apply as a search term
                $lowestTokenStart = $strlenSearch;
                foreach( $tokensPos as $fieldHuman => &$vector )
                {

                    $vector['end'] = $strlenSearch;
                    //got start now find end
                    foreach( $tokensPos as $testToken => $testVector)
                        if( $testVector['start'] > $vector['start'] && $testVector['start'] < $vector['end'] )
                            $vector['end'] = $testVector['start'];

                    //push start forward the length of the token and save $pos back into array
                    if( $vector['start'] >= 0 && $vector['end'] >= 0 )
                        $vector['value'] = trim(mb_substr($searchQuery, $vector['start']+1+mb_strlen($fieldHuman), $vector['end']-($vector['start']+mb_strlen($fieldHuman)+1)));

                    //update lowestPos to try find if untokenised string present.
                    if( $vector['start'] >= 0 && $vector['start'] < $lowestTokenStart )
                        $lowestTokenStart = $vector['start'];

                }

                //check for sort order terms (if so remove them from where field clause composition)
                if( !empty($tokensPos['sort']) )
                {
                    $sortFieldInternal = $mapAllowedHumanToInternal[$tokensPos['sort']['value']];
                    if( empty($sortFieldInternal) )
                        $sortFieldInternal = 'Users.Account';
                    $sortOrder = 'ORDER BY ' .$this->dbAdapter->quoteInto($sortFieldInternal, 'STRING') .
                        ' ' . (strtoupper($tokensPos['order']['value'])=='DESC'?'DESC':'ASC');
                    unset($tokensPos['sort']);

                }
                unset($tokensPos['order']);
                $searchArgs = array();

                foreach( $tokensPos as $fieldHuman => $vector )
                    if( !empty($vector['value']) && array_key_exists($fieldHuman, $mapAllowedHumanToInternal) )
                        $searchArgs[] = " " . $mapAllowedHumanToInternal[$fieldHuman] . " LIKE " . $this->dbAdapter->quote('%' . $vector['value'] . '%');
                $searchQueryWhereClause = '';
                if( count($searchArgs) > 0 )
                    $searchQueryWhereClause = " AND ( " . implode(" AND ", $searchArgs) . " ) ";

            }
            else
            {

                $searchArgs = array();
                foreach(array('Users.Account', 'Abook.UserFirstName', 'Abook.UserLastName', 'Abook.UserMiddleName', 'Abook.UserWorkCompany', 'Abook.UserHomeCountry', 'Abook.UserWorkCountry', 'Abook.UserHomeCity', 'Abook.UserWorkCity', 'Abook.UserHomeState', 'Abook.UserWorkState') as $search)
                    $searchArgs[] = " $search like " . $this->dbAdapter->quote('%' . $searchQuery . '%');
                $searchQueryWhereClause = '';

                if( count($searchArgs) > 0 )
                    $searchQueryWhereClause = " AND ( " . implode(" OR ", $searchArgs) . " ) ";

            }

        }
        //special case searchQuery _ should not be interpreted as a wildcard (match one char) so convert to \_
        $searchQueryWhereClause = str_replace('\_','_', $searchQueryWhereClause); //not to miss existing \_
        $searchQueryWhereClause = str_replace('_','\_', $searchQueryWhereClause);

        // Scan thro optional arguments on what to select
        if( substr($searchQuery,0,10) == "lastLogin=" && (!isset($fields) || !is_array($fields)) )
            $abookQuery[] = 'LastLogin';
        elseif( is_array($fields) )
            foreach( $fields as $name )
                $abookQuery[] = "Abook.$name";
        else
            $abookQuery[] = "Abook.*";

        $abookQuery = implode(",", $abookQuery);

        //join on UserSession (heavier) if searchign for lastLogin
        if( substr($searchQuery,0,10) == "lastLogin=" )
        {
            if(isset($fields['countOnly']))
            {
                $queryStr = "SELECT count(DISTINCT Users.Account) as count ";
            }
            else
            {
                $queryStr = "SELECT DISTINCT UserSession.LastLogin, Users.Account, Users.id AS userId, Users.id AS id, " . $abookQuery;
            }

            // Select the users that match, with an optional creteria
            $q = $queryStr . "
            FROM UserSession
            LEFT JOIN Users ON UserSession.Account = Users.Account
            LEFT JOIN Abook ON Users.Account=Abook.Account
            WHERE Abook.Global='1'
            $domainRestrictionWhereClause
            $domainFilterWhereClause
            $searchQueryWhereClause
            ORDER BY Users.Account
            $limit";

        }
        else
        {
            if(isset($fields['countOnly']))
                        {
                if (empty($searchQueryWhereClause))
                {
                                    $q = "SELECT count(DISTINCT Users.Account) as count from Users where true
                                              $domainRestrictionWhereClause
                                              $domainFilterWhereClause";
                }
                else
                {
                    $q = "SELECT count(DISTINCT Users.Account) as count FROM Users
                          LEFT JOIN Abook ON Users.Account=Abook.Account
                                  WHERE Abook.Global='1'
                                  $domainRestrictionWhereClause
                                  $domainFilterWhereClause
                                  $searchQueryWhereClause";
                }
            }
                        else
                        {
                $q = "SELECT DISTINCT Users.Account, Users.id AS userId, Users.id AS id, " . $abookQuery .
                                     " FROM Users LEFT JOIN Abook ON Users.Account=Abook.Account WHERE Abook.Global='1'
                          $domainRestrictionWhereClause
                          $domainFilterWhereClause
                          $searchQueryWhereClause
                          $sortOrder
                          $limit";
                        }

        }
        //Zend_Registry::get('log')->debug( __METHOD__ . ' #' . __LINE__ . ': $q=' . $q);

        $matches = $this->dbAdapter->fetchAll($q);
        return $matches;

    }

    public function userUpdate( $args = array() )
    {

        //validate if ACL authorized
        //RULES:
        // subadmin must be allowed to edit users UAdd
        // new user domain anf/or group must be in subadmin allowed list

        // subadmin must be allowed to add users UAdd
        if( !$this->directApi && $this->adminInfo['UModify'] != 1 && $this->adminInfo['UMasterAdmin'] != true )
            return array( 'response' => array('message' => 'Not allowed to edit users'), 'status' => 'failed' );

        if( !array_key_exists('name', $args) && !array_key_exists('userId', $args) && !array_key_exists('id', $args) )
            return array( 'response' => array('message' => 'Required name or userId argument missing'), 'status' => 'failed' );

        //standardise interface fields
        if( array_key_exists('id', $args) )
            $args['userId'] = $args['id'];
        //CONSIDER allowing Account to be changed (would allow user to change box without having to migrate/delete-re-add
        //N.B. name refers to Account, not firstname.
        if( !array_key_exists('name', $args) )
            $args['name'] = users::getAccountById( $args['userId'] );

        // see if user account name change required
        if( array_key_exists('NewUsername', $args) )
        {
            $username = $args['name'];
            $domain = ($pos = strpos($username, '@')) !== false ? substr($username, $pos + 1) : '';
            $username = (($domain == '') ?  $args['NewUsername'] : $args['NewUsername'] . '@' . $domain);
            if(($result = users::changeAccountID($args['name'], $username)) !== true)
            {
                // some sort of error changing accounts
                return array( 'response' => array('message' => 'Unable to update username. Details : ' . $result), 'status' => 'failed' );
            }
            $args['name'] = $username;
        }

        // Check the disk quota has not exceeded on editing the user
        if($this->adminInfo['NumQuota'] > 0 && $this->adminInfo['UMasterAdmin'] != true)
        {

            //Total allocated quota for domain
            $currentQuota = $this->getUsedQuota(array(Atmail_Quota::TYPE => Atmail_Quota::TYPE_SUBADMIN, Atmail_Quota::DATA => $this->subadminDomains));

            // Find the original users disk quota
            $origQuota = Atmail_Quota::getTotalQuota(array(Atmail_Quota::TYPE => Atmail_Quota::TYPE_DB, Atmail_Quota::ACCOUNT => $args['name'] ));

            if( $args['UserQuota'] < 0 )
                $args['UserQuota'] = 0;

            //check requested quota doesnt push total quota over limit
            $requestedQuota = round($args['UserQuota']);
            $requestedTotalQuota = round($currentQuota + $args['UserQuota'] - $origQuota);

            if( $requestedTotalQuota > $this->adminInfo['NumQuota'])
                return array( 'response' => array('message' => 'Unable to update user. Requested disk space quota of ' . $args['UserQuota'] . 'MB exceeds available ' . round($this->adminInfo['NumQuota'] - $currentQuota + $origQuota) . 'MB quota.'), 'status' => 'failed' );
        }

        $rfc822 = new Mail_RFC822;
        $emailObject = $rfc822->parseAddressList(stripslashes($args['name']), null, false);
        $domain = $emailObject[0]->host;

        //now check if allowed to edit current user, then check if allowed to save with new data
        try
        {

            $existingData = users::getAllUserData( $args['name'] ); //will throw exception if user does not exist or old group not found

        }
        catch( Exception $e )
        {

            if( $e->getMessage() == 'Single match for Users was not found.')
            {
                header('x',true,412);
                return array( 'response' => array('message' => $args['name'] . ' Account does not exist'), 'status' => 'failed' );
            }
        }

        if( !$this->groupAllowed($existingData['Users']['Ugroup']) && $this->adminInfo['UMasterAdmin'] != true )
            return array( 'response' => array('message' => 'Permission denied'), 'status' => 'failed' );

        if( !$this->domainAllowed($domain) && $this->adminInfo['UMasterAdmin'] != true )
            return array( 'response' => array('message' => 'Permission denied'), 'status' => 'failed' );

        // new user domain must be in subadmin allowed list
        if( array_key_exists('Ugroup', $args) || array_key_exists('group', $args) )
        {

            if( !array_key_exists('Ugroup', $args) )
                $args['Ugroup'] = $args['group'];

            if( $args['Ugroup'] == '' )
            {

                $groupExistsResult = $this->groupExists( $domain );
                if( $groupExistsResult['response']['exists'] )
                    $args['Ugroup'] = $domain;
                else
                    $args['Ugroup'] = 'default';

            }

            if( !$this->groupAllowed($args['Ugroup']) && $this->adminInfo['UMasterAdmin'] != true )
                return array( 'response' => array('message' => 'Permission denied changing user to group: ' . $args['Ugroup']), 'status' => 'failed' );

            //Log user out of their current session if password or username changed
            if( $args['Password']  != $existingData['Password'] || $args['name'] != $existingData['name'])
                $args['UserSession']['SessionData'] = '';

            //now check if license conditions are valid
            //if changing from one license type to another and now type does not have >0 seats available then fail
            if( $args['Ugroup'] != $existingData['Users']['Ugroup'] )
            {

                $domainGroupsList = $this->domainGroupsList();
                $domainGroupsList = $domainGroupsList['response']['results'];
                $seatsAvailable = Zend_Registry::get('w4m70Yt0hYUdlM2r2lrrIuY0zgKG4A3vvftu4NbbCWfW');

                foreach( $domainGroupsList as $group )
                {

                    if( $group['GroupName'] == $args['Ugroup'] && $seatsAvailable[$group['licenseType']] < 1)
                        return array( 'response' => array('message' => 'Failed changing user to group: ' . $args['Ugroup'] . '. Insufficient license seats available for this group.'), 'status' => 'failed' );

                }

            }

        }

        //Prepare all data for users::add($name, $userArray)
        $userArray = array( 'Users' => array(), 'UserSession' => array(), 'UserSettings' => array() );
        $userArray['Users'] = array('Account' => $args['name']);
        $userArray['UserSession'] = array('Account' => $args['name']);
        $userArray['UserSettings'] = array('Account' => $args['name']);

        //STEP1: set up Users data
        //allowed fields stripped down because we moving to using Abook instead for this data (transitional)
        $allowedInputUserFields = array('PasswordQuestion', 'Ugroup', 'UserStatus', 'MailDir', 'Forward', 'AutoReply', 'UserQuota');
        foreach( $allowedInputUserFields as $k)
        {

            if( array_key_exists($k, $args) )
                $userArray['Users'][$k] = $args[$k];

        }

        // Step 2: set up UserSession data
        // TODO: Add crypt, MD5, and plain text password support
        //CONSIDER SHA1 and other supported mechanisms

        $userArray['UserSession']['SessionData'] = $args['UserSession']['SessionData'];

        $allowedInputUserFields = array('ChangePass', 'lifetime', 'CalendarUserStatus', 'SessionData');
        foreach( $allowedInputUserFields as $k)
        {
            if( array_key_exists($k, $args) )
                $userArray['UserSession'][$k] = $args[$k];
        }

        if( array_key_exists('password', $args) )
            $args['Password'] = $args['password'];

        if( array_key_exists('Password', $args) && strlen($args['Password']) >= 4 )
        {

            //crypt Password field if crypt enabled
            if( $this->config->global['userPasswordEncryptionType'] == Atmail_Enum::PASSWORD_MD5_CRYPT )
            {
                try
                {
                    $userArray['UserSession']['Password'] = crypt($args['Password'], "$1$".substr(md5(microtime()),rand(0,26),8)."$");
                }
                catch (Exception $e)
                {
                    $userArray['UserSession']['Password'] = crypt($args['Password']);
                }
            }
            elseif( $this->config->global['userPasswordEncryptionType'] == Atmail_Enum::PASSWORD_MD5 )
                $userArray['UserSession']['Password'] = md5($args['Password']);
            elseif( $this->config->global['userPasswordEncryptionType'] == Atmail_Enum::PASSWORD_ENCRYPTED )
                $userArray['UserSession']['Password'] = Atmail_Password::hexaes_encrypt($args['Password']);
            else
                $userArray['UserSession']['Password'] = $args['Password'];
        }

        $allowedInputUserFields = array('MboxOrder', 'RealName', 'Refresh', 'EmailHeaders', 'TimeZone', 'MsgNum',
                                      'EmptyTrash', 'NewWindow', 'HtmlEditor', 'ReplyTo', 'Signature', 'FontStyle', 'LeaveMsgs',
                                      'LoginType', 'Advanced', 'PrimaryColor', 'SecondaryColor', 'LinkColor', 'VlinkColor', 'BgColor',
                                      'TextColor', 'HeaderColor', 'HeadColor', 'MailType', 'ThirdColor', 'Mode', 'Service', 'Language',
                                      'StartPage', 'OnColor', 'OffColor', 'TextHeadColor', 'SelectColor', 'TopBg', 'AutoTrash', 'MailServer',
                                      'MailAuth', 'PGPenable', 'PGPappend', 'PGPsign', 'SpamTreatment', 'AbookTrusted', 'AntiVirus', 'PassCode',
                                      'DateFormat', 'TimeFormat', 'AutoComplete', 'EmailEncoding', 'DisplayImages', 'Ajax', 'UseSSL', 'CalDavUrl',
                                      'CalDavUser', /* 'CalDavPass', */ 'DateDisplay', 'DefaultView', 'CalDavType');
        foreach( $allowedInputUserFields as $k)
        {
            if( array_key_exists($k, $args) )
                $userArray['UserSettings'][$k] = $args[$k];

        }

        //handle en/diabling user
        if( array_key_exists('enabled', $args) )
            $userArray['Users']['UserStatus'] = ($args['enabled']=='true'?'0':'1');

        users::saveAllUserData($args['name'], $userArray );

        // Step 3: Update the users personal information in the global address-book ( Name, Address, etc )
        // Create a contact object with allowed fields

        $contacts = new contacts( array('Account' => $args['name'], 'admin' => true ) );
        $Abook = $args;
        $contactId = $contacts->searchContact( array('Global' => 1, 'Account' => $args['name']) );

        // Decide which phone fields are active
        //contact number fields
        $contactNumberFields = array('UserHomePhone', 'UserHomeMobile', 'UserHomeFax','UserWorkPhone', 'UserWorkMobile', 'UserWorkFax');

        //create zeroed fields first so that deleted fields do actually get zeroed on the db, but only if set on input
        $updateAbook = false;

        foreach( $contactNumberFields as $k)
        {
            if(array_key_exists($k, $args))
            {
                $Abook[$k] = $args[$k];
                $updateAbook = true;
            }
        }
        $args['firstName'] = array_key_exists("UserFirstName", $args) ? $args['UserFirstName'] : $args['firstName'];
        $args['lastName'] = array_key_exists("UserLastName", $args) ? $args['UserLastName'] : $args['lastName'];
        if( !empty($args['firstName']) )
        {
            $Abook['UserFirstName'] = $args['firstName'];
            $updateAbook = true;
        }
        if( !empty($args['lastName']) )
        {
            $Abook['UserLastName'] = $args['lastName'];
            $updateAbook = true;
        }

        if($updateAbook)
        {
            $Abook['id'] = $contactId;
            $contacts->updateContact($Abook);
        }

        // Step 4: Update user status cache for all accounts for this user
        $result = $this->userUpdateCachedFieldData( $args['name'] );

        return array('status' => 'success' );

    }

    public function groupUserUpdateCachedData( $groupName )
    {

        //sanity check
        $groupData = $this->groupGet( $groupName );

        if( $groupData['status'] != 'success' )
        {

            return false;

        }
    }

    public function userUpdateCachedFieldData( $Account )
    {
        //all user related cached data rules go here
    }

   public function userMessagesCount($name)
    {

        $q = "SELECT MailDir From Users WHERE Account = " . $this->dbAdapter->quote( $name );
        $arr = $this->dbAdapter->select('MailDir')->from('Users')->where('Account = ' . $this->dbAdapter->quote($name))->query()->fetchAll();
        $mailDir = $arr[0]['MailDir'];
        $fileList = scandir($mailDir . 'new');
        return array('response' => array('new' => count($fileList)-2) );
    }

    public function userDelete($name)
    {
        /*
        RULES:
        if admin has domain AND admin has group then allowed to delete

        */
        $rfc822 = new Mail_RFC822;
        $emailObject = $rfc822->parseAddressList(stripslashes($name), null, false);
        $domain = $emailObject[0]->host;

        if( !$this->domainAllowed( $domain ) && $this->adminInfo['UMasterAdmin'] != true )
            return array( 'response' => array('message' => 'No permission at that domain'), 'status' => 'failed' );

        $arr = $this->dbAdapter->select('Account, Ugroup')->from( $this->dbTables->Users)->where('Account = ' . $this->dbAdapter->quote($name))->query()->fetchAll();

        if(isset($arr[0]) && $arr[0]['Account'])
        {

            if( !$this->groupAllowed( $arr[0]['Ugroup'] ) && $this->adminInfo['UMasterAdmin'] != true )
                return array( 'response' => array('message' => 'No permission at that group'), 'status' => 'failed' );

            users::deleteUser($arr[0]['Account']);

            return array('response' => array('message' => "$name deleted") );
        }
        else
        {
            return array( 'response' => array('message' => "$name does not exist"), 'status' => 'failed' );
        }
    }

    /**
     * User functions
     */

    private function domainAllowed( $domain )
    {

        //RULES:
        // one or more domains defined = access only to users in those domains
        if( $this->directApi || $this->adminInfo['UMasterAdmin'] == true )
        {

            return true;

        }

        if( in_array($domain, $this->subadminDomains) )
        {

            return true;

        }

        return false;

    }

    private function anyGroupAllowed()
    {

        //return true until subadmin groups functionality implimented
        return true;

        if( $this->directApi || $this->adminInfo['UMasterAdmin'] == true )
        {

            return true;

        }

        if( count($this->subadminGroups) == 1 && $this->subadminGroups[0] == '' )// N.B. '' is a marker for no group restrictions
        {

            return true;

        }

        return false;

    }

    private function groupAllowed( $group )
    {

        //RULES:
        // one or more domains defined = access only to users in those domains
        if( $this->anyGroupAllowed() )
        {

            return true;

        }

        if( $this->adminInfo['UMasterAdmin'] == true )
        {

            return true;

        }

        if( in_array($group, $this->subadminGroups) )
        {

            return true;

        }

        return false;

    }

    public function getDomainAndGroupUsers($optionalFilters = array() )
    {

        // number of existing subadmin users must be < NumUsers
        if(isset($optionalFilters['countOnly']))
        {
            $users = $this->dbAdapter
                      ->select()
                      ->from( $this->dbTables->Users, 'count(*) as count' );
        }
        else
        {
            $users = $this->dbAdapter
                      ->select()
                      ->from( $this->dbTables->Users );
        }

        //add any optional filters (AND) - First check its defined
        if(is_array($optionalFilters))
        {
            foreach( $optionalFilters as $filterWhereClause )
            {
                $users = $users->where($filterWhereClause);
            }
        }

        if( $this->adminInfo['UMasterAdmin'] != true )
        {

            if( !$this->anyGroupAllowed() )
            {

                $groupWhereClause = '';
                $foundAGroupItem = false;
                foreach( $this->subadminGroups as $group )
                {

                        if( $foundAGroupItem )
                        {

                            $groupWhereClause .= " OR ";

                        }
                        $groupWhereClause .= "Ugroup = '" . $group . "'";
                        $foundAGroupItem = true;

                }
                if( $foundAGroupItem )
                {

                    $users = $users->where($groupWhereClause);

                }
                else
                {

                    return array( 'response' => array('message' => "No group permissions"), 'status' => 'failed' );

                }

            }

            $domainWhereClause = '';
            $foundADomainItem = false;

            foreach( $this->subadminDomains as $domain )
            {

                if($foundADomainItem)
                {

                    $domainWhereClause .= " OR ";

                }
                $domainWhereClause .= "Account LIKE '%@" . $domain . "'";
                $foundADomainItem = true;

            }

            if($foundADomainItem)
            {

                $users = $users->where($domainWhereClause);

            }
            else
            {

                return array( 'response' => array('message' => "No domain permissions"), 'status' => 'failed' );

            }

        }
        return $users->query()->fetchAll();

    }

    public function listfolder_file($pathname)
    {
        $folder = array();

        if (!$dh = @opendir($pathname))
        {
            return $folder;
        }

        while (false !== $f = readdir($dh))
        {
            if ( $f == "." || $f == ".." || !preg_match('/^\./', $f))
            {
                continue;
            }

            array_push($folder, $f);
        }

        array_push($folder, "Inbox");
        array_push($folder, "");

        return $folder;
    }

    public function getUsedQuota($configuration = array())
    {
        return Atmail_Quota::getUsedQuota($configuration);
    }

    public function setPassword( $id, $Username, $passwordOld, $passwordNew )
    {

        //EXPECTING: $passwordOld and $passwordNew to be plaintext
        //only allow Master admin or self user to change password
        if( $this->adminInfo['UMasterAdmin'] != 1 )
        {
            if( !isset($Username) || $Username != $this->adminInfo['Username'])
                return array( 'response' => array('message' => 'Not authorised.'), 'status' => 'failed' );
        }

        $passwordOldMD5 = md5( $passwordOld );
        $passwordNewMD5 = md5( $passwordNew );

        $oldAdminDataMatches = $this->dbAdapter
                        ->select()
                        ->from($this->dbTables->AdminUsers)
                        ->where('id = ' . $this->dbAdapter->quote($id, "INTERGER"))
                        ->where('Username = ' . $this->dbAdapter->quote($Username))
                        ->where('Password = ' . $this->dbAdapter->quote($passwordOldMD5))
                        ->where('UMasterAdmin = ' . $this->dbAdapter->quote($this->adminInfo['UMasterAdmin'], "INTEGER"))
                        ->query()
                        ->fetchAll();

        if( count($oldAdminDataMatches) < 1 )
        {

            return array( 'response' => array('message' => "No match found."), 'status' => 'failed' );

        }

        $oldAdminData = $oldAdminDataMatches[0];

        $newAdminData = array('Password' => $passwordNewMD5 );

        $where = array();
        $where[] = $this->dbAdapter->quoteInto('id = ?', $oldAdminData['id']);
        $where[] = $this->dbAdapter->quoteInto('Username = ?', $oldAdminData['Username']);
        $where[] = $this->dbAdapter->quoteInto('Password = ?', $oldAdminData['Password']);

        //if user is no Master admin then force update to only on subadmins ( to prevent subadmins from trying to change passwords on admins)
        if( $this->adminInfo['UMasterAdmin'] != 1 )
        {

            $where[] = $this->dbAdapter->quoteInto('UMasterAdmin = ?', 0 );

        }
        else
        {

            $where[] = $this->dbAdapter->quoteInto('UMasterAdmin = ?', $oldAdminData['UMasterAdmin'] );

        }

        $this->dbAdapter->update($this->dbTables->AdminUsers, $newAdminData, $where);

        return array('response' => array('message' => 'Password changed.'), 'status' => 'success');

    }

    public function subadminCreate( $args )
    {

        //EXPECTS $args['Password'] to be plaintext

        if( $this->adminInfo['UMasterAdmin'] != true )
        {

            return array( 'response' => array('message' => 'Not authorised'), 'status' => 'failed' );

        }

        //check for required fields
        $requiredFields = array('Username', 'Password');
        foreach( $requiredFields as $k )
        {

            if( !array_key_exists($k, $args) )
            {

                return array( 'response' => array('message' => 'Required argument not found: ' . $k), 'status' => 'failed' );

            }

        }
        //Username can not be 0 length
        if( strlen($args['Username']) == 0)
        {

            return array( 'response' => array('message' => 'Username can not have zero length'), 'status' => 'failed' );

        }

        if( strlen($args['Password']) < 5 )
        {

            return array( 'response' => array('message' => 'Password must be longer than 4 characters'), 'status' => 'failed' );

        }

        // If only one domain/argument is used, create an array, otherwise the function will fail
        // e.g URL call (without a POST, normal GET)
        // http://server.com/mail/index.php/api/subadmin/create/Username/testSubadmin10/Password/testSubadminPassword1/NumUsers/20/NumQuota/204800/UAdd/1/UModify/1/UDelete/1/Domains/local.com
        if(!isset($args['Domains']))
        {
            $args['Domains'] = '';
        }

        if(!is_array($args['Domains']))
        {
            $args['Domains'] = array($args['Domains']);
        }

        //check if requested domains exist
        if( isset($args['Domains']) )
        {
            if( count($args['Domains']) == 0)
            {
                return array( 'response' => array('message' => 'No domains specified'), 'status' => 'failed' );
            }
            foreach( $args['Domains'] as $domain )
            {

                if( !in_array($domain, $this->domains) )
                {

                    return array( 'response' => array('message' => 'Requested domain does not exist: ' . $domain), 'status' => 'failed' );

                }

            }

        }

        //Admin and Subadmin passwords now md5 as of 6.1.4
        $args['Password'] = md5( $args['Password'] );

        //only accept allowed fields, ignore others
        $allowedFields = array( 'Username', 'Password', 'UAdd', 'UDelete', 'UModify', 'UPurge', 'USearch', 'UList', 'UMigrate', 'ULogs', 'UAlias', 'UArchiveVault', 'NumUsers', 'Company', 'Fullname', 'NumQuota', 'UBrand', 'BrandDomain', 'UGroupAssign', 'UAll');
        $data = array();
        foreach( $args as $k => $v )
        {

            if( in_array($k, $allowedFields) )
            {

                $data[$k] = $v;

            }

        }

        //set Subadmin flag
        $data['UMasterAdmin'] = 0;
        //first check to see that doesnt already exist
        $existingAdminUSerMatches = $this->dbAdapter
                        ->select()
                        ->from($this->dbTables->AdminUsers)
                        ->where('Username = ' . $this->dbAdapter->quote($args['Username']))
                        ->query()
                        ->fetchAll();

        if( count($existingAdminUSerMatches) > 0 )
            return array( 'response' => array('message' => 'Duplicate user exists'), 'status' => 'failed' );

        //set DateCreate
        $data['DateCreate'] = new Zend_Db_Expr('NOW()');

        $numberOfAffectedRows = $this->dbAdapter->insert($this->dbTables->AdminUsers, $data);

        if( $numberOfAffectedRows == 1 )
        {

            // get last value generated by an auto-increment column
            $id = $this->dbAdapter->lastInsertId();

            //add domains if any selected
            if( array_key_exists('Domains', $args) && is_array($args['Domains']) && count($args['Domains']) > 0 )
            {

                foreach( $args['Domains'] as $Domain )
                {

                    $data = array();
                    $data['Username'] = $args['Username'];
                    $data['Domain'] = $Domain;
                    $numberOfAffectedRows = $this->dbAdapter->insert($this->dbTables->AdminGroup, $data);
                    if( $numberOfAffectedRows != 1 || $numberOfAffectedRows == false )
                        return array( 'response' => array('message' => 'Failed creating Subadmin domains'), 'status' => 'failed' );

                }

            }

            return array('success' => true, 'id' => $id);
        } else
            return array( 'response' => array('message' => 'Failed creating Subadmin'), 'status' => 'failed' );

    }

    public function subadminView( $args )
    {

        // only access to self record or if Master Admin
        if( $this->adminInfo['UMasterAdmin'] != true )
        {
            if(
                (!isset($args['Username']) && !isset($args['id']) )
                ||
                (isset($args['Username']) && $args['Username'] != $this->adminInfo['Username'])
                ||
                (isset($args['id']) && $args['id'] != $this->adminInfo['id'])
            )
                return array( 'response' => array('message' => 'Not authorised'), 'status' => 'failed' );

        }

        //check for required args
        if( !isset($args['id']) && !isset($args['Username']) )
            return array( 'response' => array('message' => 'Required argument missing: id or Username'), 'status' => 'failed' );

        $select = $this->dbAdapter
                        ->select()
                        ->from($this->dbTables->AdminUsers);

        if( isset($args['id']) )
            $select->where('id = ' . $this->dbAdapter->quote($args['id'], "INTEGER") );

        if( isset($args['Username']) )
            $select->where('Username = ' . $this->dbAdapter->quote($args['Username']) );

        if( $this->adminInfo['UMasterAdmin'] != true )
            $select->where('UMasterAdmin = 0');

        $matches = $select->query()
                          ->fetchAll();

        if( count($matches) == 0 )
            return array( 'response' => array('message' => 'No matches found'), 'status' => 'failed' );

        if( count($matches) > 1)
            return array( 'response' => array('message' => 'Multiple matches found'), 'status' => 'failed' );

        $match = $matches[0];

        //Now join domains on
        $Username = $match['Username'];
        $domainsMatches = $this->dbAdapter
                               ->select()
                               ->from($this->dbTables->AdminGroup)
                               ->where('Username = ' . $this->dbAdapter->quote($Username))
                               ->query()
                               ->fetchAll();

        $match['Domains'] = array();
        foreach( $domainsMatches as $domain )
        {
            $match['Domains'][] = $domain['Domain'];
        }

        return $match;
    }

    public function subadminList($args = array() )
    {

        // only access to Master Admin
        if( $this->adminInfo['UMasterAdmin'] != true )
        {

            return array( 'response' => array('message' => 'Not authorised'), 'status' => 'failed' );

        }

        if( empty($args['filter']) )
        {

            $select = $this->dbAdapter->select()
                                      ->from($this->dbTables->AdminUsers)
                                      ->joinLeft($this->dbTables->AdminGroup, $this->dbTables->AdminUsers . '.Username = ' . $this->dbTables->AdminGroup . '.UserName', array('Domain'))
                                      ->order( $this->dbTables->AdminUsers . '.Username ASC')
                                      ->where('UMasterAdmin = 0');

            if( !empty($args['company']) )
            {

                $select->where('Company = ?', array($args['company']) );

            }

            $results = $select->query()->fetchAll();

            //unpack domain array from results
            $finalResults = array();
            $currentSubadminNode = 0;
            foreach( $results AS $k => $v)
            {

                $domain = $results[$k]['Domain'];
                //Unset private data
                unset($results[$k]['UserName']);
                unset($results[$k]['Password']);
                unset($results[$k]['sessionData']);
                unset($results[$k]['Domain']);
                unset($results[$k]['SessionID']);

                if( $results[$k]['Username'] != $results[$currentSubadminNode]['Username'] )
                {

                    $finalResults[] = $results[$currentSubadminNode];
                    $currentSubadminNode = $k;

                }
                if( !isset($results[$currentSubadminNode]['Domains']) )
                {

                    $results[$currentSubadminNode]['Domains'] = array();

                }
                $results[$currentSubadminNode]['Domains'][] = $domain;

            }

            //add the last match to $finalResults
            if( count($results) > 0 && $results[$k]['Username'] == $results[$currentSubadminNode]['Username'] )
            {

                $finalResults[] = $results[$currentSubadminNode];

            }

            //cull subadmins that dont control specified domain
            if( !empty($args['domain']) )
            {

                foreach( $finalResults AS $k => $v )
                {

                    if( !in_array($args['domain'], $finalResults[$k]['Domains']) )
                    {

                        unset($finalResults[$k]);

                    }

                }

            }
            $results = $finalResults;

        }
        else
        {

            //TODO: convert to ZF syntax so supports diff dbs
            $results = $this->dbAdapter->fetchAll("select * from AdminUsers where (Username LIKE " . $this->dbAdapter->quote('%' . $args['filter'] . '%') . " or Fullname LIKE " . $this->dbAdapter->quote('%' . $args['filter'] . '%') . " or Company LIKE " . $this->dbAdapter->quote('%' . $args['filter'] . '%') . ") order by Username ASC");

        }
        //reset ordinal keys after kulling !domain
        $results = array_merge($results,array());

        if( count($results) > 0 )
        {

            return $results;

        }
        else
        {

            return array( 'response' => array('message' => 'No matches found'), 'status' => 'failed' );

        }

    }

    public function subadminUpdate( $args )
    {

        //Only allow Master Admin to update
        if( $this->adminInfo['UMasterAdmin'] != true )
            return array( 'response' => array('message' => 'Not authorised'), 'status' => 'failed' );

        //Check for required key
        if( !array_key_exists('id', $args) && !array_key_exists('Username', $args) )
            return array( 'response' => array('message' => 'Username and id argument missing'), 'status' => 'failed' );

        //Only accept allowed fields
        $allowedUpdateableFields = array( 'Password', 'UAdd', 'UDelete', 'UModify', 'UPurge', 'USearch', 'UList', 'UMigrate', 'ULogs', 'UAlias', 'UArchiveVault', 'NumUsers', 'Company', 'Fullname', 'EmailAddress', 'NumQuota', 'UBrand', 'BrandDomain', 'UGroupAssign', 'UAll', 'UMasterAdmin');

        $data = array();
        foreach( $args as $k => $v )
        {
            if( in_array($k, $allowedUpdateableFields) )
                $data[$k] = $v;
        }

        //Check to see already exists
        $select = $this->dbAdapter
                       ->select()
                       ->from($this->dbTables->AdminUsers);

        if( array_key_exists('id', $args) )
            $select->where('id = ' . $this->dbAdapter->quote($args['id'], Zend_Db::INT_TYPE) );

        if( array_key_exists('Username', $args) )
            $select->where('Username = ' . $this->dbAdapter->quote($args['Username']) );

        $matches = $select->query()
                          ->fetchAll();

        if( count($matches) == 0 )
            return array( 'response' => array('message' => 'Admin does not exist'), 'status' => 'failed' );

        $match = $matches[0];
        $where = array();
        if( array_key_exists('id', $args) )
            $where[] = $this->dbAdapter->quoteInto('id = ?', $args['id'] );

        if( array_key_exists('Username', $args) )
            $where[] = $this->dbAdapter->quoteInto('Username = ?', $args['Username'] );

        //Admin and Subadmin passwords now md5 as of 6.1.4
        if( isset($data['Password']) )
        {

            $data['Password'] = md5( $data['Password'] );

        }

        //check if requested domains exist
        if( isset($args['Domains']) )
        {

            foreach( $args['Domains'] as $domain )
            {

                if( !in_array($domain, $this->domains) )
                {

                    return array( 'response' => array('message' => 'Requested domain does not exist: ' . $domain), 'status' => 'failed' );

                }

            }

        }

        $numberOfAffectedRows = $this->dbAdapter->update($this->dbTables->AdminUsers, $data, $where);

        //now process domains
        //delete existing, add new NB id will change, currently nothing else dependant = redundant
        //only process Domains if set, else leave existing (logical for methods which may not be aware of Domains)
        if( isset($args['Domains']) )
        {

            $where = array();
            $numberOfAffectedRows = $this->dbAdapter->delete($this->dbTables->AdminGroup, $this->dbAdapter->quoteInto('Username = ?', $match['Username']) );
            foreach( $args['Domains'] as $domain )
            {

                $numberOfAffectedRows = $this->dbAdapter->insert($this->dbTables->AdminGroup, array('Username' => $match['Username'], 'Domain' => $domain) );
                if( $numberOfAffectedRows != 1 )
                    return array( 'response' => array('message' => 'Failed updating Subadmin Domains'), 'status' => 'failed' );

            }

        }

        return array('affected' => $numberOfAffectedRows);
    }

    public function adminUpdateSettings( $args )
    {

        //settings allowed to be updated by admin/subadmin/subsubadmin
        //Check for required key
        if( !array_key_exists('id', $args) && (!array_key_exists('Username', $args) || empty($args['Username']))  )
            return array( 'response' => array('message' => 'Username and id argument missing'), 'status' => 'failed' );

        //Only accept allowed fields
        $allowedUpdateableFields = array( 'Password', 'Company', 'Fullname', 'EmailAddress', 'BrandDomain', 'cssColorTheme');
        $data = array();
        foreach( $args as $k => $v )
        {

            if( in_array($k, $allowedUpdateableFields) )
                $data[$k] = $v;

        }
        if( count($data) == 0 )
            return array( 'response' => array('message' => 'No data supplied for update'), 'status' => 'failed' );
        $where = array();
        if( array_key_exists('id', $args) )
            $where[] = $this->dbAdapter->quoteInto('id = ?', $args['id']);
        if( array_key_exists('Username', $args) )
            $where[] = $this->dbAdapter->quoteInto('Username = ?', $args['Username']);

        $this->dbAdapter->update($this->dbTables->AdminUsers, $data, $where);
        return array( 'success' => true, 'status' => 'success' );

    }

    public function subadminDelete( $args )
    {

        //Only allow Master Admin to delete
        if( $this->adminInfo['UMasterAdmin'] != true )
            return array( 'response' => array('message' => 'Not authorised'), 'status' => 'failed' );

        //Check for required key
        if( !array_key_exists('id', $args) && !array_key_exists('Username', $args) && !array_key_exists('ids', $args) )
            return array( 'response' => array('message' => 'Username and id argument missing'), 'status' => 'failed' );

        //Check to see that it does already exist
        $select = $this->dbAdapter
                       ->select()
                       ->from($this->dbTables->AdminUsers);

        if( !array_key_exists('ids', $args) )
        {

            if( array_key_exists('id', $args) )
                $select->where('id = ' . $this->dbAdapter->quote($args['id'], Zend_Db::INT_TYPE) );

            if( array_key_exists('Username', $args) )
                $select->where('Username = ' . $this->dbAdapter->quote($args['Username']) );

        } else {
            foreach( $args['ids'] as $id )
                $select->orWhere('id = ' . $this->dbAdapter->quote($id, Zend_Db::INT_TYPE) );
        }

        $select->where('UMasterAdmin = 0');

        $matches = $select->query()
                          ->fetchAll();

        if( count($matches) == 0 )
            return array( 'response' => array('message' => 'Subadmin does not exist'), 'status' => 'failed' );

        $where = '(';
        if( array_key_exists('id', $args) )
            $where .= $this->dbAdapter->quoteInto('id = ?', $args['id'] );

        if( array_key_exists('Username', $args) )
        {
            if( strlen($where) > 1 )
                $where .= ' AND ';
            $where .= $this->dbAdapter->quoteInto('Username = ?', $args['Username'] );
        }
        $where .= ')';

        if( array_key_exists('ids', $args) )
        {
            $where = '(';
            foreach( $args['ids'] as $id )
            {

                if( strlen($where) > 1 )
                    $where .= ' OR ';
                $where .= $this->dbAdapter->quoteInto('id = ?', $id);
            }
            $where .= ')';
        }

        $where .= ' AND ' . $this->dbAdapter->quoteInto('UMasterAdmin = ?', 0);

        $numberOfAffectedRows = $this->dbAdapter->delete($this->dbTables->AdminUsers, $where);
        if( $numberOfAffectedRows == 0 )
            return array( 'response' => array('message' => 'Failed deleting Subadmin'), 'status' => 'failed' );

        foreach( $matches as $match )
            $numberOfAffectedRows = $this->dbAdapter->delete($this->dbTables->AdminGroup, $this->dbAdapter->quoteInto('Username = ?', $match['Username'] ) );

        return array('affected' => $numberOfAffectedRows);

    }

    public function aliasesCreate($args)
    {
        //if ZF REST Client request
        if( is_array($args) && count($args) == 2 && array_key_exists('rest', $args) && is_array($args[0]) )
            $args = $args[0];

        if(empty($args['AliasName']) && empty($args['AliasTo']) && !isset($args['AliasMailDir']))
            return array( 'response' => array('message' => 'AliasName and AliasTo undefined'), 'status' => 'failed' );

        if(empty($args['AliasName']) && !isset($args['AliasMailDir']))
            return array( 'response' => array('message' => 'AliasName and AliasMailDir undefined'), 'status' => 'failed' );

        if(!isset($args['aliasType']))
        {
            $args['aliasType'] = '';
        }

        if($args['aliasType'] == 'Local' || $args['aliasType'] == 'Deliver' || $args['aliasType'] == 'Domain')
        {

            $aliases = explode(",", $args['AliasTo']);

            if($args['aliasType'] == 'Local' || $args['aliasType'] == 'Deliver')
            {

                // First, validate the alias is a local domain
                $email = explode('@', $args['AliasName']);

                $localDomain = $this->dbAdapter->fetchOne("select Hostname from Domains where Hostname=" . $this->dbAdapter->quote($email[1]));

                // Check the permissions
                if( !$this->domainAllowed( $localDomain ) )
                    return array( 'response' => array('message' => "No permissions at {$email[1]}"), 'status' => 'failed' );

                else if(empty($localDomain))
                    return array( 'response' => array('message' => "{$email[1]} is not a local domain to create an alias"), 'status' => 'failed' );

            } else {

                // Check the permissions
                if( !$this->domainAllowed( $args['AliasName'] ) )
                    return array( 'response' => array('message' => "No permissions at {$args['AliasName']}"), 'status' => 'failed' );

                // Check the catch all has an "email" recipient
                $email = explode('@', $args['AliasTo']);

                if(!isset($email[1]))
                    return array( 'response' => array('message' => 'Please specify an email address to deliver the catch-all address'), 'status' => 'failed' );

            }

            // Add the local account if type is Deliver
            if($args['aliasType'] == 'Deliver')
                $aliases[] = $args['AliasName'];
            $resultIds = array();
            foreach($aliases as $alias)
            {
                // Select if the alias already exists, if so, skip
                $arr = $this->dbAdapter->fetchAll("select AliasTo from MailAliases where AliasName=" . $this->dbAdapter->quote($args['AliasName']) . " and AliasTo=" . $this->dbAdapter->quote($alias));
                if( isset($arr) && isset($arr[0]) && $arr[0]['AliasTo'] )
                    return array( 'response' => array('message' => "Duplicate $alias"), 'status' => 'failed' );

                $this->dbAdapter->insert("MailAliases", array("AliasTo" => $alias, "AliasName" => $args['AliasName'], 'DateCreate' => new Zend_Db_Expr('NOW()') ) );
                $id = $this->dbAdapter->lastInsertId();
                $resultIds[] = array('id' => $id);
            }

            return array('response' => array('message' => 'Success', 'results' => $resultIds), 'status' => 'success' );

        }

        elseif($args['aliasType'] == 'Virtual')
        {

            // Check the permissions
            if( !$this->domainAllowed( $args['AliasName'] ) && !$this->domainAllowed( $args['AliasName'] ))
                return array( 'response' => array('message' => "No permission at specified domains"), 'status' => 'failed' );

            // Append the @ prefix for the exim router
            $args['AliasName'] = '@' . $args['AliasName'];
            $args['AliasTo'] = '@' . $args['AliasTo'];

            $arr = $this->dbAdapter->fetchAll("select AliasTo from MailAliases where AliasName=". $this->dbAdapter->quote($args['AliasName']) ." and AliasTo=" . $this->dbAdapter->quote($args['AliasTo']));

            if( $arr[0]['AliasTo'] )
                return array( 'response' => array('message' => "Duplicate {$args['AliasTo']}"), 'status' => 'failed' );

            $this->dbAdapter->insert("MailAliases", array("AliasTo" => $args['AliasTo'], "AliasName" => $args['AliasName'], 'DateCreate' => new Zend_Db_Expr('NOW()')));
            $id = $this->dbAdapter->lastInsertId();
            return array('response' => array('message' => 'Success', 'results' => array('id' => $id) ), 'status' => 'success' );

        }

        // Divert an email to a maildir location on disk
        elseif($args['aliasType'] == 'MailDir')
        {

            //if( $this->adminInfo['UMasterAdmin'] != true )
            //  return array( 'response' => array('message' => "Master admin rights required"), 'status' => 'failed' );

            $arr = $this->dbAdapter->fetchAll("select AliasMailDir from MailAliases where AliasName=" . $this->dbAdapter->quote($args['AliasName']) . " and AliasMailDir=" . $this->dbAdapter->quote($args['AliasMailDir']));

            if( $arr[0]['AliasMailDir'] )
                return array( 'response' => array('message' => 'Duplicate'), 'status' => 'failed' );

            $this->dbAdapter->insert("MailAliases", array("AliasName" => $args['AliasName'], "AliasMailDir" => $args['AliasMailDir'], 'DateCreate' => new Zend_Db_Expr('NOW()')));
            return array('response' => array('message' => 'Success'), 'status' => 'success' );

        }

        return array( 'response' => array('message' => 'Specify aliasType'), 'status' => 'failed' );

    }

    public function aliasesView($args)
    {

        if( is_array($args) && count($args) == 2 && array_key_exists('rest', $args) && is_array($args[0]) )
        {
            //then this is a ZF REST Client request
            $args = $args[0];
        }

        if( $this->adminInfo['UMasterAdmin'] != true )
            return array( 'response' => array('message' => 'Permission denied viewing aliases'), 'status' => 'failed' );

        if( !array_key_exists('AliasName', $args ) )
            return array( 'response' => array('message' => 'Missing Argument AliasName required'), 'status' => 'failed' );

        //TODO: add other useful filtering arguments
        $select = $this->dbAdapter->select()
                                  ->from( $this->dbTables->MailAliases, array('AliasName', 'AliasTo', 'Domain', 'DateCreate', 'AliasMailDir') );
        if( array_key_exists('AliasName', $args ) )
            $select->where("AliasName LIKE " .  $this->dbAdapter->quote($args['AliasName']));
        $rows = $select->query()
                       ->fetchAll();
        return $rows;

    }

    public function aliasesList($args = null)
    {
        if( $this->adminInfo['UMasterAdmin'] != true && ($this->adminInfo['UAlias'] != 1) )
            return array( 'response' => array('message' => 'Permission denied listing aliases'), 'status' => 'failed' );

        $domainRestrictionWhereClause = "";
        if( count($this->subadminDomains) == 0 && $this->adminInfo['UMasterAdmin'] != true )
        {

            return array();

        // If not the Master Webadmin and demo mode is off
        }
        elseif($this->adminInfo['UMasterAdmin'] != true || !empty($this->config->global['demo']))
        {

            $foundADomainItem = false;
            foreach( $this->subadminDomains as $v )
            {

                if( $foundADomainItem )
                {

                    $domainRestrictionWhereClause .= " OR";

                }
                $domainRestrictionWhereClause .= " AliasName LIKE " . $this->dbAdapter->quote('%@' . $v) . " OR AliasName = " . $this->dbAdapter->quote($v);
                $foundADomainItem = true;

            }
            if( $foundADomainItem )
            {

                $domainRestrictionWhereClause = " AND (" . $domainRestrictionWhereClause . ")";

            }

        }

        $searchArgs = array();

        if( is_array($args) )
        {

            if( array_key_exists('filter', $args ) )
            {

                $searchArgs[] = "Domain LIKE " . $this->dbAdapter->quote('%' . $args['filter'] . '%');
                $searchArgs[] = "AliasName LIKE " . $this->dbAdapter->quote('%' . $args['filter'] . '%');
                $searchArgs[] = "AliasTo LIKE " . $this->dbAdapter->quote('%' . $args['filter'] . '%');
                $searchArgs[] = "AliasMailDir LIKE " . $this->dbAdapter->quote('%' . $args['filter'] . '%');

            }
            elseif( array_key_exists('Domain', $args ) )
            {

                $searchArgs[] = "Domain LIKE " . $this->dbAdapter->quote('%' . $args['Domain'] . '%');

            }
            elseif( array_key_exists('AliasName', $args ) )
            {

                $searchArgs[] = "AliasName LIKE " . $this->dbAdapter->quote('%' . $args['AliasName'] . '%');

            }
            elseif( array_key_exists('AliasTo', $args ) )
            {

                $searchArgs[] = "AliasTo LIKE " . $this->dbAdapter->quote('%' . $args['AliasTo'] . '%');

            }
            else
            {

                $searchArgs[] = "id IS NOT null";

            }

        }
        else
        {

            // Default, search all
            $searchArgs[] = "id IS NOT null";

        }

        $searchWhere = "WHERE (" . implode(" OR ", $searchArgs) . ")";
        $q = "SELECT * FROM " . $this->dbTables->MailAliases . " $searchWhere $domainRestrictionWhereClause ORDER BY DateCreate DESC";
        return $this->dbAdapter->fetchAll($q);

    }

    public function aliasesUpdate($args)
    {

        //TODO
        return array( 'response' => array('message' => 'not yet implemented'), 'status' => 'failed' );

    }

    public function aliasesDelete($args)
    {
        if (!is_array($args)) {
            return array( 'response' => array('message' => "Argument must be passed as an associative array"), 'status' => 'failed' );
        }

        if( count($args) == 2 && array_key_exists('rest', $args) && is_array($args[0]) )
        {
            //then this is a ZF REST Client request
            $args = $args[0];
        }

        $domainRestrictionWhereClause = "";
        if( count($this->subadminDomains) == 0 && $this->adminInfo['UMasterAdmin'] != true )
        {

            return array();

        // If not the Master Webadmin and demo mode is off
        }
        elseif($this->adminInfo['UMasterAdmin'] != true || !empty($this->config->global['demo']))
        {

            $foundADomainItem = false;
            foreach( $this->subadminDomains as $v )
            {

                if( $foundADomainItem )
                {

                    $domainRestrictionWhereClause .= " OR";

                }
                $domainRestrictionWhereClause .= " AliasName LIKE " . $this->dbAdapter->quote('%@' . $v) . " OR AliasName = " . $this->dbAdapter->quote($v);
                $foundADomainItem = true;
            }
            if( $foundADomainItem )
            {

                $domainRestrictionWhereClause = " AND (" . $domainRestrictionWhereClause . ")";

            }

        }

        $searchArgs = array();

        // Default, search all
        $searchArgs[] = "id is not null";

        $searchWhere = "where " . implode(" OR ", $searchArgs);
        $rows = $this->dbAdapter->fetchAll("select * from MailAliases $searchWhere $domainRestrictionWhereClause");

        if( count($rows) > 0 )
        {

            $where = array();
            foreach ($args as $k => $v)
            {
                if ($k == 'id' || $k == 'AliasName' || $k == 'AliasTo' || $k == 'AliasMailDir')
                    $where[]= "$k = " . $this->dbAdapter->quote($v);
            }

            $where = join(" and ", $where);
            if ($this->dbAdapter->delete("MailAliases", $where ))
                return array('response' => array('message' => "Alias(s) removed"), 'status' => 'success' );

            return array( 'response' => array('message' => "Alias does not exist"), 'status' => 'failed' );

        }
        else
            return array( 'response' => array('message' => "Alias does not exist"), 'status' => 'failed' );
    }

    public function mailrelayCreate($args)
    {

        if( !preg_match('/\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\/\d\d)?/', $args['IPaddress'], $m))
        {

            return array( 'response' => array('message' => $args['IPaddress'] . " is not a valid IP address or netblock"), 'status' => 'failed' );

        }

        $arr = $this->dbAdapter->fetchOne("select IPaddress from MailRelay where IPaddress=" . $this->dbAdapter->quote($args['IPaddress']));

        if( !empty($arr) )
        {

            throw new Exception("Duplicate IP address");

        }

        $this->dbAdapter->insert("MailRelay", array("IPaddress" => $args['IPaddress'], "Account" => $args['Account'], 'DateAdded' => new Zend_Db_Expr("NOW()")));

        return array('response' => array('message' => 'Success'), 'status' => 'success' );

    }

    public function getCurrentAdminData()
    {

        if( isset($this->config->global['demo']) && $this->config->global['demo'] == 1)
        {

            $rows = $this->dbAdapter->select()
                                        ->from( $this->dbTables->AdminUsers )
                                        ->where("UMasterAdmin = ?", '1')
                                        ->query()
                                        ->fetchAll();
            //make sure we only get one record
            $rows = array( $rows[0] );

        }
        else
        {

            $select = $this->dbAdapter->select()
                                      ->from( $this->dbTables->AdminUsers )
                                      ->where("Username = " . $this->dbAdapter->quote($this->Username));
            if( $this->directApi != 1 )
                $select->where("Password = " . $this->dbAdapter->quote($this->Password));
            $rows = $select->query()->fetchAll();

        }

        if( count($rows) != 1 )
        {

            return null; //throw new Exception("Single unique admin user record not found.");

        }
        unset( $rows[0]['sessionData']);
        //unset( $rows[0]['Password']);
        unset( $rows[0]['SessionID']);
        unset( $rows[0]['ipAddress']);
        return $rows[0];

    }

    public function mailrelayDelete($ip)
    {

        //if( $this->adminInfo['UMasterAdmin'] != true )
        //  return array( 'response' => array('message' => 'Permission denied deleting relay'), 'status' => 'failed' );

        $select = $this->dbAdapter->select()
                                  ->from('MailRelay')
                                  ->where("IPaddress = " . $this->dbAdapter->quote($ip) );

        $rows = $select->query()->fetchAll();

        if( count($rows) > 0 )
        {

            $this->dbAdapter->delete("MailRelay", 'IPaddress = ' . $this->dbAdapter->quote($ip) );
            return array('response' => array('message' => "Records removed"), 'status' => 'success' );

        }
        else
        {

            return array( 'response' => array('message' => "$ip does not exist"), 'status' => 'failed' );

        }

    }

    public function getDomainStatus($domain)
    {
        $status = domains::getStatus($domain);
        return array( 'response' => array('message' => $status), 'status' => 'success' );
    }

    public function domainLock($domain)
    {

        $status = domains::lock($domain);
        //now get list of users and update them for new domain state so that cache updated
        $users = $this->userList( $domain );
        foreach( $users as $user )
        {

            $this->userUpdateCachedFieldData( $user['Account'] );

        }

        return array( 'response' => array('message' => $status), 'status' => 'success' );

    }

    public function domainUnlock($domain)
    {

        $status = domains::unlock($domain);

        //now get list of users and update them for new domain state so that cache updated
        $users = $this->userList( $domain );
        foreach( $users as $user )
        {

            $this->userUpdateCachedFieldData( $user['Account'] );

        }

        return array( 'response' => array('message' => $status), 'status' => 'success' );

    }

    private function _pluginCall($call, $args=null)
    {
        $return = '';

        foreach (Zend_Controller_Front::getInstance()->getPlugins() as $plugin)  {
            // Only call custom events on Atmail_Controller_Plugin based plugins
            if ($plugin instanceof Atmail_Controller_Plugin && method_exists($plugin, $call)) {
                $return .= $plugin->$call($args);
            }
        }

        return $return;
    }

    // View log files and parse data into an array the WebUI can build
    public function logsearch( $args = array() )
    {
        $domainWhereClause = '';
        // $args['log'] is filtered by validateLogType - no db quoting needed for use
        // $args['index'] $args['Range'] are numeric Only
        $whereList = '';
        $filter = new Atmail_Filter_Input_Controller_Api( __METHOD__, $args );
        $args = $filter->getEscapedData();

        if( !empty($args['type']) ) {
            $where[] = "LogType=" . $this->dbAdapter->quote($args['type']) . "";
        }

        if( !empty($args['filter']) ) {

            $searchFilter = preg_split('/ /', $args['filter']);

            foreach($searchFilter as $searchQuery) {

                if(empty($searchQuery))
                    continue;

                $searchWhere = array();

                // Base Search
                $searchWhere[] = "Account like " . $this->dbAdapter->quote('%' . $searchQuery . '%');

                // Depending on the search type
                if($args['log'] == 'Log_Login' || $args['log'] == 'Log_RecvMail' || $args['log'] == 'Log_SendMail')
                    $searchWhere[] = "LogIP like " . $this->dbAdapter->quote('%' . $searchQuery . '%');

                // Search the EmailFrom if RecvMail, Spam or Virus log
                if($args['log'] == 'Log_RecvMail' || $args['log'] == 'Log_Spam' || $args['log'] == 'Log_Virus')
                    $searchWhere[] = "EmailFrom like " . $this->dbAdapter->quote('%' . $searchQuery . '%');

                // Search the EmailTo for outgoing messages
                if($args['log'] == 'Log_SendMail')
                    $searchWhere[] = "EmailTo like " . $this->dbAdapter->quote('%' . $searchQuery . '%');

                // Search the Virus name
                if($args['log'] == 'Log_Virus')
                    $searchWhere[] = "VirusName like " . $this->dbAdapter->quote('%' . $searchQuery . '%');

                $searchList[] = "AND ( " . implode($searchWhere, " OR ") . ") ";

            }
        }

        if( !empty($args['view']) ) {
            switch(strtolower($args['view']))
            {
                case 'week' :
                {
                    $range = 'INTERVAL 1 WEEK';
                }
                break;
                case 'month' :
                {
                    $range = 'INTERVAL 1 MONTH';
                }
                break;
                case 'year' :
                {
                    $range = 'INTERVAL 1 YEAR';
                }
                break;
                default :
                    $range = 'INTERVAL 1 DAY';
            }
            $where[] = "LogDate > DATE_SUB(CURDATE(), $range )";

        } else {

        }

        if(count($where))
        {
            $whereList = implode($where, " AND ");
        }

        if(!empty($searchList))
            $searchListQuery = implode($searchList, " ");

        // Show all logs, else a range between X > 100
        if(!empty($args['all'])) {
            $range = "";
        } else if(!empty($args['index']) && is_numeric($args['index'])) {
            $indexRange = $args['index'];
            $indexEnd = $indexRange + 100;
            $range = " LIMIT $indexRange, 100";
        } else {
            $range = " LIMIT 100";
        }

        $query = "";

        // If we are a subadmin, limit the results to our account(s) only
        if( $this->adminInfo['UMasterAdmin'] != true )
        {

            if (!count($this->subadminDomains)) {
                return;
            }
            $foundADomainItem = false;

            foreach( $this->subadminDomains as $domain )
            {
                if( $foundADomainItem )
                {

                    $domainWhereClause .= " OR ";

                }
                $domainWhereClause .= "Account LIKE " . $this->dbAdapter->quote("%@" . $domain);
                $foundADomainItem = true;

            }

            $query = $args['log'] . " where ($whereList $searchListQuery) AND ( $domainWhereClause )";

        } else if( $this->adminInfo['UMasterAdmin'] == true ) {
            $query = $args['log'];
            if(!empty($whereList) || !empty($searchListQuery))
            {
                $query .= " where $whereList $searchListQuery";
            }
        }

        $count = $this->dbAdapter->fetchOne("select count(id) from " . $query);
        $list = $this->dbAdapter->fetchAll( "select * from " . $query . " order by id desc $range");

        return array($count, $list);

    }

    // HTTP response headers will be one of the following:
    // 100 Continue
    // 200 OK
    // 201 Created
    // 202 Accepted
    // 304 Not Modified
    // 400 Bad Request
    // 401 Authorization required
    // 403 Forbidden
    // 404 Not Found
    // 405 Method Not Allowed
    // 412 Precondition Failed
    // 500 Internal Server Error
    // 501 Not Implemented
    const STATUS_CODE_CONTINUE               = 100;
    const STATUS_CODE_OK                     = 200;
    const STATUS_CODE_CREATED                = 201;
    const STATUS_CODE_ACCEPTED               = 202;
    const STATUS_CODE_NOT_MODIFIED           = 304;
    const STATUS_CODE_BAD_REQUEST            = 400;
    const STATUS_CODE_AUTHORIZATION_REQUIRED = 401;
    const STATUS_CODE_FORBIDDEN              = 403;
    const STATUS_CODE_NOT_FOUND              = 404;
    const STATUS_CODE_METHOD_NOT_ALLOWED     = 405;
    const STATUS_CODE_PRECONDITION_FAILED    = 412;
    const STATUS_CODE_INTERNAL_SERVER_ERROR  = 500;
    const STATUS_CODE_NOT_IMPLEMENTED        = 501;

    //
    // Contact Functions
    //
    public function contactCreate($args = array())
    {
        // check for minimum argument requirements
        if( !array_key_exists('account', $args) )
        {
            header('x', true, self::STATUS_CODE_PRECONDITION_FAILED);
            return array( 'response' => array('message' => 'required account argument missing'), 'status' => 'failed' );
        }

        // Lowercase the username
        $args['account'] = strtolower($args['account']);

        // verify that account exists
        $existingAccount = $this->dbAdapter
                             ->select()
                             ->from($this->dbTables->Users)
                             ->where('Account = ' . $this->dbAdapter->quote($args['account']))
                             ->query()
                             ->fetchAll();

        if (count($existingAccount) == 0 )
        {
            return array( 'response' => array('message' => "User " . $args['account'] . " does not exist"), 'status' => 'failed');
        }

        if (!empty($args['firstName']))
        {
            $args['UserFirstName'] = $args['firstName'];
        }

        if (!empty($args['lastName']))
        {
            $args['UserLastName'] = $args['lastName'];
        }

        // Create a contact object with allowed fields
        $Abook['Global'] = '0';

        $contacts = new contacts(array('Account' => $args['account']));
        foreach ($contacts->allowedFields as $field)
        {
            if (isset($args[$field]))
            {
                $Abook[$field] = $args[$field];
            }
        }

        // Insert the entry into the GAL
        $id = $contacts->addContact($Abook);

        return array('response' => array('message' => "Contact added", 'id' => $id), 'status' =>'success');
    }

    public function contactView($id = '')
    {
        if ($id == '')
        {
            return array( 'response' => array('message' => 'Specify name or id argument'), 'status' => 'failed' );
        }

        $idWhereClause = " Abook.id=" . $this->dbAdapter->quote($id, "INTEGER");

        $q = "
            SELECT DISTINCT
                Abook.*
                FROM Abook
                WHERE $idWhereClause";

        // Zend_Registry::get('log')->debug( __METHOD__ . ' #' . __LINE__ . ': $q=' . $q);

        $contacts = $this->dbAdapter->fetchAll($q);

        if( count($contacts) > 0 )
        {
            $contacts[0]['status'] = 'success';
            return $contacts[0];
        }
        else
        {
            return array( 'response' => array('message' => "$id does not exist"), 'status' => 'failed' );
        }
    }

    public function contactList($account = null)
    {
        $accountWhereClause = '';

        if (isset($account))
        {
            $accountWhereClause = "where account='$account'";
        }

        $q = "SELECT * from Abook $accountWhereClause";

        // Zend_Registry::get('log')->debug( __METHOD__ . ' #' . __LINE__ . ': $q=' . $q);

        $contacts = $this->dbAdapter->fetchAll($q);

        if( count($contacts) == 0 )
        {
            return array( 'response' => array('message' => "Account $account does not exist"), 'status' => 'failed' );
        }

        return $contacts;
    }

    public function contactUpdate($args = array())
    {
        if(!array_key_exists('id', $args))
        {
            header('x', true, self::STATUS_CODE_PRECONDITION_FAILED);
            return array( 'response' => array('message' => 'Required id argument missing'), 'status' => 'failed' );
        }

        $contactId = $args['id'];

        $existingContact = $this->dbAdapter
            ->select()
            ->from($this->dbTables->Abook)
            ->where('id = ' . $this->dbAdapter->quote($contactId))
            ->query()
            ->fetchAll();

        if( count($existingContact) == 0 )
        {
            return array('response' => array('message' => "Contact $contactId does not exist"), 'status' => 'failed');
        }

        // Create a contact object with allowed fields
        $contacts = new contacts( array('Account' => $existingContact[0]['Account'], 'admin' => false ) );

        $Abook = $args;

        $contacts->updateContact($Abook);

        return array('response' => array('message' => "Contact udpated"), 'status' => 'success');
    }

    public function contactDelete($id = '')
    {
        if ($id == '')
        {
            header('x', true, self::STATUS_CODE_PRECONDITION_FAILED);
            return array( 'response' => array('message' => 'Required id argument missing'), 'status' => 'failed' );
        }

        $arr = $this->dbAdapter->select('id')->from( $this->dbTables->Abook)->where('id = ' . $this->dbAdapter->quote($id))->query()->fetchAll();

        if(isset($arr[0]) && $arr[0]['id'])
        {
            $this->dbAdapter->delete( $this->dbTables->Abook, $this->dbAdapter->quoteInto('id = ?', $id) );

            return array('response' => array('message' => "Contact $id deleted") );
        }
        else
        {
            return array( 'response' => array('message' => "Contact $id does not exist"), 'status' => 'failed' );
        }
    }















    //
    // Inbox Functions
    //
    public function inboxCreate($args = array())
    {
        // check for minimum argument requirements
        if( !array_key_exists('account', $args) )
        {
            header('x', true, self::STATUS_CODE_PRECONDITION_FAILED);
            return array( 'response' => array('message' => 'required account argument missing'), 'status' => 'failed' );
        }

        // Lowercase the username
        $args['account'] = strtolower($args['account']);

        // verify that account exists
        $existingAccount = $this->dbAdapter
                             ->select()
                             ->from($this->dbTables->Users)
                             ->where('Account = ' . $this->dbAdapter->quote($args['account']))
                             ->query()
                             ->fetchAll();

        if (count($existingAccount) == 0 )
        {
            return array( 'response' => array('message' => "User " . $args['account'] . " does not exist"), 'status' => 'failed');
        }

        if (!empty($args['firstName']))
        {
            $args['UserFirstName'] = $args['firstName'];
        }

        if (!empty($args['lastName']))
        {
            $args['UserLastName'] = $args['lastName'];
        }

        // Create a contact object with allowed fields
        $Abook['Global'] = '0';

        $contacts = new contacts(array('Account' => $args['account']));
        foreach ($contacts->allowedFields as $field)
        {
            if (isset($args[$field]))
            {
                $Abook[$field] = $args[$field];
            }
        }

        // Insert the entry into the GAL
        $id = $contacts->addContact($Abook);

        return array('response' => array('message' => "Contact added", 'id' => $id), 'status' =>'success');
    }

    public function inboxView($id = '')
    {
        if ($id == '')
        {
            return array( 'response' => array('message' => 'Specify name or id argument'), 'status' => 'failed' );
        }

        $idWhereClause = " Abook.id=" . $this->dbAdapter->quote($id, "INTEGER");

        $q = "
            SELECT DISTINCT
                Abook.*
                FROM Abook
                WHERE $idWhereClause";

        // Zend_Registry::get('log')->debug( __METHOD__ . ' #' . __LINE__ . ': $q=' . $q);

        $contacts = $this->dbAdapter->fetchAll($q);

        if( count($contacts) > 0 )
        {
            $contacts[0]['status'] = 'success';
            return $contacts[0];
        }
        else
        {
            return array( 'response' => array('message' => "$id does not exist"), 'status' => 'failed' );
        }
    }

    public function inboxList($account = null)
    {
        $accountWhereClause = '';

        if (isset($account))
        {
            $accountWhereClause = "where account='$account'";
        }

        $q = "SELECT * from Abook $accountWhereClause";

        // Zend_Registry::get('log')->debug( __METHOD__ . ' #' . __LINE__ . ': $q=' . $q);

        $contacts = $this->dbAdapter->fetchAll($q);

        if( count($contacts) == 0 )
        {
            return array( 'response' => array('message' => "Account $account does not exist"), 'status' => 'failed' );
        }

        return $contacts;
    }

    public function inboxUpdate($args = array())
    {
        if(!array_key_exists('id', $args))
        {
            header('x', true, self::STATUS_CODE_PRECONDITION_FAILED);
            return array( 'response' => array('message' => 'Required id argument missing'), 'status' => 'failed' );
        }

        $contactId = $args['id'];

        $existingContact = $this->dbAdapter
            ->select()
            ->from($this->dbTables->Abook)
            ->where('id = ' . $this->dbAdapter->quote($contactId))
            ->query()
            ->fetchAll();

        if( count($existingContact) == 0 )
        {
            return array('response' => array('message' => "Contact $contactId does not exist"), 'status' => 'failed');
        }

        // Create a contact object with allowed fields
        $contacts = new contacts( array('Account' => $existingContact[0]['Account'], 'admin' => false ) );

        $Abook = $args;

        $contacts->updateContact($Abook);

        return array('response' => array('message' => "Contact udpated"), 'status' => 'success');
    }

    public function inboxDelete($id = '')
    {
        if ($id == '')
        {
            header('x', true, self::STATUS_CODE_PRECONDITION_FAILED);
            return array( 'response' => array('message' => 'Required id argument missing'), 'status' => 'failed' );
        }

        $arr = $this->dbAdapter->select('id')->from( $this->dbTables->Abook)->where('id = ' . $this->dbAdapter->quote($id))->query()->fetchAll();

        if(isset($arr[0]) && $arr[0]['id'])
        {
            $this->dbAdapter->delete( $this->dbTables->Abook, $this->dbAdapter->quoteInto('id = ?', $id) );

            return array('response' => array('message' => "Contact $id deleted") );
        }
        else
        {
            return array( 'response' => array('message' => "Contact $id does not exist"), 'status' => 'failed' );
        }
    }
}
