<?php
/******************************************************************************
 *  SiteBar 3 - The Bookmark Server for Personal and Team Use.                *
 *  Copyright (C) 2003,2004  Ondrej Brablc <http://brablc.com/mailto?o>       *
 *                                                                            *
 *  This program is free software; you can redistribute it and/or modify      *
 *  it under the terms of the GNU General Public License as published by      *
 *  the Free Software Foundation; either version 2 of the License, or         *
 *  (at your option) any later version.                                       *
 *                                                                            *
 *  This program is distributed in the hope that it will be useful,           *
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of            *
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             *
 *  GNU General Public License for more details.                              *
 *                                                                            *
 *  You should have received a copy of the GNU General Public License         *
 *  along with this program; if not, write to the Free Software               *
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA *
 ******************************************************************************/

require_once('./inc/database.inc.php');
require_once('./inc/errorhandler.inc.php');

define ('ADMIN',  1);
define ('ANONYM', 2);

class UserManager extends ErrorHandler
{
    var $user = null;

    var $config;
    var $setupDone;
    var $db;
    var $pmode; // personal mode
    var $plugins; // plugin cache

    var $uid;
    var $name;
    var $email;
    var $comment;
    var $verified;
    var $demo;
    var $params = array('config'=>array(),'user'=>array());
    var $hiddenFolders = array();

    function UserManager()
    {
        $this->db =& Database::staticInstance();

        /* Read SiteBar configuration - must be the first step ! */
        if ($this->db->hasTable('sitebar_config'))
        {
            $rset = $this->db->select(null, 'sitebar_config');
            $this->config = $this->db->fetchRecord($rset);
        }
        else
        {
            $this->config['release'] = '';
        }

        if ($this->db->currentRelease() != $this->config['release'])
        {
            header('Location: config.php');
            exit;
        }

        $this->explodeParams($this->config['params'],'config');

        $this->pmode = $this->getParam('config','personal_mode');

        $this->loadPlugins();

        /* Check whether Admin has password if not we will run setup */
        $rset = $this->db->select(null, 'sitebar_user',
            array( 'uid'=> ADMIN, '^1'=> 'AND', 'pass'=>null));

        $this->setupDone = ($this->db->fetchRecord($rset)===false);

        if (!$this->isLogged())
        {
            $rec = $this->getUser(ANONYM);

            if (!$rec)
            {
                $this->error('Database corrupted - missing anonymous account!');
            }
            else
            {
                $this->initUser($rec);
                unset($this->user['pass']); // Security
            }
        }

        $lang = $this->getParam('user','lang');

        if (!$lang)
        {
            $l =& Localizer::getInstance();
            $browserLang = $l->getBrowserLang();
            if (!$this->getParam('config','lang'))
            {
                $this->setParam('config','lang',$l->langDefault);
            }
            $lang = $browserLang?$browserLang:$this->getParam('config','lang');
            $this->setParam('user','lang',$lang);
        }

        // Set our language
        SetLanguage($lang);
    }

    function statistics(&$data)
    {
        $rset = $this->db->select('count(*) count', 'sitebar_user');
        $rec = $this->db->fetchRecord($rset);
        $data['users'] = $rec['count'];
        $rset = $this->db->select('count(*) count', 'sitebar_group');
        $rec = $this->db->fetchRecord($rset);
        $data['groups'] = $rec['count'];
    }

    function & staticInstance()
    {
        static $um;

        if (!$um)
        {
            $um = new UserManager();
        }

        return $um;
    }

    function initUser(&$rec)
    {
        $this->user = $rec;
        $this->uid = $rec['uid'];
        $this->email = $rec['email'];
        $this->name = $rec['name'];
        $this->comment = $rec['comment'];
        $this->verified = $rec['verified'];
        $this->demo = $rec['demo'];
        $this->explodeParams($rec['params'],'user');

        if ($this->getParam('user','use_hiding') && $this->getParam('user','hidden_folders'))
        {
            $ids = explode(':',$this->getParam('user','hidden_folders'));
            $this->hiddenFolders = array();
            foreach ($ids as $id)
            {
                $this->hiddenFolders[$id] = 1;
            }
        }
    }

    function setCookie($name, $value='', $expires=null)
    {
        if ($expires===null)
        {
            // Default expiration 7 days
            $expires = time()+60*60*24*7;
        }

        if (!$value)
        {
            $expires = time()-60*60;
        }
        setcookie($name, $value, $expires);
        $_COOKIE[$name] = $value;
    }

