<?php

	/**
	 * Generalized Socket class.
	 *
	 */
	class kSocket extends kBase {
	
	    /**
	     * Socket file pointer.
	     * @var resource $fp
	     */
	    var $fp = null;
	
	    /**
	     * Whether the socket is blocking. Defaults to true.
	     * @var boolean $blocking
	     */
	    var $blocking = true;
	
	    /**
	     * Whether the socket is persistent. Defaults to false.
	     * @var boolean $persistent
	     */
	    var $persistent = false;
	
	    /**
	     * The IP address to connect to.
	     * @var string $addr
	     */
	    var $addr = '';
	
	    /**
	     * The port number to connect to.
	     * @var integer $port
	     */
	    var $port = 0;
	
	    /**
	     * Number of seconds to wait on socket connections before assuming
	     * there's no more data. Defaults to no timeout.
	     * @var integer $timeout
	     */
	    var $timeout = false;
	
	    /**
	     * Number of bytes to read at a time in readLine() and
	     * readAll(). Defaults to 2048.
	     * @var integer $lineLength
	     */
	    var $lineLength = 2048;
	
	    /**
	     * Connect to the specified port. If called when the socket is
	     * already connected, it disconnects and connects again.
	     *
	     * @param string  $addr        IP address or host name.
	     * @param integer $port        TCP port number.
	     * @param boolean $persistent  (optional) Whether the connection is
	     *                             persistent (kept open between requests
	     *                             by the web server).
	     * @param integer $timeout     (optional) How long to wait for data.
	     *
	     * @access public
	     *
	     * @return boolean | PEAR_Error  True on success or a PEAR_Error on failure.
	     */
	    function connect($addr, $port = 0, $persistent = null, $timeout = null)
	    {
	        if (is_resource($this->fp)) {
	            @fclose($this->fp);
	            $this->fp = null;
	        }
	
	        // convert hostname to ip address
	        if (!$addr) {
	            return $this->raiseError('host address cannot be empty');
	        } elseif (strspn($addr, '.0123456789') == strlen($addr) || strstr($addr, '/') !== false) {
	            $this->addr = $addr;
	        } else {
	            $this->addr = @gethostbyname($addr);
	        }
	
	        $this->port = $port % 65536;
	
	        if ($persistent !== null) {
	            $this->persistent = $persistent;
	        }
	
	        if ($timeout !== null) {
	            $this->timeout = $timeout;
	        }
	
	        $openfunc = $this->persistent ? 'pfsockopen' : 'fsockopen';
	        $errno = 0;
	        $errstr = '';
	        
	        if ($this->timeout) {
	            $fp = @$openfunc($this->addr, $this->port, $errno, $errstr, $this->timeout);
	        } else {
	            $fp = @$openfunc($this->addr, $this->port, $errno, $errstr);
	        }
	
	        if (!$fp) {
	            return $this->raiseError($errstr, Array($errno));
	        }
	
	        $this->fp = $fp;
	
	        return $this->setBlocking($this->blocking);
	    }
	
	    /**
	     * Disconnects from the peer, closes the socket.
	     *
	     * @access public
	     * @return mixed true on success or an error object otherwise
	     */
	    function disconnect()
	    {
	        if (!is_resource($this->fp)) {
	            return $this->raiseError('not connected');
	        }
	
	        @fclose($this->fp);
	        $this->fp = null;
	        return true;
	    }
	
	    /**
	     * Find out if the socket is in blocking mode.
	     *
	     * @access public
	     * @return boolean  The current blocking mode.
	     */
	    function isBlocking()
	    {
	        return $this->blocking;
	    }
	
	    /**
	     * Sets whether the socket connection should be blocking or
	     * not. A read call to a non-blocking socket will return immediately
	     * if there is no data available, whereas it will block until there
	     * is data for blocking sockets.
	     *
	     * @param boolean $mode  True for blocking sockets, false for nonblocking.
	     * @access public
	     * @return mixed true on success or an error object otherwise
	     */
	    function setBlocking($mode)
	    {
	        if (!is_resource($this->fp)) {
	            return $this->raiseError('not connected');
	        }
	
	        $this->blocking = $mode;
	        socket_set_blocking($this->fp, $this->blocking);
	        return true;
	    }
	
	    /**
	     * Sets the timeout value on socket descriptor,
	     * expressed in the sum of seconds and microseconds
	     *
	     * @param integer $seconds  Seconds.
	     * @param integer $microseconds  Microseconds.
	     * @access public
	     * @return mixed true on success or an error object otherwise
	     */
	    function setTimeout($seconds, $microseconds)
	    {
	        if (!is_resource($this->fp)) {
	            return $this->raiseError('not connected');
	        }
	
	        return socket_set_timeout($this->fp, $seconds, $microseconds);
	    }
	
	    /**
	     * Returns information about an existing socket resource.
	     * Currently returns four entries in the result array:
	     *
	     * <p>
	     * timed_out (bool) - The socket timed out waiting for data<br>
	     * blocked (bool) - The socket was blocked<br>
	     * eof (bool) - Indicates EOF event<br>
	     * unread_bytes (int) - Number of bytes left in the socket buffer<br>
	     * </p>
	     *
	     * @access public
	     * @return mixed Array containing information about existing socket resource or an error object otherwise
	     */
	    function getStatus()
	    {
	        if (!is_resource($this->fp)) {
	            return $this->raiseError('not connected');
	        }
	
	        return socket_get_status($this->fp);
	    }
	
	    /**
	     * Get a specified line of data
	     *
	     * @access public
	     * @return $size bytes of data from the socket, or a PEAR_Error if
	     *         not connected.
	     */
	    function gets($size)
	    {
	        if (!is_resource($this->fp)) {
	            return $this->raiseError('not connected');
	        }
	
	        return @fgets($this->fp, $size);
	    }
	
	    /**
	     * Read a specified amount of data. This is guaranteed to return,
	     * and has the added benefit of getting everything in one fread()
	     * chunk; if you know the size of the data you're getting
	     * beforehand, this is definitely the way to go.
	     *
	     * @param integer $size  The number of bytes to read from the socket.
	     * @access public
	     * @return $size bytes of data from the socket, or a PEAR_Error if
	     *         not connected.
	     */
	    function read($size)
	    {
	        if (!is_resource($this->fp)) {
	            return $this->raiseError('not connected');
	        }
	
	        return @fread($this->fp, $size);
	    }
	
	    /**
	     * Write a specified amount of data.
	     *
	     * @param string  $data       Data to write.
	     * @param integer $blocksize  Amount of data to write at once.
	     *                            NULL means all at once.
	     *
	     * @access public
	     * @return mixed true on success or an error object otherwise
	     */
	    function write($data, $blocksize = null)
	    {
	        if (!is_resource($this->fp)) {
	            return $this->raiseError('not connected');
	        }
	
	        if (is_null($blocksize) && !OS_WINDOWS) {
	            return fwrite($this->fp, $data);
	        } else {
	            if (is_null($blocksize)) {
	                $blocksize = 1024;
	            }
	
	            $pos = 0;
	            $size = strlen($data);
	            while ($pos < $size) {
	                $written = @fwrite($this->fp, substr($data, $pos, $blocksize));
	                if ($written === false) {
	                    return false;
	                }
	                $pos += $written;
	            }
	
	            return $pos;
	        }
	    }
	
	    /**
	     * Write a line of data to the socket, followed by a trailing "\r\n".
	     *
	     * @access public
	     * @return mixed fputs result, or an error
	     */
	    function writeLine($data)
	    {
	        if (!is_resource($this->fp)) {
	            return $this->raiseError('not connected');
	        }
	
	        return fwrite($this->fp, $data . "\r\n");
	    }
	
	    /**
	     * Tests for end-of-file on a socket descriptor.
	     *
	     * @access public
	     * @return bool
	     */
	    function eof()
	    {
	        return (is_resource($this->fp) && feof($this->fp));
	    }
	   
	    /**
	     * Read until either the end of the socket or a newline, whichever
	     * comes first. Strips the trailing newline from the returned data.
	     *
	     * @access public
	     * @return All available data up to a newline, without that
	     *         newline, or until the end of the socket, or a PEAR_Error if
	     *         not connected.
	     */
	    function readLine()
	    {
	        if (!is_resource($this->fp)) {
	            return $this->raiseError('not connected');
	        }
	
	        $line = '';
	        $timeout = time() + $this->timeout;
	        while (!feof($this->fp) && (!$this->timeout || time() < $timeout)) {
	            $line .= @fgets($this->fp, $this->lineLength);
	            if (substr($line, -1) == "\n") {
	                return rtrim($line, "\r\n");
	            }
	        }
	        return $line;
	    }
	
	    /**
	     * Read until the socket closes, or until there is no more data in
	     * the inner PHP buffer. If the inner buffer is empty, in blocking
	     * mode we wait for at least 1 byte of data. Therefore, in
	     * blocking mode, if there is no data at all to be read, this
	     * function will never exit (unless the socket is closed on the
	     * remote end).
	     *
	     * @access public
	     *
	     * @return string  All data until the socket closes, or a PEAR_Error if
	     *                 not connected.
	     */
	    function readAll()
	    {
	        if (!is_resource($this->fp)) {
	            return $this->raiseError('not connected');
	        }
	
	        $data = '';
	        while (!feof($this->fp)) {
	            $data .= @fread($this->fp, $this->lineLength);
	        }
	        return $data;
	    }
	    
	    function raiseError($text, $params = Array())
	    {
	    	trigger_error(vsprintf($text, $params), E_USER_WARNING);
	    	return false;
	    }
	}
	
?>