6.0.0-git
2024-04-24

Diff for AltQuotaH3 between and 1

[[toc]]
+++ Notes
This describes modifications to IMP quota that can be configured to use one or two different devices (file systems). If using two devices, one is for INBOX and the other is for IMAP folders. 

You can use one or two different devices (file systems) with quota enabled. This allows different quota values for INBOX and IMAP folders. Users use to keep messages on the INBOX. The bigger the quota, the bigger the INBOX file, with performance hit. In the other hand, messages are documents that must be filed, so you may want to give more space for IMAP folders than for the INBOX.

Uses system quota command.

Tested on Debian 5.0.5 (lenny), horde-3.3.8 and imp-h3-4.3.7.
----
+++ Modifications

||~ File ||~ Function(s) ||
|| imp/lib/Quota/command.php || IMP_Quota_command, getQuota ||
|| imp/lib/IMP.php || quota, quotaData ||
|| imp/lib/Quota.php || getQuota, getMessages ||
|| imp/config/servers.php ||   ||

You must modify the four files. Don't forget to change permission to each of them:
<code>
chown www-data command.php
</code>
Where www-data is the web user (Apache or equivalent).

You may need to give permission to your web user to execute quota:
<code>
chmod  +s  /usr/bin/quota
</code>

//Last updated 2010-06-28//
----
+++ Descriptions
----
++++ command.php
* Function IMP_Quota_command accepts 2 new parameters: 
> '**dev_inbx**' => User´s INBOX file system device  - **REQUIRED**
> If you have INBOX and IMAP folders in the same device, use //ONLY//  this parameter.
> If you have INBOX and IMAP folders in different devices and quota enabled on them you //MUST//  use 'dev_fldrs' parameter also.
> Usually maps to /var/mail, /var/spool/mail. 
> Examples: '/dev/hda6', '/dev/sdb2', '/dev/md2', '/dev/mapper/VOL1-VarM'.
> '**dev_fldrs**' => User´s home file system device  - **OPTIONAL** 
> Use only if you have INBOX and IMAP folders in different devices and quota enabled on them. \
Used for IMAP folders. Usually maps to /home. 
> Examples: '/dev/hda7', '/dev/sda3', '/dev/md1', '/dev/mapper/VOL2-Home'.
> Obsolete parameters: grep_path, partition
* function getQuota:
> Function now takes care of exceeded quotas, quota not defined for that user and if it is using one or two devices (dev_inbx, dev_fldrs).

Backup your original imp/lib/Quota/command.php and create a new command.php with the following code:
<code type="php">
<?php
/**
 * Implementation of the Quota API for IMAP servers with a unix quota command.
 * This requires a modified "quota" command that allows the httpd server
 * account to get quotas for other users. It also requires that your
 * web server and imap server be the same server or at least have shared
 * authentication and file servers (e.g. via NIS/NFS).  And last, it (as
 * written) requires the POSIX PHP extensions.
 *
 * You must configure this driver in horde/imp/config/servers.php.  The
 * driver supports the following parameters:
 *   'quota_path' => Path to the quota binary - REQUIRED
 *   'dev_inbx'   => User´s INBOX file system device  - REQUIRED
 *                   If you have INBOX and IMAP folders in the same 
 *                   device, use ONLY this parameter. 
 *                   If you have INBOX and IMAP folders in different 
 *                   devices and quota enabled on them you MUST use 
 *                   'dev_fldrs' parameter also.
 *                   Usually: /,  /var/mail,  /var/spool/mail
 *                   Examples: '/dev/hda6', '/dev/sdb2', '/dev/md2', 
 *                             '/dev/mapper/VOL1-VarM'
 *   'dev_fldrs'  => User´s home file system device  - OPTIONAL
 *                   Use only if you have INBOX and IMAP folders in 
 *                   different devices and quota enabled on them.
 *                   For IMAP folders. Usually: /home
 *                   Examples: '/dev/hda7', '/dev/sda3', '/dev/md1', 
 *                             '/dev/mapper/VOL2-Home'
 *
 * -------------------
 * Modified by Mauricio Jose T. Tecles <mtecles@biof.ufrj.br>
 * Functions IMP_Quota_command, blockSize, getQuota
 * Updated 2010 February 28
 * 
 * -------------------
 * $Horde: imp/lib/Quota/command.php,v 1.11.10.17 2009-01-06 15:24:11 jan Exp $
 *
 * Copyright 2002-2009 The Horde Project (http://www.horde.org/)
 *
 * See the enclosed file COPYING for license information (GPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
 *
 * @author  Eric Rostetter <eric.rostetter@physics.utexas.edu>
 * @package IMP_Quota
 */
class IMP_Quota_command extends IMP_Quota {

    /**
     * Constructor
     *
     * @param array $params  Hash containing connection parameters.
     */
    function IMP_Quota_command($params = array())
    {
        $params = array_merge(array('quota_path' => 'quota',
                                    'dev_inbx'  => null,
                                    'dev_fldrs'  => null),
                              $params);
        parent::IMP_Quota($params);
    }

    /**
     * Get the disk block size, if possible.
     *
     * We try to find out the disk block size from stat(). If not
     * available, stat() should return -1 for this value, in which
     * case we default to 1024 (for historical reasons). There are a
     * large number of reasons this may fail, such as OS support,
     * SELinux interference, the file being > 2 GB in size, the file
     * we're referring to not being readable, etc.
     */
    function blockSize()
    {
        $results = stat(__FILE__);
        if ($results['blksize'] > 1) {
            $blocksize = $results['blksize'];
        } else {
            $blocksize = 1024;
        }
		$blocksize = 1024;
        return $blocksize;
    }