    function explodeParams(&$params, $prefix)
    {
        $default = array();

        switch ($prefix)
        {
            case 'config':
                $default['auth']='';
                $default['allow_contact']=1;
                $default['allow_sign_up']=1;
                $default['allow_user_trees']=1;
                $default['allow_user_tree_deletion']=1;
                $default['backend_server']=1;
                $default['comment_impex']=0;
                $default['comment_limit']=1024;
                $default['max_icon_age']=30;
                $default['max_icon_cache']=1000;
                $default['max_icon_size']=20000;
                $default['max_icon_user']=100;
                $default['personal_mode']=0;
                $default['sender_email']='';
                $default['show_logo']=1;
                $default['show_sponsor']=1;
                $default['show_statistics']=1;
                $default['skin']='Modern';
                $default['use_compression']=1;
                $default['use_conv_engine']=1;
                $default['use_favicon_cache']=1;
                $default['use_hit_counter']=1;
                $default['use_mail_features']=1;
                $default['use_outbound_connection']=1;
                break;

            case 'tmp':
            case 'user':
            default:
                $default['allow_given_membership']=1;
                $default['allow_info_mails']=1;
                $default['auto_close']=1;
                $default['auto_retrieve_favicon']=1;
                $default['default_folder']='';
                $default['default_search']='name';
                $default['extern_commander']=0;
                $default['hidden_folders']='';
                $default['keep_in_cache']='0';
                $default['menu_icon']=0;
                $default['paste_mode']='ask';
                $default['mix_mode']='nodes';
                $default['link_sort_mode']='abc';
                $default['show_acl']=0;
                $default['skin']= $this->getParam('config','skin');
                $default['use_favicons']=1;
                $default['use_hiding']=1;
                break;
        }

        // Clear old values
        $this->params[$prefix] = $default;

        // If we have some params then explode them
        if ($params)
        {
            foreach (explode(';',$params) as $param)
            {
                list($key,$value) = explode('=',$param);
                $this->setParam($prefix,$key,$value);
            }
        }

        switch ($prefix)
        {
            case 'config':
                if (!strlen($this->getParam('config','sender_email')))
                {
                    $admin = $this->getUser(ADMIN);
                    if ($admin['email']!='Admin')
                    {
                        $this->setParam('config', 'sender_email', $admin['email']);
                    }
                }
                if (!$this->getParam('config', 'use_outbound_connection'))
                {
                    $this->setParam('config', 'use_favicon_cache', 0);
                }
                if ($this->getParam('config', 'auth'))
                {
                    $this->setParam('config', 'allow_sign_up', 0);
                }

                break;

            case 'user':
                if (!$this->getParam('config','use_hit_counter'))
                {
                    if (!in_array($this->getParam('user','link_sort_mode'),array('abc','added')))
                    {
                        $this->setParam('user', 'link_sort_mode', 'abc');
                    }
                }
                if (!$this->getParam('config', 'use_outbound_connection') ||
                    !$this->getParam('user', 'use_favicons'))
                {
                    $this->setParam('user', 'auto_retrieve_favicon', 0);
                }
                break;
        }
    }

    function implodeParams($prefix)
    {
        $params = array();
        foreach ($this->params[$prefix] as $name => $value)
        {
            $params[] = $name.'='.$value;
        }
        return implode(';',$params);
    }

    function getParam($prefix, $name)
    {
        return isset($this->params[$prefix][$name])
            ?$this->params[$prefix][$name]:null;
    }

    function getParamB64($prefix, $name)
    {
        return isset($this->params[$prefix][$name])
            ?base64_decode($this->params[$prefix][$name]):null;
    }

    function getParamCheck($prefix, $name)
    {
        return $this->getParam($prefix,$name)?null:'';
    }

    function setParam($prefix, $name, $value)
    {
        $this->params[$prefix][$name] = $value;
    }

    function setParamB64($prefix, $name, $value)
    {
        $this->params[$prefix][$name] = base64_encode($value);
    }

    function isAnonymous()
    {
        return $this->uid == ANONYM;
    }

    function isAdmin()
    {
        if (!$this->user)        return false;
        if ($this->uid == ADMIN) return true;

        static $isAdmin = null;

        if ($isAdmin === null)
        {
            $rset = $this->db->select('g.gid',
                'sitebar_group g natural join sitebar_member m',
                array('uid'=>$this->uid, '^1'=> 'AND',
                    'g.gid'=> $this->config['gid_admins']));

            $rec = $this->db->fetchRecord($rset);
            $isAdmin = is_array($rec);
        }

        return $isAdmin;
    }

