Net_NNTP
[ class tree: Net_NNTP ] [ index: Net_NNTP ] [ all elements ]

Source for file Client.php

Documentation is available at Client.php

  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
  3. // +-----------------------------------------------------------------------+
  4. // |                                                                       |
  5. // |                  http://www.heino.gehlsen.dk/software/license         |
  6. // |                                                                       |
  7. // +-----------------------------------------------------------------------+
  8. // |                                                                       |
  9. // | This work (including software, documents, or other related items) is  |
  10. // | being provided by the copyright holders under the following license.  |
  11. // | By obtaining, using and/or copying this work, you (the licensee)      |
  12. // | agree that you have read, understood, and will comply with the        |
  13. // | following terms and conditions:                                       |
  14. // |                                                                       |
  15. // | Permission to use, copy, modify, and distribute this software and     |
  16. // | its documentation, with or without modification, for any purpose and  |
  17. // | without fee or royalty is hereby granted, provided that you include   |
  18. // | the following on ALL copies of the software and documentation or      |
  19. // | portions thereof, including modifications, that you make:             |
  20. // |                                                                       |
  21. // | 1. The full text of this NOTICE in a location viewable to users of    |
  22. // |    the redistributed or derivative work.                              |
  23. // |                                                                       |
  24. // | 2. Any pre-existing intellectual property disclaimers, notices, or    |
  25. // |    terms and conditions. If none exist, a short notice of the         |
  26. // |    following form (hypertext is preferred, text is permitted) should  |
  27. // |    be used within the body of any redistributed or derivative code:   |
  28. // |     http://www.heino.gehlsen.dk/software/license"                     |
  29. // |                                                                       |
  30. // | 3. Notice of any changes or modifications to the files, including     |
  31. // |    the date changes were made. (We recommend you provide URIs to      |
  32. // |    the location from which the code is derived.)                      |
  33. // |                                                                       |
  34. // | THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT    |
  35. // | HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED,    |
  36. // | INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR        |
  37. // | FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE    |
  38. // | OR DOCUMENTATION WILL NOT INFRINGE ANY THIRD PARTY PATENTS,           |
  39. // | COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS.                               |
  40. // |                                                                       |
  41. // | COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT,        |
  42. // | SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE        |
  43. // | SOFTWARE OR DOCUMENTATION.                                            |
  44. // |                                                                       |
  45. // | The name and trademarks of copyright holders may NOT be used in       |
  46. // | advertising or publicity pertaining to the software without specific, |
  47. // | written prior permission. Title to copyright in this software and any |
  48. // | associated documentation will at all times remain with copyright      |
  49. // | holders.                                                              |
  50. // |                                                                       |
  51. // +-----------------------------------------------------------------------+
  52. // |                                                                       |
  53. // | except for the references to the copyright holder, which has either   |
  54. // | been changes or removed.                                              |
  55. // |                                                                       |
  56. // +-----------------------------------------------------------------------+
  57. // $Id: Client.php,v 1.3.4.1 2005/03/13 17:55:00 heino Exp $
  58.  
  59. require_once 'PEAR.php';
  60. require_once 'Net/Socket.php';
  61.  
  62. // {{{ constants
  63.  
  64. define('NET_NNTP_PROTOCOL_CLIENT_DEFAULT_HOST''localhost');
  65. define('NET_NNTP_PROTOCOL_CLIENT_DEFAULT_PORT''119');
  66.  
  67. // }}}
  68. // {{{ Net_NNTP_Protocol_Client
  69.  
  70. /**
  71.  * The Net_NNTP_Protocol_Client class implements the NNTP standard acording to
  72.  * RFX 977, RFC 2980, RFC 850/1036, and RFC 822/2822
  73.  *
  74.  * @category   Net
  75.  * @package    Net_NNTP
  76.  * @author     Heino H. Gehlsen <heino@gehlsen.dk>
  77.  * @version    $Id: Client.php,v 1.3.4.1 2005/03/13 17:55:00 heino Exp $
  78.  * @access     public
  79.  * @see        Net_NNTP_Client
  80.  * @since      Class available since Release 0.11.0
  81.  */
  82. {
  83.     // {{{ properties
  84.  
  85.     /**
  86.      * The socket resource being used to connect to the IMAP server.
  87.      *
  88.      * @var resource 
  89.      * @access private
  90.      */
  91.     var $_socket = null;
  92.  
  93.     /**
  94.      *
  95.      *
  96.      * @var resource 
  97.      * @access private
  98.      */
  99.     var $_currentStatusResponse = null;
  100.  
  101.     /**
  102.      * Whether to enable internal debug messages.
  103.      *
  104.      * @var     bool 
  105.      * @access  private
  106.      */
  107.     var $_debug = false;
  108.  
  109.     // }}}
  110.     // {{{ constructor
  111.         
  112.     /**
  113.      *
  114.      */
  115.     function Net_NNTP_Protocol_Client({
  116.         $this->_socket = new Net_Socket();
  117.     }
  118.  
  119.     // }}}
  120.     // {{{ Connect()
  121.  
  122.     /**
  123.      * Connect to the server
  124.      *
  125.      * @param optional string $host The adress of the NNTP-server to connect to, defaults to 'localhost'.
  126.      * @param optional int $port The port number to connect to, defaults to 119.
  127.      *
  128.      * @return mixed (bool) true on success or (object) pear_error on failure
  129.      * @access public
  130.      */
  131.     function connect($host = NET_NNTP_PROTOCOL_CLIENT_DEFAULT_HOST$port = NET_NNTP_PROTOCOL_CLIENT_DEFAULT_PORT)
  132.     {
  133.         if ($this->isConnected() ) {
  134.             return PEAR::throwError('Already connected, disconnect first!'null);
  135.         }
  136.  
  137.         // Open Connection
  138.         $R @$this->_socket->connect($host$portfalse15);
  139.         if (PEAR::isError($R)) {
  140.             return PEAR::throwError('Could not connect to the server'null$R->getMessage());
  141.         }
  142.  
  143.         // Retrive the server's initial response.
  144.         $response $this->_getStatusResponse();
  145.         if (PEAR::isError($response)) {
  146.             return $response;
  147.         }
  148.  
  149.         switch ($response{
  150.             case 200: // Posting allowed
  151.                 // TODO: Set some variable
  152.                 return true;
  153.                 break;
  154.             case 201: // Posting NOT allowed
  155.                 // TODO: Set some variable
  156.                 return true;
  157.                 break;
  158.             case 502: // 'access restriction or permission denied'
  159.                 return PEAR::throwError('Server refused connection'$response$this->currentStatusResponse());
  160.                 break;
  161.             default:
  162.                 return PEAR::throwError('Unidentified response code'$response$this->currentStatusResponse());
  163.         }
  164.     }
  165.  
  166.     // }}}
  167.     // {{{ disconnect()
  168.  
  169.     /**
  170.      * alias for cmdQuit()
  171.      *
  172.      * @access public
  173.      */
  174.     function disconnect()
  175.     {
  176.         return $this->cmdQuit();
  177.     }
  178.  
  179.     // }}}
  180.     // {{{ cmdQuit()
  181.  
  182.     /**
  183.      * Close connection to the server
  184.      *
  185.      * @access public
  186.      */
  187.     function cmdQuit()
  188.     {
  189.         // Tell the server to close the connection
  190.         $response $this->_sendCommand('QUIT');
  191.         if (PEAR::isError($response)) {
  192.             return $response;
  193.         }
  194.     
  195.         switch ($response{
  196.             case 205: // RFC977: 'closing connection - goodbye!'
  197.                 // If socket is still open, close it.
  198.                 if ($this->isConnected()) {
  199.                     $this->_socket->disconnect();
  200.                 }
  201.                 return true;
  202.                 break;
  203.             default:
  204.                 return PEAR::throwError('Unidentified response code'$response$this->currentStatusResponse());
  205.         }
  206.     }
  207.  
  208.     // }}}
  209.  
  210.     /**
  211.      * The authentication process i not yet standarized but used any way
  212.      * (http://www.mibsoftware.com/userkt/nntpext/index.html).
  213.      */
  214.      
  215.     // {{{ cmdAuthinfo()
  216.  
  217.     /**
  218.      * Authenticates the user using the original method
  219.      *
  220.      * @param string $user The username to authenticate as.
  221.      * @param string $pass The password to authenticate with.
  222.      *
  223.      * @return mixed (bool) true on success or (object) pear_error on failure
  224.      * @access private
  225.      */
  226.     function cmdAuthinfo($user$pass)
  227.     {
  228.         // Send the username
  229.         $response $this->_sendCommand('AUTHINFO user '.$user);
  230.         if (PEAR::isError($response)) {
  231.             return $response;
  232.         }
  233.  
  234.         // Send the password, if the server asks
  235.         if (($response == 381&& ($pass !== null)) {
  236.             // Send the password
  237.             $response $this->_sendCommand('AUTHINFO pass '.$pass);
  238.             if (PEAR::isError($response)) {
  239.                 return $response;
  240.             }
  241.         }
  242.  
  243.         switch ($response{
  244.             case 281: // RFC2980: 'Authentication accepted'
  245.                 return true;
  246.                 break;
  247.             case 381: // RFC2980: 'More authentication information required'
  248.                 return PEAR::throwError('Authentication uncompleted'$response$this->currentStatusResponse());
  249.                 break;
  250.             case 482: // RFC2980: 'Authentication rejected'
  251.                 return PEAR::throwError('Authentication rejected'$response$this->currentStatusResponse());
  252.                 break;
  253.             case 502: // RFC2980: 'No permission'
  254.                 return PEAR::throwError('Authentication rejected'$response$this->currentStatusResponse());
  255.                 break;
  256. //            case 500:
  257. //            case 501:
  258. //                return PEAR::throwError('Authentication failed', $response, $this->currentStatusResponse());
  259. //                break;
  260.             default:
  261.                 return PEAR::throwError('Unexpected authentication error!'$response$this->currentStatusResponse());
  262.         }
  263.     }
  264.     
  265.     // }}}
  266.     // {{{ cmdAuthinfoSimple()
  267.  
  268.     /**
  269.      * Authenticates the user using the simple method
  270.      *
  271.      * @param string $user The username to authenticate as.
  272.      * @param string $pass The password to authenticate with.
  273.      *
  274.      * @return mixed (bool) true on success or (object) pear_error on failure
  275.      * @access private
  276.      */
  277.     function cmdAuthinfoSimple($user$pass)
  278.     {
  279.         return PEAR::throwError("The auth mode: 'simple' is has not been implemented yet"null);
  280.     }
  281.     
  282.     // }}}
  283.     // {{{ cmdAuthinfoGeneric()
  284.  
  285.     /**
  286.      * Authenticates the user using the simple method
  287.      *
  288.      * @param string $user The username to authenticate as.
  289.      * @param string $pass The password to authenticate with.
  290.      *
  291.      * @return mixed (bool) true on success or (object) pear_error on failure
  292.      * @access private
  293.      */
  294.     function cmdAuthinfoGeneric($user$pass)
  295.     {
  296.         return PEAR::throwError("The auth mode: 'generic' is has not been implemented yet"null);
  297.     }
  298.     
  299.     // }}}
  300.     // {{{ cmdModeReader()
  301.  
  302.     /**
  303.      *
  304.      * @return mixed (bool) true when one can post on success or (object) pear_error on failure
  305.      * @access public
  306.      */
  307.     function cmdModeReader()
  308.     {
  309.         // tell the newsserver we want an article
  310.         $response $this->_sendCommand('MODE READER');
  311.         if (PEAR::isError($response)) {
  312.             return $response;
  313.         }
  314.     
  315.         switch ($response{
  316.             case 200: // RFC2980: 'Hello, you can post'
  317.                 break;
  318.             case 201: // RFC2980: 'Hello, you can't post'
  319.                 break;
  320.             default:
  321.                 return PEAR::throwError('Unidentified response code'$response$this->currentStatusResponse());
  322.         }
  323.     }
  324.  
  325.     // }}}
  326.     // {{{ cmdNext()
  327.  
  328.     /**
  329.      * 
  330.      *
  331.      * @return 
  332.      * @access public
  333.      */
  334.     function cmdNext($x = 1)
  335.     {
  336.         // 
  337.         $response $this->_sendCommand('NEXT');
  338.         if (PEAR::isError($response)) {
  339.             return $response;
  340.         }
  341.  
  342.         switch ($response{
  343.             case 223: // RFC977: 'n a article retrieved - request text separately (n = article number, a = unique article id)'
  344.                 $response_arr split(' 'trim($this->currentStatusResponse()));
  345.  
  346.                 switch ($x{
  347. /*
  348.                     case 0:
  349.                         $data = array();
  350.                         $data['number'] = $response_arr[0];
  351.                         $data['id'] = $response_arr[1];
  352.                         return $data;
  353.                         break;
  354. */
  355.                     case 1:
  356.                         return (int) $response_arr[0];
  357.                         break;
  358.                     case 2:
  359.                         return (string) $response_arr[1];
  360.                         break;
  361.                 }
  362.                 break;
  363.             case 412: // RFC977: 'no newsgroup selected'
  364.                 return PEAR::throwError('No newsgroup has been selected'$response$this->currentStatusResponse());
  365.                 break;
  366.             case 420: // RFC977: 'no current article has been selected'
  367.                 return PEAR::throwError('No current article has been selected'$response$this->currentStatusResponse());
  368.                 break;
  369.             case 421: // RFC977: 'no next article in this group'
  370.                 return PEAR::throwError('No next article in this group'$response$this->currentStatusResponse());
  371.                 break;
  372.             default:
  373.                 return PEAR::throwError('Unidentified response code'$response$this->currentStatusResponse());
  374.         }
  375.     }
  376.  
  377.     // }}}
  378.     // {{{ cmdLast()
  379.  
  380.     /**
  381.      * 
  382.      *
  383.      * @return 
  384.      * @access public
  385.      */
  386.     function cmdLast($x = 1)
  387.     {
  388.         // 
  389.         $response $this->_sendCommand('LAST');
  390.         if (PEAR::isError($response)) {
  391.             return $response;
  392.         }
  393.  
  394.         switch ($response{
  395.             case 223: // RFC977: 'n a article retrieved - request text separately (n = article number, a = unique article id)'
  396.                 $response_arr split(' 'trim($this->currentStatusResponse()));
  397.  
  398.                 switch ($x{
  399. /*
  400.                     case 0:
  401.                         $data = array();
  402.                         $data['number'] = $response_arr[0];
  403.                         $data['id'] = $response_arr[1];
  404.                         return $data;
  405.                         break;
  406. */
  407.                     case 1:
  408.                         return (int) $response_arr[0];
  409.                         break;
  410.                     case 2:
  411.                         return (string) $response_arr[1];
  412.                         break;
  413.                 }
  414.                 break;
  415.             case 412: // RFC977: 'no newsgroup selected'
  416.                 return PEAR::throwError('No newsgroup has been selected'$response$this->currentStatusResponse());
  417.                 break;
  418.             case 420: // RFC977: 'no current article has been selected'
  419.                 return PEAR::throwError('No current article has been selected'$response$this->currentStatusResponse());
  420.                 break;
  421.             case 422: // RFC977: 'no previous article in this group'
  422.                 return PEAR::throwError('No previous article in this group'$response$this->currentStatusResponse());
  423.                 break;
  424.             default:
  425.                 return PEAR::throwError('Unidentified response code'$response$this->currentStatusResponse());
  426.         }
  427.     }
  428.  
  429.     // }}}
  430.     // {{{ cmdStat
  431.  
  432.     /**
  433.      * 
  434.      *
  435.      * @param mixed $article 
  436.      *
  437.      * @return mixed (???) ??? on success or (object) pear_error on failure
  438.      * @access public
  439.      */
  440.     function cmdStat($article)
  441.     {
  442.         // tell the newsserver we want an article
  443.         $response $this->_sendCommand('STAT '.$article);
  444.         if (PEAR::isError($response)) {
  445.             return $response;
  446.         }
  447.  
  448.         switch ($response{
  449.             case 223: // RFC977: 'n <a> article retrieved - request text separately' (actually not documented, but copied from the ARTICLE command)
  450.                 $response_arr split(' 'trim($this->currentStatusResponse()));
  451.  
  452.                 switch (2{
  453. /*
  454.                     case 0:
  455.                         $data = array();
  456.                         $data['number'] = $response_arr[0];
  457.                         $data['id'] = $response_arr[1];
  458.                   return $data;
  459.                         break;
  460.                     case 1:
  461.                         return (int) $response_arr[0];
  462.                         break;
  463.             default:
  464. */
  465.                     case 2:
  466.                         return (string) $response_arr[1];
  467.                         break;
  468.         }
  469.         
  470.                 break;
  471.             case 412: // RFC977: 'no newsgroup has been selected' (actually not documented, but copied from the ARTICLE command)
  472.                 return PEAR::throwError('No newsgroup has been selected'$response$this->currentStatusResponse());
  473.                 break;
  474.             case 423: // RFC977: 'no such article number in this group' (actually not documented, but copied from the ARTICLE command)
  475.                 return PEAR::throwError('No such article number in this group'$response$this->currentStatusResponse());
  476.                 break;
  477.             case 430: // RFC977: 'no such article found' (actually not documented, but copied from the ARTICLE command)
  478.                 return PEAR::throwError('No such article found'$response$this->currentStatusResponse());
  479.                 break;
  480.             default:
  481.                 return PEAR::throwError('Unidentified response code'$response$this->currentStatusResponse());
  482.         }
  483.     }
  484.  
  485.     // }}}
  486.     // {{{ cmdArticle()
  487.  
  488.     /**
  489.      * Get an article from the currently open connection.
  490.      *
  491.      * @param mixed $article Either a message-id or a message-number of the article to fetch. If null or '', then use current article.
  492.      *
  493.      * @return mixed (array) article on success or (object) pear_error on failure
  494.      * @access public
  495.      */
  496.     function cmdArticle($article)
  497.     {
  498.         // tell the newsserver we want an article
  499.         $response $this->_sendCommand('ARTICLE '.$article);
  500.         if (PEAR::isError($response)) {
  501.             return $response;
  502.         }
  503.     
  504.         switch ($response{
  505.             case 220: // RFC977: 'n <a> article retrieved - head and body follow (n = article number, <a> = message-id)'
  506.             case 221: // RFC977: 'n <a> article retrieved - head follows'
  507.             case 222: // RFC977: 'n <a> article retrieved - body follows'
  508.             case 223: // RFC977: 'n <a> article retrieved - request text separately'
  509.                 $data $this->_getTextResponse();
  510.                 if (PEAR::isError($data)) {
  511.                     return $data;
  512.                 }
  513.                 return $data;
  514.                 break;
  515.             case 412: // RFC977: 'no newsgroup has been selected'
  516.                 return PEAR::throwError('No newsgroup has been selected'$response$this->currentStatusResponse());
  517.                 break;
  518.             case 420: // RFC977: 'no current article has been selected'
  519.                 return PEAR::throwError('No current article has been selected'$response$this->currentStatusResponse());
  520.                 break;
  521.             case 423: // RFC977: 'no such article number in this group'
  522.                 return PEAR::throwError('No such article number in this group'$response$this->currentStatusResponse());
  523.                 break;
  524.             case 430: // RFC977: 'no such article found'
  525.                 return PEAR::throwError('No such article found'$response$this->currentStatusResponse());
  526.                 break;
  527.             default:
  528.                 return PEAR::throwError('Unidentified response code'$response$this->currentStatusResponse());
  529.         }
  530.     }
  531.  
  532.     // }}}
  533.     // {{{ cmdHead()
  534.  
  535.     /**
  536.      * Get the headers of an article from the currently open connection.
  537.      *
  538.      * @param mixed $article Either a message-id or a message-number of the article to fetch the headers from. If null or '', then use current article.
  539.      *
  540.      * @return mixed (array) headers on success or (object) pear_error on failure
  541.      * @access public
  542.      */
  543.     function cmdHead($article)
  544.     {
  545.         // tell the newsserver we want the header of an article
  546.         $response $this->_sendCommand('HEAD '.$article);
  547.         if (PEAR::isError($response)) {
  548.             return $response;
  549.         }
  550.  
  551.         switch ($response{
  552.             case 220: // RFC977: 'n <a> article retrieved - head and body follow (n = article number, <a> = message-id)'
  553.             case 221: // RFC977: 'n <a> article retrieved - head follows'
  554.             case 222: // RFC977: 'n <a> article retrieved - body follows'
  555.             case 223: // RFC977: 'n <a> article retrieved - request text separately'
  556.                 $data $this->_getTextResponse();
  557.                 if (PEAR::isError($data)) {
  558.                     return $data;
  559.             }
  560.                 return $data;
  561.                 break;
  562.             case 412: // RFC977: 'no newsgroup has been selected'
  563.                 return PEAR::throwError('No newsgroup has been selected'$response$this->currentStatusResponse());
  564.                 break;
  565.             case 420: // RFC977: 'no current article has been selected'
  566.                 return PEAR::throwError('No current article has been selected'$response$this->currentStatusResponse());
  567.                 break;
  568.             case 423: // RFC977: 'no such article number in this group'
  569.                 return PEAR::throwError('No such article number in this group'$response$this->currentStatusResponse());
  570.                 break;
  571.             case 430: // RFC977: 'no such article found'
  572.                 return PEAR::throwError('No such article found'$response$this->currentStatusResponse());
  573.                 break;
  574.             default:
  575.                 return PEAR::throwError('Unidentified response code'$response$this->currentStatusResponse());
  576.         }
  577.     }
  578.  
  579.     // }}}
  580.     // {{{ cmdBody()
  581.  
  582.     /**
  583.      * Get the body of an article from the currently open connection.
  584.      *
  585.      * @param mixed $article Either a message-id or a message-number of the article to fetch the body from. If null or '', then use current article.
  586.      *
  587.      * @return mixed (array) body on success or (object) pear_error on failure
  588.      * @access public
  589.      */
  590.     function cmdBody($article)
  591.     {
  592.         // tell the newsserver we want the body of an article
  593.         $response $this->_sendCommand('BODY '.$article);
  594.         if (PEAR::isError($response)) {
  595.             return $response;
  596.         }
  597.  
  598.         switch ($response{
  599.             case 220: // RFC977: 'n <a> article retrieved - head and body follow (n = article number, <a> = message-id)'
  600.             case 221: // RFC977: 'n <a> article retrieved - head follows'
  601.             case 222: // RFC977: 'n <a> article retrieved - body follows'
  602.             case 223: // RFC977: 'n <a> article retrieved - request text separately'
  603.                 $data $this->_getTextResponse();
  604.                 if (PEAR::isError($data)) {
  605.                     return $data;
  606.                 }
  607.                 return $data;
  608.                 break;
  609.             case 412: // RFC977: 'no newsgroup has been selected'
  610.                 return PEAR::throwError('No newsgroup has been selected'$response$this->currentStatusResponse());
  611.                 break;
  612.             case 420: // RFC977: 'no current article has been selected'
  613.                 return PEAR::throwError('No current article has been selected'$response$this->currentStatusResponse());
  614.                 break;
  615.             case 423: // RFC977: 'no such article number in this group'
  616.                 return PEAR::throwError('No such article number in this group'$response$this->currentStatusResponse());
  617.                 break;
  618.             case 430: // RFC977: 'no such article found'
  619.                 return PEAR::throwError('No such article found'$response$this->currentStatusResponse());
  620.                 break;
  621.             default:
  622.                 return PEAR::throwError('Unidentified response code'$response$this->currentStatusResponse());
  623.         }
  624.     }
  625.  
  626.     // }}}
  627.     // {{{ cmdPost()
  628.  
  629.     /**
  630.      * Post an article to a newsgroup.
  631.      *
  632.      * Among the aditional headers you might think of adding could be:
  633.      * "NNTP-Posting-Host: <ip-of-author>", which should contain the IP-adress
  634.      * of the author of the post, so the message can be traced back to him.
  635.      * "Organization: <org>" which contain the name of the organization
  636.      * the post originates from.
  637.      *
  638.      * @param string $newsgroup The newsgroup to post to.
  639.      * @param string $subject The subject of the post.
  640.      * @param string $body The body of the post itself.
  641.      * @param string $from Name + email-adress of sender.
  642.      * @param optional string $aditional Aditional headers to send.
  643.      *
  644.      * @return mixed (bool) true on success or (object) pear_error on failure
  645.      * @access public
  646.      */
  647.     function cmdPost($newsgroup$subject$body$from$aditional '')
  648.     {
  649.         // tell the newsserver we want to post an article
  650.         $response $this->_sendCommand('POST');
  651.         if (PEAR::isError($response)) {
  652.             return $response;
  653.         }
  654.  
  655.     if ($response == 340// RFC977: 'send article to be posted. End with <CR-LF>.<CR-LF>'
  656.  
  657.             // should be presented in the format specified by RFC850
  658.         
  659.             $this->_socket->write("Newsgroups: $newsgroup\r\n");
  660.             $this->_socket->write("Subject: $subject\r\n");
  661.             $this->_socket->write("From: $from\r\n");
  662.             $this->_socket->write("X-poster: PEAR::Net_NNTP\r\n");
  663.             $this->_socket->write("$aditional\r\n");
  664.             $this->_socket->write("\r\n");
  665.             $this->_socket->write("$body\r\n");
  666.             $this->_socket->write(".\r\n");
  667.  
  668.             // Retrive server's response.
  669.             $response $this->_getStatusResponse();
  670.             if (PEAR::isError($response)) {
  671.                 return $response;
  672.             }
  673.         }
  674.  
  675.         switch ($response{
  676.             case 240: // RFC977: 'article posted ok'
  677.                 return true;
  678.                 break;
  679.             case 340: // RFC977: 'send article to be posted. End with <CR-LF>.<CR-LF>'
  680.                 // This should not happen here!
  681.                 return PEAR::throwError('Unknown error during post'$response$this->currentStatusResponse());
  682.                 break;
  683.             case 440: // RFC977: 'posting not allowed'
  684.                 return PEAR::throwError('Posting not allowed'$response$this->currentStatusResponse());
  685.                 break;
  686.             case 441: // RFC977: 'posting failed'
  687.                 return PEAR::throwError('Posting failed'$response$this->currentStatusResponse());
  688.                 break;
  689.             default:
  690.                 return PEAR::throwError('Unidentified response code'$response$this->currentStatusResponse());
  691.         }
  692.     }
  693.  
  694.     // }}}
  695.     // {{{ cmdGroup()
  696.  
  697.     /**
  698.      * Selects a news group (issue a GROUP command to the server)
  699.      *
  700.      * @param string $newsgroup The newsgroup name
  701.      *
  702.      * @return mixed (array) groupinfo on success or (object) pear_error on failure
  703.      * @access public
  704.      */
  705.     function cmdGroup($newsgroup)
  706.     {
  707.         $response $this->_sendCommand('GROUP '.$newsgroup);
  708.         if (PEAR::isError($response)) {
  709.             return $response;
  710.         }
  711.  
  712.         switch ($response{
  713.             case 211: // RFC977: 'n f l s group selected'
  714.                 $response_arr split(' 'trim($this->currentStatusResponse()));
  715.  
  716.                 $data = array();
  717.                 $data['count'$response_arr[0];
  718.                 $data['first'$response_arr[1];
  719.                 $data['last']  $response_arr[2];
  720.                 $data['group'$response_arr[3];
  721.  
  722.                 return $data;
  723.                 break;
  724.             case 411: // RFC977: 'no such news group'
  725.                 return PEAR::throwError('No such news group'$response$this->currentStatusResponse());
  726.                 break;
  727.             default:
  728.                 return PEAR::throwError('Unidentified response code'$response$this->currentStatusResponse());
  729.         }
  730.     }
  731.  
  732.     // }}}
  733.     // {{{ cmdList()
  734.  
  735.     /**
  736.      * Fetches a list of all avaible newsgroups
  737.      *
  738.      * @return mixed (array) nested array with informations about existing newsgroups on success or (object) pear_error on failure
  739.      * @access public
  740.      */
  741.     function cmdList()
  742.     {
  743.         $response $this->_sendCommand('LIST');
  744.         if (PEAR::isError($response)){
  745.             return $response;
  746.         }
  747.  
  748.         switch ($response{
  749.             case 215: // RFC977: 'list of newsgroups follows'
  750.                 $data $this->_getTextResponse();
  751.                 if (PEAR::isError($data)) {
  752.                     return $data;
  753.                 }
  754.                 foreach($data as $line{
  755.                     $arr explode(' 'trim($line));
  756.  
  757.                     $group = array();
  758.                     $group['group']    $arr[0];
  759.                     $group['last']     $arr[1];
  760.                     $group['first']    $arr[2];
  761.                     $group['posting' $arr[3];
  762.  
  763.                     $groups[$group['group']] $group;
  764.                 }
  765.                 return $groups;
  766.                 break;
  767.             default:
  768.                 return PEAR::throwError('Unidentified response code'$response$this->currentStatusResponse());
  769.         }
  770.     }
  771.  
  772.     // }}}
  773.     // {{{ cmdListNewsgroups()
  774.  
  775.     /**
  776.      * Fetches a list of (all) avaible newsgroup descriptions.
  777.      *
  778.      * @param string $wildmat Wildmat of the groups, that is to be listed, defaults to '';
  779.      *
  780.      * @return mixed (array) nested array with description of existing newsgroups on success or (object) pear_error on failure
  781.      * @access public
  782.      */
  783.     function cmdListNewsgroups($wildmat '')
  784.     {
  785.         if (empty($wildmat)) {
  786.             $command 'LIST NEWSGROUPS';
  787.         else {
  788.             $command 'LIST NEWSGROUPS '.$wildmat;
  789.         }
  790.         $response $this->_sendCommand($command);
  791.         if (PEAR::isError($response)){
  792.             return $response;
  793.         }
  794.  
  795.         switch ($response{
  796.             case 215: // RFC2980: 'information follows'
  797.                 $data $this->_getTextResponse();
  798.                 if (PEAR::isError($data)) {
  799.                     return $data;
  800.                 }
  801.  
  802.                 foreach($data as $line{
  803.                     preg_match("/^(.*?)\s(.*?$)/"trim($line)$matches);
  804.                     $groups[$matches[1]] = (string) $matches[2];
  805.                 }
  806.  
  807.                 return $groups;
  808.             break;
  809.             case 503: // RFC2980: 'program error, function not performed'
  810.                 return PEAR::throwError('Internal server error, function not performed'$response$this->currentStatusResponse());
  811.                 break;
  812.             default:
  813.                 return PEAR::throwError('Unidentified response code'$response$this->currentStatusResponse());
  814.         }
  815.     }
  816.  
  817.     // }}}
  818.     /**
  819.      * Fetches a list of (all) avaible newsgroup descriptions.
  820.      * Depresated as of RFC2980.
  821.      *
  822.      * @param string $wildmat Wildmat of the groups, that is to be listed, defaults to '*';
  823.      *
  824.      * @return mixed (array) nested array with description of existing newsgroups on success or (object) pear_error on failure
  825.      * @access public
  826.      */
  827.     function cmdXGTitle($wildmat '*')
  828.     {
  829.         $response $this->_sendCommand('XGTITLE '.$wildmat);
  830.         if (PEAR::isError($response)){
  831.             return $response;
  832.         }
  833.  
  834.         switch ($response{
  835.             case 282: // RFC2980: 'list of groups and descriptions follows'
  836.                 $data $this->_getTextResponse();
  837.                 if (PEAR::isError($data)) {
  838.                     return $data;
  839.                 }
  840.  
  841.                 foreach($data as $line{
  842.                     preg_match("/^(.*?)\s(.*?$)/"trim($line)$matches);
  843.                     $groups[$matches[1]] = (string) $matches[2];
  844.                 }
  845.  
  846.                 return $groups;
  847.                 break;
  848.           
  849.             case 481: // RFC2980: 'Groups and descriptions unavailable'
  850.                 return PEAR::throwError('Groups and descriptions unavailable'$response$this->currentStatusResponse());
  851.                 break;
  852.             default:
  853.                 return PEAR::throwError('Unidentified response code'$response$this->currentStatusResponse());
  854.         }
  855.     }
  856.  
  857.     // }}}
  858.     // {{{ cmdNewgroups()
  859.  
  860.     /**
  861.      * Fetches a list of all newsgroups created since a specified date.
  862.      *
  863.      * @param int $time Last time you checked for groups (timestamp).
  864.      * @param optional string $distributions
  865.      *
  866.      * @return mixed (array) nested array with informations about existing newsgroups on success or (object) pear_error on failure
  867.      * @access public
  868.      */
  869.     function cmdNewgroups($time$distributions = null)
  870.     {
  871.         $response $this->_sendCommand('NEWGROUPS '.date('ymd His'$time).' GMT'.($distributions !== null ? ' <'.$distributions.'>' ''));
  872.         if (PEAR::isError($response)){
  873.             return $response;
  874.         }
  875.  
  876.         switch ($response{
  877.             case 231: // REF977: 'list of new newsgroups follows'
  878.                 $groups = array();
  879.                 foreach($this->_getTextResponse(as $line{
  880.                     $arr explode(' '$line);
  881.                     $groups[$arr[0]]['group'$arr[0];
  882.                     $groups[$arr[0]]['last'$arr[1];
  883.                     $groups[$arr[0]]['first'$arr[2];
  884.                     $groups[$arr[0]]['posting'$arr[3];
  885.                 }
  886.                 return $groups;
  887.                 break;
  888.             default:
  889.                 return PEAR::throwError('Unidentified response code'$response$this->currentStatusResponse());
  890.         }
  891.     }
  892.  
  893.     // }}}
  894.     // {{{ cmdListOverviewFmt()
  895.  
  896.     /**
  897.      * Returns a list of avaible headers which are send from newsserver to client for every news message
  898.      *
  899.      * @return mixed (array) of header names on success or (object) pear_error on failure
  900.      * @access public
  901.      */
  902.     function cmdListOverviewFmt()
  903.     {
  904.         $response $this->_sendCommand('LIST OVERVIEW.FMT');
  905.         if (PEAR::isError($response)){
  906.             return $response;
  907.         }
  908.  
  909.         switch ($response{
  910.             case 215: // RFC2980: 'information follows'
  911.                 $data $this->_getTextResponse();
  912.                 if (PEAR::isError($data)) {
  913.                     return $data;
  914.                 }
  915.  
  916.                 $format = array('number');
  917.                 // XXX Use the splitHeaders() algorithm for supporting
  918.                 //     multiline headers?
  919.                 foreach ($data as $line{
  920.                     $line current(explode(':'trim($line)));
  921.                     $format[$line;
  922.                 }
  923.                 return $format;
  924.                 break;
  925.             case 503: // RFC2980: 'program error, function not performed'
  926.                 return PEAR::throwError('Internal server error, function not performed'$response$this->currentStatusResponse());
  927.                 break;
  928.             default:
  929.                 return PEAR::throwError('Unidentified response code'$response$this->currentStatusResponse());
  930.         }
  931.     }
  932.  
  933.     // }}}
  934.     // {{{ cmdXOver()
  935.  
  936.     /**
  937.      * Fetch message header from message number $first until $last
  938.      *
  939.      * The format of the returned array is:
  940.      * $messages[message_id][header_name]
  941.      *
  942.      * @param string $range articles to fetch
  943.      *
  944.      * @return mixed (array) nested array of message and there headers on success or (object) pear_error on failure
  945.      * @access public
  946.      */
  947.     function cmdXOver($range)
  948.     {
  949.     // deprecated API (the code _is_ still in alpha state)
  950.         if (func_num_args(> 1 {
  951.             die('The second parameter in cmdXOver() has been deprecated!');
  952.         }
  953.  
  954.         $format $this->cmdListOverviewFmt();
  955.         if (PEAR::isError($format)){
  956.             return $formt;
  957.         }
  958.  
  959.         $response $this->_sendCommand('XOVER '.$range);
  960.         if (PEAR::isError($response)){
  961.             return $response;
  962.         }
  963.  
  964.         switch ($response{
  965.             case 224: // RFC2980: 'Overview information follows'
  966.                 $data $this->_getTextResponse();
  967.                 if (PEAR::isError($data)) {
  968.                     return $data;
  969.                 }
  970.                 $messages = array();
  971.                 foreach($data as $line{
  972.                     $i=0;
  973.                     foreach(explode("\t"trim($line)) as $line{
  974.                         $message[$format[$i++]] $line;
  975.                     }
  976.                     $messages[$message['Message-ID']] $message;
  977.                 }
  978.                 return $messages;
  979.                 break;
  980.             case 412: // RFC2980: 'No news group current selected'
  981.                 return PEAR::throwError('No news group current selected'$response$this->currentStatusResponse());
  982.                 break;
  983.             case 420: // RFC2980: 'No article(s) selected'
  984.                 return PEAR::throwError('No article(s) selected'$response$this->currentStatusResponse());
  985.                 break;
  986.             case 502: // RFC2980: 'no permission'
  987.                 return PEAR::throwError('No permission'$response$this->currentStatusResponse());
  988.                 break;
  989.             default:
  990.                 return PEAR::throwError('Unidentified response code'$response$this->currentStatusResponse());
  991.         }
  992.     }
  993.     
  994.     // }}}
  995.     // {{{ cmdXROver()
  996.  
  997.     /**
  998.      * Fetch message references from message number $first to $last
  999.      *
  1000.      * @param string $range articles to fetch
  1001.      *
  1002.      * @return mixed (array) assoc. array of message references on success or (object) pear_error on failure
  1003.      * @access public
  1004.      */
  1005.     function cmdXROver($range)
  1006.     {
  1007.     // Warn about deprecated API (the code _is_ still in alpha state)
  1008.         if (func_num_args(> 1 {
  1009.             die('The second parameter in cmdXROver() has been deprecated!');
  1010.         }
  1011.  
  1012.         $response $this->_sendCommand('XROVER '.$range);
  1013.         if (PEAR::isError($response)){
  1014.             return $response;
  1015.         }
  1016.  
  1017.         switch ($response{
  1018.             case 224: // RFC2980: 'Overview information follows'
  1019.                 $data $this->_getTextResponse();
  1020.                 if (PEAR::isError($data)) {
  1021.                     return $data;
  1022.                 }
  1023.  
  1024.                 foreach($data as $line{
  1025.  
  1026.                     $references preg_split("/ +/"trim($line)-1PREG_SPLIT_NO_EMPTY);
  1027.  
  1028.                     $id array_shift($references);
  1029.  
  1030.                     $messages[$id$references;
  1031.                 }
  1032.                 return $messages;
  1033.                 break;
  1034.             case 412: // RFC2980: 'No news group current selected'
  1035.                 return PEAR::throwError('No news group current selected'$response$this->currentStatusResponse());
  1036.                 break;
  1037.             case 420: // RFC2980: 'No article(s) selected'
  1038.                 return PEAR::throwError('No article(s) selected'$response$this->currentStatusResponse());
  1039.                 break;
  1040.             case 502: // RFC2980: 'no permission'
  1041.                 return PEAR::throwError('No permission'$response$this->currentStatusResponse());
  1042.                 break;
  1043.             default:
  1044.                 return PEAR::throwError('Unidentified response code'$response$this->currentStatusResponse());
  1045.         }
  1046.     }
  1047.  
  1048.     // }}}
  1049.     // {{{ cmdListgroup()
  1050.  
  1051.     /**
  1052.      *
  1053.      * @param string $newsgroup 
  1054.      *
  1055.      * @return mixed (array) on success or (object) pear_error on failure
  1056.      */
  1057.     function cmdListgroup($newsgroup)
  1058.     {
  1059.         $response $this->_sendCommand('LISTGROUP '.$newsgroup);
  1060.         if (PEAR::isError($response)){
  1061.             return $response;
  1062.         }
  1063.  
  1064.         switch ($response{
  1065.             case 211: // RFC2980: 'list of article numbers follow'
  1066.                 $data $this->_getTextResponse();
  1067.                 if (PEAR::isError($data)) {
  1068.                     return $data;
  1069.                 }
  1070.                 return $data;
  1071.                 break;
  1072.             case 412: // RFC2980: 'Not currently in newsgroup'
  1073.                 return PEAR::throwError('Not currently in newsgroup'$response$this->currentStatusResponse());
  1074.                 break;
  1075.             case 502: // RFC2980: 'no permission'
  1076.                 return PEAR::throwError('No permission'$response$this->currentStatusResponse());
  1077.                 break;
  1078.             default:
  1079.                 return PEAR::throwError('Unidentified response code'$response$this->currentStatusResponse());
  1080.         }
  1081.     }
  1082.  
  1083.     // }}}
  1084.     // {{{ cmdNewnews()
  1085.  
  1086.     /**
  1087.      *
  1088.      */
  1089.     function cmdNewnews($time$newsgroups '*')
  1090.     {
  1091.     // TODO: the lenght of the request string may not exceed 510 chars
  1092.     
  1093.         $response $this->_sendCommand('NEWNEWS '.$newsgroups.' '.date('ymd His'$time));
  1094.         if (PEAR::isError($response)){
  1095.             return $response;
  1096.         }
  1097.  
  1098.         switch ($response{
  1099.             case 230: // RFC977: 'list of new articles by message-id follows'
  1100.                 $messages = array();
  1101.                 foreach($this->_getTextResponse(as $line{
  1102.                     $messages[$line;
  1103.                 }
  1104.                 return $messages;
  1105.                 break;
  1106.             default:
  1107.                 return PEAR::throwError('Unidentified response code'$response$this->currentStatusResponse());
  1108.         }
  1109.     }
  1110.  
  1111.     // }}}
  1112.     // {{{ cmdDate()
  1113.  
  1114.     /**
  1115.      * Get the date from the newsserver format of returned date
  1116.      *
  1117.      * @param bool $timestap when false function returns string, and when true function returns int/timestamp.
  1118.      *
  1119.      * @return mixed (string) 'YYYYMMDDhhmmss' / (int) timestamp on success or (object) pear_error on failure
  1120.      * @access public
  1121.      */
  1122.     function cmdDate($timestamp = false)
  1123.     {
  1124.         $response $this->_sendCommand('DATE');
  1125.         if (PEAR::isError($response)){
  1126.             return $response;
  1127.         }
  1128.  
  1129.         switch ($response{
  1130.             case 111: // RFC2980: 'YYYYMMDDhhmmss'
  1131.                 $d $this->currentStatusResponse();
  1132.                 if ($timestamp === false{
  1133.                     return (string) $d;        
  1134.                 else {
  1135.                     return (int) strtotime(substr($d08).' '.$d[8].$d[9].':'.$d[10].$d[11].':'.$d[12].$d[13]);
  1136.                 }
  1137.                 break;
  1138.             default:
  1139.                 return PEAR::throwError('Unidentified response code'$response$this->currentStatusResponse());
  1140.         }
  1141.     }
  1142.     // }}}
  1143.     // {{{ isConnected()
  1144.  
  1145.     /**
  1146.      * Test whether we are connected or not.
  1147.      *
  1148.      * @return bool true or false
  1149.      *
  1150.      * @access public
  1151.      */
  1152.     function isConnected()
  1153.     {
  1154.         return (is_resource($this->_socket->fp&& (!$this->_socket->eof()));
  1155.     }
  1156.  
  1157.     // }}}
  1158.     // {{{ setDebug()
  1159.  
  1160.     /**
  1161.      * Sets the debuging information on or off
  1162.      *
  1163.      * @param boolean True or false
  1164.      *
  1165.      * @return bool previos state
  1166.      * @access public
  1167.      */
  1168.     function setDebug($debug = true)
  1169.     {
  1170.         $tmp $this->_debug;
  1171.         $this->_debug $debug;
  1172.         return $tmp;
  1173.     }
  1174.  
  1175.     // }}}
  1176.     // {{{ _getStatusResponse()
  1177.  
  1178.     /**
  1179.      * Get servers statusresponse after a command.
  1180.      *
  1181.      * @return mixed (int) statuscode on success or (object) pear_error on failure
  1182.      * @access private
  1183.      */
  1184.     function _getStatusResponse()
  1185.     {
  1186.         // Retrieve a line (terminated by "\r\n") from the server.
  1187.         $response $this->_socket->gets(256);
  1188.         if (PEAR::isError($response) ) {
  1189.             return PEAR::throwError('Failed to read from socket!'null$response->getMessage());
  1190.         }
  1191.  
  1192.         if ($this->_debug{
  1193.             echo "S: $response\r\n";
  1194.         }
  1195.  
  1196.         // Trim the start of the response in case of misplased whitespace (should not be needen!!!)
  1197.         $response ltrim($response);
  1198.  
  1199.         $this->_currentStatusResponse = array(
  1200.                                               (int) substr($response03),
  1201.                                               (string) rtrim(substr($response4))
  1202.                                              );
  1203.  
  1204.         return $this->_currentStatusResponse[0];
  1205.     }
  1206.     
  1207.     // }}}
  1208.     // {{{ currentStatusResponse()
  1209.  
  1210.     /**
  1211.      *
  1212.      *
  1213.      * @return string status text
  1214.      * @access private
  1215.      */
  1216.     function currentStatusResponse()
  1217.     {
  1218.         return $this->_currentStatusResponse[1];
  1219.     }
  1220.     
  1221.     // }}}
  1222.     // {{{ _getTextResponse()
  1223.  
  1224.     /**
  1225.      * Get data until a line with only a '.' in it is read and return data.
  1226.      *
  1227.      * @return mixed (array) text response on success or (object) pear_error on failure
  1228.      * @access private
  1229.      */
  1230.     function _getTextResponse()
  1231.     {
  1232.         $data = array();
  1233.         $line '';
  1234.     
  1235.         // Continue until connection is lost
  1236.         while(!$this->_socket->eof()) {
  1237.  
  1238.             // Retrieve and append up to 1024 characters from the server.
  1239.             $line .= $this->_socket->gets(1024)
  1240.             if (PEAR::isError($line) ) {
  1241.                 return PEAR::throwError'Failed to read from socket!'null$line->getMessage());
  1242.             }
  1243.         
  1244.             // Continue if the line is not terminated by CRLF
  1245.             if (substr($line-2!= "\r\n" || strlen($line< 2{
  1246.                 continue;
  1247.             }
  1248.  
  1249.             // Validate recieved line
  1250.             if (false{
  1251.                 // Lines should/may not be longer than 998+2 chars (RFC2822 2.3)
  1252.                 if (strlen($line> 1000{
  1253.                     return PEAR::throwError('Invalid line recieved!'null);
  1254.                 }
  1255.             }
  1256.  
  1257.             // Remove CRLF from the end of the line
  1258.             $line substr($line0-2);
  1259.  
  1260.             // Check if the line terminates the textresponse
  1261.             if ($line == '.'{
  1262.                 // return all previous lines
  1263.                 return $data;
  1264.                 break;
  1265.             }
  1266.  
  1267.             // If 1st char is '.' it's doubled (NNTP/RFC977 2.4.1)
  1268.             if (substr($line02== '..'{
  1269.                 $line substr($line1);
  1270.             }
  1271.             
  1272.             // Add the line to the array of lines
  1273.             $data[$line;
  1274.  
  1275.             // Reset/empty $line
  1276.             $line '';
  1277.         }
  1278.  
  1279.         return PEAR::throwError('Data stream not terminated with period'null);
  1280.     }
  1281.  
  1282.     // }}}
  1283.     // {{{ _sendCommand()
  1284.  
  1285.     /**
  1286.      * Send a command to the server. A carriage return / linefeed (CRLF) sequence
  1287.      * will be appended to each command string before it is sent to the IMAP server.
  1288.      *
  1289.      * @param string $cmd The command to launch, ie: "ARTICLE 1004853"
  1290.      *
  1291.      * @return mixed (int) response code on success or (object) pear_error on failure
  1292.      * @access private
  1293.      */
  1294.     function _sendCommand($cmd)
  1295.     {
  1296.         // NNTP/RFC977 only allows command up to 512 (-2) chars.
  1297.         if (!strlen($cmd> 510{
  1298.             return PEAR::throwError('Failed to write to socket! (Command to long - max 510 chars)');
  1299.         }
  1300.  
  1301.         // Check if connected
  1302.         if (!$this->isConnected()) {
  1303.             return PEAR::throwError('Failed to write to socket! (connection lost!)');
  1304.         }
  1305.  
  1306.         // Send the command
  1307.         $R $this->_socket->writeLine($cmd);
  1308.         if PEAR::isError($R) ) {
  1309.             return PEAR::throwError('Failed to write to socket!'null$R->getMessage());
  1310.         }
  1311.     
  1312.         if ($this->_debug{
  1313.             echo "C: $cmd\r\n";
  1314.         }
  1315.  
  1316.         return $this->_getStatusResponse();
  1317.     }
  1318.     
  1319.     // }}}
  1320.  
  1321. }
  1322.  
  1323. // }}}
  1324.  
  1325. ?>

Documentation generated on Mon, 11 Mar 2019 14:30:58 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.