<?php
/**
 * $Horde: horde/lib/Links/sql.php,v 1.10 2003/07/19 18:13:26 chuck Exp $
 *
 * SQL backend implementation for Horde_Links.
 *
 * 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.
 * 
 * TODO: We should consider whether we want the links buffered, or whether 
 * we always want to read and write them directly to the database.
 *
 * @author  Jeroen Huinink <j.huinink@wanadoo.nl>
 * @version $Revision: 1.10 $
 * @since   Horde 3.0
 * @package horde.links
 */
class Horde_Links_sql extends Horde_Links {

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

    /**
     * Handle for the current database connection.
     *
     * @var object DB $_db
     */
    var $_db;

    /**
     * Boolean indicating whether or not we're connected to the SQL server.
     *
     * @var boolean $connected
     */
    var $_connected = false;

    /**
     * Adds a link to the database.
     *
     * @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                   Query statement result.
     */
    function addLink($from_application, $from_parameters, $to_application, $to_parameters, $type)
    {
        $this->_connect();

        $id = $this->_db->nextId($this->_params['table']);
        if (is_a($id, 'PEAR_Error')) {
            Horde::logMessage($id, __FILE__, __LINE__, PEAR_LOG_ERR);
            return $id;
        }

        $query = sprintf('INSERT INTO %s (link_id, link_type, link_from_provider, link_from_parameter, link_to_provider, link_to_parameter) VALUES( %s, %s, %s, %s, %s, %s)',
                         $this->_params['table'],
                         (int)$id,
                         $this->_db->quote($type),
                         $this->_db->quote($from_application),
                         $this->_db->quote(serialize($from_parameters)),
                         $this->_db->quote($to_application),
                         $this->_db->quote(serialize($to_parameters))
        );

        $result = $this->_db->query($query);
        if (is_a($result, 'PEAR_Error')) {
            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
            return $result;
        }

        return true;
    }

    /**
     * Deletes a link from the database.
     *
     * @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                   Query statement result.
     */
    function deleteLink($from_application, $from_parameters, $to_application, $to_parameters, $link_type)
    {
        $this->_connect();

        /* Treat '*' in $from_parameters and $to_parameters as
           wildcard so we can delete multiple links at once. */
        $from_param_txt = '';
        $to_param_txt = '';
        if ($from_parameters != '*') {
            $from_param_txt = ' AND link_from_parameter = ' . $this->_db->quote(serialize($from_parameters));
        }
        if ($to_parameters != '*') {
            $to_param_txt = ' AND link_to_parameter = ' . $this->_db->quote(serialize($to_parameters));
        }

        $query = sprintf('DELETE FROM %s WHERE link_type = %s AND link_from_provider = %s%s AND link_to_provider = %s%s',
                         $this->_params['table'],
                         $this->_db->quote($link_type),
                         $this->_db->quote($from_application),
                         $from_param_txt,
                         $this->_db->quote($to_application),
                         $to_param_txt
        );

        $result = $this->_db->query($query);
        if (is_a($result, 'PEAR_Error')) {
            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
            return $result;
        }
    }

    /**
     * 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) 
    {
        // TODO: We do not buffer the results at the moment. 
        // We might want to do that.
        global $registry;

        /* _connect() will die with Horde::fatal() upon failure. */
        $this->_connect();

        foreach ($this->_link_definitions as $link_type => $link_definition) {
            $this->_links[serialize($from_parameters)][$link_type] = array();
        }

        /* Build the SQL query. */
        $query = sprintf('SELECT link_type, link_from_provider, link_from_parameter, link_to_provider, link_to_parameter FROM %s WHERE (link_from_provider = %s AND link_from_parameter = %s) OR (link_to_provider = %s AND link_to_parameter = %s)',
                         $this->_params['table'],
                         $this->_db->quote($this->_from_application),
                         $this->_db->quote(serialize($from_parameters)),
                         $this->_db->quote($this->_from_application),
                         $this->_db->quote(serialize($from_parameters)));
        $result = $this->_db->getAll($query, null, DB_FETCHMODE_ORDERED);
        if (is_a($result, 'PEAR_Error')) {
            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
            return $result;
        }

        /* Loop through and build return array. */
        foreach ($result as $ar) {
            if (serialize($from_parameters) == $ar[2]) {
                if (isset($this->_link_definitions[$ar[1] . '/' . $ar[0]])) {
                    $this->_links[serialize($from_parameters)][$ar[1] . '/' . $ar[0]][] = array(
                        'to_parameters' => unserialize($ar[4])
                    );
                }
            }
            if (serialize($from_parameters) == $ar[4]) { 
                if (isset($this->_link_definitions[$ar[1] . '/' . $ar[0] . '/reverse'])) {
                    $this->_links[serialize($from_parameters)][$ar[1] . '/' . $ar[0] . '/reverse'][] = array(
                        'to_parameters' => unserialize($ar[2])
                    );
                }
            } 
        }
        return true;
    }

    /**
     * Attempts to open a persistent connection to the SQL server.
     *
     * @access private
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function _connect()
    {
        if (!$this->_connected) {
            if (!is_array($this->_params)) {
                Horde::fatal(PEAR::raiseError(_("No configuration information specified for SQL authentication.")), __FILE__, __LINE__);
            }
            if (!isset($this->_params['phptype'])) {
                Horde::fatal(PEAR::raiseError(_("Required 'phptype' not specified in authentication configuration.")), __FILE__, __LINE__);
            }
            if (!isset($this->_params['hostspec'])) {
                Horde::fatal(PEAR::raiseError(_("Required 'hostspec' not specified in authentication configuration.")), __FILE__, __LINE__);
            }
            if (!isset($this->_params['username'])) {
                Horde::fatal(PEAR::raiseError(_("Required 'username' not specified in authentication configuration.")), __FILE__, __LINE__);
            }
            if (!isset($this->_params['password'])) {
                Horde::fatal(PEAR::raiseError(_("Required 'password' not specified in authentication configuration.")), __FILE__, __LINE__);
            }
            if (!isset($this->_params['database'])) {
                Horde::fatal(PEAR::raiseError(_("Required 'database' not specified in authentication configuration.")), __FILE__, __LINE__);
            }
            if (!array_key_exists('table', $this->_params)) {
                $this->_params['table'] = 'horde_links';
            }

            /* Connect to the SQL server using the supplied parameters. */
            require_once 'DB.php';
            $this->_db = &DB::connect($this->_params,
                                      array('persistent' => !empty($this->_params['persistent'])));
            if (is_a($this->_db, 'PEAR_Error')) {
                Horde::fatal(PEAR::raiseError(_("Unable to connect to SQL server.")), __FILE__, __LINE__);
            }

            $this->_connected = true;
        }

        return true;
    }

    /**
     * Disconnect from the SQL server and clean up the connection.
     *
     * @access private
     *
     * @return boolean  True on success, false on failure.
     */
    function _disconnect()
    {
        if ($this->_connected) {
            $this->_connected = false;
            return $this->_db->disconnect();
        }

        return true;
    }

}