    /**
     * Get quota information (used/allocated), in bytes.
     *
     * @return mixed  An associative array.
     *                'soflimithome' = Maximum quota allowed in dev_fldrs
     *                'usagehome' = Currently used portion of quota in dev_fldrs
     *                'soflimitvar' = Maximum quota allowed in dev_inbx
     *                'usagevar' = Currently used portion of quota in dev_inbx
     *                Returns PEAR_Error on failure.
     */
    function getQuota()
    {
        $imap_user = $_SESSION['imp']['user'];

        $cmdline = $this->_params['quota_path'] . ' -wu ' . $imap_user;
        unset($quota_data);
        $junk = exec($cmdline, $quota_data, $return_code);
        $junk = count( $quota_data);

        $blocksize = $this->blockSize();

        /* 
        * Is quota exceeded? 
        */

        if ($return_code == 0) {
            /* 
            * Quota not exceeded 
            * Is quota defined? 
            */
            if (ereg("none$", $quota_data[0])) {
                /*
                * Quota not defined or user does not own any files.
                */
                if (empty($this->_params['dev_fldrs'])) {
                    return array('usagehome' => -1, 'soflimithome' => -1, 'usagevar' => 0, 'soflimitvar' => 0);
                } else {
                    return array('usagehome' => 0, 'soflimithome' => 0, 'usagevar' => 0, 'soflimitvar' => 0);
                }
            } else {
                /*
                * Quota defined
                */
                if ( $junk == 4 ) {
                    /*
                    * Quotas defined for dev_fldrs and dev_inbx
                    */
                    if (ereg($this->_params['dev_fldrs'], $quota_data[2])) {
                        $quotahome = split("[[:blank:]]+", trim($quota_data[2]));
                        $quotavar = split("[[:blank:]]+", trim($quota_data[3]));
                        return array('usagehome' => $quotahome[1] * $blocksize, 'soflimithome' => $quotahome[2] * $blocksize, 'usagevar' => $quotavar[1] * $blocksize, 'soflimitvar' => $quotavar[2] * $blocksize);
                    } elseif (ereg($this->_params['dev_inbx'], $quota_data[2])) {
                        $quotahome = split("[[:blank:]]+", trim($quota_data[3]));
                        $quotavar = split("[[:blank:]]+", trim($quota_data[2]));
                        return array('usagehome' => $quotahome[1] * $blocksize, 'soflimithome' => $quotahome[2] * $blocksize, 'usagevar' => $quotavar[1] * $blocksize, 'soflimitvar' => $quotavar[2] * $blocksize);
                    } 
                } else {
                    /*
                    * Either quota is defined only for dev_fldrs or dev_inbx
                    * or user owns file in only one file system.
                    */
                    if (ereg($this->_params['dev_inbx'], $quota_data[2])) {
                        $quotavar = split("[[:blank:]]+", trim($quota_data[2]));
                        if (!empty($this->_params['dev_fldrs'])) {
                            return array('usagehome' => 0, 'soflimithome' => 0, 'usagevar' => $quotavar[1] * $blocksize, 'soflimitvar' => $quotavar[2] * $blocksize);
                        } else {
                            return array('usagehome' => -1, 'soflimithome' => -1, 'usagevar' => $quotavar[1] * $blocksize, 'soflimitvar' => $quotavar[2] * $blocksize);
                        }
                    } elseif (!empty($this->_params['dev_fldrs'])) {
                        if (ereg($this->_params['dev_fldrs'], $quota_data[2])) {
                            $quotahome = split("[[:blank:]]+", trim($quota_data[2]));
                            return array('usagehome' => $quotahome[1] * $blocksize, 'soflimithome' => $quotahome[2] * $blocksize, 'usagevar' => 0, 'soflimitvar' => 0);
                        }    
                    }
                }
            }
        } else {
            /*
            * Some quota exceeded
            */
            if ( $junk == 4 ) {
                /*
                * Quotas defined for dev_fldrs and dev_inbx
                */
                if (ereg($this->_params['dev_fldrs'], $quota_data[2])) {
                    $quotahome = split("[[:blank:]]+", trim($quota_data[2]));
                    $quotavar = split("[[:blank:]]+", trim($quota_data[3]));
                } elseif (ereg($this->_params['dev_inbx'], $quota_data[2])) {
                    $quotahome = split("[[:blank:]]+", trim($quota_data[3]));
                    $quotavar = split("[[:blank:]]+", trim($quota_data[2]));
                }
                /*
                * 
                * Quota exceeded in dev_fldrs?
                */
                if (ereg("\*$", $quotahome[1])) {
                    $quotahome[1] = ereg_replace ("\*", "", $quotahome[1]);
                    $quotahome[4] = ereg_replace ("days", "", $quotahome[4]);
                } else {
                    $quotahome[4] == "";
                }
                /* 
                * Quota exceeded in dev_inbx?
                */
                if (ereg("\*$", $quotavar[1])) {
                    $quotavar[1] = ereg_replace ("\*", "", $quotavar[1]);
                    $quotavar[4] = ereg_replace ("days", "", $quotavar[4]);
                } else {
                    $quotavar[4] == "";
                }
                return array('usagehome' => $quotahome[1] * $blocksize, 'soflimithome' => $quotahome[2] * $blocksize, 'gracehome' => $quotahome[4], 'usagevar' => $quotavar[1] * $blocksize, 'soflimitvar' => $quotavar[2] * $blocksize, 'gracevar' => $quotavar[4]);
            } else {
                /*
                * Either quota is defined only for dev_fldrs or dev_inbx
                * or user owns file in only one file system.
                */
                if (ereg($this->_params[dev_inbx], $quota_data[2])) {
                    /** 
                    * Quota exceeded in dev_inbx.
                    */
                    $quotavar = split("[[:blank:]]+", trim($quota_data[2]));
                    $quotavar[1] = ereg_replace ("\*", "", $quotavar[1]);
                    $quotavar[4] = ereg_replace ("days", "", $quotavar[4]);
                    if (!empty($this->_params['dev_fldrs'])) {
                        return array('usagehome' => 0, 'soflimithome' => 0, 'usagevar' => $quotavar[1] * $blocksize, 'soflimitvar' => $quotavar[2] * $blocksize, 'gracevar' => $quotavar[4]);
                    } else {
                        return array('usagehome' => -1, 'soflimithome' => -1, 'usagevar' => $quotavar[1] * $blocksize, 'soflimitvar' => $quotavar[2] * $blocksize, 'gracevar' => $quotavar[4]);
                    }
                } else {
                    /* 
                    * Quota exceeded in dev_fldrs
                    */
                    $quotahome = split("[[:blank:]]+", trim($quota_data[2]));
                    $quotahome[1] = ereg_replace ("\*", "", $quotahome[1]);
                    $quotahome[4] = ereg_replace ("days", "", $quotahome[4]);
                    return array('usagehome' => $quotahome[1] * $blocksize, 'soflimithome' => $quotahome[2] * $blocksize, 'gracehome' => $quotahome[4], 'usagevar' => 0, 'soflimitvar' => 0);
                }
            }
        }
        return PEAR::raiseError(_("Unable to retrieve quota"), 'horde.error');
    }

}
</code>
----
++++ Configuration examples 
(imp/config/servers.php)
* One device:
<code type="php">
$servers['imap'] = array(
    ...

    'quota' => array(
      'driver' => 'command',
      'params' => array(
        'quota_path' => '/usr/bin/quota',
        'dev_inbx' => '/dev/hda6')
     )
);
</code>
* Two devices:
<code type="php">
$servers['imap'] = array(
    ...

    'quota' => array(
      'driver' => 'command',
      'params' => array(
        'quota_path' => '/usr/bin/quota',
        'dev_fldrs' => '/dev/hda7',
        'dev_inbx' => '/dev/hda6')
     )
);
</code>
----
++++ Quota.php
* New messages:
> Alerts quota exceeded, grace time or expired;
> Alerts almost full;
> Normal quota information.

Backup your original imp/lib/Quota.php and create a new Quota.php with the following code:
<code type="php">
<?php
/**
 * IMP_Quota:: provides an API for retrieving Quota details from a mail
 * server.
 *
 * $Horde: imp/lib/Quota.php,v 1.23.10.15 2009-01-06 15:24:04 jan Exp $
 *
 * Copyright 2002-2009 The Horde Project (http://www.horde.org/)
 *
 * See the enclosed file COPYING for license information (GPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
 *
 * -------------------
 * Modified by Mauricio Jose T. Tecles <mtecles@biof.ufrj.br>
 * Functions getQuota getMessages
 * Updated 2010 February 28
 * 
 * -------------------
 * @author  Mike Cochrane <mike@graftonhall.co.nz>
 * @package IMP_Quota
 */
class IMP_Quota {

    /**
     * Hash containing connection parameters.
     *
     * @var array
     */
    var $_params = array();

    /**
     * Constructor.
     *
     * @param array $params  Hash containing connection parameters.
     */
    function IMP_Quota($params = array())
    {
        $this->_params = $params;

        /* If 'password' exists in params, it has been encrypted in the
         * session so we need to decrypt. */
        if (isset($this->_params['password'])) {
            $this->_params['password'] = Secret::read(Secret::getKey('imp'), $this->_params['password']);
        }
    }

    /**
     * Get quota information (used/allocated), in bytes.
     *
     * @return mixed  An associative array.
     *                'limit...' = Maximum quota allowed
     *                'usage...' = Currently used portion of quota (in bytes)
     *                'limithome' and 'usagehome' for dev_fldrs (/home) (mail folders)
     *                'limitvar' and 'usagevar' for dev_inbx (/var/mail) (INBOX)
     *                Returns PEAR_Error on failure.
     */
    function getQuota()
    {
        return array('usagehome' => 0, 'limithome' => 0, 'usagevar' => 0, 'limitvar' => 0);
    }

