<?php
/*
 * $Horde: imp/lib/Folder.php,v 1.53.2.16 2003/11/27 22:38:12 jan Exp $
 *
 * Copyright 2000-2003 Chuck Hagenbuch <chuck@horde.org>
 * Copyright 2000-2003 Jon Parise <jon@csh.rit.edu>
 *
 * See the enclosed file COPYING for license information (GPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
 */

/**
 * The IMP_Folder:: class provides a set of methods for dealing with
 * folders, accounting for subscription, errors, etc.
 *
 * @author  Chuck Hagenbuch <chuck@horde.org>
 * @author  Jon Parise <jon@csh.rit.edu>
 * @version $Revision: 1.53.2.16 $
 * @since   IMP 2.3
 * @package imp
 */
class IMP_Folder {

    /**
     * List folders.
     *
     * @param $stream      An open connection to a mail server.
     *
     * @param $server      A string representing the specification of
     *                     the server to connect to.
     *                     Ex: {mail.foo.com/pop3:110}.
     *
     * @param $prefix      What to restrict the folder list to. For
     *                     instance, if the IMAP server makes your
     *                     entire * home directory * available, but
     *                     you only want * the folders in ~/mail/,
     *                     then this should be * 'mail/'.
     *
     * @param $subscribed  (optional) Should we list only subscribed folders?
     *
     * @param $filter      (optional) An array containing mailboxes that should be
     *                     left out of the list.
     *
     * @param $hierarchies (optional) An array containing additional
     *                     hierarchies that are either outside prefix
     *                     or not advertised by * default (#shared/
     *                     and similar with UW) to include in the
     *                     list.
     *
     * @param $dotfiles    (optional) If false, don't include folders
     *                     beginning with '.'
     *
     * @param $namespace   (optional) Namespace prefix prepended on folder
     *                     names (eg. "INBOX." in cyrus).  We remove this
     *                     for cosmetic purposes.
     *
     * @return An array of folders, where each array alement is an
     *         associative array containing three values: 'val', with
     *         entire folder name after the server specification;
     *         'label', with the full-length folder name meant for
     *         display ($prefix has been stripped off, etc); and
     *         'abbrev', containing a shortened (26 characters max)
     *         label for display in situations where space is short.
     */
    function flist($stream, $server, $prefix, $subscribed = false, $filter = array(),
                   $hierarchies = array(), $dotfiles = true, $namespace = '')
    {
        // Keep around identical lists so that we don't hit the server
        // more that once in the same page for the same thing.
        static $list_cache;

        // Keep track of mailbox names that we have complained about
        // so as not to give the user identical error messages.
        static $error_cache;

        // only initialize the static caches if this is the first time
        // flist() has been called.
        if (!isset($list_cache)) $list_cache = array();
        if (!isset($error_cache)) $error_cache = array();

        // check parameters
        if (!is_array($filter)) $filter = array();
        if (!is_array($hierarchies)) $hierarchies = array();

        // make sure we're requesting all folders.
        $folders = $prefix . '*';

        // figure out which list command to use.
        if ($subscribed) {
            $listcmd = 'imap_listsubscribed';
        } else {
            $listcmd = 'imap_listmailbox';
        }

        // compute a value that will uniquely identify this list
        $signature = implode('|', array($listcmd, $folders));

        // either get the list from the cache, or go to the IMAP
        // server to get it if we haven't gotten it yet.
        if (isset($list_cache[$signature])) {
            $mailboxes = $list_cache[$signature];
        } else {
            $mailboxes = $listcmd($stream, $server, $folders);
            if (!$mailboxes) {
                $mailboxes = array();
            }

            foreach ($hierarchies as $hierarchy) {
                $hboxes = $listcmd($stream, $server, $hierarchy . '*');
                if (is_array($hboxes)) {
                    $mailboxes = array_merge($mailboxes, $hboxes);
                }
            }

            $list_cache[$signature] = $mailboxes;
        }

        $list = array();
        if (is_array($mailboxes) && (count($mailboxes) > 0)) {
            // these only need to be assigned once
            $junk = $server . $prefix;
            $delimiter = IMP_Folder::getDelimiter($stream);

            natcasesort($mailboxes);
            $seen = array();

            $parents = array();
            foreach ($mailboxes as $mailbox) {
                if (empty($seen[$mailbox])) {
                    $seen[$mailbox] = true;
                    if (!($decoded_mailbox = IMP::utf7Decode($mailbox))) {
                        if (empty($error_cache[$mailbox])) {
                            $user_box = str_replace($server, '', $mailbox);
                            if (substr($user_box, 0, strlen($prefix)) == $prefix) {
                                $user_box = substr($user_box, strlen($prefix));
                            }
                            if (substr($user_box, 0, strlen($namespace)) == $namespace) {
                                $user_box = substr($user_box, strlen($namespace));
                            }
                            Horde::raiseMessage(sprintf(_("The folder \"%s\" contains illegal characters in its name. It may cause problems. Please see your system administrator."), $mailbox), HORDE_WARNING);
                            $error_cache[$mailbox] = true;
                        }
                    }

                    // ignore INBOX, since we always tack it on first
                    if (strcmp($junk . 'INBOX', $mailbox) != 0) {
                        $value = str_replace($server, '', $mailbox);
                        if (substr($value, 0, strlen($prefix)) == $prefix) {
                            $label = substr($value, strlen($prefix));
                        } else {
                            $label = $value;
                        }
                        if (substr($label, 0, strlen($namespace)) == $namespace) {
                            $label = substr($label, strlen($namespace));
                        }
                        $stripped = str_replace($label, '', $value);
                        $parts = explode($delimiter, $label);
                        $subfolder = array_pop($parts);
                        $parent = implode($delimiter, $parts);
                        if (!empty($parent) &&
                            ($parent != 'INBOX') &&
                            !isset($list[$stripped . $parent]) &&
                            !isset($parents[$parent])) {
                            $parents[$parent] = 1;
                            $plabel = str_repeat(' ', 4 * (count($parts) - 1)) . IMP::utf7Decode($parts[count($parts) - 1]) . $delimiter;
                            if (strlen($plabel) > 26) {
                                $pabbrev = substr($plabel, 0, 10) . '...' . substr($plabel, -13, 13);
                            } else {
                                $pabbrev = $plabel;
                            }
                            $list[$parent] = array('val' => '', 'label' => $plabel, 'abbrev' => $pabbrev);
                        }
                        $folded = str_repeat(' ', 4 * count($parts)) . IMP::utf7Decode($subfolder);

                        if (strlen($folded) > 26) {
                            $abbrev = substr($folded, 0, 10) . '...' . substr($folded, -13, 13);
                        } else {
                            $abbrev = $folded;
                        }

                        if (($dotfiles || (strlen($label) > 0 && ($label[0] != '.' && !strstr($label, $delimiter . '.'))))
                            && !in_array($value, $filter)) {
                            $list[$value] = array('val' => $value, 'label' => $folded, 'abbrev' => $abbrev);
                        }
                    }
                }
            }
        }

        if (!in_array('INBOX', $filter)) {
            array_unshift($list, array('val' => 'INBOX', 'label' => 'INBOX', 'abbrev' => 'INBOX'));
        }

        return $list;
    }

