<?php
/**
 * Chora Base Class.
 *
 * $Horde: chora/lib/Chora.php,v 1.72.6.7 2007/02/23 03:58:53 selsky Exp $
 *
 * @author  Anil Madhavapeddy <avsm@horde.org>
 * @package Chora
 */
class Chora {

    /**
     * Return a text description of how long its been since the file
     * has been last modified.
     *
     * @param integer $date  Number of seconds since epoch we wish to display.
     * @param boolean $long  If true, display a more verbose date.
     *
     * @return string  The human-readable date.
     */
    function readableTime($date, $long = false)
    {
        static $time, $desc, $breaks;

        /* Initialize popular variables. */
        if (is_null($time)) {
            $time = time();
            $desc = array(1 => array(_("second"), _("seconds")),
                          60 => array(_("minute"), _("minutes")),
                          3600 => array(_("hour"), _("hours")),
                          86400 => array(_("day"), _("days")),
                          604800 => array(_("week"), _("weeks")),
                          2628000 => array(_("month"), _("months")),
                          31536000 => array(_("year"), _("years")));
            $breaks = array_keys($desc);
        }

        $i = count($breaks);
        $secs = $time - $date;

        if ($secs < 2) {
            return _("very little time");
        }

        while (--$i && $i && $breaks[$i] * 2 > $secs);

        $break = $breaks[$i];

        $val = (int)($secs / $break);
        $retval = $val . ' ' . ($val > 1 ? $desc[$break][1] : $desc[$break][0]);
        if ($long && $i > 0) {
            $rest = $secs % $break;
            $break = $breaks[--$i];
            $rest = (int)($rest / $break);
            if ($rest > 0) {
                $resttime = $rest . ' ' . ($rest > 1 ? $desc[$break][1] : $desc[$break][0]);
                $retval .= ', ' . $resttime;
            }
        }

        return $retval;
    }