    function isModerator($gid = null)
    {
        $groups = $this->getModeratedGroups($this->uid);

        if (!count($groups))
        {
            return false;
        }

        return $gid?in_array($gid, array_keys($groups)):true;
    }

    function canUseMail()
    {
        return $this->verified && $this->getParam('config','use_mail_features');
    }

    function isAuthorized($command, $ignoreAnonymous=false, $gid=null, $nid=null, $lid=null)
    {
        $acl = null;
        $node = null;

        if ($lid)
        {
            $tree =& Tree::staticInstance();
            $link = $tree->getLink($lid);
            $nid = $link->id_parent;

            if ($link->private && !$tree->inMyTree($nid))
            {
                return false;
            }
        }

        if ($nid)
        {
            $tree =& Tree::staticInstance();
            $node = $tree->getNode($nid);

            if (!$node)
            {
                return false;
            }

            $acl =& $node->getACL();

            if (!$acl)
            {
                return false;
            }

            if ($node && $node->deleted_by != null)
            {
                if ($command != 'Purge Folder' && $command != 'Undelete')
                {
                    return false;
                }
            }
        }

        // If !$acl then we just ask for command list.

        switch ($command)
        {
            case 'Verify Email':
                return  !$this->pmode &&
                        !$this->isAnonymous() &&
                        !$this->verified &&
                        !$this->demo &&
                        $this->getParam('config','use_mail_features');

            case 'Verify Email Code':
                return true;

            case 'Set Up':
                return !$this->setupDone;

            case 'Sign Up':
                return ($this->isAnonymous() || $ignoreAnonymous)
                    && $this->getParam('config','allow_sign_up');

            case 'Help':
            case 'Display Topic':
                return true;

            case 'Log In':
                return $this->isAnonymous() || $ignoreAnonymous;

            case 'Log Out':
                return !$this->isAnonymous() || $ignoreAnonymous;

            case 'Email Link':
                return true;

            case 'Contact Admin':
                return $this->getParam('config','use_mail_features') &&
                       ($this->getParam('config','allow_contact') || !$this->isAnonymous()) &&
                       !$this->isAdmin();

            case 'Contact Moderator':
                return !$this->pmode && !$this->isAnonymous();

            case 'Add Link':
            case 'Retrieve Link Information':
            case 'Add Folder':
                return !$acl || $acl['allow_insert'];

            case 'Paste': // Paste does its own checking later
            case 'Import Bookmarks':
                return !$this->isAnonymous() && (!$acl || $acl['allow_insert']);

            case 'Hide Folder':
            case 'Unhide Subfolders':
            case 'Unhide Trees':
                return !$this->isAnonymous() &&
                    $this->getParam('user','use_hiding');

            case 'Copy':
            case 'Copy Link':
            case 'Export Bookmarks':
                return !$this->isAnonymous() && (!$acl || $acl['allow_select']);

            case 'Custom Order':
            case 'Folder Properties':
            case 'Properties':
                return !$acl || $acl['allow_update'];

            case 'Validate Links':
            case 'Validation':
                return !$acl || ($acl['allow_update'] && $this->getParam('config','use_outbound_connection'));

            case 'Export Description':
                // Select is enough but, currently update is necessary
                return !$acl || ($acl['allow_select']);

            case 'Import Description':
                return !$acl || ($acl['allow_update'] && $this->getParam('config','comment_impex'));

            case 'Delete Link':
                return !$acl || $acl['allow_delete'];

            case 'Delete Folder':
                return !$acl ||
                       ($acl['allow_delete'] &&
                            ($node->id_parent!=0 || $this->getParam('config','allow_user_tree_deletion')));

            case 'Delete Tree':
                return !$acl || $this->isAdmin() ||
                       ($acl['allow_delete'] && $this->getParam('config','allow_user_tree_deletion'));

            case 'Purge Folder':
                return !$acl || $acl['allow_purge'];

            case 'Undelete':
                return !$acl || ($acl['allow_delete'] && $acl['allow_insert']);

            case 'Maintain Trees':
            case 'Order of Trees':
            case 'User Settings':
                return !$this->isAnonymous();

            case 'Personal Data':
                // Either we are number 1 user, or we have SiteBar authorization
                return !$this->isAnonymous() && ($this->uid==ADMIN || !strlen($this->getParam('config', 'auth')));

            case 'Unhide Folders':
                return !$this->pmode && !$this->isAnonymous();

            case 'Delete Account':
                return !$this->isAnonymous() && !$this->demo && $this->uid != ADMIN;

            case 'Membership':
            case 'Security':
                return !$this->isAnonymous() && !$this->pmode;

            case 'Create Tree':
                return !$this->isAnonymous() &&
                       $this->verified &&
                       $this->getParam('config','allow_user_trees');

            case 'SiteBar Settings':
            case 'Favicon Management':
            case 'Show All Icons':
            case 'Purge Cache':
            case 'Maintain Users':
            case 'Create User':
            case 'Delete User':
            case 'Modify User':
                return $this->isAdmin();

            case 'Send Email to User':
            case 'Send Email to All':
                return $this->isAdmin() && $this->verified
                       && $this->getParam('config','use_mail_features');

            case 'Create Group':
            case 'Delete Group':
                return !$this->pmode && $this->isAdmin();

            case 'Maintain Groups':
                return !$this->pmode && ($this->isAdmin() || $this->isModerator());

            case 'Group Properties':
            case 'Group Members':
            case 'Group Moderators':
                return !$this->pmode && $this->isModerator($gid);

            case 'Send Email to Members':
            case 'Send Email to Moderators':
                return !$this->pmode && ($this->isAdmin() || $this->isModerator($gid))
                       && $this->verified
                       && $this->getParam('config','use_mail_features');
        }

        // Check if we have additional commands
        foreach ($this->plugins as $plugin)
        {
            if (in_array($command, $plugin['authorization']))
            {
                $execute = $plugin['prefix'] . 'IsAuthorized';
                if ($execute($this, $command))
                {
                    return true;
                }
            }
        }

        return false;
    }