    /**
     * List all folders which the user is not subscribed to.
     *
     * @param $stream      An open connection to a mail server.
     *
     * @param $server      A string representing the specification of
     *                     the server to connect to.
     *                     Ex: {mail.foo.com/pop3:110}.
     *
     * @param $prefix      What to restrict the folder list to. For
     *                     instance, if the IMAP server makes your
     *                     entire * home directory * available, but
     *                     you only want * the folders in ~/mail/,
     *                     then this should be * 'mail/'.
     *
     * @param $filter      (optional) An array containing mailboxes that should be
     *                     left out of the list.
     *
     * @param $hierarchies (optional) An array containing additional
     *                     hierarchies that are either outside prefix
     *                     or not advertised by * default (#shared/
     *                     and similar with UW) to include in the
     *                     list.
     *
     * @param $dotfiles    (optional) If false, do not include folders
     *                     beginning with '.'
     *
     * @param $namespace   (optional) Namespace prefix prepended on folder
     *                     names (eg. "INBOX." in cyrus).  We remove this
     *                     for cosmetic purposes.
     *
     * @return An array of folders, in the same format as IMP_Folder::flist().
     */
    function flistUnsubscribed($stream, $server, $prefix, $filter = false, $hierarchies = false, $dotfiles = true, $namespace = '')
    {
        $sub = IMP_Folder::flist($stream, $server, $prefix, true, $filter, $hierarchies, $dotfiles, $namespace);
        $all = IMP_Folder::flist($stream, $server, $prefix, false, $filter, $hierarchies, $dotfiles, $namespace);

        $usub = array();
        foreach ($all as $key => $val) {
            if (empty($sub[$key]))
                $usub[$key] = $all[$key];
        }

        return $usub;
    }

