<?php

/* 
 * This set of classes implement a subset of the DICT protocol
 * and it is meant to be use to generate clients that
 * query a DICT server
 *
 * By default, the class uses the dict.org server in
 * the port 2628.
 *
 * (c) 2000, Jesus M. Castagnetto <jmcastagnetto@zkey.com>
 * 		    Baris Efe<webmaster@freedictionary.org> - class WapDict
 * 
 * License: GPL, see http://www.gnu.org/copyleft/gpl.txt
 * 
 * Changes:
 *
 * 2000/06/04 - added the correct link to the GPL license
 * 2000/06/05 - added the in_array() method to the base class
 *              from a suggestion by Davor Cengija <davor@croart.com>
 *              who pointed out that PHP gained the in_array()
 *              function in 3.0.12, and 4.0.0
 * 2002/09/25 - added class WapDict for WAP frontend
 *              by Baris Efe<webmaster@freedictionary.org>
 *              
 *              
 */

//============================================================ 

/*
 * class DictBase
 *
 * Base class for implementing the DICT protocol to communicate
 * with dictionary servers. It defaults to dict.org and port 2628.
 */


class DictBase {

	var $host = "dict.org";
	var $port = "2628";
	var $max_length = 6144;  // 1024 * 6 to cover UTC8 chars
	var $socket;

	var $valid_codes =
		array ( 
			110, 111, 112, 113, 114, 130, 150, 151, 152, 
			210, 220, 221, 230, 250,
			330,
			420, 421,
			500, 501, 502, 503, 530, 531, 532,
			550, 551, 552, 554, 555
		);

	var $return_code = array();
	
	function DictBase() {
		// empty constructor
	}
	
	function set($var, $val) {
		$this->$var = $val;
	}

	function get($var) {
		return $this->$var;
	}

	function parse_code($str) {
		ereg("^([0-9]{3}) (.+)", $str, &$reg);
		$error = ( $reg[1] >= 300 );
		$this->return_code = array(	"error"=>$error, 
									"code"=>$reg[1], 
									"desc"=>$reg[2]);
	}

	function is_valid_code() {
		return $this->in_array($this->return_code["code"], $this->valid_codes);
	}

	function is_error_code() {
		return $this->return_code["error"];	
	}
	
	function print_code() {
		$out = $this->is_error_code() ? "<ERROR> " : "";
		$out .= "[".$this->return_code["code"]."] ".$this->return_code["desc"]."\n";
		echo $out;
	}

	function connect() {
		$fp = fsockopen($this->host, $this->port, &$errno, &$errstr, 90);
		if (!$fp) {
			echo "Cannot connect: ".$errno." = ".$errstr."\n";
			exit;
		} else {
			$this->socket = $fp;
		}
	}

	function close() {
		fputs($this->socket, "QUIT\r\n");
		$tmp = fgets($this->socket, $this->max_length);
		fclose($this->socket);
	}

	function read_data() {
		while ($read = fgets($this->socket, $this->max_length)) {
			if (ereg("^\.\r\n$",$read))
				break;
			$out .= $read;
		}
		return $out;
	}

	// To support old PHP3 versions ( older than 3.0.12 )
	// A point I forgot and Davor Cengija reminded me about
	function in_array($element, $arr) {
		// figure out version
		list($major, $minor, $release) = explode(".", phpversion());
		if (($major == 3 && $relesase >= 12) || $major == 4) {
			return in_array($element, $arr);
		} else {
			// assumes that we want to compare element value
			while (list($key, $val) = each($arr)) {
				if ($val == $element)
					return true;
			}
			return false;
		}
	}

} // end of base class Dict

//============================================================ 

/*
 * Class DictServerInfo
 *
 * To generate objects containing DICT server information.
 * Extends the DictBase class.
 */


class DictServerInfo extends DictBase {

	var $info = array();

	function DictServerInfo($host="", $port="", $extended=false) {
		$this->init($host, $port, $extended);
	}

	function init($host, $port, $extended) {
		if ($host) 
			$this->set("host", $host);
		if ($port)
			$this->set("port", $port);

		$this->connect();

		// get connection response line
		$line = fgets($this->socket, $this->max_length);
		$this->parse_code($line);
		if ($this->is_error_code()) {
			$this->print_code();
			exit;
		}
		// extract capabilities info from response line
		ereg("^[0-9]{3} (.*) <([^<]*)> <(.*)>", $line, &$reg);
		$this->info["signature"] = $reg[1];
		$this->info["capabilities"] = explode(".", $reg[2]);
		$this->info["msg-id"] = $reg[3];
		
		// get description on the server and store verbatim
		$this->info["server"] = $this->show("SERVER");

		// get the dbs and strategies for this server
		$dbs = $this->show("DB");
		$this->store("databases",$dbs);
		$strats = $this->show("STRAT");
		$this->store("strategies",$strats);

		// get the description of each database
		// if extended info is requested
		if ($extended)
			$this->get_dbs_info();

		// close the connection
		$this->close();
		}

