Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402]

   1  <?php
   2  /**
   3   * Copyright 2011-2017 Horde LLC (http://www.horde.org/)
   4   *
   5   * See the enclosed file LICENSE for license information (BSD). If you
   6   * did not receive this file, see http://www.horde.org/licenses/bsd.
   7   *
   8   * @category  Horde
   9   * @copyright 2011-2017 Horde LLC
  10   * @license   http://www.horde.org/licenses/bsd New BSD License
  11   * @package   Mail
  12   */
  13  
  14  /**
  15   * This object allows easy access to parsing mbox data (RFC 4155).
  16   *
  17   * See:
  18   * http://homepage.ntlworld.com/jonathan.deboynepollard/FGA/mail-mbox-formats
  19   *
  20   * @author    Michael Slusarz <slusarz@horde.org>
  21   * @category  Horde
  22   * @copyright 2011-2017 Horde LLC
  23   * @license   http://www.horde.org/licenses/bsd New BSD License
  24   * @package   Mail
  25   * @since     2.5.0
  26   */
  27  class Horde_Mail_Mbox_Parse
  28  implements ArrayAccess, Countable, Iterator
  29  {
  30      /**
  31       * Data stream.
  32       *
  33       * @var resource
  34       */
  35      protected $_data;
  36  
  37      /**
  38       * Parsed data. Each entry is an array containing 3 keys:
  39       *   - date: (mixed) Date information, in DateTime object. Null if date
  40       *           cannot be parsed. False if message is not MBOX data.
  41       *   - start: (integer) Start boundary.
  42       *
  43       * @var array
  44       */
  45      protected $_parsed = array();
  46  
  47      /**
  48       * Constructor.
  49       *
  50       * @param mixed $data     The mbox data. Either a resource or a filename
  51       *                        as interpreted by fopen() (string).
  52       * @param integer $limit  Limit to this many messages; additional messages
  53       *                        will throw an exception.
  54       *
  55       * @throws Horde_Mail_Parse_Exception
  56       */
  57      public function __construct($data, $limit = null)
  58      {
  59          $this->_data = is_resource($data)
  60              ? $data
  61              : @fopen($data, 'r');
  62  
  63          if ($this->_data === false) {
  64              throw new Horde_Mail_Exception(
  65                  Horde_Mail_Translation::t("Could not parse mailbox data.")
  66              );
  67          }
  68  
  69          rewind($this->_data);
  70  
  71          $i = 0;
  72          $last_line = null;
  73          /* Is this a MBOX format file? */
  74          $mbox = false;
  75  
  76          while (!feof($this->_data)) {
  77              if (is_null($last_line)) {
  78                  $start = ftell($this->_data);
  79              }
  80  
  81              $line = fgets($this->_data);
  82  
  83              if (is_null($last_line)) {
  84                  ltrim($line);
  85              }
  86  
  87              if (substr($line, 0, 5) == 'From ') {
  88                  if (is_null($last_line)) {
  89                      /* This file is in MBOX format. */
  90                      $mbox = true;
  91                  } elseif (!$mbox || (trim($last_line) !== '')) {
  92                      continue;
  93                  }
  94  
  95                  if ($limit && ($i++ > $limit)) {
  96                      throw new Horde_Mail_Exception(
  97                          sprintf(
  98                              Horde_Mail_Translation::t("Imported mailbox contains more than enforced limit of %u messages."),
  99                              $limit
 100                          )
 101                      );
 102                  }
 103  
 104                  $from_line = explode(' ', $line, 3);
 105                  try {
 106                      $date = new DateTime($from_line[2]);
 107                  } catch (Exception $e) {
 108                      $date = null;
 109                  }
 110  
 111                  $this->_parsed[] = array(
 112                      'date' => $date,
 113                      'start' => ftell($this->_data)
 114                  );
 115              }
 116  
 117              /* Strip all empty lines before first data. */
 118              if (!is_null($last_line) || (trim($line) !== '')) {
 119                  $last_line = $line;
 120              }
 121          }
 122  
 123          /* This was a single message, not a MBOX file. */
 124          if (empty($this->_parsed)) {
 125              $this->_parsed[] = array(
 126                  'date' => false,
 127                  'start' => $start
 128              );
 129          }
 130      }
 131  
 132      /* ArrayAccess methods. */
 133  
 134      /**
 135       */
 136      #[\ReturnTypeWillChange]
 137      public function offsetExists($offset)
 138      {
 139          return isset($this->_parsed[$offset]);
 140      }
 141  
 142      /**
 143       */
 144      #[\ReturnTypeWillChange]
 145      public function offsetGet($offset)
 146      {
 147          if (!isset($this->_parsed[$offset])) {
 148              return null;
 149          }
 150  
 151          $p = $this->_parsed[$offset];
 152          $end = isset($this->_parsed[$offset + 1])
 153              ? $this->_parsed[$offset + 1]['start']
 154              : null;
 155          $fd = fopen('php://temp', 'w+');
 156  
 157          fseek($this->_data, $p['start']);
 158          while (!feof($this->_data)) {
 159              $line = fgets($this->_data);
 160              if ($end && (ftell($this->_data) >= $end)) {
 161                  break;
 162              }
 163  
 164              fwrite(
 165                  $fd,
 166                  (($p['date'] !== false) && substr($line, 0, 6) == '>From ')
 167                      ? substr($line, 1)
 168                      : $line
 169              );
 170          }
 171  
 172          $out = array(
 173              'data' => $fd,
 174              'date' => ($p['date'] === false) ? null : $p['date'],
 175              'size' => intval(ftell($fd))
 176          );
 177          rewind($fd);
 178  
 179          return $out;
 180      }
 181  
 182      /**
 183       */
 184      #[\ReturnTypeWillChange]
 185      public function offsetSet($offset, $value)
 186      {
 187          // NOOP
 188      }
 189  
 190      /**
 191       */
 192      #[\ReturnTypeWillChange]
 193      public function offsetUnset($offset)
 194      {
 195          // NOOP
 196      }
 197  
 198      /* Countable methods. */
 199  
 200      /**
 201       * Index count.
 202       *
 203       * @return integer  The number of messages.
 204       */
 205      #[\ReturnTypeWillChange]
 206      public function count()
 207      {
 208          return count($this->_parsed);
 209      }
 210  
 211      /* Magic methods. */
 212  
 213      /**
 214       * String representation of the object.
 215       *
 216       * @return string  String representation.
 217       */
 218      public function __toString()
 219      {
 220          rewind($this->_data);
 221          return stream_get_contents($this->_data);
 222      }
 223  
 224      /* Iterator methods. */
 225  
 226      #[\ReturnTypeWillChange]
 227      public function current()
 228      {
 229          $key = $this->key();
 230  
 231          return is_null($key)
 232              ? null
 233              : $this[$key];
 234      }
 235  
 236      #[\ReturnTypeWillChange]
 237      public function key()
 238      {
 239          return key($this->_parsed);
 240      }
 241  
 242      #[\ReturnTypeWillChange]
 243      public function next()
 244      {
 245          if ($this->valid()) {
 246              next($this->_parsed);
 247          }
 248      }
 249  
 250      #[\ReturnTypeWillChange]
 251      public function rewind()
 252      {
 253          reset($this->_parsed);
 254      }
 255  
 256      #[\ReturnTypeWillChange]
 257      public function valid()
 258      {
 259          return !is_null($this->key());
 260      }
 261  
 262  }