    function canMove($sid,$tid,$isnode=true)
    {
        if ($this->isAuthorized(($isnode?'Delete Folder':'Delete Link'), false, null, $sid))
        {
            $tree = Tree::staticInstance();
            $sroot = $tree->getRootNode($sid);
            $troot = $tree->getRootNode($tid);

            if ($sroot->id == $troot->id)
            {
                return true;
            }
            else // Another tree and the source tree does not have purge right
            {
                return $this->isAuthorized('Purge Folder', false, null, $sid);
            }
        }

        return false;
    }

    function & inPlaceCommands()
    {
        static $commands = array
        (
            'Log In',
            'Log Out',
            'Sign Up',
            'Set Up',
            'SiteBar Settings',
            'Favicon Management',
                'Purge Cache',
                'Show All Icons',
            'User Settings',
                'Personal Data',
                'Delete Account',
        );
        return $commands;
    }

    // expires as delta time in seconds
    function login($email, $pass, $expires=0)
    {
        $auth = $this->getParam('config', 'auth');
        $addedRealName = '';
        $addedComment = '';
        $where = array('ucase(email)' => array('ucase' => $email));

        // Get UID
        $rset = $this->db->select('uid', 'sitebar_user', $where);
        $rec = $this->db->fetchRecord($rset);
        // We have another setting and do not know the user or we know him ans he is not admin
        $useAltAuth = strlen($auth) && (!is_array($rec) || $rec['uid'] != ADMIN);

        // Plugin based authorization
        if ($useAltAuth)
        {
            include_once('./plugins/auth/' . $auth . '.inc.php');
            if (!authorize($this, $email, $pass, $addedRealName, $addedComment))
            {
                $this->error('Unknown username or wrong password!');
                return false;
            }
        }
        else
        {
            $where['^1'] = 'AND';
            $where['pass'] = array('md5' => $pass);
        }

        $rset = $this->db->select(null, 'sitebar_user', $where);
        $rec = $this->db->fetchRecord($rset);

        if (!is_array($rec))
        {
            if ($useAltAuth)
            {
                // Auto add user to database
                $status = $this->signUp(
                    $email, 'NOPASSWORD',
                    (strlen($addedRealName)?$addedRealName:$email),
                    $addedComment,
                    $createdByAdmin=true,
                    $verified=true,
                    $demoAccount=false,
                    $lang=null);

                if (!$status)
                {
                    return false;
                }

                return $this->login($email, $pass, $expires);
            }
            else
            {
                $this->error('Unknown username or wrong password!');
            }

            return false;
        }

        $this->initUser($rec);
        unset($this->user['pass']); // Security

        // Noone from outside can reconstruct the password, because
        // only half of its md5 is used to generate another md5 and
        // hence we use password noone from outside can guess the code.
        // Is it obscure or slow? Please tell me.
        $code = md5(substr(md5($pass),6,18).date('r').$email);

        $expires = ($expires?$expires+time():0);

        $this->db->insert('sitebar_session', array(
            'uid' => $this->uid,
            'code' => $code,
            'created' => array('now' => null),
            'expires' => $expires,
            'ip' => $_SERVER['REMOTE_ADDR']
        ));

        $this->setCookie('SB3AUTH', $code, $expires);
        $this->remote = false;
        return true;
    }