	function show($str) {
		fputs($this->socket, "SHOW ".$str."\r\n");
		$tmp = chop(fgets($this->socket, $this->max_length));
		$tmp2 = explode (" ", $tmp);
		if ($str == "DB")
			$this->info["num_dbs"] = (int) $tmp2[1];
		if ($str == "STRAT")
			$this->info["num_strat"] = (int) $tmp2[1];
		$data = $this->read_data();
		$tmp = fgets($this->socket, $this->max_length);
		return $data;
	}

	function store($str, $data) {
		$arr = explode("\r\n", $data);
		$out = array();
		for ($i=0; $i<count($arr); $i++) {
			if (chop($arr[$i]) == "")
				continue;
			ereg("^([^ ]+) \"?([^\"]+)\"?", $arr[$i], &$reg);
			$out[$reg[1]] = $reg[2];
		}
		$this->info[$str] = $out;
	}

	function get_dbs_info() {
		$ndb = $this->info["num_dbs"];
		$dbs = $this->info["databases"];
		$dbinfo = array();
		while (list($k, $v) = each($dbs)) {
			$dbinfo[$k] = $this->show("INFO ".$k);
		}
		$this->info["dbs_desc"] = $dbinfo;
		
	}

	function get_info ($str) {
		return $this->info[$str];
	}

} // end of class DictServerInfo

//============================================================ 

/*
 * class DictQuery
 *
 * To create query objects to search a DICT server
 */

class DictQuery extends DictBase {

	var $term = "";
	var $method = "exact";
	var $searchdb = "*";
	var $query_type = "DEFINE";
	var $valid_methods = array ("exact", "prefix", "substring", "suffix",
								"re", "regexp", "soundex", "lev");
	var $result = array();
	var $numres = 0;

	function DictQuery($host="", $port="") {
		$this->init($host, $port);
	}
	
	function init($host, $port) {
		if ($host) 
			$this->set("host", $host);
		if ($port)
			$this->set("port", $port);
		
		$this->connect();
		// get connection response line
		$line = fgets($this->socket, $this->max_length);
		$this->parse_code($line);
		if ($this->is_error_code()) {
			$this->print_code();
			exit;
		}
	}


	function search($term, $method, $db) {
		if (!$this->is_method($method)) {
			echo "**ERROR** invalid method: ".$method."\n";
			exit;
		}
		$this->clear_results();
		$this->term = $term;
		$this->method = $method;
		$this->searchdb = $db;

		$query = ($method=="exact") ? "DEFINE $db " : "MATCH $db $method ";
		$query .= "\"".$term."\"\r\n";
		
		fputs($this->socket, $query);
		$line = fgets($this->socket, $this->max_length);
		ereg("^[0-9]{3} ([0-9]+) .+", $line, &$reg);
		$this->numres = (int) $reg[1];

		if ($method != "exact") {
			$rlist = $this->read_data();
			$this->result = explode("\r\n", chop($rlist));
		} else {
			$regex = "^[0-9]{3} \"([^\"]+)\" ([^\" ]+) \"([^\"]+)\"";
			$allres = array();
			$entry = array();
			for ($i=0; $i<$this->numres; $i++) {
				$line = chop(fgets($this->socket, $this->max_length));
				if ($line == "")
					continue;
				ereg($regex, $line, &$reg);
				$entry["term"] = $reg[1];
				$entry["dbcode"] = $reg[2];
				$entry["dbname"] = $reg[3];
				$entry["definition"] = $this->read_data();

				$this->result[$i] = $entry;
			}
		}
	}	


	function define($term, $db="*") {
		$this->search($term, "exact", $db);
		$this->close();
	}

	function match($term, $method="prefix", $db="*") {
		$this->search($term, $method, $db);
		$this->close();
	}

	function is_method($method) {
		return $this->in_array($method, $this->valid_methods);
	}

	function clear_results() {
		$this->numres = 0;
		$this->result = array();
	}

} // end of class DictQuery

//============================================================ 

/*
 * class WapDict
 *
 * Dictionary Server Frontend For WAP
 */

class WapDict extends DictBase {

