Source

CitadelPHP / Citadel.php

<?php defined('CITADEL_PHP') || exit("[ERROR] No direct access!");
#### DO NOT CHANGE THE TOP LINE! ####

/***************************************************************
 * The MIT License (MIT)
 *
 * Copyright © 2014 Dennis T Kaplan http://www.robotamer.com
 * 
 * Permission is hereby granted, free of charge, to any person 
 * obtaining a copy of this software and associated documentation 
 * files (the "Software"), to deal in the Software without 
 * restriction, including without limitation the rights to use, 
 * copy, modify, merge, publish, distribute, sublicense, and/or 
 * sell copies of the Software, and to permit persons to whom the 
 * Software is furnished to do so, subject to the following 
 * conditions:
 * 
 * The above copyright notice and this permission notice shall be 
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 
 * OTHER DEALINGS IN THE SOFTWARE.
 * 
 ***************************************************************/

/***************************************************************
 * Citadel PHP Class
 * A library to access Citadel email and collaboration servers 
 * from php, using the Citadel Protocol.
 ***************************************************************/
class Citadel {

	const EOL = "\n";

	private $messanger = array();

	private $socket_path = NULL;
	private $socket_timeout = NULL;

	private $stream = FALSE;

	private $conn_status = array();


	/**
	 * Citadel Server Response Array
	 **/
	private $response = array(); 

	public static $response_keys = array('cmd', 'status_code', 'first_line', 'lines');

	public function __call($key, $args = array()) {
		if( in_array($key, self::$response_keys)){
			if( $this->response[$key] !== NULL ){
				return $this->response[$key]; 			
			}else{
				$this->Messanger(502);				
			}
		}else{
			$this->Messanger(501);
		}
	}

	public function Socket($path = NULL) {
		if($path !== NULL) {
			$this->socket_path = $path;		
		}
		return $this->socket_path;
	}

	public function Timeout($seconds = NULL) {
		if($seconds !== NULL) {
			$this->socket_timeout = $seconds;
		}
		return $this->socket_timeout;
	}

	public function Open() {
		$this->stream = fsockopen('unix://'.$this->socket(), 0, $errno, $errstr);
		if($this->stream) {
			if($this->socket_timeout !== NULL){
				stream_set_timeout($this->stream , $this->socket_timeout, 0);	
			}
			$this->read();
		}else{
			$this->Messanger($errno, $errstr);
		}
	}

	public function Close() { 
		$status = fclose($this->stream);
		if($status) {
			$this->stream = FALSE;
		}
		return $status;
	}

	/**
	 * @param developer ID int	(same as the server developer ID numbers in the INFO command – please obtain one if you are a new developer)
	 * @param client ID int	(which does not have to be globally unique - only unique within the domain of the developer number)
	 * @param client Version int	the Version of your Client
	 * @param Client Name string	afree-form text string describing the client
	 * @return bool
	 **/
	public function Identify($dev_id, $client_id, $client_version, $client_name) {
		$this->Send('IDEN '.$dev_id.'|'.$client_id.'|'.$client_version.'|'.$client_name.'|'.gethostname());
		if (self::status_code() != '200') {
			$this->Messanger(self::status_code());
			return FALSE;
		}
		return TRUE;
	}

	public function Login($user, $pass) {
		$keys = array('username','access','times_called', 'posted', 'flags', 'uid', 'last');
		$this->Send('USER '.$user);
		if ($this->status_code() != '300') {
			$this->Messanger(self::status_code());
		}else{
			$this->Send('PASS '.$pass);
			if (self::status_code() != '200'){
				$this->Messanger(self::status_code());
			}else{
				$this->conn_status['auth'] = TRUE;
				return $this->Label($this->first_line_array() , $keys);
			}
		}
		return array();
	}

	public function Logout() {
		$this->Send('LOUT');
		if (self::status_code() != '200') {
			$this->Messanger(self::status_code());
			return FALSE;
		}
		$this->conn_status['auth'] = FALSE;
		return TRUE;
	}

	public function Password($pass) {
		$this->Send('SETP '.$pass);
		if (self::status_code() != '200') {
			$this->Messanger(self::status_code());
			return FALSE;
		}
		return TRUE;
	}

	public function Send($string) {
		$this->write($string);
		$this->read();
	}

	public function write($string = NULL) {
		if( $this->stream === FALSE ) {
			$this->Messanger(553);
			return;
		} 

		foreach( self::$response_keys as $key ){
			$this->response[$key] = NULL;	
		}

		$tmp = explode(" ", $string);
		$this->response['cmd'] = $tmp[0];
		fwrite($this->stream, $string.self::EOL);
	}

	private function read() {
		if( $this->stream === FALSE ) {
			$this->Messanger(553);
			return;
		}

		$data = fread( $this->stream, 8192 );
		/*
			$data = '';
			while( feof($this->stream) === FALSE ) {
				$data .= fread( $this->stream, 8192 );
				echo "Reading Stream\n";

			}
		*/
		
		if( empty($data) ){
			$this->Messanger(512);
			return;
		}

		if( $this->timedout() === FALSE ){
			$data = explode(self::EOL, $data);
			$this->response['status_code'] = substr($data[0], 0, 3);
			$this->response['first_line']  = substr($data[0], 4, strlen($data[0]));
			unset($data[0]);
			foreach ($data as $line) {
				if($line == '000') break;
				if(empty($line)) continue;
				$this->response['lines'][] = $line;
			}
		}
	}

	public function first_line_array() { 
		return explode( '|', self::first_line() );
	}