    function logout()
    {
        $this->user = null;
        $this->setCookie('SB3AUTH');
    }

    function isLogged()
    {
        if (!isset($_COOKIE['SB3AUTH']))
        {
            return false;
        }

        // Check if we have valid session
        $rset = $this->db->select('uid', 'sitebar_session',
            array('code' => $_COOKIE['SB3AUTH'],
                '^1' => 'AND (expires = 0 OR expires>=unix_timestamp())'));

        $rec = $this->db->fetchRecord($rset);

        // Delete invalid cookie
        if (!is_array($rec))
        {
            $this->setCookie('SB3AUTH');
            return false;
        }

        // If yes then let us go in.
        $rset = $this->db->select(null, 'sitebar_user', array('uid' => $rec['uid']));

        $rec = $this->db->fetchRecord($rset);

        // User deleted?
        if (!is_array($rec))
        {
            $this->setCookie('SB3AUTH');
            return false;
        }

        $this->initUser($rec);
        unset($this->user['pass']); // Security

        return true;
    }

    function setUp($email, $pass,$name)
    {
        $rset = $this->db->update('sitebar_user',
            array(
                'email' => $email,
                'pass' => array('md5' => $pass),
                'name' => $name,
                'verified' => 1,
            ),
            array('uid'=>ADMIN));

        return $this->login($email, $pass);
    }

    function saveConfiguration()
    {
        $rset = $this->db->update('sitebar_config',
            array('params' => $this->implodeParams('config')));

        return true;
    }

/*** User functions ***/

    function signUp($email, $pass, $name, $comment,
        $createdByAdmin=false, $verified=false, $demoAccount=false, $lang=null)
    {
        $rset = $this->db->select(null, 'sitebar_user', array(
            'ucase(email)' => array('ucase' => $email)));

        $user = $this->db->fetchRecord($rset);

        if (is_array($user))
        {
            $this->error('This email is already used. Did you forget your password?');
            return false;
        }

        $params = '';

        if ($lang)
        {
            $params = 'lang='.$lang;
        }

        $this->db->insert('sitebar_user', array(
            'email' => $email,
            'pass' => array('md5' => $pass),
            'name' => $name,
            'comment' => $comment,
            'verified' => ($verified?1:0),
            'demo' => ($demoAccount?1:0),
            'params' => $params
        ));

        $newUID = $this->db->getLastId();

        // Join groups where verification is not neccessary and
        // return list of groups when it is.
        $closedMatchingGroups = $this->autoJoinGroups($newUID, $email,
            $createdByAdmin);

        if ($this->getParam('config','use_mail_features') && !$createdByAdmin)
        {
            if (count($closedMatchingGroups))
            {
                $groups = implode(', ', $closedMatchingGroups);
                $url = $this->getVerificationUrl($newUID, $email);
                $subject = T('SiteBar: Email Verification');
                $msg = P('usermanager::auto_verify_email', array($groups,$url));
                // Verify email
                $this->sendMail(array('name'=>$name, 'email'=>$email), $subject, $msg);
            }

            $admins = $this->getMembers($this->config['gid_admins']);
            foreach ($admins as $uid => $user)
            {
                $this->explodeParams($user['params'], 'tmp');
                SetLanguage($this->getParam('tmp','lang'));
                $msg = P('usermanager::signup_info', array($name,$email,Page::baseurl()));
                $this->sendMail($user, T('SiteBar: New SiteBar User'), $msg);
            }
            SetLanguage($lang?$lang:$this->getParam('user','lang'));
        }

        // Always greater then zero
        return $newUID;
    }

    function getVerificationUrl($uid='', $email='')
    {
        if (!$uid)
        {
            $uid = $this->uid;
        }

        if (!$email)
        {
            $email = $this->email;
        }

        $code = rand(100000, 999999);

        // Update user with generated code
        $this->db->update('sitebar_user', array('code'=> $code),
            array('uid'=>$uid));

        return Page::baseurl().
            '/command.php'.
            '?command=Verify%20Email%20Code'.
            '&code='.$code.
            '&email='.$email;
    }