    /**
     * Initialize global variables and objects.
     */
    function init()
    {
        global $acts, $defaultActs, $conf, $where, $atdir,
            $fullname, $prefs, $sourceroots, $sourceroot, $scriptName;

        /**
         * Variables we wish to propagate across web pages
         *  sbt = Sort By Type (name, age, author, etc)
         *  ha  = Hide Attic Files
         *  ord = Sort order
         *
         * Obviously, defaults go into $defaultActs :)
         * TODO: defaults of 1 will not get propagated correctly - avsm
         * XXX: Rewrite this propagation code, since it sucks - avsm
         */
        $defaultActs = array('sbt' => constant($conf['options']['defaultsort']),
                             'sa'  => 0,
                             'ord' => VC_SORT_ASCENDING,
                             'ws'  => 1);

        /* Use the last sourceroot used as the default value if the user
         * has that preference. */
        $remember_last_file = $prefs->getValue('remember_last_file');
        if ($remember_last_file) {
            $last_file = $prefs->getValue('last_file') ? $prefs->getValue('last_file') : null;
            $last_sourceroot = $prefs->getValue('last_sourceroot') ? $prefs->getValue('last_sourceroot') : null;
        }

        if ($remember_last_file && !empty($last_sourceroot) &&
            is_array(@$sourceroots[$last_sourceroot])) {
            $defaultActs['rt'] = $last_sourceroot;
        } else {
            foreach ($sourceroots as $key => $val) {
                if (isset($val['default']) || !isset($defaultActs['rt'])) {
                    $defaultActs['rt'] = $key;
                }
            }
        }

        /* See if any have been passed as GET variables, and if so,
         * assign them into the acts array. */
        $acts = array();
        foreach ($defaultActs as $key => $default) {
            $acts[$key] = Util::getFormData($key, $default);
        }

        if (!isset($sourceroots[$acts['rt']])) {
            Chora::fatal('Malformed URL');
        }

        $sourcerootopts = $sourceroots[$acts['rt']];
        $sourceroot = $acts['rt'];

        $conf['paths']['temp'] = Horde::getTempDir();
        $GLOBALS['VC'] = &VC::factory($sourcerootopts['type'],
                                      array('sourceroot' => $sourcerootopts['location'],
                                            'paths' => $conf['paths']));

        $conf['paths']['sourceroot'] = $sourcerootopts['location'];
        $conf['paths']['cvsusers'] = $sourcerootopts['location'] . '/' . @$sourcerootopts['cvsusers'];
        $conf['paths']['introText'] = CHORA_BASE . '/config/' . @$sourcerootopts['intro'];
        $conf['options']['introTitle'] = @$sourcerootopts['title'];
        $conf['options']['sourceRootName'] = $sourcerootopts['name'];

        $where = Util::getFormData('f', '');

        /* Override $where with PATH_INFO if appropriate. */
        if (($conf['options']['urls'] == 'path_info' || $conf['options']['urls'] == 'rewrite') &&
            isset($_SERVER['PATH_INFO'])) {
            $where = $_SERVER['PATH_INFO'];
        }

        if ($where == '') {
            $where = '/';
        }

        /* Location relative to the SOURCEROOT. */
        $where = preg_replace('|^/|', '', $where);
        $where = preg_replace('|\.\.|', '', $where);

        /* Location of this script (e.g. /chora/browse.php). */
        $scriptName = preg_replace('|^/?|', '/', $_SERVER['PHP_SELF']);
        $scriptName = preg_replace('|/$|', '', $scriptName);

        /* Store last file/repository viewed, and set 'where' to
         * last_file if necessary. */
        if ($remember_last_file) {
            if (!isset($_SESSION['chora']['login'])) {
                $_SESSION['chora']['login'] = 0;
            }

            /* We store last_sourceroot and last_file only when we have
             * already displayed at least one page. */
            if (!empty($_SESSION['chora']['login'])) {
                $prefs->setValue('last_sourceroot', $acts['rt']);
                $prefs->setValue('last_file', $where);
            } else {
                /* We are displaying the first page. */
                if ($last_file && !$where) {
                    $where = $last_file;
                }
                $_SESSION['chora']['login'] = 1;
            }
        }

        $fullname = $sourcerootopts['location'] . (substr($sourcerootopts['location'], -1) == '/' ? '' : '/') . $where;
        if ($sourcerootopts['type'] == 'cvs') {
            $fullname = preg_replace('|/$|', '', $fullname);
            $atdir = @is_dir($fullname);
        } else {
            $atdir = !$where || (substr($where, -1) == '/');
        }
        $where = preg_replace('|/$|', '', $where);

        if ($sourcerootopts['type'] == 'cvs' && !@is_dir($sourcerootopts['location'])) {
            Chora::fatal(_("SourceRoot not found! This could be a misconfiguration by the server administrator, or the server could be having temporary problems. Please try again later."));
        }
    }

    function whereMenu()
    {
        global $where, $atdir;

        $bar = '';
        $wherePath = '';

        $dirs = explode('/', $where);
        $last = count($dirs) - 1;
        $i = 0;
        foreach ($dirs as $dir) {
            if (!$atdir && $i++ == $last) {
                $wherePath .= "/$dir";
            } else {
                $wherePath .= "/$dir/";
            }
            $wherePath = str_replace('//', '/', $wherePath);
            if (!empty($dir) && ($dir != 'Attic')) {
                $bar .= '/ <a href="' . Chora::url('', $wherePath) . '">'. Text::htmlallspaces($dir) . '</a> ';
            }
        }
        return $bar;
    }

    /**
     * Output an error page.
     *
     * @param string $msg  The verbose error message to be displayed.
     */
    function fatal($msg)
    {
        global $registry, $conf, $notification, $browser, $prefs;

        /* Don't store the bad file in the user's preferences. */
        $prefs->setValue('last_file', '');

        $notification->push($msg, 'horde.error');
        require CHORA_TEMPLATES . '/common-header.inc';
        require CHORA_TEMPLATES . '/menu.inc';
        require $registry->get('templates', 'horde') . '/common-footer.inc';
        exit;
    }