    /**
     * Returns the quota messages variants, including sprintf placeholders.
     *
     * @return array  A hash with quota message templates.
     */
    function getMessages()
    {
        return array(
            'longh' => (isset($this->_params['format']['longh']))
                ? $this->_params['format']['longh']
                : _("Folders: %.2fMB / %.2fMB  (%.2f%%)"),
            'longhaf' => (isset($this->_params['format']['longhaf']))
                ? $this->_params['format']['longhaf']
                : _("ATTENTION! Folders area almost full: %.2fMB / %.2fMB  (%.2f%%)"),
            'longhle' => (isset($this->_params['format']['longhle']))
                ? $this->_params['format']['longhle']
                : _("ATTENTION! Folders: Limit exceeded: %.2fMB / %.2fMB  (%.2f%%). Please, solve before %s day(s)."),
            'longhlee' => (isset($this->_params['format']['longhlee']))
                ? $this->_params['format']['longhlee']
                : _("ATTENTION! Folders: Limit exceeded: %.2fMB / %.2fMB  (%.2f%%). Expired."),
            'shorth' => isset($this->_params['format']['shorth'])
                ? $this->_params['format']['shorth']
                : _("Folders: %.0f%% of %.0fMB"),
            'shorthaf' => isset($this->_params['format']['shorthaf'])
                ? $this->_params['format']['shorthaf']
                : _("ATTENTION! Folders area almost full: %.0f%% of %.0fMB"),
            'shorthle' => isset($this->_params['format']['shorthle'])
                ? $this->_params['format']['shorthle']
                : _("ATTENTION! Folders: Limit exceeded: %.0f%% of %.0fMB. Please, solve before %s day(s)."),
            'shorthlee' => isset($this->_params['format']['shorthlee'])
                ? $this->_params['format']['shorthlee']
                : _("ATTENTION! Folders: Limit exceeded: %.0f%% of %.0fMB. Expired."),
            'nolimit_longh' => isset($this->_params['format']['nolimit_longh'])
                ? $this->_params['format']['nolimit_longh']
                : _("Folders: %.2fMB / NO limit."),
            'nolimit_shorth' => isset($this->_params['format']['nolimit_shorth'])
                ? $this->_params['format']['nolimit_shorth']
                : _("Folders: %.0f MB"),
            'longi' => (isset($this->_params['format']['longi']))
                ? $this->_params['format']['longi']
                : _("Inbox: %.2fMB / %.2fMB  (%.2f%%)"),
            'longiaf' => (isset($this->_params['format']['longiaf']))
                ? $this->_params['format']['longiaf']
                : _("ATTENTION! Inbox almost full: %.2fMB / %.2fMB  (%.2f%%)"),
            'longile' => (isset($this->_params['format']['longile']))
                ? $this->_params['format']['longile']
                : _("ATTENTION! Inbox: limit exceeded: %.2fMB / %.2fMB  (%.2f%%). Please, solve in %s day(s)."),
            'longilee' => (isset($this->_params['format']['longilee']))
                ? $this->_params['format']['longilee']
                : _("ATTENTION! Inbox: limit exceeded: %.2fMB / %.2fMB  (%.2f%%). Date expired."),
            'shorti' => isset($this->_params['format']['shorti'])
                ? $this->_params['format']['shorti']
                : _("Inbox: %.0f%% of %.0fMB"),
            'shortiaf' => isset($this->_params['format']['shortiaf'])
                ? $this->_params['format']['shortiaf']
                : _("ATTENTION! Inbox almost full: %.0f%% of %.0fMB"),
            'shortile' => isset($this->_params['format']['shortile'])
                ? $this->_params['format']['shortile']
                : _("ATTENTION! Inbox: Limit exceeded: %.0f%% of %.0fMB. Please, solve before %s day(s)."),
            'shortilee' => isset($this->_params['format']['shortilee'])
                ? $this->_params['format']['shortilee']
                : _("ATTENTION! Inbox: Limit exceeded: %.0f%% of %.0fMB. Expired."),
            'nolimit_longi' => isset($this->_params['format']['nolimit_longi'])
                ? $this->_params['format']['nolimit_longi']
                : _("Inbox: %.2f MB / NO LIMIT"),
            'nolimit_shorti' => isset($this->_params['format']['nolimit_shorti'])
                ? $this->_params['format']['nolimit_shorti']
                : _("Inbox: %.0f MB"));
    }

    /**
     * Attempts to return a concrete Quota instance based on $driver.
     *
     * @param string $driver  The type of concrete Quota subclass to return.
     * @param array $params   A hash containing any additional configuration or
     *                        connection parameters a subclass might need.
     *
     * @return mixed  The newly created concrete Quota instance, or false
     *                on error.
     */
    function &factory($driver, $params = array())
    {
        $driver = basename($driver);
        require_once dirname(__FILE__) . '/Quota/' . $driver . '.php';
        $class = 'IMP_Quota_' . $driver;
        if (class_exists($class)) {
            $quota = new $class($params);
        } else {
            $quota = false;
        }

        return $quota;
    }

    /**
     * Attempts to return a reference to a concrete Quota instance based on
     * $driver.
     *
     * It will only create a new instance if no Quota instance with the same
     * parameters currently exists.
     *
     * This should be used if multiple quota sources are required.
     *
     * This method must be invoked as: $var = &Quota::singleton()
     *
     * @param string $driver  The type of concrete Quota subclass to return.
     * @param array $params   A hash containing any additional configuration or
     *                        connection parameters a subclass might need.
     *
     * @return mixed  The created concrete Quota instance, or false on error.
     */
    function &singleton($driver, $params = array())
    {
        static $instances = array();

        $signature = serialize(array($driver, $params));
        if (!isset($instances[$signature])) {
            $instances[$signature] = &IMP_Quota::factory($driver, $params);
        }

        return $instances[$signature];
    }

}
</code>
----
++++ IMP.php 
* Function quotaData takes care of using one or two devices (dev_inbx, dev_fldrs). New information scale:
> quota >= 100%, quotaalert. Alerts quota exceeded, grace time or expired;
> 90% <= quota < 100%, quotaalert. Alerts almost full;
> 75% <= quota < 90%, quotawarn;
> quota < 75%, control.
> To change the control and warn percentages just edit quotaData function to the desired values.

Backup your original imp/lib/IMP.php and create a new IMP.php with the following code:
<code type="php">
<?php
// Compose encryption options
/**
 * Send Message w/no encryption.
 */
define('IMP_ENCRYPT_NONE', 1);

/**
 * Send Message - PGP Encrypt.
 */
define('IMP_PGP_ENCRYPT', 2);

/**
 * Send Message - PGP Sign.
 */
define('IMP_PGP_SIGN', 3);

/**
 * Send Message - PGP Sign/Encrypt.
 */
define('IMP_PGP_SIGNENC', 4);

/**
 * Send Message - S/MIME Encrypt.
 */
define('IMP_SMIME_ENCRYPT', 5);

/**
 * Send Message - S/MIME Sign.
 */
define('IMP_SMIME_SIGN', 6);

/**
 * Send Message - S/MIME Sign/Encrypt.
 */
define('IMP_SMIME_SIGNENC', 7);

/**
 * Send Message - PGP Encrypt with passphrase.
 */
define('IMP_PGP_SYM_ENCRYPT', 8);

