[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/horde/framework/Horde/Imap/Client/Socket/ -> Pop3.php (source)

   1  <?php
   2  /**
   3   * Copyright 2009-2014 Horde LLC (http://www.horde.org/)
   4   *
   5   * See the enclosed file COPYING for license information (LGPL). If you
   6   * did not receive this file, see http://www.horde.org/licenses/lgpl21.
   7   *
   8   * ---------------------------------------------------------------------------
   9   *
  10   * Based on the PEAR Net_POP3 package (version 1.3.6) by:
  11   *     Richard Heyes <richard@phpguru.org>
  12   *     Damian Fernandez Sosa <damlists@cnba.uba.ar>
  13   *
  14   * Copyright (c) 2002, Richard Heyes
  15   * All rights reserved.
  16   *
  17   * Redistribution and use in source and binary forms, with or without
  18   * modification, are permitted provided that the following conditions
  19   * are met:
  20   *
  21   * o Redistributions of source code must retain the above copyright
  22   *   notice, this list of conditions and the following disclaimer.
  23   * o Redistributions in binary form must reproduce the above copyright
  24   *   notice, this list of conditions and the following disclaimer in the
  25   *   documentation and/or other materials provided with the distribution.
  26   * o The names of the authors may not be used to endorse or promote
  27   *   products derived from this software without specific prior written
  28   *   permission.
  29   *
  30   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  31   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  32   * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  33   * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  34   * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  36   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  37   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  38   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  39   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  40   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  41   *
  42   * ---------------------------------------------------------------------------
  43   *
  44   * @category  Horde
  45   * @copyright 2002 Richard Heyes
  46   * @copyright 2009-2014 Horde LLC
  47   * @license   http://www.horde.org/licenses/lgpl21 LGPL 2.1
  48   * @package   Imap_Client
  49   */
  50  
  51  /**
  52   * An interface to a POP3 server using PHP functions.
  53   *
  54   * It is an abstraction layer allowing POP3 commands to be used based on
  55   * IMAP equivalents.
  56   *
  57   * This driver implements the following POP3-related RFCs:
  58   * <pre>
  59   *   - STD 53/RFC 1939: POP3 specification
  60   *   - RFC 2195: CRAM-MD5 authentication
  61   *   - RFC 2449: POP3 extension mechanism
  62   *   - RFC 2595/4616: PLAIN authentication
  63   *   - RFC 2831: DIGEST-MD5 SASL Authentication (obsoleted by RFC 6331)
  64   *   - RFC 3206: AUTH/SYS response codes
  65   *   - RFC 4616: AUTH=PLAIN
  66   *   - RFC 5034: POP3 SASL
  67   * </pre>
  68   *
  69   * @author    Richard Heyes <richard@phpguru.org>
  70   * @author    Michael Slusarz <slusarz@horde.org>
  71   * @category  Horde
  72   * @copyright 2002 Richard Heyes
  73   * @copyright 2009-2014 Horde LLC
  74   * @license   http://www.horde.org/licenses/lgpl21 LGPL 2.1
  75   * @package   Imap_Client
  76   */
  77  class Horde_Imap_Client_Socket_Pop3 extends Horde_Imap_Client_Base
  78  {
  79      /**
  80       * The default ports to use for a connection.
  81       *
  82       * @var array
  83       */
  84      protected $_defaultPorts = array(110, 995);
  85  
  86      /**
  87       * The list of deleted messages.
  88       *
  89       * @var array
  90       */
  91      protected $_deleted = array();
  92  
  93      /**
  94       * This object returns POP3 Fetch data objects.
  95       *
  96       * @var string
  97       */
  98      protected $_fetchDataClass = 'Horde_Imap_Client_Data_Fetch_Pop3';
  99  
 100      /**
 101       */
 102      protected function _initCache($current = false)
 103      {
 104          return parent::_initCache($current) &&
 105                 $this->queryCapability('UIDL');
 106      }
 107  
 108      /**
 109       */
 110      public function getIdsOb($ids = null, $sequence = false)
 111      {
 112          return new Horde_Imap_Client_Ids_Pop3($ids, $sequence);
 113      }
 114  
 115      /**
 116       */
 117      protected function _capability()
 118      {
 119          $this->_connect();
 120  
 121          $capability = array();
 122  
 123          try {
 124              $res = $this->_sendLine('CAPA', array(
 125                  'multiline' => 'array'
 126              ));
 127  
 128              foreach ($res['data'] as $val) {
 129                  $prefix = explode(' ', $val);
 130                  $capability[strtoupper($prefix[0])] = (count($prefix) > 1)
 131                      ? array_slice($prefix, 1)
 132                      : true;
 133              }
 134          } catch (Horde_Imap_Client_Exception $e) {
 135              $this->_temp['no_capa'] = true;
 136  
 137              /* Need to probe for capabilities if CAPA command is not
 138               * available. */
 139              $capability = array('USER' => true);
 140  
 141              /* Capability sniffing only guaranteed after authentication is
 142               * completed (if any). */
 143              if (!empty($this->_init['authmethod'])) {
 144                  $this->_pop3Cache('uidl');
 145                  if (empty($this->_temp['no_uidl'])) {
 146                      $capability['UIDL'] = true;
 147                  }
 148  
 149                  $this->_pop3Cache('top', 1);
 150                  if (empty($this->_temp['no_top'])) {
 151                      $capability['TOP'] = true;
 152                  }
 153              }
 154          }
 155  
 156          $this->_setInit('capability', $capability);
 157      }
 158  
 159      /**
 160       */
 161      protected function _noop()
 162      {
 163          $this->_sendLine('NOOP');
 164      }
 165  
 166      /**
 167       * @throws Horde_Imap_Client_Exception_NoSupportPop3
 168       */
 169      protected function _getNamespaces()
 170      {
 171          throw new Horde_Imap_Client_Exception_NoSupportPop3('Namespaces');
 172      }
 173  
 174      /**
 175       */
 176      public function alerts()
 177      {
 178          return array();
 179      }
 180  
 181      /**
 182       */
 183      protected function _login()
 184      {
 185          /* Blank passwords are not allowed, so no need to even try
 186           * authentication to determine this. */
 187          if (is_null($this->getParam('password'))) {
 188              throw new Horde_Imap_Client_Exception(
 189                  Horde_Imap_Client_Translation::r("No password provided."),
 190                  Horde_Imap_Client_Exception::LOGIN_AUTHENTICATIONFAILED
 191              );
 192          }
 193  
 194          $this->_connect();
 195  
 196          $first_login = empty($this->_init['authmethod']);
 197  
 198          // Switch to secure channel if using TLS.
 199          if (!$this->isSecureConnection()) {
 200              $secure = $this->getParam('secure');
 201  
 202              if (($secure === 'tls') || $secure === true) {
 203                  // Switch over to a TLS connection.
 204                  if ($first_login && !$this->queryCapability('STLS')) {
 205                      if ($secure === 'tls') {
 206                          throw new Horde_Imap_Client_Exception(
 207                              Horde_Imap_Client_Translation::r("Could not open secure connection to the POP3 server.") . ' ' . Horde_Imap_Client_Translation::r("Server does not support secure connections."),
 208                              Horde_Imap_Client_Exception::LOGIN_TLSFAILURE
 209                          );
 210                      } else {
 211                          $this->setParam('secure', false);
 212                      }
 213                  } else {
 214                      $this->_sendLine('STLS');
 215  
 216                      $this->setParam('secure', 'tls');
 217  
 218                      if (!$this->_connection->startTls()) {
 219                          $this->logout();
 220                          throw new Horde_Imap_Client_Exception(
 221                              Horde_Imap_Client_Translation::r("Could not open secure connection to the POP3 server."),
 222                              Horde_Imap_Client_Exception::LOGIN_TLSFAILURE
 223                          );
 224                      }
 225                      $this->_debug->info('Successfully completed TLS negotiation.');
 226                  }
 227  
 228                  // Expire cached CAPABILITY information
 229                  $this->_setInit('capability');
 230              } else {
 231                  $this->setParam('secure', false);
 232              }
 233          }
 234  
 235          if ($first_login) {
 236              /* Sanity checking: at least one server (Dovecot 1.x) may return
 237               * SASL response with no arguments. */
 238              $auth_mech = (($sasl = $this->queryCapability('SASL')) && is_array($sasl))
 239                  ? $sasl
 240                  : array();
 241  
 242              if (isset($this->_temp['pop3timestamp'])) {
 243                  $auth_mech[] = 'APOP';
 244              }
 245  
 246              $auth_mech[] = 'USER';
 247          } else {
 248              $auth_mech = array($this->_init['authmethod']);
 249          }
 250  
 251          foreach ($auth_mech as $method) {
 252              try {
 253                  $this->_tryLogin($method);
 254                  $this->_setInit('authmethod', $method);
 255  
 256                  if (!empty($this->_temp['no_capa']) ||
 257                      !$this->queryCapability('UIDL')) {
 258                      $this->_capability();
 259                  }
 260  
 261                  return true;
 262              } catch (Horde_Imap_Client_Exception $e) {
 263                  if (!empty($this->_init['authmethod']) &&
 264                      ($e->getCode() != $e::LOGIN_UNAVAILABLE) &&
 265                      ($e->getCode() != $e::POP3_TEMP_ERROR)) {
 266                      $this->_setInit();
 267                      return $this->login();
 268                  }
 269              }
 270          }
 271  
 272          throw new Horde_Imap_Client_Exception(
 273              Horde_Imap_Client_Translation::r("POP3 server denied authentication."),
 274              $e->getCode() ?: $e::LOGIN_AUTHENTICATIONFAILED
 275          );
 276      }
 277  
 278      /**
 279       * Connects to the server.
 280       *
 281       * @throws Horde_Imap_Client_Exception
 282       */
 283      protected function _connect()
 284      {
 285          if (!is_null($this->_connection)) {
 286              return;
 287          }
 288  
 289          try {
 290              $this->_connection = new Horde_Imap_Client_Socket_Connection_Pop3(
 291                  $this->getParam('hostspec'),
 292                  $this->getParam('port'),
 293                  $this->getParam('timeout'),
 294                  $this->getParam('secure'),
 295                  array(
 296                      'debug' => $this->_debug
 297                  )
 298              );
 299          } catch (Horde\Socket\Client\Exception $e) {
 300              $e2 = new Horde_Imap_Client_Exception(
 301                  Horde_Imap_Client_Translation::r("Error connecting to mail server."),
 302                  Horde_Imap_Client_Exception::SERVER_CONNECT
 303              );
 304              $e2->details = $e->details;
 305              throw $e2;
 306          }
 307  
 308          $line = $this->_getResponse();
 309  
 310          // Check for string matching APOP timestamp
 311          if (preg_match('/<.+@.+>/U', $line['resp'], $matches)) {
 312              $this->_temp['pop3timestamp'] = $matches[0];
 313          }
 314      }
 315  
 316      /**
 317       * Authenticate to the POP3 server.
 318       *
 319       * @param string $method  POP3 login method.
 320       *
 321       * @throws Horde_Imap_Client_Exception
 322       */
 323      protected function _tryLogin($method)
 324      {
 325          $username = $this->getParam('username');
 326          $password = $this->getParam('password');
 327  
 328          switch ($method) {
 329          case 'CRAM-MD5':
 330          case 'CRAM-SHA1':
 331          case 'CRAM-SHA256':
 332              // RFC 5034: CRAM-MD5
 333              // CRAM-SHA1 & CRAM-SHA256 supported by Courier SASL library
 334              $challenge = $this->_sendLine('AUTH ' . $method);
 335              $response = base64_encode($username . ' ' . hash_hmac(strtolower(substr($method, 5)), base64_decode(substr($challenge['resp'], 2)), $password, true));
 336              $this->_sendLine($response, array(
 337                  'debug' => sprintf('[%s Response - username: %s]', $method, $username)
 338              ));
 339              break;
 340  
 341          case 'DIGEST-MD5':
 342              // RFC 2831; Obsoleted by RFC 6331
 343              $challenge = $this->_sendLine('AUTH DIGEST-MD5');
 344              $response = base64_encode(new Horde_Imap_Client_Auth_DigestMD5(
 345                  $username,
 346                  $password,
 347                  base64_decode(substr($challenge['resp'], 2)),
 348                  $this->getParam('hostspec'),
 349                  'pop3'
 350              ));
 351              $sresponse = $this->_sendLine($response, array(
 352                  'debug' => sprintf('[%s Response - username: %s]', $method, $username)
 353              ));
 354              if (stripos(base64_decode(substr($sresponse['resp'], 2)), 'rspauth=') === false) {
 355                  throw new Horde_Imap_Client_Exception(
 356                      Horde_Imap_Client_Translation::r("Unexpected response from server when authenticating."),
 357                      Horde_Imap_Client_Exception::SERVER_CONNECT
 358                  );
 359              }
 360  
 361              /* POP3 doesn't use protocol's third step. */
 362              $this->_sendLine('');
 363              break;
 364  
 365          case 'LOGIN':
 366              // RFC 4616 (AUTH=PLAIN) & 5034 (POP3 SASL)
 367              $this->_sendLine('AUTH LOGIN');
 368              $this->_sendLine(base64_encode($username), array(
 369                  'debug' => sprintf('[AUTH LOGIN Command - username: %s]', $username)
 370              ));
 371              $this->_sendLine(base64_encode($password), array(
 372                  'debug' => '[AUTH LOGIN Command - password]'
 373              ));
 374              break;
 375  
 376          case 'PLAIN':
 377              // RFC 5034
 378              $this->_sendLine('AUTH PLAIN ' . base64_encode(implode("\0", array(
 379                  $username,
 380                  $username,
 381                  $password
 382              ))), array(
 383                  'debug' => sprintf('[AUTH PLAIN Command - username: %s]', $username)
 384              ));
 385              break;
 386  
 387          case 'APOP':
 388              // RFC 1939 [7]
 389              $this->_sendLine('APOP ' . $username . ' ' . hash('md5', $this->_temp['pop3timestamp'] . $password));
 390              break;
 391  
 392          case 'USER':
 393              // RFC 1939 [7]
 394              $this->_sendLine('USER ' . $username);
 395              $this->_sendLine('PASS ' . $password, array(
 396                  'debug' => '[USER Command - password]'
 397              ));
 398              break;
 399  
 400          default:
 401              throw new Horde_Imap_Client_Exception(
 402                  sprintf(Horde_Imap_Client_Translation::r("Unknown authentication method: %s"), $method),
 403                  Horde_Imap_Client_Exception::SERVER_CONNECT
 404              );
 405          }
 406      }
 407  
 408      /**
 409       */
 410      protected function _logout()
 411      {
 412          try {
 413              $this->_sendLine('QUIT');
 414          } catch (Horde_Imap_Client_Exception $e) {}
 415          $this->_deleted = array();
 416      }
 417  
 418      /**
 419       * @throws Horde_Imap_Client_Exception_NoSupportPop3
 420       */
 421      protected function _sendID($info)
 422      {
 423          throw new Horde_Imap_Client_Exception_NoSupportPop3('ID command');
 424      }
 425  
 426      /**
 427       * Return implementation information from the POP3 server (RFC 2449 [6.9]).
 428       */
 429      protected function _getID()
 430      {
 431          $id = $this->queryCapability('IMPLEMENTATION');
 432          return empty($id)
 433              ? array()
 434              : array('implementation' => $id);
 435      }
 436  
 437      /**
 438       * @throws Horde_Imap_Client_Exception_NoSupportPop3
 439       */
 440      protected function _setLanguage($langs)
 441      {
 442          throw new Horde_Imap_Client_Exception_NoSupportPop3('LANGUAGE extension');
 443      }
 444  
 445      /**
 446       * @throws Horde_Imap_Client_Exception_NoSupportPop3
 447       */
 448      protected function _getLanguage($list)
 449      {
 450          throw new Horde_Imap_Client_Exception_NoSupportPop3('LANGUAGE extension');
 451      }
 452  
 453      /**
 454       * @throws Horde_Imap_Client_Exception_NoSupportPop3
 455       */
 456      protected function _openMailbox(Horde_Imap_Client_Mailbox $mailbox, $mode)
 457      {
 458          if ($mailbox != 'INBOX') {
 459              throw new Horde_Imap_Client_Exception_NoSupportPop3('Mailboxes other than INBOX');
 460          }
 461          $this->_changeSelected($mailbox, $mode);
 462      }
 463  
 464      /**
 465       * @throws Horde_Imap_Client_Exception_NoSupportPop3
 466       */
 467      protected function _createMailbox(Horde_Imap_Client_Mailbox $mailbox, $opts)
 468      {
 469          throw new Horde_Imap_Client_Exception_NoSupportPop3('Creating mailboxes');
 470      }
 471  
 472      /**
 473       * @throws Horde_Imap_Client_Exception_NoSupportPop3
 474       */
 475      protected function _deleteMailbox(Horde_Imap_Client_Mailbox $mailbox)
 476      {
 477          throw new Horde_Imap_Client_Exception_NoSupportPop3('Deleting mailboxes');
 478      }
 479  
 480      /**
 481       * @throws Horde_Imap_Client_Exception_NoSupportPop3
 482       */
 483      protected function _renameMailbox(Horde_Imap_Client_Mailbox $old,
 484                                        Horde_Imap_Client_Mailbox $new)
 485      {
 486          throw new Horde_Imap_Client_Exception_NoSupportPop3('Renaming mailboxes');
 487      }
 488  
 489      /**
 490       * @throws Horde_Imap_Client_Exception_NoSupportPop3
 491       */
 492      protected function _subscribeMailbox(Horde_Imap_Client_Mailbox $mailbox,
 493                                           $subscribe)
 494      {
 495          throw new Horde_Imap_Client_Exception_NoSupportPop3('Mailboxes other than INBOX');
 496      }
 497  
 498      /**
 499       */
 500      protected function _listMailboxes($pattern, $mode, $options)
 501      {
 502          $tmp = array(
 503              'mailbox' => Horde_Imap_Client_Mailbox::get('INBOX')
 504          );
 505  
 506          if (!empty($options['attributes'])) {
 507              $tmp['attributes'] = array();
 508          }
 509          if (!empty($options['delimiter'])) {
 510              $tmp['delimiter'] = '';
 511          }
 512  
 513          return array('INBOX' => $tmp);
 514      }
 515  
 516      /**
 517       * @param integer $flags   This driver only supports the options listed
 518       *                         under Horde_Imap_Client::STATUS_ALL.
 519       * @throws Horde_Imap_Client_Exception_NoSupportPop3
 520       */
 521      protected function _status($mboxes, $flags)
 522      {
 523          if ((count($mboxes) > 1) || (reset($mboxes) != 'INBOX')) {
 524              throw new Horde_Imap_Client_Exception_NoSupportPop3('Mailboxes other than INBOX');
 525          }
 526  
 527          $this->openMailbox('INBOX');
 528  
 529          $ret = array();
 530  
 531          if ($flags & Horde_Imap_Client::STATUS_MESSAGES) {
 532              $res = $this->_pop3Cache('stat');
 533              $ret['messages'] = $res['msgs'];
 534          }
 535  
 536          if ($flags & Horde_Imap_Client::STATUS_RECENT) {
 537              $res = $this->_pop3Cache('stat');
 538              $ret['recent'] = $res['msgs'];
 539          }
 540  
 541          // No need for STATUS_UIDNEXT_FORCE handling since STATUS_UIDNEXT will
 542          // always return a value.
 543          if ($flags & Horde_Imap_Client::STATUS_UIDNEXT) {
 544              $res = $this->_pop3Cache('stat');
 545              $ret['uidnext'] = $res['msgs'] + 1;
 546          }
 547  
 548          if ($flags & Horde_Imap_Client::STATUS_UIDVALIDITY) {
 549              $ret['uidvalidity'] = $this->queryCapability('UIDL')
 550                  ? 1
 551                  : microtime(true);
 552          }
 553  
 554          if ($flags & Horde_Imap_Client::STATUS_UNSEEN) {
 555              $ret['unseen'] = 0;
 556          }
 557  
 558          return array('INBOX' => $ret);
 559      }
 560  
 561      /**
 562       * @throws Horde_Imap_Client_Exception_NoSupportPop3
 563       */
 564      protected function _append(Horde_Imap_Client_Mailbox $mailbox, $data,
 565                                 $options)
 566      {
 567          throw new Horde_Imap_Client_Exception_NoSupportPop3('Appending messages');
 568      }
 569  
 570      /**
 571       */
 572      protected function _check()
 573      {
 574          $this->noop();
 575      }
 576  
 577      /**
 578       */
 579      protected function _close($options)
 580      {
 581          if (!empty($options['expunge'])) {
 582              $this->logout();
 583          }
 584      }
 585  
 586      /**
 587       * @param array $options  Additional options. 'ids' has no effect in this
 588       *                        driver.
 589       */
 590      protected function _expunge($options)
 591      {
 592          $msg_list = $this->_deleted;
 593          $this->logout();
 594          return empty($options['list'])
 595              ? null
 596              : $msg_list;
 597      }
 598  
 599      /**
 600       * @throws Horde_Imap_Client_Exception_NoSupportPop3
 601       */
 602      protected function _search($query, $options)
 603      {
 604          $sort = empty($options['sort'])
 605              ? null
 606              : reset($options['sort']);
 607  
 608          // Only support a single query: an ALL search sorted by sequence.
 609          if ((strval($options['_query']['query']) != 'ALL') ||
 610              ($sort &&
 611               ((count($options['sort']) > 1) ||
 612                ($sort != Horde_Imap_Client::SORT_SEQUENCE)))) {
 613              throw new Horde_Imap_Client_Exception_NoSupportPop3('Server search');
 614          }
 615  
 616          $status = $this->status($this->_selected, Horde_Imap_Client::STATUS_MESSAGES);
 617          $res = range(1, $status['messages']);
 618  
 619          if (empty($options['sequence'])) {
 620              $tmp = array();
 621              $uidllist = $this->_pop3Cache('uidl');
 622              foreach ($res as $val) {
 623                  $tmp[] = $uidllist[$val];
 624              }
 625              $res = $tmp;
 626          }
 627  
 628          if (!empty($options['partial'])) {
 629              $partial = $this->getIdsOb($options['partial'], true);
 630              $min = $partial->min - 1;
 631              $res = array_slice($res, $min, $partial->max - $min);
 632          }
 633  
 634          $ret = array();
 635          foreach ($options['results'] as $val) {
 636              switch ($val) {
 637              case Horde_Imap_Client::SEARCH_RESULTS_COUNT:
 638                  $ret['count'] = count($res);
 639                  break;
 640  
 641              case Horde_Imap_Client::SEARCH_RESULTS_MATCH:
 642                  $ret['match'] = $this->getIdsOb($res);
 643                  break;
 644  
 645              case Horde_Imap_Client::SEARCH_RESULTS_MAX:
 646                  $ret['max'] = empty($res) ? null : max($res);
 647                  break;
 648  
 649              case Horde_Imap_Client::SEARCH_RESULTS_MIN:
 650                  $ret['min'] = empty($res) ? null : min($res);
 651                  break;
 652              }
 653          }
 654  
 655          return $ret;
 656      }
 657  
 658      /**
 659       * @throws Horde_Imap_Client_Exception_NoSupportPop3
 660       */
 661      protected function _setComparator($comparator)
 662      {
 663          throw new Horde_Imap_Client_Exception_NoSupportPop3('Search comparators');
 664      }
 665  
 666      /**
 667       * @throws Horde_Imap_Client_Exception_NoSupportPop3
 668       */
 669      protected function _getComparator()
 670      {
 671          throw new Horde_Imap_Client_Exception_NoSupportPop3('Search comparators');
 672      }
 673  
 674      /**
 675       * @throws Horde_Imap_Client_Exception_NoSupportPop3
 676       */
 677      protected function _thread($options)
 678      {
 679          throw new Horde_Imap_Client_Exception_NoSupportPop3('Server threading');
 680      }
 681  
 682      /**
 683       */
 684      protected function _fetch(Horde_Imap_Client_Fetch_Results $results,
 685                                $queries)
 686      {
 687          foreach ($queries as $options) {
 688              $this->_fetchCmd($results, $options);
 689          }
 690  
 691          $this->_updateCache($results);
 692      }
 693  
 694       /**
 695       * Fetch data for a given fetch query.
 696       *
 697       * @param Horde_Imap_Client_Fetch_Results $results  Fetch results.
 698       * @param array $options                            Fetch query options.
 699       */
 700      protected function _fetchCmd(Horde_Imap_Client_Fetch_Results $results,
 701                                   $options)
 702      {
 703          // Grab sequence IDs - IDs will always be the message number for
 704          // POP3 fetch commands.
 705          $seq_ids = $this->_getSeqIds($options['ids']);
 706          if (empty($seq_ids)) {
 707              return;
 708          }
 709  
 710          $lookup = $options['ids']->sequence
 711              ? array_combine($seq_ids, $seq_ids)
 712              : $this->_pop3Cache('uidl');
 713  
 714          foreach ($options['_query'] as $type => $c_val) {
 715              switch ($type) {
 716              case Horde_Imap_Client::FETCH_FULLMSG:
 717                  foreach ($seq_ids as $id) {
 718                      $tmp = $this->_pop3Cache('msg', $id);
 719  
 720                      if (empty($c_val['start']) && empty($c_val['length'])) {
 721                          $tmp2 = fopen('php://temp', 'r+');
 722                          stream_copy_to_stream($tmp, $tmp2, empty($c_val['length']) ? -1 : $c_val['length'], empty($c_val['start']) ? 0 : $c_val['start']);
 723                          $results->get($lookup[$id])->setFullMsg($tmp2);
 724                      } else {
 725                          $results->get($lookup[$id])->setFullMsg($tmp);
 726                      }
 727                  }
 728                  break;
 729  
 730              case Horde_Imap_Client::FETCH_HEADERTEXT:
 731                  // Ignore 'peek' option
 732                  foreach ($c_val as $key => $val) {
 733                      foreach ($seq_ids as $id) {
 734                          /* Message header can be retrieved via TOP, if the
 735                           * command is available. */
 736                          try {
 737                              $tmp = ($key == 0)
 738                                  ? $this->_pop3Cache('hdr', $id)
 739                                  : Horde_Mime_Part::getRawPartText(stream_get_contents($this->_pop3Cache('msg', $id)), 'header', $key);
 740                              $results->get($lookup[$id])->setHeaderText($key, $this->_processString($tmp, $c_val));
 741                          } catch (Horde_Mime_Exception $e) {}
 742                      }
 743                  }
 744                  break;
 745  
 746              case Horde_Imap_Client::FETCH_BODYTEXT:
 747                  // Ignore 'peek' option
 748                  foreach ($c_val as $key => $val) {
 749                      foreach ($seq_ids as $id) {
 750                          try {
 751                              $results->get($lookup[$id])->setBodyText($key, $this->_processString(Horde_Mime_Part::getRawPartText(stream_get_contents($this->_pop3Cache('msg', $id)), 'body', $key), $val));
 752                          } catch (Horde_Mime_Exception $e) {}
 753                      }
 754                  }
 755                  break;
 756  
 757              case Horde_Imap_Client::FETCH_MIMEHEADER:
 758                  // Ignore 'peek' option
 759                  foreach ($c_val as $key => $val) {
 760                      foreach ($seq_ids as $id) {
 761                          try {
 762                              $results->get($lookup[$id])->setMimeHeader($key, $this->_processString(Horde_Mime_Part::getRawPartText(stream_get_contents($this->_pop3Cache('msg', $id)), 'header', $key), $val));
 763                          } catch (Horde_Mime_Exception $e) {}
 764                      }
 765                  }
 766                  break;
 767  
 768              case Horde_Imap_Client::FETCH_BODYPART:
 769                  // Ignore 'decode', 'peek'
 770                  foreach ($c_val as $key => $val) {
 771                      foreach ($seq_ids as $id) {
 772                          try {
 773                              $results->get($lookup[$id])->setBodyPart($key, $this->_processString(Horde_Mime_Part::getRawPartText(stream_get_contents($this->_pop3Cache('msg', $id)), 'body', $key), $val));
 774                          } catch (Horde_Mime_Exception $e) {}
 775                      }
 776                  }
 777                  break;
 778  
 779              case Horde_Imap_Client::FETCH_HEADERS:
 780                  // Ignore 'length', 'peek'
 781                  foreach ($seq_ids as $id) {
 782                      $ob = $this->_pop3Cache('hdrob', $id);
 783                      foreach ($c_val as $key => $val) {
 784                          $tmp = $ob;
 785  
 786                          if (empty($val['notsearch'])) {
 787                              $tmp2 = $tmp->toArray(array('nowrap' => true));
 788                              foreach (array_keys($tmp2) as $hdr) {
 789                                  if (!in_array($hdr, $val['headers'])) {
 790                                      $tmp->removeHeader($hdr);
 791                                  }
 792                              }
 793                          } else {
 794                              foreach ($val['headers'] as $hdr) {
 795                                  $tmp->removeHeader($hdr);
 796                              }
 797                          }
 798  
 799                          $results->get($lookup[$id])->setHeaders($key, $tmp);
 800                      }
 801                  }
 802                  break;
 803  
 804              case Horde_Imap_Client::FETCH_STRUCTURE:
 805                  foreach ($seq_ids as $id) {
 806                      if ($ptr = $this->_pop3Cache('msg', $id)) {
 807                          try {
 808                              $results->get($lookup[$id])->setStructure(Horde_Mime_Part::parseMessage(stream_get_contents($ptr), array('no_body' => true)));
 809                          } catch (Horde_Exception $e) {}
 810                      }
 811                  }
 812                  break;
 813  
 814              case Horde_Imap_Client::FETCH_ENVELOPE:
 815                  foreach ($seq_ids as $id) {
 816                      $tmp = $this->_pop3Cache('hdrob', $id);
 817                      $results->get($lookup[$id])->setEnvelope(array(
 818                          'date' => $tmp->getValue('date'),
 819                          'subject' => $tmp->getValue('subject'),
 820                          'from' => $tmp->getOb('from'),
 821                          'sender' => $tmp->getOb('sender'),
 822                          'reply_to' => $tmp->getOb('reply-to'),
 823                          'to' => $tmp->getOb('to'),
 824                          'cc' => $tmp->getOb('cc'),
 825                          'bcc' => $tmp->getOb('bcc'),
 826                          'in_reply_to' => $tmp->getValue('in-reply-to'),
 827                          'message_id' => $tmp->getValue('message-id')
 828                      ));
 829                  }
 830                  break;
 831  
 832              case Horde_Imap_Client::FETCH_IMAPDATE:
 833                  foreach ($seq_ids as $id) {
 834                      $tmp = $this->_pop3Cache('hdrob', $id);
 835                      $results->get($lookup[$id])->setImapDate($tmp->getValue('date'));
 836                  }
 837                  break;
 838  
 839              case Horde_Imap_Client::FETCH_SIZE:
 840                  $sizelist = $this->_pop3Cache('size');
 841                  foreach ($seq_ids as $id) {
 842                      $results->get($lookup[$id])->setSize($sizelist[$id]);
 843                  }
 844                  break;
 845  
 846              case Horde_Imap_Client::FETCH_SEQ:
 847                  foreach ($seq_ids as $id) {
 848                      $results->get($lookup[$id])->setSeq($id);
 849                  }
 850                  break;
 851  
 852              case Horde_Imap_Client::FETCH_UID:
 853                  $uidllist = $this->_pop3Cache('uidl');
 854                  foreach ($seq_ids as $id) {
 855                      if (isset($uidllist[$id])) {
 856                          $results->get($lookup[$id])->setUid($uidllist[$id]);
 857                      }
 858                  }
 859                  break;
 860              }
 861          }
 862      }
 863  
 864      /**
 865       * Retrieve locally cached message data.
 866       *
 867       * @param string $type    Either 'hdr', 'hdrob', 'msg', 'size', 'stat',
 868       *                        'top', or 'uidl'.
 869       * @param integer $index  The message index.
 870       * @param mixed $data     Additional information needed.
 871       *
 872       * @return mixed  The cached data. 'msg' returns a stream resource. All
 873       *                other types return strings.
 874       *
 875       * @throws Horde_Imap_Client_Exception
 876       */
 877      protected function _pop3Cache($type, $index = null, $data = null)
 878      {
 879          if (isset($this->_temp['pop3cache'][$index][$type])) {
 880              if ($type == 'msg') {
 881                  rewind($this->_temp['pop3cache'][$index][$type]);
 882              }
 883              return $this->_temp['pop3cache'][$index][$type];
 884          }
 885  
 886          switch ($type) {
 887          case 'hdr':
 888          case 'top':
 889              $data = null;
 890              if ($this->queryCapability('TOP') || ($type == 'top')) {
 891                  try {
 892                      $res = $this->_sendLine('TOP ' . $index . ' 0', array(
 893                          'multiline' => 'stream'
 894                      ));
 895                      rewind($res['data']);
 896                      $data = stream_get_contents($res['data']);
 897                      fclose($res['data']);
 898                  } catch (Horde_Imap_Client_Exception $e) {
 899                      $this->_temp['no_top'] = true;
 900                      if ($type == 'top') {
 901                          return null;
 902                      }
 903                  }
 904              }
 905  
 906              if (is_null($data)) {
 907                  $data = Horde_Mime_Part::getRawPartText(stream_get_contents($this->_pop3Cache('msg', $index)), 'header', 0);
 908              }
 909              break;
 910  
 911          case 'hdrob':
 912              $data = Horde_Mime_Headers::parseHeaders($this->_pop3Cache('hdr', $index));
 913              break;
 914  
 915          case 'msg':
 916              $res = $this->_sendLine('RETR ' . $index, array(
 917                  'multiline' => 'stream'
 918              ));
 919              $data = $res['data'];
 920              rewind($data);
 921              break;
 922  
 923          case 'size':
 924          case 'uidl':
 925              $data = array();
 926              try {
 927                  $res = $this->_sendLine(($type == 'size') ? 'LIST' : 'UIDL', array(
 928                      'multiline' => 'array'
 929                  ));
 930                  foreach ($res['data'] as $val) {
 931                      $resp_data = explode(' ', $val, 2);
 932                      $data[$resp_data[0]] = $resp_data[1];
 933                  }
 934              } catch (Horde_Imap_Client_Exception $e) {
 935                  if ($type == 'uidl') {
 936                      $this->_temp['no_uidl'] = true;
 937                  }
 938              }
 939              break;
 940  
 941          case 'stat':
 942              $resp = $this->_sendLine('STAT');
 943              $resp_data = explode(' ', $resp['resp'], 2);
 944              $data = array('msgs' => $resp_data[0], 'size' => $resp_data[1]);
 945              break;
 946          }
 947  
 948          $this->_temp['pop3cache'][$index][$type] = $data;
 949  
 950          return $data;
 951      }
 952  
 953      /**
 954       * Process a string response based on criteria options.
 955       *
 956       * @param string $str  The original string.
 957       * @param array $opts  The criteria options.
 958       *
 959       * @return string  The requested string.
 960       */
 961      protected function _processString($str, $opts)
 962      {
 963          if (!empty($opts['length'])) {
 964              return substr($str, empty($opts['start']) ? 0 : $opts['start'], $opts['length']);
 965          } elseif (!empty($opts['start'])) {
 966              return substr($str, $opts['start']);
 967          }
 968  
 969          return $str;
 970      }
 971  
 972      /**
 973       * @throws Horde_Imap_Client_Exception_NoSupportPop3
 974       */
 975      protected function _vanished($modseq, Horde_Imap_Client_Ids $ids)
 976      {
 977          throw new Horde_Imap_Client_Exception_NoSupportPop3('QRESYNC commands');
 978      }
 979  
 980      /**
 981       * @param array $options  Additional options. This driver does not support
 982       *                        'unchangedsince'.
 983       */
 984      protected function _store($options)
 985      {
 986          $delete = $reset = false;
 987  
 988          /* Only support deleting/undeleting messages. */
 989          if (isset($options['replace'])) {
 990              $delete = (bool)(count(array_intersect($options['replace'], array(
 991                  Horde_Imap_Client::FLAG_DELETED
 992              ))));
 993              $reset = !$delete;
 994          } else {
 995              if (!empty($options['add'])) {
 996                  $delete = (bool)(count(array_intersect($options['add'], array(
 997                      Horde_Imap_Client::FLAG_DELETED
 998                  ))));
 999              }
1000  
1001              if (!empty($options['remove'])) {
1002                  $reset = !(bool)(count(array_intersect($options['remove'], array(
1003                      Horde_Imap_Client::FLAG_DELETED
1004                  ))));
1005              }
1006          }
1007  
1008          if ($reset) {
1009              $this->_sendLine('RSET');
1010          } elseif ($delete) {
1011              foreach ($this->_getSeqIds($options['ids']) as $id) {
1012                  try {
1013                      $this->_sendLine('DELE ' . $id);
1014                      $this->_deleted[] = $id;
1015                  } catch (Horde_Imap_Client_Exception $e) {}
1016              }
1017          }
1018  
1019          return $this->getIdsOb();
1020      }
1021  
1022      /**
1023       * @throws Horde_Imap_Client_Exception_NoSupportPop3
1024       */
1025      protected function _copy(Horde_Imap_Client_Mailbox $dest, $options)
1026      {
1027          throw new Horde_Imap_Client_Exception_NoSupportPop3('Copying messages');
1028      }
1029  
1030      /**
1031       * @throws Horde_Imap_Client_Exception_NoSupportPop3
1032       */
1033      protected function _setQuota(Horde_Imap_Client_Mailbox $root, $options)
1034      {
1035          throw new Horde_Imap_Client_Exception_NoSupportPop3('Quotas');
1036      }
1037  
1038      /**
1039       * @throws Horde_Imap_Client_Exception_NoSupportPop3
1040       */
1041      protected function _getQuota(Horde_Imap_Client_Mailbox $root)
1042      {
1043          throw new Horde_Imap_Client_Exception_NoSupportPop3('Quotas');
1044      }
1045  
1046      /**
1047       * @throws Horde_Imap_Client_Exception_NoSupportPop3
1048       */
1049      protected function _getQuotaRoot(Horde_Imap_Client_Mailbox $mailbox)
1050      {
1051          throw new Horde_Imap_Client_Exception_NoSupportPop3('Quotas');
1052      }
1053  
1054      /**
1055       * @throws Horde_Imap_Client_Exception_NoSupportPop3
1056       */
1057      protected function _setACL(Horde_Imap_Client_Mailbox $mailbox, $identifier,
1058                                 $options)
1059      {
1060          throw new Horde_Imap_Client_Exception_NoSupportPop3('ACLs');
1061      }
1062  
1063      /**
1064       * @throws Horde_Imap_Client_Exception_NoSupportPop3
1065       */
1066      protected function _deleteACL(Horde_Imap_Client_Mailbox $mailbox, $identifier)
1067      {
1068          throw new Horde_Imap_Client_Exception_NoSupportPop3('ACLs');
1069      }
1070  
1071      /**
1072       * @throws Horde_Imap_Client_Exception_NoSupportPop3
1073       */
1074      protected function _getACL(Horde_Imap_Client_Mailbox $mailbox)
1075      {
1076          throw new Horde_Imap_Client_Exception_NoSupportPop3('ACLs');
1077      }
1078  
1079      /**
1080       * @throws Horde_Imap_Client_Exception_NoSupportPop3
1081       */
1082      protected function _listACLRights(Horde_Imap_Client_Mailbox $mailbox,
1083                                        $identifier)
1084      {
1085          throw new Horde_Imap_Client_Exception_NoSupportPop3('ACLs');
1086      }
1087  
1088      /**
1089       * @throws Horde_Imap_Client_Exception_NoSupportPop3
1090       */
1091      protected function _getMyACLRights(Horde_Imap_Client_Mailbox $mailbox)
1092      {
1093          throw new Horde_Imap_Client_Exception_NoSupportPop3('ACLs');
1094      }
1095  
1096      /**
1097       * @throws Horde_Imap_Client_Exception_NoSupportPop3
1098       */
1099      protected function _getMetadata(Horde_Imap_Client_Mailbox $mailbox,
1100                                      $entries, $options)
1101      {
1102          throw new Horde_Imap_Client_Exception_NoSupportPop3('Metadata');
1103      }
1104  
1105      /**
1106       * @throws Horde_Imap_Client_Exception_NoSupportPop3
1107       */
1108      protected function _setMetadata(Horde_Imap_Client_Mailbox $mailbox, $data)
1109      {
1110          throw new Horde_Imap_Client_Exception_NoSupportPop3('Metadata');
1111      }
1112  
1113      /**
1114       */
1115      protected function _getSearchCache($type, $options)
1116      {
1117          /* POP3 does not support search caching. */
1118          return null;
1119      }
1120  
1121      /**
1122       */
1123      public function resolveIds(Horde_Imap_Client_Mailbox $mailbox,
1124                                 Horde_Imap_Client_Ids $ids, $convert = 0)
1125      {
1126          if (!$ids->special &&
1127              (!$convert ||
1128               (!$ids->sequence && ($convert == 1)) ||
1129               $ids->isEmpty())) {
1130              return clone $ids;
1131          }
1132  
1133          $uids = $this->_pop3Cache('uidl');
1134  
1135          return $this->getIdsOb(
1136              $ids->all ? array_values($uids) : array_intersect_keys($uids, $ids->ids)
1137          );
1138      }
1139  
1140      /* Internal functions. */
1141  
1142      /**
1143       * Perform a command on the server. A connection to the server must have
1144       * already been made.
1145       *
1146       * @param string $cmd     The command to execute.
1147       * @param array $options  Additional options:
1148       * <pre>
1149       *   - debug: (string) When debugging, send this string instead of the
1150       *            actual command/data sent.
1151       *            DEFAULT: Raw data output to debug stream.
1152       *   - multiline: (mixed) 'array', 'none', or 'stream'.
1153       * </pre>
1154       *
1155       * @return array  See _getResponse().
1156       *
1157       * @throws Horde_Imap_Client_Exception
1158       */
1159      protected function _sendLine($cmd, $options = array())
1160      {
1161          $old_debug = $this->_debug->debug;
1162          if (!empty($options['debug'])) {
1163              $this->_debug->raw($options['debug'] . "\n");
1164              $this->_debug->debug = false;
1165          }
1166  
1167          if ($old_debug) {
1168              $timer = new Horde_Support_Timer();
1169              $timer->push();
1170          }
1171  
1172          try {
1173              $this->_connection->write($cmd);
1174          } catch (Horde_Imap_Client_Exception $e) {
1175              $this->_debug->debug = $old_debug;
1176              throw $e;
1177          }
1178  
1179          $this->_debug->debug = $old_debug;
1180  
1181          $resp = $this->_getResponse(
1182              empty($options['multiline']) ? false : $options['multiline']
1183          );
1184  
1185          if ($old_debug) {
1186              $this->_debug->info(sprintf(
1187                  'Command took %s seconds.',
1188                  round($timer->pop(), 4)
1189              ));
1190          }
1191  
1192          return $resp;
1193      }
1194  
1195      /**
1196       * Gets a line from the stream and parses it.
1197       *
1198       * @param mixed $multiline  'array', 'none', 'stream', or null.
1199       *
1200       * @return array  An array with the following keys:
1201       *   - data: (mixed) Stream, array, or null.
1202       *   - resp: (string) The server response text.
1203       *
1204       * @throws Horde_Imap_Client_Exception
1205       */
1206      protected function _getResponse($multiline = false)
1207      {
1208          $ob = array('resp' => '');
1209  
1210          $read = explode(' ', rtrim($this->_connection->read(), "\r\n"), 2);
1211          if (!in_array($read[0], array('+OK', '-ERR', '+'))) {
1212              $this->_debug->info('ERROR: IMAP read/timeout error.');
1213              throw new Horde_Imap_Client_Exception(
1214                  Horde_Imap_Client_Translation::r("Error when communicating with the mail server."),
1215                  Horde_Imap_Client_Exception::SERVER_READERROR
1216              );
1217          }
1218  
1219          $respcode = null;
1220          if (isset($read[1]) &&
1221              isset($this->_init['capability']) &&
1222              $this->queryCapability('RESP-CODES')) {
1223              $respcode = $this->_parseResponseCode($read[1]);
1224          }
1225  
1226          switch ($read[0]) {
1227          case '+OK':
1228          case '+':
1229              if ($respcode) {
1230                  $ob['resp'] = $respcode->text;
1231              } elseif (isset($read[1])) {
1232                  $ob['resp'] = $read[1];
1233              }
1234              break;
1235  
1236          case '-ERR':
1237              $errcode = 0;
1238              if ($respcode) {
1239                  $errtext = $respcode->text;
1240  
1241                  if (isset($respcode->code)) {
1242                      switch ($respcode->code) {
1243                      // RFC 2449 [8.1.1]
1244                      case 'IN-USE':
1245                      // RFC 2449 [8.1.2]
1246                      case 'LOGIN-DELAY':
1247                          $errcode = Horde_Imap_Client_Exception::LOGIN_UNAVAILABLE;
1248                          break;
1249  
1250                      // RFC 3206 [4]
1251                      case 'SYS/TEMP':
1252                          $errcode = Horde_Imap_Client_Exception::POP3_TEMP_ERROR;
1253                          break;
1254  
1255                      // RFC 3206 [4]
1256                      case 'SYS/PERM':
1257                          $errcode = Horde_Imap_Client_Exception::POP3_PERM_ERROR;
1258                          break;
1259  
1260                      // RFC 3206 [5]
1261                      case 'AUTH':
1262                          $errcode = Horde_Imap_Client_Exception::LOGIN_AUTHENTICATIONFAILED;
1263                          break;
1264                      }
1265                  }
1266              } elseif (isset($read[1])) {
1267                  $errtext = $read[1];
1268              } else {
1269                  $errtext = '[No error message provided by server]';
1270              }
1271  
1272              $e = new Horde_Imap_Client_Exception(
1273                  Horde_Imap_Client_Translation::r("POP3 error reported by server."),
1274                  $errcode
1275              );
1276              $e->details = $errtext;
1277              throw $e;
1278          }
1279  
1280          switch ($multiline) {
1281          case 'array':
1282              $ob['data'] = array();
1283              break;
1284  
1285          case 'none':
1286              $ob['data'] = null;
1287              break;
1288  
1289          case 'stream':
1290              $ob['data'] = fopen('php://temp', 'r+');
1291              break;
1292  
1293          default:
1294              return $ob;
1295          }
1296  
1297          do {
1298              $orig_read = $this->_connection->read();
1299              $read = rtrim($orig_read, "\r\n");
1300  
1301              if ($read === '.') {
1302                  break;
1303              } elseif (substr($read, 0, 2) === '..') {
1304                  $read = substr($read, 1);
1305              }
1306  
1307              if (is_array($ob['data'])) {
1308                  $ob['data'][] = $read;
1309              } elseif (!is_null($ob['data'])) {
1310                  fwrite($ob['data'], $orig_read);
1311              }
1312          } while (true);
1313  
1314          return $ob;
1315      }
1316  
1317      /**
1318       * Returns a list of sequence IDs.
1319       *
1320       * @param Horde_Imap_Client_Ids $ids  The ID list.
1321       *
1322       * @return array  A list of sequence IDs.
1323       */
1324      protected function _getSeqIds(Horde_Imap_Client_Ids $ids)
1325      {
1326          if (!count($ids)) {
1327              $status = $this->status($this->_selected, Horde_Imap_Client::STATUS_MESSAGES);
1328              return range(1, $status['messages']);
1329          } elseif ($ids->sequence) {
1330              return $ids->ids;
1331          }
1332  
1333          return array_keys(array_intersect($this->_pop3Cache('uidl'), $ids->ids));
1334      }
1335  
1336      /**
1337       * Parses response text for response codes (RFC 2449 [8]).
1338       *
1339       * @param string $text  The response text.
1340       *
1341       * @return object  An object with the following properties:
1342       *   - code: (string) The response code, if it exists.
1343       *   - data: (string) The response code data, if it exists.
1344       *   - text: (string) The human-readable response text.
1345       */
1346      protected function _parseResponseCode($text)
1347      {
1348          $ret = new stdClass;
1349  
1350          $text = trim($text);
1351          if ($text[0] === '[') {
1352              $pos = strpos($text, ' ', 2);
1353              $end_pos = strpos($text, ']', 2);
1354              if ($pos > $end_pos) {
1355                  $ret->code = strtoupper(substr($text, 1, $end_pos - 1));
1356              } else {
1357                  $ret->code = strtoupper(substr($text, 1, $pos - 1));
1358                  $ret->data = substr($text, $pos + 1, $end_pos - $pos - 1);
1359              }
1360              $ret->text = trim(substr($text, $end_pos + 1));
1361          } else {
1362              $ret->text = $text;
1363          }
1364  
1365          return $ret;
1366      }
1367  
1368  }


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1