    function verifyEmail($email, $code)
    {
        $user = $this->getUserByEmail($email);

        if (!$user)
        {
            $this->error('Access denied!');
            return;
        }

        if (!$user['verified'])
        {
            // Code matching
            if ($user['code']==$code)
            {
                $this->autoJoinGroups($user['uid'], $user['email'], true);
                $this->db->update('sitebar_user', array('verified'=> 1),
                    array('uid'=>$user['uid']));
            }
            else
            {
                $this->error('Invalid code, verification disabled!');
                $this->db->update('sitebar_user', array('code'=> null),
                    array('uid'=>$user['uid']));
            }
        }
    }

    function decodeValue($value, $header=false)
    {
        // translator.php needs &#39; instead of apostrope, there
        // are places when we do not like it -> in the mail.
        $tmp = str_replace("&#39;", "'", $value);

        if ($header)
        {
            return '=?UTF-8?B?'.base64_encode($tmp).'?=';
        }
        else
        {
            return stripslashes($tmp);
        }
    }

    function sendMail($user, $subject, $msg, $from_name='', $from_email='', $cc=false)
    {
        require_once("./inc/page.inc.php");

        $headers  = "Content-Type: text/plain; ".CHARSET."\n";
        $headers .= "Content-Transfer-Encoding: 8bit\n";
        $sender   = $this->getParam('config','sender_email');
        $email    = $user['email'];

        if (!($from_name && $from_email))
        {
            $from_name = T('SiteBar Server');
            $from_email = $sender;
        }

        $headers .= sprintf("From: \"%s\" <%s>\n",
                $this->decodeValue($from_name, true), $from_email);

        if ($cc)
        {
            $headers .= sprintf("cc: %s\n", $from_email);
        }

        $headers .= sprintf("Reply-to: \"%s\" <%s>\n",
                $this->decodeValue($from_name, true), $from_email);
        $headers .= sprintf("Sender: \"%s\" <%s>\n",
                $this->decodeValue(T('SiteBar Server'), true), $sender);
        $headers .= sprintf("Return-path: <%s>\n", $sender);

        // Do not set "To:" - it would duplicate mails.
        if (!mail($email,
            $this->decodeValue($subject, true),
            $this->decodeValue($msg), $headers))
        {
            return false;
        }

        return true;
    }

    function getLastModeratorGroups($uid)
    {
        $groups = $this->getModeratedGroups($uid);
        $names = array();

        foreach ($groups as $gid => $rec)
        {
            $members = $this->getMembers($gid);
            $moderators = $this->getMembers($gid, true);
            if (count($moderators)==1 && count($members)>1)
            {
                $names[] = $rec['name'];
            }
        }

        return $names;
    }

    function removeUser($uid)
    {
        $names = $this->getLastModeratorGroups($uid);

        if (count($names))
        {
            $this->error('Cannot delete user because he is the only moderator of the following group(s): %s!',
                array(implode(', ',$names)));

            return false;
        }

        $rset = $this->db->delete('sitebar_member', array('uid' => $uid));
        $rset = $this->db->delete('sitebar_user', array('uid' => $uid));

        return true;
    }

    function deleteAccount()
    {
        $names = $this->getLastModeratorGroups($this->uid);

        if (count($names))
        {
            $this->error('You are the last moderator of used group(s): %s. Account cannot be deleted!',
                array(implode(', ',$names)));

            return false;
        }

        $tree =& Tree::staticInstance();

        if (!$this->removeUser($this->uid))
        {
            return false;
        }

        if (!$tree->changeOwner($this->uid, ADMIN, $this->email))
        {
            return false;
        }

        return true;
    }

    function personalData($email,$pass,$name,$comment)
    {
        if ($email!=$this->email)
        {
            if ($this->verified)
            {
                $this->verified = 0;
            }

            $rset = $this->db->select(null, 'sitebar_user', array(
                'ucase(email)' => array('ucase' => $email)));

            $user = $this->db->fetchRecord($rset);

            if (is_array($user))
            {
                $this->error('This email is already used. Did you forget your password?');
                return false;
            }
        }

        $this->db->update('sitebar_user',
            array
            (
                'email' => $email,
                'name' => $name,
                'comment' => $comment,
                'verified' => $this->verified,
            ),
            array('uid'=>$this->uid));

        $this->email = $email;

        if ($pass)
        {
            $this->changePassword($this->uid, $pass);
            $this->login($email, $pass);
        }

        return true;
    }