    /**
     * Delete one or more folders.
     *
     * @param resource $stream      a valid c-client stream
     * @param array $folder_array   an array of full utf encoded folder names
     *                              to be deleted
     * @param boolean $subscribe    a boolean describing whether or not to use
     *                              folder subscriptions
     *
     * @return boolean Whether or not the folders were successfully deleted.
     */
    function delete($stream, $folder_array, $subscribe)
    {
        global $prefs;

        $server = IMP::serverString();
        $return_value = true;
        if ($subscribe) {
            $subscribed_folders = imap_listsubscribed($stream, $server, '*');
        }

        $pollfolders = @unserialize($prefs->getValue('nav_poll'));
        foreach ($folder_array as $folder) {
            if (!imap_deletemailbox($stream, $server . $folder)) {
                Horde::raiseMessage(sprintf(_("The folder \"%s\" was not deleted. This is what the server said"), IMP::displayFolder($folder)) .
                                    ': ' . imap_last_error(), HORDE_ERROR);
                $return_value = false;
            } else {
                if ($subscribe && in_array($server . $folder, $subscribed_folders) &&
                      !imap_unsubscribe($stream, $server . $folder)) {
                    Horde::raiseMessage(sprintf(_("The folder \"%s\" was deleted but you were not unsubscribed from it."), IMP::displayFolder($folder)), HORDE_WARNING);
                    $return_value = false;
                } else {
                    Horde::raiseMessage(sprintf(_("The folder \"%s\" was successfully deleted."), IMP::displayFolder($folder)), HORDE_SUCCESS);
                }
                if (isset($pollfolders) && is_array($pollfolders)
                    && in_array($folder, $pollfolders)) {
                    unset($pollfolders[$folder]);
                }
            }
        }
        if (isset($pollfolders) && is_array($pollfolders)) {
            $prefs->setValue('nav_poll', serialize($pollfolders));
            $prefs->store();
        }

        return $return_value;
    }