/**
 * Send Message - PGP Sign/Encrypt with passphrase.
 */
define('IMP_PGP_SYM_SIGNENC', 9);

// IMAP Flags
/**
 * Match all IMAP flags.
 */
define('IMP_ALL', 0);

/**
 * \\UNSEEN flag
.*/
define('IMP_UNSEEN', 1);

/**
 * \\DELETED flag
.*/
define('IMP_DELETED', 2);

/**
 * \\ANSWERED flag.
 */
define('IMP_ANSWERED', 4);

/**
 * \\FLAGGED flag.
 */
define('IMP_FLAGGED', 8);

/**
 * \\DRAFT flag.
 */
define('IMP_DRAFT', 16);

/**
 * An email is personal.
 */
define('IMP_PERSONAL', 32);

// IMAP Sorting Constant
/**
 * Sort By Thread.
 */
@define('SORTTHREAD', 161);

// IMP Mailbox view constants
/**
 * Start on the page with the first unseen message.
 */
define('IMP_MAILBOXSTART_FIRSTUNSEEN', 1);

/**
 * Start on the page with the last unseen message.
 */
define('IMP_MAILBOXSTART_LASTUNSEEN', 2);

/**
 * Start on the first page.
 */
define('IMP_MAILBOXSTART_FIRSTPAGE', 3);

/**
 * Start on the last page.
 */
define('IMP_MAILBOXSTART_LASTPAGE', 4);

// IMP mailbox labels
/**
 * The mailbox name to use for search results.
 */
define('IMP_SEARCH_MBOX', '**search_');

// IMP internal indexing strings
/**
 * String used to separate messages.
 */
define('IMP_MSG_SEP', "\0");

/**
 * String used to separate indexes.
 */
define('IMP_IDX_SEP', "\1");

/**
 * IMP Base Class.
 *
 * $Horde: imp/lib/IMP.php,v 1.449.4.128 2009-10-12 22:36:33 slusarz Exp $
 *
 * Copyright 1999-2009 The Horde Project (http://www.horde.org/)
 *
 * See the enclosed file COPYING for license information (GPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
 *
 * -------------------
 * Modified by Mauricio Jose T. Tecles <mtecles@biof.ufrj.br>
 * Functions quota, quotaData
 * Updated 2010 February 28
 * 
 * -------------------
 * @author  Chuck Hagenbuch <chuck@horde.org>
 * @author  Jon Parise <jon@horde.org>
 * @author  Michael Slusarz <slusarz@horde.org>
 * @package IMP
 */
class IMP {

    /**
     * Returns the AutoLogin server key.
     *
     * @param boolean $first  Return the first value?
     *
     * @return string  The server key.
     */
    function getAutoLoginServer($first = false)
    {
        if (is_callable(array('Horde', 'loadConfiguration'))) {
            $result = Horde::loadConfiguration('servers.php', array('servers'), 'imp');
            if (is_a($result, 'PEAR_Error')) {
                Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
                return false;
            }
            extract($result);
        } else {
            require IMP_BASE . '/config/servers.php';
        }

        $server_key = null;
        foreach ($servers as $key => $curServer) {
            if (is_null($server_key) && substr($key, 0, 1) != '_') {
                $server_key = $key;
            }
            if (IMP::isPreferredServer($curServer, ($first) ? $key : null)) {
                $server_key = $key;
                if ($first) {
                    break;
                }
            }
        }

        return $server_key;
    }

    /**
     * Returns whether we can log in without a login screen for $server_key.
     *
     * @param string $server_key  The server to check. Defaults to
     *                            IMP::getCurrentServer().
     * @param boolean $force      If true, check $server_key even if there is
     *                            more than one server available.
     *
     * @return boolean  True or false.
     */
    function canAutoLogin($server_key = null, $force = false)
    {
        if (is_callable(array('Horde', 'loadConfiguration'))) {
            $result = Horde::loadConfiguration('servers.php', array('servers'), 'imp');
            if (is_a($result, 'PEAR_Error')) {
                Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
                return false;
            }
            extract($result);
        } else {
            require IMP_BASE . '/config/servers.php';
        }

        $auto_server = IMP::getAutoLoginServer();
        if (is_null($server_key)) {
            $server_key = $auto_server;
        }

        return ((!empty($auto_server) || $force) &&
                Auth::getAuth() &&
                !empty($servers[$server_key]['hordeauth']));
    }

    /**
     * Makes sure the user has been authenticated to view the page.
     *
     * @param boolean $return     If this is true, return false instead of
     *                            exiting/redirecting if authentication fails.
     * @param boolean $hordeauth  Just check for Horde auth and don't bother
     *                            the IMAP server.
     *
     * @return boolean  True on success, false on error.
     */
    function checkAuthentication($return = false, $hordeauth = false)
    {
        if ($hordeauth) {
            $reason = Auth::isAuthenticated();
        } else {
            $auth_imp = &Auth::singleton(array('imp', 'imp'));
            $reason = $auth_imp->authenticate(null, array(), false);
        }

        if ($reason !== true) {
            if ($return) {
                return false;
            }

            if (Util::getFormData('popup')) {
                Util::closeWindowJS();
            } else {
                $url = Auth::addLogoutParameters(IMP::logoutUrl());
                $url = Util::addParameter($url, 'url', Horde::selfUrl(true));
                header('Location: ' . $url);
            }
            exit;
        }

        return true;
    }

    /**
     * Determines if the given mail server is the "preferred" mail server for
     * this web server.  This decision is based on the global 'SERVER_NAME'
     * and 'HTTP_HOST' server variables and the contents of the 'preferred'
     * either field in the server's definition.  The 'preferred' field may
     * take a single value or an array of multiple values.
     *
     * @param string $server  A complete server entry from the $servers hash.
     * @param TODO $key       TODO
     *
     * @return boolean  True if this entry is "preferred".
     */
    function isPreferredServer($server, $key = null)
    {
        static $urlServer;

        if (!isset($urlServer)) {
            $urlServer = Util::getFormData('server');
        }

        if (!empty($urlServer)) {
            return ($key == $urlServer);
        }

        if (!empty($server['preferred'])) {
            if (is_array($server['preferred'])) {
                if (in_array($_SERVER['SERVER_NAME'], $server['preferred']) ||
                    in_array($_SERVER['HTTP_HOST'], $server['preferred'])) {
                    return true;
                }
            } elseif (($server['preferred'] == $_SERVER['SERVER_NAME']) ||
                      ($server['preferred'] == $_SERVER['HTTP_HOST'])) {
                return true;
            }
        }

        return false;
    }

    /**
     * Generates a full c-client server specification string.
     *
     * @param string $mbox  The mailbox to append to end of the server string.
     *
     * @return string  The full spec string.
     */
    function serverString($mbox = null, $protocol = null)
    {
        if (substr($mbox, 0, 1) == '{') {
            return $mbox;
        }

        $srvstr = '{' . $_SESSION['imp']['server'];

        /* If port is not specified, don't include it in the string. */
        if (!empty($_SESSION['imp']['port'])) {
            $srvstr .= ':' . $_SESSION['imp']['port'];
        }

        return $srvstr . '/' . $_SESSION['imp']['protocol'] . '}' . $mbox;
    }

    /**
     * Get a token for protecting a form.
     *
     * @since IMP 4.2
     */
    function getRequestToken($slug)
    {
        require_once 'Horde/Token.php';
        $token = Horde_Token::generateId($slug);
        $_SESSION['horde_form_secrets'][$token] = time();
        return $token;
    }