    function saveUserParams()
    {
        $this->db->update('sitebar_user',
            array('params' => $this->implodeParams('user')),
            array('uid'=>$this->uid));
    }

    function modifyUser($uid, $pass, $name, $comment, $verified, $demo)
    {
        if ($pass)
        {
            $this->changePassword($uid, $pass);
        }

        $this->db->update('sitebar_user',
            array(
                'name' => $name,
                'comment' => $comment,
                'verified' => ($verified?1:0),
                'demo' => ($demo?1:0)
                ),
            array('uid'=>$uid));
    }

    function checkPassword($uid, $pass)
    {
        $rset = $this->db->select(null,'sitebar_user', array(
            'pass' => array('md5' => $pass),
            '^1' => 'AND',
            'uid'=>$uid));

        return is_array($this->db->fetchRecord($rset));
    }

    function changePassword($uid, $pass)
    {
        $this->db->update('sitebar_user',
            array('pass' => array('md5' => $pass)),
            array('uid'=>$uid));
    }

/*** Group functions ***/

    function addGroup($name, $comment, $uid, $allow_addself, $allow_contact, $auto_join)
    {
        $rset = $this->db->select(null, 'sitebar_group', array(
            'name' => $name));

        $group = $this->db->fetchRecord($rset);

        if (is_array($group))
        {
            $this->error('Group name "%s" is already used!', array($group['name']));
            return false;
        }

        $this->db->insert('sitebar_group', array(
            'name' => $name,
            'comment' => $comment,
            'allow_addself' => $allow_addself,
            'allow_contact' => $allow_contact,
            'auto_join' => $auto_join,
        ));

        $gid = $this->db->getLastId();
        $this->addMember($gid, $uid, true);

        // Always greater then zero
        return $gid;
    }

    function removeGroup($gid)
    {
        $this->db->delete('sitebar_acl', array('gid'=>$gid));
        $this->db->delete('sitebar_member', array('gid'=>$gid));
        $this->db->delete('sitebar_group', array('gid'=>$gid));
    }

    function updateGroup($gid, $name, $comment, $allow_addself, $allow_contact, $auto_join)
    {
        $this->db->update('sitebar_group',
            array('name' => $name,
                'comment' => $comment,
                'allow_addself' => $allow_addself,
                'allow_contact' => $allow_contact,
                'auto_join' => $auto_join),
            array('gid'=>$gid));
    }

    function addMember($gid, $uid, $moderator=false)
    {
        $this->db->insert('sitebar_member', array(
            'gid' => $gid,
            'uid' => $uid,
            'moderator' => $moderator?1:0),
            array(1062)); // Ignore duplicate membership
    }

    function removeMember($gid, $uid)
    {
        $this->db->delete('sitebar_member',
            array('gid'=>$gid, '^1'=>'AND', 'uid'=>$uid));
    }

    function updateMember($gid, $uid, $moderator)
    {
        $this->db->update('sitebar_member',
            array('moderator' => ($moderator?1:0)),
            array('gid'=>$gid, '^1'=>'AND', 'uid'=>$uid));
    }

    function showPublic()
    {
        return in_array($this->config['gid_everyone'],
            array_keys($this->getUserGroups()));
    }

/*** Search functions ***/

    function getUser($uid)
    {
        $rset = $this->db->select(null, 'sitebar_user',
            array('uid' => $uid));
        $users = array( $this->db->fetchRecord($rset));

        $this->cryptMail($users);
        return $users[0];
    }

    function getUserByEmail($email)
    {
        $rset = $this->db->select(null, 'sitebar_user',
            array( 'email'=> $email));
        return $this->db->fetchRecord($rset);
    }

    function cryptMail(&$users)
    {
        $unique = array();
        foreach ($users as $uid => $rec)
        {
            $crypt = $rec['email'];
            if (!$this->isAdmin())
            {
                // Create unique short email
                for ($i=2;;$i++)
                {
                    $crypt = preg_replace("/^([^@]+@.{1,".$i."}).*$/","$1...",$rec['email']);
                    if (!isset($unique[$crypt]))
                    {
                        break;
                    }
                }
                $unique[$crypt] = 1;
            }
            $users[$uid]['cryptmail'] = $crypt;
        }
    }

    function getUsers()
    {
        $rset = $this->db->select('uid, email, verified, name, params',
            'sitebar_user', null, 'ucase(email)');
        $users = array();

        foreach ($this->db->fetchRecords($rset) as $rec)
        {
            if ($rec['uid'] == ANONYM) continue;
            $users[$rec['uid']] = $rec;
        }

        $this->cryptMail($users);
        return $users;
    }

