1<?php
2
3/* vim: set expandtab tabstop=4 shiftwidth=4: */
4
5/**
6 * Net_Dict
7 *
8 * PHP Versions 4 and 5
9 *
10 * LICENSE: This source file is subject to version 2.02 of the PHP license
11 * that is available through the world-wide-web at the following URI:
12 * http://www.php.net/license/2_02.txt.  If you did not receive a copy of
13 * the PHP License and are unable to obtain it through the web, please
14 * send a note to license@php.net so we can mail you a copy immediately.
15 *
16 * @category  Networking
17 * @package   Net_Dict
18 * @author    Chandrashekhar Bhosle <cnb@freedomink.org>
19 * @author    Ian Eure <ieure@php.net>
20 * @copyright 2002 Chandrashekhar Bhosle
21 * @copyright 2005 - 2006 Ian Eure
22 * @license   http://www.php.net/license/2_02.txt  PHP License 2.02
23 * @version   CVS: $Revision: 296454 $
24 * @link      http://pear.php.net/package/Net_Dict
25 */
26
27require_once 'PEAR.php';
28require_once 'Net/Socket.php';
29
30
31define('NET_DICT_SERVER', 'dict.org');
32define('NET_DICT_PORT', '2628');
33
34/**
35 * The main Net_Dict class
36 *
37 * Net_Dict is a PHP interface for talking to dictd servers.
38 *
39 * @category Networking
40 * @package  Net_Dict
41 * @author   Chandrashekhar Bhosle <cnb@freedomink.org>
42 * @license  http://www.php.net/license/2_02.txt PHP License v2.02
43 * @version  Release: @package_version@
44 * @link     http://pear.php.net/package/Net_Dict
45 */
46class Net_Dict
47{
48    /**
49     * Default DICT server name
50     *
51     * @var string
52     */
53    var $server = NET_DICT_SERVER;
54
55    /**
56     * Default DICT Port
57     *
58     * @var int
59     */
60    var $port = NET_DICT_PORT;
61
62    /**
63     * Socket object
64     *
65     * @var object
66     */
67    var $_socket;
68
69    /**
70     * Server Information
71     *
72     * @var string
73     */
74    var $servinfo;
75
76    /**
77     * if caching is on or off
78     *
79     * @var boolean
80     */
81    var $caching = false;
82
83    /**
84     * PEAR Cache
85     *
86     * @var object
87     */
88    var $cache;
89
90
91    /**
92     * Gets definitions for the specified word in the specified database.
93     *
94     * @param string $word     specified word
95     * @param string $database specified database
96     *
97     * @return  mixed   Array of definitions if sucessful, else PEAR_Error
98     */
99    function define($word, $database = '*')
100    {
101        if ($this->caching) {
102            if ($defines = $this->cache->get($word, 'Net_Dict_Defs')) {
103                return $defines;
104            }
105        }
106
107        if (!is_object($this->_socket)) {
108            $res = $this->connect();
109            if (PEAR::isError($res)) {
110                return $res;
111            }
112        }
113
114        $resp = $this->_sendCmd("DEFINE $database '$word'");
115
116        if (PEAR::isError($resp)) {
117            return $resp;
118        }
119
120        list($num) = explode(' ', $resp['text'], 2);
121
122        for ($i = 0; $i < $num; $i++) {
123            $resp = $this->_socket->readLine();
124
125            preg_match(
126                "/(\d{3})\s+?\"(.+)?\"\s+?(\S+)\s+?\"(.+)?\"/",
127                $resp,
128                $matches
129            );
130
131            $defines[$i]['response']    = $resp;
132            $defines[$i]['word']        = $matches[2];
133            $defines[$i]['database']    = $matches[3];
134            $defines[$i]['description'] = $matches[4];
135
136            $resp = $this->_getMultiline();
137
138            $defines[$i]['definition'] = $resp['text'];
139        }
140
141        $this->readLine(); /* discard status */
142
143        if ($this->caching) {
144            $this->cache->save($word, $defines, 0, 'Net_Dict_Defs');
145        }
146
147        return $defines;
148    }
149
150    /**
151     * Searches an index for the dictionary, and reports words
152     * which were found using a particular strategy.
153     *
154     * @param string $word     word to search for
155     * @param string $strategy strategy used, defaults to 'substring'
156     * @param string $database database to search, all if not specified.
157     *
158     * @return  mixed   Array of matches if successful, else PEAR_Error
159     */
160    function match($word, $strategy = 'substring', $database = '*')
161    {
162        $resp = $this->_sendCmd("MATCH $database $strategy '$word'");
163
164        if (PEAR::isError($resp)) {
165            return $resp;
166        }
167
168        $resp = $this->_getMultiLine();
169
170        $this->readLine(); /* discard status */
171
172        preg_match_all("/(\S+)?\s\"(.+?)\"/", $resp['text'], $matches);
173
174        for ($i = 0; $i < count($matches[0]); $i++) {
175            $matched[$i]['database'] = $matches[1][$i];
176            $matched[$i]['word']     = $matches[2][$i];
177        }
178
179        return $matched;
180    }
181
182    /**
183     * Gets list of available databases
184     *
185     * @return  mixed  Array of databases if successful, else PEAR_Error
186     */
187    function showDatabases()
188    {
189        $resp = $this->_sendCmd('SHOW DB');
190
191        if (PEAR::isError($resp)) {
192            return $resp;
193        }
194
195        $resp = $this->_getMultiLine();
196
197        $this->readLine(); /* discard status */
198
199        preg_match_all("/(\S+)?\s+?\"(.+?)\"/", $resp['text'], $matches);
200
201        for ($i = 0; $i < count($matches[0]); $i++) {
202            $databases[$i]['database']    = $matches[1][$i];
203            $databases[$i]['description'] = $matches[2][$i];
204        }
205
206        return $databases;
207    }
208
209    /**
210     * Gets a list of available strategies
211     *
212     * @return mixed Array of strategies if successful, else PEAR_Error
213     */
214    function showStrategies()
215    {
216        $resp = $this->_sendCmd('SHOW STRAT');
217
218        if (PEAR::isError($resp)) {
219            return $resp;
220        }
221
222        $resp = $this->_getMultiLine();
223
224        $this->readLine(); /* discard status */
225
226        preg_match_all("/(\S+)?\s+?\"(.+?)\"/", $resp['text'], $matches);
227
228        for ($i = 0; $i < count($matches[0]); $i++) {
229            $strategies[$i]['strategy']    = $matches[1][$i];
230            $strategies[$i]['description'] = $matches[2][$i];
231        }
232
233        return $strategies;
234    }
235
236    /**
237     * Gets source, copyright, and licensing information about the
238     * specified database.
239     *
240     * @param string $database database name
241     *
242     * @return  mixed   string if successful, else PEAR_Error
243     */
244    function showInfo($database)
245    {
246        return $this->simpleQuery('SHOW INFO ' . $database);
247    }
248
249    /**
250     * Gets local server information written by the local administrator.
251     * This could include information about local databases or strategies,
252     * or administrative information such as who to contact for access to
253     * databases requiring authentication.
254     *
255     * @return  mixed  string if sucessful, else PEAR_Error
256     */
257    function showServer()
258    {
259        return $this->simpleQuery('SHOW SERVER');
260    }
261
262    /**
263     * Allows the client to provide information about itself
264     * for possible logging and statistical purposes.  All clients SHOULD
265     * send this command after connecting to the server.  All DICT servers
266     * MUST implement this command (note, though, that the server doesn't
267     * have to do anything with the information provided by the client).
268     *
269     * @param string $text defaults to 'cnb'
270     *
271     * @return  mixed   string if successful, else PEAR_Error
272     */
273    function client($text = 'cnb')
274    {
275        $this->_sendCmd('CLIENT ' . $text);
276    }
277
278    /**
279     * Display some server-specific timing or debugging information.  This
280     * information may be useful in debugging or tuning a DICT server.  All
281     * DICT servers MUST implement this command (note, though, that the text
282     * part of the response is not specified and may be omitted).
283     *
284     * @return  mixed  string if successful, else PEAR_Error
285     */
286    function status()
287    {
288        $resp = $this->_sendCmd('STATUS');
289        return $resp['text'];
290    }
291
292    /**
293     * Provides a short summary of commands that are understood by this
294     * implementation of the DICT server.  The help text will be presented
295     * as a textual response, terminated by a single period on a line by
296     * itself.  All DICT servers MUST implement this command.
297     *
298     * @return  mixed  string on success, else PEAR_Error
299     */
300    function help()
301    {
302        return $this->simpleQuery('HELP');
303    }
304
305    /**
306     * This command is used by the client to cleanly exit the server.
307     * All DICT servers MUST implement this command.
308     *
309     * @return  mixed  string on success, else PEAR_Error
310     */
311    function quit()
312    {
313        return $this->_sendCmd('QUIT');
314    }
315
316    /**
317     * Requests that all text responses be prefaced by a MIME header
318     * [RFC2045] followed by a single blank line (CRLF).
319     *
320     * @return  mixed
321     * @todo    Implement this method
322     */
323    function optionMIME()
324    {
325    }
326
327    /**
328     * The client can authenticate itself to the server using a username and
329     * password.  The authentication-string will be computed as in the APOP
330     * protocol discussed in [RFC1939].
331     *
332     * @param string $user username
333     * @param string $auth password
334     *
335     * @return  mixed
336     * @todo    Implement this method.
337     */
338    function auth($user, $auth)
339    {
340    }
341
342    /**
343     * The Simple Authentication and Security Layer (SASL) is currently
344     * being developed [RFC2222].  The DICT protocol reserves the SASLAUTH
345     * and SASLRESP commands for this method of authentication.
346     *
347     * @param string $mechanism        mechanism used
348     * @param string $initial_response initial response
349     *
350     * @return  mixed
351     *
352     * @todo    Implement this method.
353     */
354    function SASLAuth($mechanism, $initial_response)
355    {
356    }
357
358    /**
359     * The client will send all responses using the SASLRESP command and a
360     * BASE64-encoded parameter.
361     *
362     * @param string $response the response
363     *
364     * @return mixed
365     * @todo   Implement this method.
366     */
367    function SASLResp($response)
368    {
369    }
370
371    /**
372     * Connects to a dict server and sets up a socket
373     *
374     * @param string  $server dict server
375     * @param integer $port   port to connect to
376     *
377     * @return mixed    true on success, else PEAR_Error
378     */
379    function connect($server = '', $port = 0)
380    {
381        $s = new Net_Socket;
382
383        if (empty($server)) {
384            $server = $this->server;
385        }
386
387        if (0 == $port) {
388            $port = $this->port;
389        }
390
391        $err = $s->connect($server, $port);
392
393        if (PEAR::isError($err)) {
394
395            return $err;
396        }
397
398        $banner = $s->readLine();
399        $resp['code'] = substr($banner, 0, 3);
400        $resp['text'] = ltrim(substr($banner, 3));
401
402        if (!Net_Dict::isOK($resp)) {
403            return new PEAR_Error($resp['text'],
404                                  $resp['code']);
405        }
406
407        $reg = array();
408        preg_match("/\d{3} (.*) <(.*)> <(.*)>/", $banner, $reg);
409        $this->servinfo["signature"]    = $reg[1];
410        $this->servinfo["capabilities"] = explode(".", $reg[2]);
411        $this->servinfo["msg-id"]       = $reg[3];
412
413        $this->_socket =& $s;
414
415        return true;
416    }
417
418    /**
419     * Disconnect from the dict server
420     *
421     * @see     Net_Socket::disconnect()
422     * @return  mixed  Net_Socket::disconnect()'s return value
423     * @author  Ian Eure <ieure@php.net>
424     */
425    function disconnect()
426    {
427        if (isset($this->_socket)) {
428            return $this->_socket->disconnect();
429        }
430        return new PEAR_Error('not connected');
431    }
432
433    /**
434     * Sets the server and port of dict server
435     *
436     * @param string $server server address
437     * @param int    $port   port number to use
438     *
439     * @return  void
440     */
441    function setServer($server, $port = 0)
442    {
443        $this->server = $server;
444
445        if (0 < $port) {
446            $this->port = $port;
447        }
448    }
449
450    /**
451     * Sets caching on or off and provides the cache type and parameters
452     *
453     * @param boolean $flag              true if caching is required
454     * @param string  $container         name of container
455     * @param array   $container_options options
456     *
457     * @return  void
458     */
459    function setCache($flag = false, $container = '', $container_options = '')
460    {
461        $this->caching = $flag;
462
463        if ($this->caching) {
464            include_once 'Cache.php';
465
466            if (is_object($this->cache)) {
467                unset($this->cache);
468            }
469
470            $this->cache = new Cache($container, $container_options);
471        }
472    }
473
474    /**
475     * Sends a command, checks the reponse, and
476     * if good returns the reponse, other wise
477     * returns false.
478     *
479     * @param string $cmd Command to send (\r\n will be appended)
480     *
481     * @return  mixed  First line of response if successful, otherwise false
482     */
483    function _sendCmd($cmd)
484    {
485        $result = $this->_socket->writeLine($cmd);
486
487        if (PEAR::isError($result) && $result) {
488            return $result;
489        }
490
491        $data = $this->_socket->readLine();
492
493        if (PEAR::isError($data)) {
494            return $data;
495        }
496
497        $resp['code'] = substr($data, 0, 3);
498        $resp['text'] = ltrim(substr($data, 3));
499
500        if (!Net_Dict::isOK($resp)) {
501            return new PEAR_Error($resp['text'],
502                                  $resp['code']);
503        }
504
505        return $resp;
506    }
507
508    /**
509     * Reads a multiline reponse and returns the data
510     *
511     * @return mixed string on success or PEAR_Error
512     */
513    function _getMultiline()
514    {
515        $data = '';
516        while (($tmp = $this->readLine()) != '.') {
517            if (substr($tmp, 0, 2) == '..') {
518                $tmp = substr($tmp, 1);
519            }
520            $data .= $tmp . "\r\n";
521        }
522
523        $resp['text'] = substr($data, 0, -2);
524
525        return $resp;
526    }
527
528    /**
529     * Alias to Net_Socket::readLine();
530     *
531     * @see Net_Socket::readLine();
532
533     * @return All available data up to a newline, without that
534     *         newline, or until the end of the socket, or a PEAR_Error if
535     *         not connected.
536     */
537    function readLine()
538    {
539        return $this->_socket->readLine();
540    }
541
542    /**
543     * Runs a generic dict query
544     *
545     * @param string $query dict query
546     *
547     * @return  mixed   string on success, else PEAR_Error
548     */
549    function simpleQuery($query)
550    {
551        $resp = $this->_sendCmd($query);
552
553        if (PEAR::isError($resp)) {
554            return $resp;
555        }
556
557        $resp = $this->_getMultiLine();
558
559        $this->readLine(); /* discard status */
560
561        return $resp['text'];
562    }
563
564    /**
565     * Checks if a response code is positive
566     *
567     * @param array $resp response code
568     *
569     * @return  boolean
570     */
571    function isOK($resp)
572    {
573        $positives = array(1, 2, 3);
574
575        return in_array(substr($resp['code'], 0, 1), $positives);
576    }
577}
578
579?>
580