<?php

require_once HORDE_BASE . '/lib/Category.php';

/**
 * The History:: class provides a method of tracking changes in Horde
 * objects using the Horde Categories backend.
 *
 * $Horde: horde/lib/History.php,v 1.1 2003/08/05 21:08:29 chuck Exp $
 *
 * Copyright 2003 Chuck Hagenbuch <chuck@horde.org>
 *
 * See the enclosed file COPYING for license information (LGPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * @author  Chuck Hagenbuch <chuck@horde.org>
 * @version $Revision: 1.1 $
 * @since   Horde 2.1
 * @package Horde_History
 */
class Horde_History {

    /**
     * Pointer to a category instance to manage the history.
     * @var object Category $_history
     */
    var $_history;

    /**
     * Constructor.
     */
    function History()
    {
        global $conf;

        if (!isset($conf['category']['driver'])) {
            Horde::fatal('You must configure a Category backend to use History.');
        }
        $driver = $conf['category']['driver'];
        $this->_history = &Category::singleton($driver,
                                               array_merge(Horde::getDriverConfig('category', $driver),
                                                           array('history' => 'horde.history')));
    }

    function log($itemGuid, $action, $comment = '', $who = null)
    {
        if (is_null($who)) {
            $who = Auth::getAuth();
        }

        if ($this->_history->exists($itemGuid)) {
            $item = &$this->_getHistory($itemGuid);
        } else {
            $item = &$this->_newHistory($itemGuid);
        }
    }

    /**
     * Return a CategoryObject_History object corresponding to the
     * named history, with the data retrieved appropriately.
     * @access private
     *
     * @param string $name  The name of the history to retrieve.
     */
    function &_getHistory($name)
    {
        /* cache of previous retrieved history objects */
        static $historyCache;

        if (!is_array($historyCache)) {
            $historyCache = array();
        }

        if (!isset($historyCache[$name])) {
            $historyCache[$name] = $this->_history->getCategory($name, 'CategoryObject_History');
            if (!is_a($historyCache[$name], 'PEAR_Error')) {
                $historyCache[$name]->setHistoryOb($this);
            }
        }

        return $historyCache[$name];
    }

    /**
     * Return a new history object.
     * @access private
     *
     * @param string $name The history's name.
     *
     * @return object CategoryObject_History A new history object.
     */
    function &_newHistory($name)
    {
        if (empty($name)) {
            return PEAR::raiseError(_("History names must be non-empty"));
        }
        $history = &new CategoryObject_History($name);
        $history->setHistoryOb($this);
        return $history;
    }

    /**
     * Add a history to the history system. The history must first be
     * created with History::newHistory() before this function is
     * called.
     *
     * @param object CategoryObject_History $history  The new history object.
     */
    function addHistory($history)
    {
        if (!is_a($history, 'CategoryObject_History')) {
            return PEAR::raiseError('Histories must be CategoryObject_History objects or extend that class.');
        }
        return $this->_history->addCategory($history);
    }

    /**
     * Store updated data of a history to the backend system.
     *
     * @param object CategoryObject_History $history  The history to update.
     */
    function updateHistory($history)
    {
        if (!is_a($history, 'CategoryObject_History')) {
            return PEAR::raiseError('Histories must be CategoryObject_History objects or extend that class.');
        }
        return $this->_history->updateCategoryData($history);
    }

    /**
     * Remove a history from the history system permanently.
     *
     * @param object CategoryObject_History $history  The history to remove.
     *
     * @param optional boolean force [default = false] Force to remove
     *                         every child
     */
    function removeHistory($history, $force = false)
    {
        if (!is_a($history, 'CategoryObject_History')) {
            return PEAR::raiseError('Histories must be CategoryObject_History objects or extend that class.');
        }

        return $this->_history->removeCategory($history, $force);
    }

    /**
     * Get a list of every history, in the format cid => historyname.
     *
     * @return array  CID => historyname hash.
     */
    function listHistories()
    {
        static $histories;

        if (is_null($histories)) {
            $histories = $this->_history->get(CATEGORY_FORMAT_FLAT, '-1', true);
            unset($histories['-1']);
        }

        return $histories;
    }

    /**
     * Attempts to return a reference to a concrete History instance.
     * It will only create a new instance if no History instance
     * currently exists.
     *
     * This method must be invoked as: $var = &History::singleton()
     *
     * @return object Horde_History  The concrete History reference, or false on an
     *                               error.
     */
    function &singleton()
    {
        static $history;

        if (!isset($history)) {
            $history = new History();
        }

        return $history;
    }

}

/**
 * Extension of the CategoryObject class for storing History information
 * in the Categories driver. If you want to store specialized History
 * information, you should extend this class instead of extending
 * CategoryObject directly.
 *
 * @author  Chuck Hagenbuch <chuck@horde.org>
 * @version $Revision: 1.1 $
 * @since   Horde 2.1
 * @package horde.history
 */
class CategoryObject_History extends CategoryObject {

    /** The History object which this history came from - needed for
        updating data in the backend to make changes stick, etc.
        @var object History $historyOb */
    var $_historyOb;

    /**
     * The CategoryObject_History constructor. Just makes sure to call
     * the parent constructor so that the history's name is set
     * properly.
     *
     * @param string $name The name of the history.
     */
    function CategoryObject_History($name)
    {
        parent::CategoryObject($name);
    }

    /**
     * Associates a History object with this history.
     *
     * @param object History $historyOb The History object.
     */
    function setHistoryOb(&$historyOb)
    {
        $this->_historyOb = &$historyOb;
    }

    /**
     * Save any changes to this object to the backend permanently.
     */
    function save()
    {
        $this->_historyOb->updateHistory($this);
    }

    /**
     * Map this object's attributes from the data array into a format
     * that we can store in the attributes storage backend.
     *
     * @return array  The attributes array.
     */
    function _toAttributes()
    {
        // Default to no attributes.
        $attributes = array();

        // Loop through all users, if any.
        if (isset($this->data['users']) && is_array($this->data['users']) && count($this->data['users'])) {
            foreach ($this->data['users'] as $user => $active) {
                $attributes[] = array('name' => 'user',
                                      'key' => $user,
                                      'value' => $active);
            }
        }
        $attributes[] = array('name' => 'email',
                              'key' => '',
                              'value' => $this->get('email'));

        return $attributes;
    }

    /**
     * Take in a list of attributes from the backend and map it to our
     * internal data array.
     *
     * @param array $attributes  The list of attributes from the
     *                           backend (attribute name, key, and value).
     */
    function _fromAttributes($attributes)
    {
        // Initialize data array.
        $this->data['users'] = array();

        foreach ($attributes as $attr) {
            if ($attr['name'] == 'user') {
                $this->data['users'][$attr['key']] = $attr['value'];
            } else {
                $this->data[$attr['name']] = $attr['value'];
            }
        }
    }

}