    /**
     * Given a return object from a VC:: call, make sure
     * that it's not a PEAR_Error object.
     *
     * @param mixed $e  Return object from a VC:: call.
     */
    function checkError($e)
    {
        if (is_a($e, 'PEAR_Error')) {
            Chora::fatal($e->getMessage());
        }
    }

    /**
     * Return an array with the names of any of the variables we need
     * to keep, that are different from the defaults.
     *
     * @return array  Names/vals of differing variables.
     */
    function differingVars()
    {
        global $acts, $defaultActs;
        reset($acts);
        $ret = array();
        foreach ($acts as $key => $val) {
            if ($val != $defaultActs[$key]) {
                $ret[$key] = $val;
            }
        }
        return $ret;
    }

    /**
     * Generate a series of hidden input variables based on the GET
     * parameters which are different from the defaults.
     *
     * @param array $except  Array of exceptions to never output.
     *
     * @return string  A set of input tags with the different variables.
     */
    function generateHiddens($except = array())
    {
        global $acts;
        $toOut = Chora::differingVars();
        $ret = Util::formInput() . "\n";
        while (list($key, $val) = each($toOut)) {
            if (is_array($except) && !in_array($key, $except)) {
                $ret .= "<input type=\"hidden\" name=\"$key\" value=\"$val\" />\n";
            }
        }
        return $ret;
    }

    /**
     * Convert a commit-name into whatever the user wants.
     *
     * @param string $name  Account name.
     *
     * @return string  The transformed name.
     */
    function showAuthorName($name, $fullname = false)
    {
        global $VC;

        $users = $VC->getUsers($GLOBALS['conf']['paths']['cvsusers']);
        if (is_array($users) && isset($users[$name])) {
            return '<a href="mailto:' . $users[$name]['mail'] . '">' .
                ($fullname ? $users[$name]['name'] : $name) .
                '</a>' . ($fullname ? " <i>($name)</i>" : '');
        } else {
            return $name;
        }
    }

    /**
     * Generate a URL that links into Chora.
     *
     * @param string $script  Name of the Chora script to link into
     * @param string $uri     Any PATH_INFO portion that should be included
     * @param array  $args    Key/value pair of any GET parameters to append
     * @param string $anchor  Anchor entity name
     *
     * @return string  The URL, with session information if necessary.
     */
    function url($script = '', $uri = '', $args = array(), $anchor = '')
    {
        global $conf;

        $arglist = array_merge(Chora::differingVars(), $args);
        $script = $script ? $script . '.php' : '';

        if ($conf['options']['urls'] != 'get') {
            if ($conf['options']['urls'] == 'path_info' && empty($script)) {
                $script = 'browse.php';
            }

            if (substr($uri, 0, 1) != '/' && $script) {
                $script .= '/';
            }
            $script .= $uri;
            if (substr($script, 0, 1) == '/') {
                $script = substr($script, 1);
            }
        } else {
            $arglist['f'] = $uri;
            if (empty($script)) {
                $script = 'browse.php';
            }
        }

        $url = Horde::applicationUrl($script);
        $url = Util::addParameter($url, $arglist);

        if (!empty($anchor)) {
            $url .= "#$anchor";
        }

        return $url;
    }

    /**
     * Generate a list of repositories available from this
     * installation of Chora.
     *
     * @return string  XHTML code representing links to the repositories.
     */
    function repositories()
    {
        global $sourceroot, $sourceroots, $defaultActs;

        $arr = array();
        foreach ($sourceroots as $key => $val) {
            if ($sourceroot != $key) {
                $arr[] = '<b><a href="' . Chora::url('', '', array('rt' => $key)) . '">' .
                    $val['name'] . '</a></b>';
            }
        }

        if (count($arr)) {
            return _("Other Repositories") . ': ' . implode(' , ', $arr);
        } else {
            return '';
        }
    }