	public static function Label(array $data, array $keys) {
		$return = array();
		if(count($data) !== count($keys)) {
			$this->Messanger(561);
		}else{
			foreach ($keys as $id => $text) {
				$return[$text] = $data[$id];
			}			
		}
		return $return;
	}

	public function meta_data() { 
		return stream_get_meta_data($this->stream); 
	}

	public function timedout() {
		$meta = $this->meta_data();
		if( $meta['timed_out'] ) {
			$this->Messanger(511);			
		}
		return $meta['timed_out'];
	}	

	public function stream_check() {
		if( $this->stream === FALSE ) {
			$this->Messanger(553);
			return FALSE;
		}
		return TRUE;
	}

	public function __construct(){
		foreach( self::$response_keys as $key ){
			$this->response[$key] = NULL;	
		}
		$this->conn_status['auth'] = FALSE;
	}

	public function Messanger( $code = NULL, $text = NULL ) {
		if($code !== NULL){
			if( is_string($code) ){
				$this->messanger[] = array('Cmd' => self::cmd(), 'Code' => $code, 'Text' => $this->cit_status_code() );
			}elseif( $text === NULL ){
				$this->messanger[] = array('Cmd' => self::cmd(), 'Code' => $code, 'Text' => self::$status_code[$code]);
			}else{
				$this->messanger[] = array('Cmd' => self::cmd(), 'Code' => $code, 'Text' => $text);				
			}
			if(php_sapi_name() === 'cli'){
				debug_print_backtrace();				
			}
		}
		return $this->messanger;
	}

	# Returns the first error
	public function Status(){
		$m = $this->Messanger();
		return $m[0]['Cmd'] . ' ' . $m[0]['Code'] . ' ' . $m[0]['Text'];
	}

	public function cit_status_code() {
		if(substr($this->status_code(), 1,2) == '00') {
			return self::$status_code_prefix[substr(self::status_code(), 0, 1)];
		} else {
			return self::$status_code_prefix[substr(self::status_code(), 0, 1)].':'.self::$status_code_suffix[substr(self::status_code(), 1,2)];
		}
	}


	public function __destruct() {
		if($this->stream !== FALSE) {
			if($this->conn_status['auth']) 
				$this->Logout();
			$this->Close();
		}	
	}

    /**
     * Citadel PHP Messages
     * @var array $status_code
     * @access public
     **/
	public static $status_code = array(
			501 => 'COMMAND_NOT_FOUND', 
			502 => 'COMMAND_NOT_SET', 
			511 => 'RESOURCE_TIMEOUT', 
			512 => 'RESOURCE_BAD_DATA',
			553 => 'RESOURCE_NOT_OPEN',
			561 => 'LABEL_KEY_MATCH', 
		);

    /**
     * Citadel Messages
     * @var array $status_code_prefix
     * @access public
     * @link http://www.citadel.org/doku.php/documentation:appproto:statuscodes
	 * @link http://www.citadel.org/doku.php/documentation:appproto:app_proto#resultcodes
     **/
    public static $status_code_prefix = array(
        '1' => 'LISTING_FOLLOWS',   # The requested operation is progressing and is now delivering text.The client *must* now read lines of text until it receives thetermination sequence (?000? on a line by itself).
        '2' => 'CIT_OK',            # The requested operation succeeded.
        '3' => 'MORE_DATA',         # The requested operation succeeded so far', but another command is required to complete it.
        '4' => 'SEND_LISTING',      # The requested operation is progressing and is now expecting text. The client *must* now transmit zero or more lines of text followed by the termination sequence (?000? on a line by itself).
        '5' => 'ERROR',             # The requested operation failed. The second and third digits of the error code and/or the error message following it describes why.
        '6' => 'BINARY_FOLLOWS',    # After this line please read n bytes. (n follows after a blank)
        '7' => 'SEND_BINARY',       # You now may send us n bytes binary data. (n follows after a blank)
        '8' => 'START_CHAT_MODE',   # Ok, we are in chat mode now. every line you send will be broadcasted.
        '9' => 'ASYNC_MSG');        # There is a page waiting for you, please fetch it.

    /**
     * Citadel Messages Extended
     * @var array $status_code_suffix
     * @access public
     * @link http://www.citadel.org/doku.php/documentation:appproto:statuscodes
	 * @link http://www.citadel.org/doku.php/documentation:appproto:app_proto#resultcodes
     **/
    public static $status_code_suffix = array(
        '02' => 'ASYNC_GEXP',
        '10' => 'INTERNAL_ERROR',
        '11' => 'TOO_BIG',
        '12' => 'ILLEGAL_VALUE',
        '20' => 'NOT_LOGGED_IN',
        '30' => 'CMD_NOT_SUPPORTED',
        '31' => 'SERVER_SHUTTING_DOWN',
        '40' => 'PASSWORD_REQUIRED',
        '41' => 'ALREADY_LOGGED_IN',
        '42' => 'USERNAME_REQUIRED',
        '50' => 'HIGHER_ACCESS_REQUIRED',
        '51' => 'MAX_SESSIONS_EXCEEDED',
        '52' => 'RESOURCE_BUSY',
        '53' => 'RESOURCE_NOT_OPEN',
        '60' => 'NOT_HERE',
        '61' => 'INVALID_FLOOR_OPERATION',
        '70' => 'NO_SUCH_USER',
        '71' => 'FILE_NOT_FOUND',
        '72' => 'ROOM_NOT_FOUND',
        '73' => 'NO_SUCH_SYSTEM',
        '74' => 'ALREADY_EXISTS',
        '75' => 'MESSAGE_NOT_FOUND');
}
?>