    /**
     * Check if a token for a form is valid.
     *
     * @since IMP 4.2
     */
    function checkRequestToken($slug, $token)
    {
        if (empty($_SESSION['horde_form_secrets'][$token])) {
            return PEAR::raiseError(_("We cannot verify that this request was really sent by you. It could be a malicious request. If you intended to perform this action, you can retry it now."));
        }

        if ($_SESSION['horde_form_secrets'][$token] + $GLOBALS['conf']['server']['token_lifetime'] < time()) {
            return PEAR::raiseError(sprintf(_("This request cannot be completed because the link you followed or the form you submitted was only valid for %d minutes. Please try again now."), round($GLOBALS['conf']['server']['token_lifetime'] / 60)));
        }

        return true;
    }

    /**
     * Returns the plain text label that is displayed for the current mailbox,
     * replacing IMP_SEARCH_MBOX with an appropriate string and removing
     * namespace and folder prefix information from what is shown to the user.
     *
     * @param string $mbox  The mailbox to use for the label.
     *
     * @return string  The plain text label.
     */
    function getLabel($mbox)
    {
        return ($GLOBALS['imp_search']->isSearchMbox($mbox))
            ? $GLOBALS['imp_search']->getLabel($mbox)
            : IMP::displayFolder($mbox);
    }

    /**
     * Returns the bare address.
     *
     * @param string $address    The address string.
     * @param boolean $multiple  Should we return multiple results?
     *
     * @return mixed  See {@link MIME::bareAddress}.
     */
    function bareAddress($address, $multiple = false)
    {
        static $addresses;

        if (!isset($addresses[(string)$multiple][$address])) {
            require_once 'Horde/MIME.php';
            $addresses[(string)$multiple][$address] = MIME::bareAddress($address, $_SESSION['imp']['maildomain'], $multiple);
        }

        return $addresses[(string)$multiple][$address];
    }