    /**
     * Create a new IMAP folder if it does not already exist, and
     * subcribe to it as well if requested.
     *
     * @param resource $stream      a valid c-client stream
     * @param string $folder        the full utf encoded folder to be created
     * @param boolean $subscribe    a boolean describing whether or not to use
     *                              folder subscriptions
     *
     * @return boolean Whether or not the folder was successfully created.
     */
    function create($stream, $folder, $subscribe)
    {
        /* Make sure we are not trying to create a duplicate folder */
        if (IMP_Folder::exists($stream, $folder)) {
            Horde::raiseMessage(sprintf(_("The folder \"%s\" already exists"), IMP::displayFolder($folder)), HORDE_WARNING);
            return false;
        }

        /* Attempt to create the mailbox */
        if (!imap_createmailbox($stream, IMP::serverString() . $folder)) {
            Horde::raiseMessage(sprintf(_("The folder \"%s\" was not created. This is what the server said"), IMP::displayFolder($folder)) .
                            ': ' . imap_last_error(), HORDE_ERROR);
            return false;
        }

        /* If the user uses SUBSCRIBE, then add to the subscribe list */
        if ($subscribe && !imap_subscribe($stream, IMP::serverString() . $folder)) {
            Horde::raiseMessage(sprintf(_("The folder \"%s\" was created but you were not subscribed to it."), IMP::displayFolder($folder)), HORDE_WARNING);
            return false;
        }

        /* The folder creation has been successful */
        Horde::raiseMessage(sprintf(_("The folder \"%s\" was successfully created."), IMP::displayFolder($folder)), HORDE_SUCCESS);
        return true;
    }

    /**
     * Find out if a specific folder exists or not.
     *
     * @param resource $stream  a valid c-client stream
     * @param string $folder    the full utf encoded folder name to be checked.
     *
     * @return boolean Whether or not the folder exists
     */
    function exists($stream, $folder, $recheck = false)
    {
        static $results;
        if (!isset($results)) {
            $results = array();
        }

        if ($recheck || !isset($results[$folder])) {
            $path = IMP::serverString() . $folder;
            $res = imap_listmailbox($stream, '', $path);
            $results[$folder] = ($res ? in_array($path, $res) : false);
        }

        return $results[$folder];
    }

    /**
     * Rename an IMAP folder.
     *
     * @param resource $stream      a valid c-client stream
     * @param string $old           the old utf encoded folder name
     * @param string $new           the new utf encoded folder name
     * @param boolean $subscribe    a boolean saying whether or not to use
     *                              folder subscriptions
     *
     * @return boolean Whether or not the folder was successfully renamed.
     */
    function rename($stream, $old, $new, $subscribe)
    {
        $server = IMP::serverString();
        if ($subscribe) {
            $subscribed_folders = imap_listsubscribed($stream, $server, '*');
        }

        if (imap_renamemailbox($stream, $server . $old, $server . $new)) {
            if ($subscribe && in_array($server . $old, $subscribed_folders)) {
                imap_unsubscribe($stream, $server . $old);
                imap_subscribe($stream, $server . $new);
            }
            Horde::raiseMessage(sprintf(_("The folder \"%s\" was successfully renamed to \"%s\"."), IMP::displayFolder($old), IMP::displayFolder($new)), HORDE_SUCCESS);
            return true;
        } else {
            Horde::raiseMessage(sprintf(_("Renaming \"%s\" to \"%s\" failed. This is what the server said"), IMP::displayFolder($old), IMP::displayFolder($new)) .
                                ': ' . imap_last_error(), HORDE_ERROR);
            return false;
        }
    }

    /**
     * Subscribe to one or more IMAP folders.
     *
     * @param resource $stream      a valid c-client stream
     * @param array $folder_array   an array of full utf encoded folder names
     *                              to be subscribed
     *
     * @return boolean Whether or not the folders were successfully
     *                 subscribed to.
     */
    function subscribe($stream, $folder_array)
    {
        $return_value = true;

        if (is_array($folder_array)) {
            foreach ($folder_array as $folder) {
                if ($folder != ' ') {
                    if (!imap_subscribe($stream, IMP::serverString() . $folder)) {
                        Horde::raiseMessage(sprintf(_("You were not subscribed to \"%s\". Here is what the server said"), IMP::displayFolder($folder)) .
                                            ': ' . imap_last_error(), HORDE_ERROR);
                        $return_value = false;
                    } else {
                        Horde::raiseMessage(sprintf(_("You were successfully subscribed to \"%s\""), IMP::displayFolder($folder)), HORDE_SUCCESS);
                    }
                }
            }
        } else {
            Horde::raiseMessage(_("No folders were specified"), HORDE_MESSAGE);
            $return_value = false;
        }

        return $return_value;
    }