    function getMembers($gid, $moderators=false)
    {
        $where = array('gid'=>$gid);
        if ($moderators)
        {
            $where['^1'] = 'AND';
            $where['moderator'] = 1;
        }
        $rset = $this->db->select('u.uid, email, verified, name, params, moderator',
            'sitebar_user u natural join sitebar_member m', $where, 'ucase(email)');
        $members = array();

        foreach ($this->db->fetchRecords($rset) as $rec)
        {
            $members[$rec['uid']] = $rec;
        }

        $this->cryptMail($members);
        return $members;
    }

    function getModeratedGroups($uid=null)
    {
        if (!$uid)
        {
            $uid = $this->uid;
        }

        $rset = $this->db->select('g.gid, g.name',
            'sitebar_group g natural join sitebar_member m',
            array('uid'=>$uid, '^1'=> 'AND', 'moderator'=>1 ), 'name');

        $groups = array();

        foreach ($this->db->fetchRecords($rset) as $rec)
        {
            $groups[$rec['gid']] = $rec;
        }

        return $groups;
    }

    function getUserGroups($uid=null)
    {
        if (!$uid)
        {
            $uid = $this->uid;
        }

        $rset = $this->db->select('g.gid, g.name, moderator',
            'sitebar_group g natural join sitebar_member m',
            array('uid'=>$uid), 'name');

        $groups = array();

        foreach ($this->db->fetchRecords($rset) as $rec)
        {
            $groups[$rec['gid']] = $rec;
        }

        return $groups;
    }

    function getGroup($gid)
    {
        $rset = $this->db->select(null, 'sitebar_group',
            array( 'gid'=> $gid), 'name');
        $group = $this->db->fetchRecord($rset);

        if (!$group)
        {
            $this->error('Group has already been deleted.');
            return null;
        }

        return $group;
    }

    function getGroups()
    {
        $rset = $this->db->select(null, 'sitebar_group', null, 'name');
        $groups = array();

        foreach ($this->db->fetchRecords($rset) as $rec)
        {
            $this->renameGroup($rec);
            $groups[$rec['gid']] = $rec;
        }

        return $groups;
    }

    function renameGroup(&$rec)
    {
        if ($rec['gid']<=2)
        {
            $rec['name'] = T($rec['name']);
        }
    }

    function autoJoinGroups($uid, $email, $verified=false)
    {
        $groups = array();

        // Add member to all groups that have auto_join filled and matching
        foreach ($this->getGroups() as $gid => $rec)
        {
            $res = $this->canJoinGroup($rec, $uid, $email, $verified);

            if ($res === true)
            {
                $this->addMember($gid, $uid);
            }
            elseif ($res === null) // Verification missing, note group name
            {
                // Otherwise return
                $groups[] = $rec['name'];
            }
        }

        return $groups;
    }

    function canJoinGroup(&$groupRec, $uid=null, $email=null, $verified=null)
    {
        if ($uid===null) $uid = $this->uid;
        if ($email===null) $email = $this->email;
        if ($verified===null) $verified= $this->verified;

        if ($groupRec['auto_join'])
        {
            if ($groupRec['auto_join']{0} != '/')
            {
                $groupRec['auto_join'] = '/'.$groupRec['auto_join'].'/i';
            }

            // It can happen that the pattern cannot compile - ignore it then
            if (preg_match($groupRec['auto_join'], $email))
            {
                // If open group or mail already verified or demo account
                // then allow direct add self
                if ($groupRec['allow_addself'] || $verified || $this->demo)
                {
                    return true;
                }
                else
                {
                    // Otherwise signal that verification required
                    return null;
                }
            }
        }

        return false;
    }

    function loadPlugins()
    {
        $this->plugins = array();
        $this->pluginPaths = array();

        $dirname = "./plugins/commands";

        if (is_dir($dirname) && ($dir = opendir($dirname)))
        {
            while (($plugin = readdir($dir)) !== false)
            {
                if (!strstr($plugin,'.inc.php'))
                {
                    continue;
                }

                $this->pluginPaths[] = $plugin;
                include($dirname. '/' .$plugin);
                // $plugin gets injected
                $this->plugins[] = $plugin;
            }
            closedir($dir);
        }

        if (count($this->plugins))
        {
            $l =& Localizer::getInstance();
            $l->setPlugins($this->pluginPaths);
        }
    }
}
?>