    /**
     * Pretty-print the checked out copy, using the
     * Horde::Mime::Viewer package.
     *
     * @param string $mime_type File extension of the checked out file
     * @param resource fp File pointer to the head of the checked out copy
     * @return object The MIME_Viewer object which can be rendered or
     *                false on failure
     */
    function &pretty($mime_type, $fp)
    {
        $lns = '';
        while ($ln = fread($fp, 8192)) {
            $lns .= $ln;
        }

        $mime = &new MIME_Part($mime_type, $lns);
        $viewer = &MIME_Viewer::factory($mime);
        return $viewer;
    }

    /**
     * Check if the given item is restricted from being shown.
     * @return boolean whether or not the item is allowed to be displayed
     **/
    function isRestricted($item)
    {
        global $conf, $sourceroots, $sourceroot;
        static $restricted;

        if (!isset($restricted)) {
            $restricted = array();
            if (isset($conf['restrictions']) && is_array($conf['restrictions'])) {
                $restricted = $conf['restrictions'];
            }

            foreach ($sourceroots as $key => $val) {
                if ($sourceroot == $key) {
                    if (isset($val['restrictions']) && is_array($val['restrictions'])) {
                        $restricted = array_merge($restricted, $val['restrictions']);
                        break;
                    }
                }
            }
        }

        if (!empty($restricted) && is_array($restricted) && count($restricted)) {
            for ($i = 0; $i < count($restricted); $i++) {
                if (preg_match('|' . str_replace('|', '\|', $restricted[$i]) . '|', $item)) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Build Chora's list of menu items.
     */
    function getMenu($returnType = 'object')
    {
        require_once 'Horde/Menu.php';

        $menu = &new Menu();
        $menu->add(Chora::url(), _("_Browse"), 'chora.png');

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

    function getFileViews()
    {
        global $where;

        $views = array();
        $current = $_SERVER['PHP_SELF'];
        if (!empty($_SERVER['PATH_INFO'])) {
            $current = str_replace($_SERVER['PATH_INFO'], '', $current);
        }
        $current = str_replace('.php', '', basename($current));

        $views[] = $current == 'browse' ? '<i class="widget">' . _("Logs") . '</i>' : Horde::widget(Chora::url('', $where), _("Logs"), 'widget', '', '', _("_Logs"));
        // Subversion supports patchsets natively.
        if (!empty($GLOBALS['conf']['paths']['cvsps']) || is_a($GLOBALS['VC'], 'VC_svn')) {
            $views[] = $current == 'patchsets' ? '<i class="widget">' . _("Patchsets") . '</i>' : Horde::widget(Chora::url('patchsets', $where), _("Patchsets"), 'widget', '', '', _("_Patchsets"));
        }
        if (!is_a($GLOBALS['VC'], 'VC_svn')) {
            $views[] = $current == 'history' ? '<i class="widget">' . _("Branches") . '</i>' : Horde::widget(Chora::url('history', $where), _("Branches"), 'widget', '', '', _("_Branches"));
        }
        if (!empty($GLOBALS['conf']['paths']['cvsgraph']) && !is_a($GLOBALS['VC'], 'VC_svn')) {
            $views[] = $current == 'cvsgraph' ? '<i class="widget">' . _("Graph") . '</i>' : Horde::widget(Chora::url('cvsgraph', $where), _("Graph"), 'widget', '', '', _("_Graph"));
        }
        $views[] = $current == 'stats' ? '<i class="widget">' . _("Statistics") . '</i>' : Horde::widget(Chora::url('stats', $where), _("Statistics"), 'widget', '', '', _("_Statistics"));

        return _("View:") . ' ' . implode(' | ', $views);
    }

    function formatLogMessage($log)
    {
        global $conf;

        require_once 'Horde/Text/Filter.php';

        $log = Text_Filter::filter($log, 'text2html', array('parselevel' => TEXT_HTML_MICRO, 'charset' => NLS::getCharset(), 'class' => ''));

        if (!empty($conf['tickets']['regexp']) && !empty($conf['tickets']['replacement'])) {
            $log = preg_replace($conf['tickets']['regexp'], $conf['tickets']['replacement'], $log);
        }

        return $log;
    }

}
