* each embedded array having the following
* entries:
* <pre>
- * 'data' - (mixed) The data to append. Either a string or a stream
- * resource.
+ * 'data' - (mixed) The data to append. If a string or a stream resource,
+ * this will be used as the entire contents of a single message.
+ * If an array, will catenate all given parts into a single
+ * message. This array contains one or more arrays with two keys:
+ * 't' - (string) Either 'url' or 'text'.
+ * 'v' - (mixed) If 't' is 'url', this is the IMAP URL to the
+ * message part to append. If 't' is 'text', this is
+ * either a string or resource representation of the
+ * message part data.
* DEFAULT: NONE (entry is MANDATORY)
* 'flags' - (array) An array of flags/keywords to set on the appended
* message.
* returned, the starting position is identified here.
* DEFAULT: The entire text is returned.
* Return key: 'headertext'
- * Return format: (mixed) If 'parse' is true, a Horde_Mime_Headers
- * object. Else, the raw text of the header (or the
- * (portion of the text delineated by the 'start' &
- * 'length' parameters).
+ * Return format: (array) An array of header text entries. Keys are
+ * the 'id'. If 'parse' is true, values are
+ * Horde_Mime_Headers objects. Otherwise, values are the
+ * raw text of the header (or the portion of the text
+ * delineated by the 'start' & 'length' parameters).
*
* Key: Horde_Imap_Client::FETCH_BODYTEXT
* Desc: Returns the body text. Body text is defined only for the
}
/**
+ * Given an IMAP body section string, fetches the corresponding part.
+ *
+ * @param string $mailbox The IMAP mailbox name.
+ * @param integer $uid The IMAP UID.
+ * @param string $section The IMAP section string.
+ *
+ * @return resource The section contents in a stream.
+ * @throws Horde_Imap_Client_Exception
+ */
+ public function fetchFromSectionString($mailbox, $uid, $section = null)
+ {
+ $section = trim($section);
+
+ // BODY[]
+ if (!strlen($section)) {
+ $fetch = $this->fetch($mailbox, array(
+ Horde_Imap_Client::FETCH_FULLMSG => array(
+ 'peek' => true,
+ 'stream' => true
+ )
+ ), array('ids' => array($uid)));
+ return $fetch[$uid]['fullmsg'];
+ }
+
+ // BODY[<#.>HEADER.FIELDS<.NOT>()]
+ if (($pos = stripos($section, 'HEADER.FIELDS')) !== false) {
+ $hdr_pos = strpos($section, '(');
+ $cmd = substr($section, 0, $hdr_pos);
+
+ $fetch = $this->fetch($mailbox, array(
+ Horde_Imap_Client::FETCH_HEADERS => array(
+ array(
+ 'headers' => explode(' ', substr($section, $hdr_pos + 1, strrpos($section, ')') - $hdr_pos)),
+ 'id' => ($pos ? substr($section, 0, $pos - 1) : 0),
+ 'label' => 'section',
+ 'notsearch' => (stripos($cmd, '.NOT') !== false),
+ 'peek' => true
+ )
+ )
+ ), array('ids' => array($uid)));
+
+ $stream = fopen('php://temp', 'w+');
+ fwrite($stream, $fetch[$uid]['headers']['section']);
+ return $stream;
+ }
+
+ // BODY[#]
+ if (is_numeric(substr($section, -1))) {
+ $fetch = $this->fetch($mailbox, array(
+ Horde_Imap_Client::FETCH_BODYPART => array(
+ array(
+ 'id' => $section,
+ 'peek' => true,
+ 'stream' => true
+ )
+ )
+ ), array('ids' => array($uid)));
+ return $fetch[$uid]['bodypart'][$section];
+ }
+
+ // BODY[<#.>HEADER]
+ if (($pos = stripos($section, 'HEADER')) !== false) {
+ $id = ($pos ? substr($section, 0, $pos - 1) : 0);
+ $fetch = $this->fetch($mailbox, array(
+ Horde_Imap_Client::FETCH_HEADERTEXT => array(
+ array(
+ 'id' => $id,
+ 'peek' => true
+ )
+ )
+ ), array('ids' => array($uid)));
+
+ $stream = fopen('php://temp', 'w+');
+ fwrite($stream, $fetch[$uid]['headertext'][$id]);
+ return $stream;
+ }
+
+ // BODY[<#.>TEXT]
+ if (($pos = stripos($section, 'TEXT')) !== false) {
+ $id = ($pos ? substr($section, 0, $pos - 1) : 0);
+ $fetch = $this->fetch($mailbox, array(
+ Horde_Imap_Client::FETCH_BODYTEXT => array(
+ array(
+ 'id' => $id,
+ 'peek' => true,
+ 'stream' => true
+ )
+ )
+ ), array('ids' => array($uid)));
+ return $fetch[$uid]['bodytext'][$id];
+ }
+
+ // BODY[<#.>MIMEHEADER]
+ if (($pos = stripos($section, 'MIME')) !== false) {
+ $id = ($pos ? substr($section, 0, $pos - 1) : 0);
+ $fetch = $this->fetch($mailbox, array(
+ Horde_Imap_Client::FETCH_MIMEHEADER => array(
+ array(
+ 'id' => $id,
+ 'peek' => true
+ )
+ )
+ ), array('ids' => array($uid)));
+
+ $stream = fopen('php://temp', 'w+');
+ fwrite($stream, $fetch[$uid]['mimeheader'][$id]);
+ return $stream;
+ }
+
+ return null;
+ }
+
+ /**
* Returns UIDs for an ALL search, or for a sequence number -> UID lookup.
*
* @param mixed $ids If null, return all UIDs for the mailbox. If an
$this->cache->setMetaData($mailbox, $uidvalid, $data);
}
+ /**
+ * Prepares append message data for insertion into the IMAP command
+ * string.
+ *
+ * @param mixed $data Either a resource or a string.
+ *
+ * @param resource A stream containing the message data.
+ */
+ protected function _prepareAppendData($data)
+ {
+ $stream = fopen('php://temp', 'w+');
+ stream_filter_register('horde_eol', 'Horde_Stream_Filter_Eol');
+ stream_filter_append($stream, 'horde_eol', STREAM_FILTER_WRITE);
+
+ if (is_resource($data)) {
+ rewind($data);
+ stream_copy_to_stream($data, $stream);
+ } else {
+ fwrite($stream, $data);
+ }
+
+ return $stream;
+ }
+
+ /**
+ * Builds a stream from CATENATE input to append().
+ *
+ * @param array $data See append() - array input for the 'data' key to
+ * the $data parameter.
+ *
+ * @return resource The data combined into a single stream.
+ * @throws Horde_Imap_Client_Exception
+ */
+ protected function _buildCatenateData($data)
+ {
+ $parts = array();
+
+ foreach (array_keys($data) as $key2) {
+ switch ($data[$key2]['t']) {
+ case 'text':
+ $parts[] = $this->_prepareAppendData($data[$key2]['v']);
+ break;
+
+ case 'url':
+ $part = null;
+ $url = $this->utils->parseUrl($data[$key2]['v']);
+
+ if (isset($url['mailbox']) &&
+ isset($url['uid'])) {
+ try {
+ $status_res = isset($url['uidvalidity'])
+ ? $this->status($url['mailbox'], Horde_Imap_Client::STATUS_UIDVALIDITY)
+ : null;
+
+ if (is_null($status_res) ||
+ ($status_res['uidvalidity'] == $url['uidvalidity'])) {
+ $part = $this->fetchFromSectionString($url['mailbox'], $url['uid'], isset($url['section']) ? $url['section'] : null);
+ }
+ } catch (Horde_Imap_Client_Exception $e) {}
+ }
+
+ if (is_null($part)) {
+ throw new Horde_Imap_Client_Exception('Bad IMAP URL given in CATENATE data', Horde_Imap_Client_Exception::CATENATE_BADURL);
+ } else {
+ $parts[] = $part;
+ }
+ break;
+ }
+ }
+
+ $swrapper = new Horde_Support_CombineStream($parts);
+ return $swrapper->fopen();
+ }
+
}
* RFC 4315 - UIDPLUS
* RFC 4422 - SASL Authentication (for DIGEST-MD5)
* RFC 4466 - Collected extensions (updates RFCs 2088, 3501, 3502, 3516)
+ * RFC 4469/5550 - CATENATE
* RFC 4551 - CONDSTORE
* RFC 4731 - ESEARCH
* RFC 4959 - SASL-IR
* time by each HTTP/PHP request)
* RFC 2193 - MAILBOX-REFERRALS
* RFC 4467/5092/5524/5550 - URLAUTH, URLFETCH=BINARY, URL-PARTIAL
- * RFC 4469/5550 - CATENATE
* RFC 4978 - COMPRESS=DEFLATE
* See: http://bugs.php.net/bug.php?id=48725
* RFC 5257 - ANNOTATE (Experimental)
// If the mailbox is currently selected read-only, we need to close
// because some IMAP implementations won't allow an append.
- if (($this->_selected == $mailbox) &&
- ($this->_mode == Horde_Imap_Client::OPEN_READONLY)) {
- $this->close();
- }
+ $this->close();
+
+ // Check for CATENATE extension (RFC 4469)
+ $catenate = $this->queryCapability('CATENATE');
$t = &$this->_temp;
$t['appenduid'] = array();
array('t' => Horde_Imap_Client::DATA_MAILBOX, 'v' => $mailbox)
);
- stream_filter_register('horde_eol', 'Horde_Stream_Filter_Eol');
-
foreach (array_keys($data) as $key) {
if (!empty($data[$key]['flags'])) {
$tmp = array();
$cmd[] = $data[$key]['internaldate']->format('j-M-Y H:i:s O');
}
- $text = fopen('php://temp', 'w+');
- stream_filter_append($text, 'horde_eol', STREAM_FILTER_WRITE);
+ if (is_array($data[$key]['data'])) {
+ if ($catenate) {
+ $cmd[] = 'CATENATE';
- if (is_resource($data[$key]['data'])) {
- rewind($data[$key]['data']);
- stream_copy_to_stream($data[$key]['data'], $text);
+ $tmp = array();
+ foreach (array_keys($data[$key]['data']) as $key2) {
+ switch ($data[$key]['data'][$key2]['t']) {
+ case 'text':
+ $tmp[] = 'TEXT';
+ $tmp[] = $this->_prepareAppendData($data[$key]['data'][$key2]['v']);
+ break;
+
+ case 'url':
+ $tmp[] = 'URL';
+ $tmp[] = $data[$key]['data'][$key2]['v'];
+ break;
+ }
+ }
+ $cmd[] = $tmp;
+ } else {
+ $cmd[] = $this->_buildCatenateData($data[$key]['data']);
+ }
} else {
- fwrite($text, $data[$key]['data']);
+ $cmd[] = $this->_prepareAppendData($data[$key]['data']);
}
-
- $cmd[] = $text;
}
try {
$this->_temp['mailbox']['uidnotsticky'] = true;
break;
+ case 'BADURL':
+ // Defined by RFC 4469 [4.1]
+ $this->_temp['parsestatuserr'] = array(
+ Horde_Imap_Client_Exception::CATENATE_BADURL,
+ substr($ob['line'], $end_pos + 2)
+ );
+ break;
+
+ case 'TOOBIG':
+ // Defined by RFC 4469 [4.2]
+ $this->_temp['parsestatuserr'] = array(
+ Horde_Imap_Client_Exception::CATENATE_TOOBIG,
+ substr($ob['line'], $end_pos + 2)
+ );
+ break;
+
case 'HIGHESTMODSEQ':
// Defined by RFC 4551 [3.1.1]
$this->_temp['mailbox']['highestmodseq'] = $data;