    /**
     * Adds a contact to the user defined address book.
     *
     * @param string $newAddress  The contact's email address.
     * @param string $newName     The contact's name.
     *
     * @return string  A link or message to show in the notification area.
     */
    function addAddress($newAddress, $newName)
    {
        global $registry, $prefs;

        if (empty($newName)) {
            $newName = $newAddress;
        }

        $result = $registry->call('contacts/import',
                                  array(array('name' => $newName, 'email' => $newAddress),
                                        'array', $prefs->getValue('add_source')));
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        } else {
            $contact_link = $registry->link('contacts/show', array('uid' => $result, 'source' => $prefs->getValue('add_source')));
            if (!empty($contact_link) && !is_a($contact_link, 'PEAR_Error')) {
                $contact_link = Horde::link(Horde::url($contact_link), sprintf(_("Go to address book entry of \"%s\""), $newName)) . @htmlspecialchars($newName, ENT_COMPAT, NLS::getCharset()) . '</a>';
            } else {
                $contact_link = @htmlspecialchars($newName, ENT_COMPAT, NLS::getCharset());
            }
            return $contact_link;
        }
    }

    /**
     * Parses an address or address list into the address components.
     *
     * @param string $address    An address or address list.
     * @param boolean $validate  Validate the addresses?
     * @param boolean $domain    Use the local default domain?
     *
     * @return array  A list of address objects or a PEAR_Error object.
     */
    function parseAddressList($address, $validate = false, $domain = false)
    {
        static $parser;

        /* Don't use imap_rfc822_parse_adrlist() since it always expects MIME
         * encoded data (not-useful if $validate is false), doesn't return
         * data easy to parse to create a PEAR_Error object, and doesn't
         * handle lists properly. */

        if (!isset($parser)) {
            require_once 'Mail/RFC822.php';
            $parser = new Mail_RFC822();
        }

        return $parser->parseAddressList($address, ($domain) ? $_SESSION['imp']['maildomain'] : '', null, $validate);
    }

    /**
     * Wrapper around IMP_Folder::flist() which generates the body of a
     * &lt;select&gt; form input from the generated folder list. The
     * &lt;select&gt; and &lt;/select&gt; tags are NOT included in the output
     * of this function.
     *
     * @param string $heading         The label for an empty-value option at
     *                                the top of the list.
     * @param boolean $abbrev         If true, abbreviate long mailbox names
     *                                by replacing the middle of the name with
     *                                '...'.
     * @param array $filter           An array of mailboxes to ignore.
     * @param string $selected        The mailbox to have selected by default.
     * @param boolean $new_folder     If true, display an option to create a
     *                                new folder.
     * @param boolean $inc_tasklists  Should the user's editable tasklists be
     *                                included in the list?
     * @param boolean $inc_vfolder    Should the user's virtual folders be
     *                                included in the list?
     * @param boolean $inc_tasklists  Should the user's editable notepads be
     *                                included in the list?
     *
     * @return string  A string containing <option> elements for each mailbox
     *                 in the list.
     */
    function flistSelect($heading = '', $abbrev = true, $filter = array(),
                         $selected = null, $new_folder = false,
                         $inc_tasklists = false, $inc_vfolder = false,
                         $inc_notepads = false)
    {
        require_once 'Horde/Text.php';
        require_once IMP_BASE . '/lib/Folder.php';

        $imp_folder = &IMP_Folder::singleton();

        /* Don't filter here - since we are going to parse through every
         * member of the folder list below anyway, we can filter at that time.
         * This allows us the have a single cached value for the folder list
         * rather than a cached value for each different mailbox we may
         * visit. */
        $mailboxes = $imp_folder->flist_IMP();
        $text = '';

        if (strlen($heading) > 0) {
            $text .= '<option value="">' . $heading . "</option>\n";
        }

        if ($new_folder &&
            (!empty($GLOBALS['conf']['hooks']['permsdenied']) ||
             (IMP::hasPermission('create_folders') &&
              IMP::hasPermission('max_folders')))) {
            $text .= '<option value="" disabled="disabled">- - - - - - - - -</option>' . "\n";
            $text .= '<option value="*new*">' . _("New Folder") . "</option>\n";
            $text .= '<option value="" disabled="disabled">- - - - - - - - -</option>' . "\n";
        }

        /* Add the list of mailboxes to the lists. */
        $filter = array_flip($filter);
        foreach ($mailboxes as $mbox) {
            if (isset($filter[$mbox['val']])) {
                continue;
            }

            $val = isset($filter[$mbox['val']]) ? '' : htmlspecialchars($mbox['val']);
            $sel = ($mbox['val'] && ($mbox['val'] === $selected)) ? ' selected="selected"' : '';
            $label = ($abbrev) ? $mbox['abbrev'] : $mbox['label'];
            $text .= sprintf('<option value="%s"%s>%s</option>%s', $val, $sel, Text::htmlSpaces($label), "\n");
        }

        /* Add the list of virtual folders to the list. */
        if ($inc_vfolder) {
            $vfolders = $GLOBALS['imp_search']->listQueries(true);
            if (!empty($vfolders)) {
                $vfolder_sel = $GLOBALS['imp_search']->searchMboxID();
                $text .= '<option value="" disabled="disabled">- - - - - - - - -</option>' . "\n";
                foreach ($vfolders as $id => $val) {
                    $text .= sprintf('<option value="%s"%s>%s</option>%s', $GLOBALS['imp_search']->createSearchID($id), ($vfolder_sel == $id) ? ' selected="selected"' : '', Text::htmlSpaces($val), "\n");
                }
            }
        }

        /* Add the list of editable tasklists to the list. */
        if ($inc_tasklists && $_SESSION['imp']['tasklistavail']) {
            $tasklists = $GLOBALS['registry']->call('tasks/listTasklists',
                                                    array(false, PERMS_EDIT));

            if (!is_a($tasklists, 'PEAR_Error') && count($tasklists)) {
                $text .= '<option value="" disabled="disabled">&nbsp;</option><option value="" disabled="disabled">- - ' . _("Task Lists") . ' - -</option>' . "\n";

                foreach ($tasklists as $id => $tasklist) {
                    $text .= sprintf('<option value="%s">%s</option>%s',
                                     '_tasklist_' . $id,
                                     Text::htmlSpaces($tasklist->get('name')),
                                     "\n");
                }
            }
        }

        /* Add the list of editable notepads to the list. */
        if ($inc_notepads && $_SESSION['imp']['notepadavail']) {
            $notepads = $GLOBALS['registry']->call('notes/listNotepads',
                                                    array(false, PERMS_EDIT));

            if (!is_a($notepads, 'PEAR_Error') && count($notepads)) {
                $text .= '<option value="" disabled="disabled">&nbsp;</option><option value="" disabled="disabled">- - ' . _("Notepads") . ' - -</option>' . "\n";

                foreach ($notepads as $id => $notepad) {
                    $text .= sprintf('<option value="%s">%s</option>%s',
                                     '_notepad_' . $id,
                                     Text::htmlSpaces($notepad->get('name')),
                                     "\n");
                }
            }
        }

        return $text;
    }

    /**
     * Checks for To:, Subject:, Cc:, and other compose window arguments and
     * pass back either a URI fragment or an associative array with any of
     * them which are present.
     *
     * @param string $format  Either 'uri' or 'array'.
     *
     * @return string  A URI fragment or an associative array with any compose
     *                 arguments present.
     */
    function getComposeArgs()
    {
        $args = array();
        $fields = array('to', 'cc', 'bcc', 'message', 'body', 'subject');

        foreach ($fields as $val) {
            if (($$val = Util::getFormData($val))) {
                $args[$val] = $$val;
            }
        }

        /* Decode mailto: URLs. */
        if (isset($args['to']) && (strpos($args['to'], 'mailto:') === 0)) {
            $mailto = @parse_url($args['to']);
            if (is_array($mailto)) {
                $args['to'] = isset($mailto['path']) ? $mailto['path'] : '';
                if (!empty($mailto['query'])) {
                    parse_str($mailto['query'], $vals);
                    foreach ($fields as $val) {
                        if (isset($vals[$val])) {
                            $args[$val] = $vals[$val];
                        }
                    }
                }
            }
        }

        return $args;
    }

    /**
     * Open a compose window.
     */
    function openComposeWin($options = array())
    {
        global $prefs;

        if ($prefs->getValue('compose_popup')) {
            return true;
        } else {
            $options += IMP::getComposeArgs();
            $url = Util::addParameter(Horde::applicationUrl('compose.php', true),
                                      $options, null, false);
            header('Location: ' . $url);
            return false;
        }
    }

    /**
     * Prepares the arguments to use for composeLink().
     *
     * @since IMP 4.2
     *
     * @param mixed $args   List of arguments to pass to compose.php. If this
     *                      is passed in as a string, it will be parsed as a
     *                      toaddress?subject=foo&cc=ccaddress (mailto-style)
     *                      string.
     * @param array $extra  Hash of extra, non-standard arguments to pass to
     *                      compose.php.
     *
     * @return array  The array of args to use for composeLink().
     */
    function composeLinkArgs($args = array(), $extra = array())
    {
        if (is_string($args)) {
            $string = $args;
            $args = array();
            if (($pos = strpos($string, '?')) !== false) {
                parse_str(substr($string, $pos + 1), $args);
                $args['to'] = substr($string, 0, $pos);
            } else {
                $args['to'] = $string;
            }
        }

        /* Merge the two argument arrays. */
        if (is_array($extra) && !empty($extra)) {
            $args = array_merge($args, $extra);
        }

        return $args;
    }

    /**
     * Returns the appropriate link to call the message composition screen.
     *
     * @param mixed $args   List of arguments to pass to compose.php. If this
     *                      is passed in as a string, it will be parsed as a
     *                      toaddress?subject=foo&cc=ccaddress (mailto-style)
     *                      string.
     * @param array $extra  Hash of extra, non-standard arguments to pass to
     *                      compose.php.
     *
     * @return string  The link to the message composition screen.
     */
    function composeLink($args = array(), $extra = array())
    {
        $args = IMP::composeLinkArgs($args, $extra);

        if ($GLOBALS['prefs']->getValue('compose_popup')
            && $GLOBALS['browser']->hasFeature('javascript')) {
            Horde::addScriptFile('prototype.js', 'imp', true);
            Horde::addScriptFile('popup.js', 'imp', true);
            if (isset($args['to'])) {
                $args['to'] = addcslashes($args['to'], '\\"');
            }
            return "javascript:" . IMP::popupIMPString('compose.php', $args);
        } else {
            return Util::addParameter(Horde::applicationUrl('compose.php'), $args);
        }
    }

    /**
     * Generates an URL to the logout screen that includes any known
     * information, such as username, server, etc., that can be filled in on
     * the login form.
     *
     * @return string  Logout URL with logout parameters added.
     */
    function logoutUrl()
    {
        $params = array(
            'imapuser' => isset($_SESSION['imp']['user']) ?
                          $_SESSION['imp']['user'] :
                          Util::getFormData('imapuser'),
            'server'   => isset($_SESSION['imp']['server']) ?
                          $_SESSION['imp']['server'] :
                          Util::getFormData('server'),
            'port'     => isset($_SESSION['imp']['port']) ?
                          $_SESSION['imp']['port'] :
                          Util::getFormData('port'),
            'protocol' => isset($_SESSION['imp']['protocol']) ?
                          $_SESSION['imp']['protocol'] :
                          Util::getFormData('protocol'),
            'language' => isset($_SESSION['imp']['language']) ?
                          $_SESSION['imp']['language'] :
                          Util::getFormData('language'),
            'smtphost' => isset($_SESSION['imp']['smtphost']) ?
                          $_SESSION['imp']['smtphost'] :
                          Util::getFormData('smtphost'),
            'smtpport' => isset($_SESSION['imp']['smtpport']) ?
                          $_SESSION['imp']['smtpport'] :
                          Util::getFormData('smtpport'),
        );

        return Util::addParameter($GLOBALS['registry']->get('webroot', 'imp') . '/login.php', array_diff($params, array('')), null, false);
    }

    /**
     * If there is information available to tell us about a prefix in front of
     * mailbox names that shouldn't be displayed to the user, then use it to
     * strip that prefix out.
     *
     * @param string $folder        The folder name to display.
     * @param boolean $notranslate  Do not translate the folder prefix.
     *
     * @return string  The folder, with any prefix gone.
     */
    function displayFolder($folder, $notranslate = false)
    {
        static $cache = array();

        if (isset($cache[$folder])) {
            return $cache[$folder];
        }

        if ($folder == 'INBOX') {
            $out = _("Inbox");
        } else {
            $namespace_info = IMP::getNamespace($folder);
            if (($namespace_info !== null) &&
                !empty($namespace_info['name']) &&
                ($namespace_info['type'] == 'personal') &&
                substr($folder, 0, strlen($namespace_info['name'])) == $namespace_info['name']) {
                $out = substr($folder, strlen($namespace_info['name']));
            } elseif (!$notranslate &&
                      !is_null($namespace_info) &&
                      (strpos($folder, 'INBOX' . $namespace_info['delimiter']) === 0)) {
                $out = _("Inbox") . substr($folder, 5);
            } else {
                $out = $folder;
            }

            $out = String::convertCharset($out, 'UTF7-IMAP');
        }

        if (!$notranslate) {
            $cache[$folder] = $out;
        }

        return $out;
    }

    /**
     * Filters a string, if requested.
     *
     * @param string $text  The text to filter.
     *
     * @return string  The filtered text (if requested).
     */
    function filterText($text)
    {
        global $conf, $prefs;

        if ($prefs->getValue('filtering') && strlen($text)) {
            require_once 'Horde/Text/Filter.php';
            $text = Text_Filter::filter($text, 'words', array('words_file' => $conf['msgsettings']['filtering']['words'], 'replacement' => $conf['msgsettings']['filtering']['replacement']));
        }

        return $text;
    }

    /**
     * Returns the specified permission for the current user.
     *
     * @since IMP 4.1
     *
     * @param string $permission  A permission.
     * @param boolean $value      If true, the method returns the value of a
     *                            scalar permission, otherwise whether the
     *                            permission limit has been hit already.
     *
     * @return mixed  The value of the specified permission.
     */
    function hasPermission($permission, $value = false)
    {
        global $perms;

        if (!$perms->exists('imp:' . $permission)) {
            return true;
        }

        $allowed = $perms->getPermissions('imp:' . $permission);
        if (is_array($allowed)) {
            switch ($permission) {
            case 'create_folders':
                $allowed = (bool)count(array_filter($allowed));
                break;

            case 'max_folders':
            case 'max_recipients':
            case 'max_timelimit':
                $allowed = max($allowed);
                break;
            }
        }
        if ($permission == 'max_folders' && !$value) {
            $folder = &IMP_Folder::singleton();
            $allowed = $allowed > count($folder->flist_IMP(array(), false));
        }

        return $allowed;
    }

    /**
     * Build IMP's list of menu items.
     *
     * @param string $returnType  Either 'object' or 'string'.
     *
     * @return mixed  Either a Horde_Menu object or the rendered menu text.
     */
    function getMenu($returnType = 'object')
    {
        global $conf, $prefs, $registry;

        require_once 'Horde/Menu.php';

        $menu_search_url = Horde::applicationUrl('search.php');
        $menu_mailbox_url = Horde::applicationUrl('mailbox.php');

        $spam_folder = IMP::folderPref($prefs->getValue('spam_folder'), true);

        $menu = new Menu(HORDE_MENU_MASK_ALL & ~HORDE_MENU_MASK_LOGIN);

        $menu->add(IMP::generateIMPUrl($menu_mailbox_url, 'INBOX'), _("_Inbox"), 'folders/inbox.png');

        if (($_SESSION['imp']['base_protocol'] != 'pop3') &&
            $prefs->getValue('use_trash') &&
            $prefs->getValue('empty_trash_menu')) {
            $mailbox = null;
            if ($prefs->getValue('use_vtrash')) {
                $mailbox = $GLOBALS['imp_search']->createSearchID($prefs->getValue('vtrash_id'));
            } else {
                $trash_folder = IMP::folderPref($prefs->getValue('trash_folder'), true);
                if (($trash_folder !== null)) {
                    $mailbox = $trash_folder;
                }
            }

            if (!empty($mailbox)) {
                $menu_trash_url = Util::addParameter(IMP::generateIMPUrl($menu_mailbox_url, $mailbox), array('actionID' => 'empty_mailbox', 'mailbox_token' => IMP::getRequestToken('imp.mailbox')));
                $menu->add($menu_trash_url, _("Empty _Trash"), 'empty_trash.png', null, null, "return window.confirm('" . addslashes(_("Are you sure you wish to empty your trash folder?")) . "');", '__noselection');
            }
        }

        if (($_SESSION['imp']['base_protocol'] != 'pop3') &&
            !empty($spam_folder) &&
            $prefs->getValue('empty_spam_menu')) {
            $menu_spam_url = Util::addParameter(IMP::generateIMPUrl($menu_mailbox_url, $spam_folder), array('actionID' => 'empty_mailbox', 'mailbox_token' => IMP::getRequestToken('imp.mailbox')));
            $menu->add($menu_spam_url, _("Empty _Spam"), 'empty_spam.png', null, null, "return window.confirm('" . addslashes(_("Are you sure you wish to empty your spam folder?")) . "');", '__noselection');
        }

        $menu->add(IMP::composeLink(array('mailbox' => $GLOBALS['imp_mbox']['mailbox'])), _("_New Message"), 'compose.png');

        if ($conf['user']['allow_folders']) {
            $menu->add(Util::nocacheUrl(Horde::applicationUrl('folders.php')), _("_Folders"), 'folders/folder.png');
        }
        $menu->add($menu_search_url, _("_Search"), 'search.png', $registry->getImageDir('horde'));
        if (($_SESSION['imp']['base_protocol'] != 'pop3') && $prefs->getValue('fetchmail_menu')) {
            if ($prefs->getValue('fetchmail_popup')) {
                $menu->add(Horde::applicationUrl('fetchmail.php'), _("F_etch Mail"), 'fetchmail.png', null, 'fetchmail', 'window.open(this.href, \'fetchmail\', \'toolbar=no,location=no,status=yes,scrollbars=yes,resizable=yes,width=300,height=450,left=100,top=100\'); return false;');
            } else {
                $menu->add(Horde::applicationUrl('fetchmail.php'), _("F_etch Mail"), 'fetchmail.png');
            }
        }
        if ($prefs->getValue('filter_menuitem')) {
            $menu->add(Horde::applicationUrl('filterprefs.php'), _("Fi_lters"), 'filters.png');
        }

        /* Logout. If IMP can auto login or IMP is providing authentication,
         * then we only show the logout link if the sidebar isn't shown or if
         * the configuration says to always show the current user a logout
         * link. */
        $impAuth = Auth::getProvider() == 'imp';
        $impAutoLogin = IMP::canAutoLogin();
        if (!($impAuth || $impAutoLogin) ||
            !$prefs->getValue('show_sidebar') ||
            Horde::showService('logout')) {

            /* If IMP provides authentication and the sidebar isn't always on,
             * target the main frame for logout to hide the sidebar while
             * logged out. */
            $logout_target = null;
            if ($impAuth || $impAutoLogin) {
                $logout_target = '_parent';
            }

            /* If IMP doesn't provide Horde authentication then we need to use
             * IMP's logout screen since logging out should *not* end a Horde
             * session. */
            $logout_url = IMP::getLogoutUrl();

            $id = $menu->add($logout_url, _("_Log out"), 'logout.png', $registry->getImageDir('horde'), $logout_target);
            $menu->setPosition($id, HORDE_MENU_POS_LAST);
        }

        if ($returnType == 'object') {
            return $menu;
        } else {
            return $menu->render();
        }
    }

    /**
     * Outputs IMP's menu to the current output stream.
     *
     * @since IMP 4.2
     */
    function menu()
    {
        require_once IMP_BASE . '/lib/Template.php';
        $t = new IMP_Template();
        $t->set('forminput', Util::formInput());
        $t->set('webkit', $GLOBALS['browser']->isBrowser('konqueror'));
        $t->set('use_folders', ($_SESSION['imp']['base_protocol'] != 'pop3') &&
                               $GLOBALS['conf']['user']['allow_folders'], true);
        if ($t->get('use_folders')) {
            $t->set('accesskey', $GLOBALS['prefs']->getValue('widget_accesskey') ? Horde::getAccessKey(_("Open Fo_lder")) : '', true);
            $t->set('flist', IMP::flistSelect('', true, array(), $GLOBALS['imp_mbox']['mailbox'], false, false, true));

            $menu_view = $GLOBALS['prefs']->getValue('menu_view');
            $link = Horde::link('#', '', '', '', 'folderSubmit(true); return false;');
            $t->set('flink', sprintf('<ul><li class="rightFloat">%s%s<br />%s</a></li></ul>', $link, ($menu_view != 'text') ? Horde::img('folders/folder_open.png', _("Open Folder"), ($menu_view == 'icon') ? array('title' => _("Open Folder")) : array()) : '', ($menu_view != 'icon') ? Horde::highlightAccessKey(_("Open Fo_lder"), $t->get('accesskey')) : ''));
        }
        $t->set('menu_string', IMP::getMenu('string'));

        echo $t->fetch(IMP_TEMPLATES . '/menu.html');
    }

    /**
     * Outputs IMP's status/notification bar.
     */
    function status()
    {
        global $notification;

        $imp_imap = &IMP_IMAP::singleton();

        if ($imp_imap->stream()) {
            $alerts = imap_alerts();
            if (is_array($alerts)) {
                $alerts = str_replace('[ALERT] ', '', $alerts);
                foreach ($alerts as $alert) {
                    $notification->push($alert, 'horde.warning');
                }
            }
        }

        /* BC check. */
        if (class_exists('Notification_Listener_audio')) {
            $notification->notify(array('listeners' => array('status', 'audio')));
        }
    }

    /**
     * Outputs IMP's quota information.
     * Modified by Mauricio Jose T. Tecles <mtecles@biof.ufrj.br>
     * Updated 2010 February 28
     */
    function quota()
    {
        $quotadata = IMP::quotaData(true);
        if (!empty($quotadata)) {
            require_once IMP_BASE . '/lib/Template.php';
            if (!empty($quotadata['messagehome'])) {
                $t = new IMP_Template();
                $t->set('class', $quotadata['classhome']);
                $t->set('message', $quotadata['messagehome']);
                echo $t->fetch(IMP_TEMPLATES . '/quota/quota.html');
            }
            $t = new IMP_Template();
            $t->set('class', $quotadata['classvar']);
            $t->set('message', $quotadata['messagevar']);
            echo $t->fetch(IMP_TEMPLATES . '/quota/quota.html');
        }
    }

    /**
     * Returns data needed to output quota.
     *
     * @since IMP 4.2
     *
     * @param boolean $long  Output long messages?
     *
     * @return array  Array with these keys: class, message, percent.
     * 
     * Modified by Mauricio Jose T. Tecles <mtecles@biof.ufrj.br>
     * Updated 2010 February 28
     */
    function quotaData($long = true)
    {
        if (!isset($_SESSION['imp']['quota']) ||
            !is_array($_SESSION['imp']['quota'])) {
            return false;
        }

        require_once IMP_BASE . '/lib/Quota.php';
        $quotaDriver = &IMP_Quota::singleton($_SESSION['imp']['quota']['driver'], $_SESSION['imp']['quota']['params']);
        if ($quotaDriver === false) {
            return false;
        }

        $quota = $quotaDriver->getQuota();
        if (is_a($quota, 'PEAR_Error')) {
            Horde::logMessage($quota, __FILE__, __LINE__, PEAR_LOG_ERR);
            return false;
        }

        $strings = $quotaDriver->getMessages();
        $ret = array('percent' => 0);

        /* Quota for dev_fldrs */

        unset($ret['messagehome']);
        if ($quota['soflimithome'] > 0) {
            $quota['usagehome'] = $quota['usagehome'] / (1024 * 1024.0);
            $quota['soflimithome'] = $quota['soflimithome'] / (1024 * 1024.0);
            $ret['percent'] = ($quota['usagehome'] * 100) / $quota['soflimithome'];

            if ($ret['percent'] >= 100) {
                $ret['classhome'] = 'quotaalert';
                if (ereg("none", $quota['gracehome'])) {
                    if ($long) {
                        $ret['messagehome'] = sprintf($strings['longhlee'], $quota['usagehome'], $quota['soflimithome'], $ret['percent']);
                    } else {
                        $ret['messagehome'] = sprintf($strings['shorthlee'], $ret['percent'], $quota['soflimithome']);
                    }
                } else {
                    if ($long) {
                        $ret['messagehome'] = sprintf($strings['longhle'], $quota['usagehome'], $quota['soflimithome'], $ret['percent'], $quota['gracehome']);
                    } else {
                        $ret['messagehome'] = sprintf($strings['shorthle'], $ret['percent'], $quota['soflimithome'], $quota['gracehome']);
                    }
                }
            } elseif ($ret['percent'] >= 90) {
                $ret['classhome'] = 'quotaalert';
                if ($long) {
                    $ret['messagehome'] = sprintf($strings['longhaf'], $quota['usagehome'], $quota['soflimithome'], $ret['percent']);
                } else {
                    $ret['messagehome'] = sprintf($strings['shorthaf'], $ret['percent'], $quota['soflimithome']);
                }
            } elseif ($ret['percent'] >= 75) {
                $ret['classhome'] = 'quotawarn';
                if ($long) {
                    $ret['messagehome'] = sprintf($strings['longh'], $quota['usagehome'], $quota['soflimithome'], $ret['percent']);
                } else {
                    $ret['messagehome'] = sprintf($strings['shorth'], $ret['percent'], $quota['soflimithome']);
                }
            } else {
                $ret['classhome'] = 'control';
                if ($long) {
                    $ret['messagehome'] = sprintf($strings['longh'], $quota['usagehome'], $quota['soflimithome'], $ret['percent']);
                } else {
                    $ret['messagehome'] = sprintf($strings['shorth'], $ret['percent'], $quota['soflimithome']);
                }
            }
        } else {
            // Hide unlimited quota message?
            if (!empty($_SESSION['imp']['quota']['params']['hide_quota_when_unlimited'])) {
                return false;
            }

            $ret['classhome'] = 'control';
            if ($quota['usagehome'] > 0) {
                $quota['usagehome'] = $quota['usagehome'] / (1024 * 1024.0);
                if ($long) {
                    $ret['messagehome'] = sprintf($strings['nolimit_longh'], $quota['usagehome']);
                } else {
                    $ret['messagehome'] = sprintf($strings['nolimit_shorth'], $quota['usagehome']);
                }
            } elseif ($quota['usagehome'] == 0) {
                $ret['messagehome'] = sprintf(_("Folders: NO LIMIT"));
            }
        }

        /* Quota for dev_inbx */

        if ($quota['soflimitvar'] != 0) {
            $quota['usagevar'] = $quota['usagevar'] / (1024 * 1024.0);
            $quota['soflimitvar'] = $quota['soflimitvar'] / (1024 * 1024.0);
            $ret['percent'] = ($quota['usagevar'] * 100) / $quota['soflimitvar'];

            if ($ret['percent'] >= 100) {
                $ret['classvar'] = 'quotaalert';
                if (ereg("none", $quota['gracevar'])) {
                    if ($long) {
                        $ret['messagevar'] = sprintf($strings['longilee'], $quota['usagevar'], $quota['soflimitvar'], $ret['percent']);
                    } else {
                        $ret['messagevar'] = sprintf($strings['shortilee'], $ret['percent'], $quota['soflimitvar']);
                    }
                } else {
                    if ($long) {
                        $ret['messagevar'] = sprintf($strings['longile'], $quota['usagevar'], $quota['soflimitvar'], $ret['percent'], $quota['gracevar']);
                    } else {
                        $ret['messagevar'] = sprintf($strings['shortile'], $ret['percent'], $quota['soflimitvar'], $quota['gracevar']);
                    }
                }
            } elseif ($ret['percent'] >= 90) {
                $ret['classvar'] = 'quotaalert';
                if ($long) {
                    $ret['messagevar'] = sprintf($strings['longiaf'], $quota['usagevar'