    /**
     * Unsubscribe from one or more IMAP folders.
     *
     * @param resource $stream      a valid c-client stream
     * @param array $folder_array   an array of full utf encoded folder names
     *                              to be unsubscribed
     *
     * @return boolean Whether or not the folders were successfully
     *                 unsubscribed from.
     */
    function unsubscribe($stream, $folder_array)
    {
        $return_value = true;

        if (is_array($folder_array)) {
            foreach ($folder_array as $folder) {
                if ($folder != ' ') {
                    if (!imap_unsubscribe($stream, IMP::serverString() . $folder)) {
                        Horde::raiseMessage(sprintf(_("You were not unsubscribed from \"%s\". Here is what the server said"), IMP::displayFolder($folder)) .
                                        ': ' . imap_last_error(), HORDE_ERROR);
                        $return_value = false;
                    } else {
                        Horde::raiseMessage(sprintf(_("You were successfully unsubscribed from \"%s\""), IMP::displayFolder($folder)), HORDE_SUCCESS);
                    }
                }
            }
        } else {
            Horde::raiseMessage(_("No folders were specified"), HORDE_MESSAGE);
            $return_value = false;
        }

        return $return_value;
    }

    /**
     * Generate a string that can be saved out to an mbox format
     * mailbox file for a folder or set of folders, optionally
     * including all subfolders of the selected folders as well. All
     * folders will be put into the same string.
     *
     * @param resource $stream      a valid c-client stream.
     * @param array $folder_list    an array of full utf encoded folder names
     *                              to generate an mbox file for.
     * @param boolean $recursive    (optional) Whether or not to include subfolders.
     *
     * @return string An mbox format mailbox file.
     * @author Didi Rieder <adrieder@sbox.tugraz.at>
     */
    function generateMbox($stream, $folder_list, $recursive = false)
    {
        $body = '';
        if (is_array($folder_list)) {
            foreach ($folder_list as $folder) {
                imap_reopen($stream, IMP::serverString() . $folder, OP_READONLY);
                $count = imap_num_msg($stream);
                for ($i = 1; $i <= $count; $i++) {
                    $h = imap_header($stream, $i);
                    $from = '<>';
                    if (isset($h->from[0])) {
                        if (isset($h->from[0]->mailbox) && isset($h->from[0]->host)) {
                            $from = $h->from[0]->mailbox . '@' . $h->from[0]->host;
                        }
                    }

                    /* We need this long command since some MUAs (e.g. pine)
                       require a space in front of single digit days. */
                    $date = sprintf('%s %2s %s', date('D M', $h->udate), date('j', $h->udate), date('H:i:s Y', $h->udate));
                    $body .= 'From ' . $from . ' ' . $date . "\n";
                    $body .= imap_fetchheader($stream, $i, FT_PREFETCHTEXT);
                    $body .= imap_body($stream, $i) . "\n";
                }
            }
        }

        $body = str_replace("\r\n", "\n", $body);
        return $body;
    }

    /**
     * Determine the hierarchy delimiter (generally '.' or '/') for
     * the current IMAP stream.
     *
     * @param resource $stream  A valid c-client stream.
     * @param string $default   (optional) The default delimiter in case we
     *                          can't determine one.
     *
     * @return string The hierarchy delimiter.
     */
    function getDelimiter($stream, $default = '/')
    {
        static $delimiter;

        if (!isset($delimiter)) {
            $box = imap_getmailboxes($stream, IMP::serverString(), 'INBOX');
            $delimiter = !empty($box[0]->delimiter) ? $box[0]->delimiter : $default;
        }

        return $delimiter;
    }

}
?>