	// Perform a query to the server
	function doQuery($str, $db, $strategy, $DIV_LENGHT) {
    		global $PHP_SELF;
    		$query = new DictQuery();
    
		// If strategy is * chage it to exact
		if ($strategy=="*") $strategy="exact";

		if ($strategy == "exact") {
			$query->define($str, $db);
		} else {
			$query->match($str, $strategy, $db);
		}
	    	$n = $query->get("numres");
	    	$res = $query->get("result");
	    	$out = "<b>Found ".count($res);
	    	$out .= "</b>";
	    	$out .= "<br/>\n";

		for ($i=0; $i<count($res); $i++) {
	    		$entry = $res[$i];
	    		if ($strategy == "exact") {		
	    			$out .= "<br/><b>[".($i + 1)."] : ".$entry["dbname"]."</b>\n";
	    			$out .= "".$this->mkLinks($entry["definition"], $db)."\n";
	    		} else {
	    			$match = explode(" ",chop($entry));
	    			$match_term = str_replace("\"", "", $match[1]);
	    			$out .= "[". ($i + 1 ) . "] : ";
	    			$out .= "<A HREF=\"".$PHP_SELF."?Query=".urlencode($match_term);
	    			$out .= "&database=".urlencode($db);
	    			$out .= "&strategy=exact\">";
	    			$out .= $match_term."</a>\n";
	    			$out .= "Database: ".$match[0]."";
	    		}
	    	}

		$strLen=strlen($out);
		for($i=0;$i<=floor($strLen/$DIV_LENGHT);$i++) {
			$start=$i*$DIV_LENGHT-$j;
			$end=($i+1)*$DIV_LENGHT-$j;
			while($end>0) {
				if($out{$end} == " ")break;
				else $end--;
				$j++;
			};
		
			$lenStrPart=$end-$start;
		
			$tmpOut=substr($out,$start,$lenStrPart);
		
                    // To control <b></b> rule
			$tagStart=$this->str_rpos_str($tmpOut,"<b>");
			$tagEnd=$this->str_rpos_str($tmpOut,"/b>");
			if (($tagStart>$tagEnd) || (($tagStart>0) && ($tagEnd==0))) {
				$j=$j+($end-($start+$tagStart));
				$end=$start+$tagStart;
				$lenStrPart=$tagStart;

				$tmpOut=substr($out,$start,$lenStrPart);
			} 			
			
			// Replace & with &amp; for WAP
			$tmpOut=str_replace("&", "&amp;", $tmpOut);

			$arrOut[$i]=$tmpOut;
		} // End for loop

	    	return $arrOut;
	}

	function str_rpos_str($sourcestring,$needle){
	    /* just for easier understanding */
		$tempString=$sourcestring;
		do {
			$oldTempPos=$tempPos;
			$tempPos=strpos($tempString,$needle);
			$tempString=substr($tempString,$tempPos+strlen($needle));
			$realPos=$realPos+$tempPos;
			$i++;
		} while (!is_bool($tempPos));
		$realPos=$realPos+($i-2)*strlen($needle);
		return $realPos;
	}
	
	//make the links to other words in the description
	function mkLinks($str, $db) {
		global $PHP_SELF;
	
		// modified the regexes to fix the bug reported by <davor@croart.com>
		$patt = "\{+([^{}]+)\}+";
		$regex = "<b>\\1</b>";
		$out = ereg_replace($patt, $regex, $str);
		$patt = "/\{+([^{}]+)\}+/";
		preg_match_all($patt, $str, &$reg);
		$link = $reg[1];
		// clean up array
		$link = $this->cleanArray($link);
	
		for ($i=0; $i<count($link); $i++) {
			// added the line below to fix a second bug reported by <davor@croart.com>
			$link[$i] = ereg_replace("[[:space:]]+", " ", $link[$i]);
			// observed myself another bug with references to URLs - JMC
			// check if it is a HTTP URL or a crossrefence
			$protocols = "(http|https|ftp|telnet|gopher)://|(mailto|news):";
			if (ereg($protocols, $link[$i])) {
				// parse the link and mark it using <>
				$prot1 = "^((mailto|news):.+)$";
				$prot2 = "(http|https|ftp|telnet|gopher)://";
				$prot2 = "^(.*) *\((".$prot2.".+)\)$";
				if (ereg($prot1, $link[$i], &$regurl)) {
					list ($tmp, $url) = $regurl;
					$desc = $url;
				} elseif (ereg($prot2, $link[$i], &$regurl)) {
					list ($tmp, $desc, $url) = $regurl;
					if ($desc == "")
						$desc = $url;
				}
				$out .= "&lt;<a href=\"".chop($url)."\">";
				$out .= chop($desc)."</a>&gt; ";
			} else {
				if (($i % 5) == 0 && $i > 0)
					$out .= "\n";
			}
		}
		$out .= "\n";
		return $out;
	}

	// remove duplicates from array
	// and eliminate the patterns in $nolinks
	function cleanArray($arr) {
		$nolinks = "rfc:";
		$out = array();
		for ($i=0; $i<count($arr); $i++)
			if (!$this->inArray($arr[$i], $out) && !ereg($nolinks, $arr[$i])) 
				$out[] = $arr[$i];
		return $out;
	}

	// check if element is in the array
	function inArray($element, $arr) {
		// figure out version
		list($major, $minor, $release) = explode(".", phpversion());
		if (($major == 3 && $relesase >= 12) || $major == 4) {
			return in_array($element, $arr);
		} else {
			// assumes that we want to compare element value
			while (list($key, $val) = each($arr)) {
				if ($val == $element)
					return true;
			}
			return false;
		}
	}

	// functions to make the form
	function mkSelect($name, $nopt, $options, $default) {
		$out = "<select name=\"".$name."\">\n";
		list($defkey,$defval) = each($default);
		$out .= "<option value=\"".$defkey."\" selected>".$defval."</option>\n";
		while (list($key,$val) = each($options)) {
			if ($key != $defkey)
				$out .= "<option value=\"".$key."\">".$val."</option>\n";
		}
		$out .= "</select>\n";
		return $out;
	}

} // end of class WapDict

?>
