<?php
/**
 * $Horde: horde/lib/Links.php,v 1.18 2003/07/31 03:32:52 chuck Exp $
 *
 * Horde_links class
 *
 * Copyright 2003, Jeroen Huinink <j.huinink@wanadoo.nl>
 *
 * 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  Jeroen Huinink <j.huinink@wanadoo.nl>
 * @version $Revision: 1.18 $
 * @since   Horde 3.0
 * @package horde.links
 *
 * We refer to link_type as we mean the (string) link types name or
 * identifier. We refer to link_definition if we mean the (hash) with
 * link properties. In the _link_definitions hash the link_definitions
 * are identified by the link_types.
 */
class Horde_Links {

    /**
     * Does this backend provide persistent storage?
     *
     * @var boolean $_storage
     */
    var $_storage = false;

    /**
     * Name of the application from which the links are created.
     *
     * @access private
     *
     * @var string $_from_application
     */
    var $_from_application;

    /**
     * Hash with available link definitions
     *
     * @access private
     *
     * @var string $_link_definitions;
     */
    var $_link_definitions = array();

    /**
     * Array to hold retrieved links
     *
     * @access private
     *
     * @var string $_links;
     *
     */
    var $_links = array();

    /**
     * Constructor.
     *
     * @access public
     *
     * @param string $from_application   The 'provides' value for the
     * @param optional array $params     A hash containing any additional
     *                                   configuration or connection
     *                                   parameters.
     */
    function Horde_Links($from_application = false, $params = array())
    {
        $this->_params = $params;

        global $registry;
        if ($from_application) {
            $this->_from_application = $from_application;
            $link_definitions = $registry->getParam('links');
            if (isset($link_definitions) && count($link_definitions)) {
                foreach ($link_definitions as $link_type => $link_definition) {
                    $description = $link_definition['description'];
                    $this->_link_definitions[$this->_from_application . '/' . $link_type]['provider'] = $link_definition['provider'];
                    $this->_link_definitions[$this->_from_application . '/' . $link_type]['description'] = gettext($description);
                    $this->_link_definitions[$this->_from_application . '/' . $link_type]['type'] = $link_type;
                    $this->_link_definitions[$this->_from_application . '/' . $link_type]['readonly'] = (isset($link_definition['readonly']) && $link_definition['readonly']);
                    $this->_link_definitions[$this->_from_application . '/' . $link_type]['show'] = (isset($link_definition['show'])) ? $link_definition['show'] : true;
                }
            } else {
                $this->_link_definitions = array();
            }

            $provides = $registry->getParam('provides');
            foreach ($registry->listApps() as $app) {
                $link_definitions = $registry->getParam('links', $app);
                if (isset($link_definitions) && count($link_definitions)) {
                    foreach ($link_definitions as $link_type => $link_definition) {
                        if ($link_definition['provider'] == $provides) {
                            if (!empty($link_definition['reverse'])) {
                                $reverse_provider = $registry->getParam('provides', $app);
                                if (isset($link_definition['reverse']['description'])) {
                                    $reverse_description = $link_definition['reverse']['description'];
                                } else {
                                    $reverse_description = $link_definition['description'];
                                }
                                $this->_link_definitions[$reverse_provider . '/' . $link_type . '/reverse'] = array(
                                    'provider' => $reverse_provider,
                                    'description' => $reverse_description,
                                    'type' => $link_type,
                                    'readonly' => (isset($link_definition['reverse']['readonly']) && $link_definition['reverse']['readonly']), 
                                    'show' => (isset($link_definition['reverse']['show']) ? $link_definition['reverse']['show'] : true),
                                );
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * This function returns the list of link definitions. If the
     * driver doesn't provide persistent storage (the null/'none'
     * driver, for example) this should be empty.
     *
     * @access public
     *
     * @return array  The list of link definitions.
     */
    function listLinkTypes()
    {
        return $this->persistentStorage() ? $this->_link_definitions : array();
    }

    /**
     * This function returns a list of links for the specified
     * link_type and the specified from_parameters.
     *
     * @access public
     *
     * @param string $link_type
     * @param array  $from_parameters
     *
     * @return array  The list of links.
     */
    function listLinks($link_type, $from_parameters)
    {
        global $registry;
        $this->_getLinks($from_parameters);
        $key = serialize($from_parameters); 
        return isset($this->_links[$key][$link_type]) ?
            $this->_links[$key][$link_type] :
            array();
    }

    /**
     * This function provides the general (shared) user interface for
     * links.
     *
     * @access public
     *
     * @param array $link_parameters
     *
     * @return array  The list of links.
     */
    function viewLinks($link_parameters)
    {
        global $registry;

        foreach ($this->_link_definitions as $link_type => $link_definition) {
            $app = $this->_link_definitions[$link_type]['provider'];
            $show = $this->_link_definitions[$link_type]['show'];

            if ($show && ($registry->hasMethod($app . '/addLink') ||
                $registry->hasMethod($app . '/getLinkDescription'))) {
                $type = $this->_link_definitions[$link_type]['type'];
                $reverse = (substr($link_type, -8) == '/reverse') ? '/reverse' : '';
                require $registry->getParam('templates', 'horde') . '/links/header.inc';
                require $registry->getParam('templates', 'horde') . '/links/links.inc';
                require $registry->getParam('templates', 'horde') . '/links/footer.inc';
            }
        }
    }

    /**
     * This function provides (part of) the general (shared) user
     * interface for links.
     *
     * @access public
     *
     * @param string $link_type
     * @param array  $from_parameters
     * @param array  $to_parameters
     */
    function display($link_type, $from_parameters, $to_parameters)
    {
        global $registry;

        $app = $this->_link_definitions[$link_type]['provider'];

        if ($registry->hasMethod($app . '/getLinkDescription')) {
            $description = $registry->call($app . '/getLinkDescription', $to_parameters);
            if (is_a($description, 'PEAR_Error')) {
                Horde::logMessage($description, __FILE__, __LINE__, PEAR_LOG_ERR);
                printf(_("An error occurred following this link: %s"), $description->getMessage());
            } else {
                $url = $registry->link($app . '/followLink', $to_parameters);
                if (!is_a($url, 'PEAR_Error')) {
                    $description = '<a href="' . Horde::url($url) . '">' . $description . '</a>';
                }
                echo $description;
            }
        } else {
            echo _("No description found");
        }

        if ($registry->hasMethod('deleteLink', 'horde') && !$this->_link_definitions[$link_type]['readonly']) {
            $url = $registry->linkByPackage('horde', 'deleteLink',
                                            array('from_application' => $registry->getParam('provides'),
                                                  'from_parameters' => serialize($from_parameters),
                                                  'to_application' => $app,
                                                  'to_parameters' => serialize($to_parameters),
                                                  'link_type' => $this->_link_definitions[$link_type]['type'],
                                                  'url' => Horde::selfURL(true, false, true)));
            echo '&nbsp;[' . Horde::link(Horde::url($url), _("Delete this link"), 'widget') . _("Delete") . ']</a>';
        }
    }

    /**
     * Does the current backend provide persistent storage of links?
     *
     * @return boolean  True if it does, false if it does not.
     */
    function persistentStorage()
    {
        return $this->_storage;
    }

    /**
     * This function retrieves all the links to the current object.
     *
     * @param array $from_parameters  A hash containing the link's
     *                                source parameters.
     *
     * @access private
     */
    function _getLinks($from_parameters)
    {
    }

    /**
     * Attempts to return a concrete Horde_Links instance based on
     * $driver.
     *
     * @param string $from_appliction    A string identifying the source
     *                                   application ('provides')
     *
     * @param optional array $params     A hash containing any additional
     *                                   configuration or connection
     *                                   parameters a subclass might need.
     *
     * @access public
     *
     * @return object Horde_Links        The newly created concrete
     *                                   Horde_Links instance, or false
     *                                   on an error.
     */
    function &factory($driver, $from_application, $params = null)
    {
        if (is_array($driver)) {
            list($app, $driver) = $driver;
        }

        $driver = basename($driver);

        if (empty($driver) || strcmp($driver, 'none') == 0) {
            return new Horde_Links($from_application, $params);
        }

        if (is_null($params)) {
            $params = Horde::getDriverConfig('links',$driver);
        }

        if (!empty($app)) {
            require_once $GLOBALS['registry']->getParam('fileroot', $app) . '/lib/Links/' . $driver . '.php';
        } elseif (@file_exists(dirname(__FILE__) . '/Links/' . $driver . '.php')) {
            require_once dirname(__FILE__) . '/Links/' . $driver . '.php';
        } else {
            @include_once 'Horde/Links/' . $driver . '.php';
        }
        $class = 'Horde_Links_' . $driver;
        if (class_exists($class)) {
            return new $class($from_application, $params);
        } else {
            return PEAR::raiseError('Class definition of ' . $class . ' not found.');
        }
    }

    /**
     * Attempts to return a reference to a concrete Horde_Links
     * instance based on $driver. It will only create a new instance if no
     * Horde_Links instance with the same parameter currently
     * exists.
     *
     * This method must be invoked as: $var = &Horde_Links::singleton()
     *
     * @param string $from_application     The 'provides' value for the
     *                                     application requesting the links
     *                                     or an application name.
     *
     * @param optional mixed $driver       If no driver is specified we read
     *                                     the driver from the configuration.
     * TODO: is this something that we should do within the framework? I
     * haven't seen other optional $driver parameters
     *
     * @param optional array $params     A hash containing any additional
     *                                   configuration or connection
     *                                   parameters a subclass might need.
     *
     * @return object Horde_Links  The concrete Horde_Links
     *                                     reference or false on an error.
     *
     * @access public
     */
    function &singleton($from_application, $driver = '', $params = null)
    {
        global $conf, $registry;

        static $instances;
        if (empty($driver)) {
            $driver = $conf['links']['driver'];
        }

        if ($registry->getParam('provides', $from_application) != '') {
            $from_application = $registry->getParam('provides', $from_application);
        }
        if (!isset($instances)) {
            $instances = array();
        }

        if (is_null($params)) {
            $params = Horde::getDriverConfig('links', $driver);
        }

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

        return $instances[$signature];
    }

    /**
     * Adds a link. This is a stub (for the 'none' driver).
     * @access public
     *
     * @param string $from_appliction  A string identifying the source 
     *                                 application ('provides') 
     * @param array $from_parameters   A hash containing the link's source 
     *                                 parameters.
     * @param string $to_appliction    A string identifying the target 
     *                                 application ('provides')
     * @param array $to_parameters     A hash containing the link's target 
     *                                 parameters.
     * @param string $type             A string specifying the link type.
     *
     * @return mixed                   Result.
     */
    function addLink($from_application, $from_parameters, $to_application, $to_parameters, $type)
    {
        return false;
    }

    /**
     * Deletes a link. This is a stub (for the 'none' driver).
     * @access public
     *
     * @param string $from_appliction  A string identifying the source 
     *                                 application ('provides') 
     * @param array $from_parameters   A hash containing the link's source 
     *                                 parameters.
     * @param string $to_appliction    A string identifying the target 
     *                                 application ('provides')
     * @param array $to_parameters     A hash containing the link's target 
     *                                 parameters.
     * @param string $type             A string specifying the link type.
     *
     * @return mixed                   Result
     */
    function deleteLink($from_application, $from_parameters, $to_application, $to_parameters, $link_type)
    {
        return false;
    }

}
