/**
  * Initializes object with SORT command response
  *
  * @param string $data IMAP response string
  */
 public function init($data = null)
 {
     $this->meta = array();
     $data = explode('*', (string) $data);
     // ...skip unilateral untagged server responses
     for ($i = 0, $len = count($data); $i < $len; $i++) {
         $data_item =& $data[$i];
         if (preg_match('/^ SORT/i', $data_item)) {
             $data_item = substr($data_item, 5);
             break;
         } else {
             if (preg_match('/^ (E?SEARCH)/i', $data_item, $m)) {
                 $data_item = substr($data_item, strlen($m[0]));
                 if (strtoupper($m[1]) == 'ESEARCH') {
                     $data_item = trim($data_item);
                     // remove MODSEQ response
                     if (preg_match('/\\(MODSEQ ([0-9]+)\\)$/i', $data_item, $m)) {
                         $data_item = substr($data_item, 0, -strlen($m[0]));
                         $this->params['MODSEQ'] = $m[1];
                     }
                     // remove TAG response part
                     if (preg_match('/^\\(TAG ["a-z0-9]+\\)\\s*/i', $data_item, $m)) {
                         $data_item = substr($data_item, strlen($m[0]));
                     }
                     // remove UID
                     $data_item = preg_replace('/^UID\\s*/i', '', $data_item);
                     // ESEARCH parameters
                     while (preg_match('/^([a-z]+) ([0-9:,]+)\\s*/i', $data_item, $m)) {
                         $param = strtoupper($m[1]);
                         $value = $m[2];
                         $this->params[$param] = $value;
                         $data_item = substr($data_item, strlen($m[0]));
                         if (in_array($param, array('COUNT', 'MIN', 'MAX'))) {
                             $this->meta[strtolower($param)] = (int) $value;
                         }
                     }
                     // @TODO: Implement compression using compressMessageSet() in __sleep() and __wakeup() ?
                     // @TODO: work with compressed result?!
                     if (isset($this->params['ALL'])) {
                         $data_item = implode(self::SEPARATOR_ELEMENT, rcube_imap_generic::uncompressMessageSet($this->params['ALL']));
                     }
                 }
                 break;
             }
         }
         unset($data[$i]);
     }
     $data = array_filter($data);
     if (empty($data)) {
         return;
     }
     $data = array_shift($data);
     $data = trim($data);
     $data = preg_replace('/[\\r\\n]/', '', $data);
     $data = preg_replace('/\\s+/', ' ', $data);
     $this->raw_data = $data;
 }
 /**
  * Test for uncompressMessageSet
  */
 function test_uncompressMessageSet()
 {
     $result = rcube_imap_generic::uncompressMessageSet(null);
     $this->assertSame(array(), $result);
     $this->assertCount(0, $result);
     $result = rcube_imap_generic::uncompressMessageSet('1');
     $this->assertSame(array(1), $result);
     $this->assertCount(1, $result);
     $result = rcube_imap_generic::uncompressMessageSet('1:3');
     $this->assertSame(array(1, 2, 3), $result);
     $this->assertCount(3, $result);
 }
 /**
  * Parse message UIDs input
  *
  * @param mixed $uids UIDs array or comma-separated list or '*' or '1:*'
  *
  * @return array Two elements array with UIDs converted to list and ALL flag
  */
 protected function parse_uids($uids)
 {
     if ($uids === '*' || $uids === '1:*') {
         if (empty($this->search_set)) {
             $uids = '1:*';
             $all = true;
         } else {
             $uids = join(',', $this->search_set->get());
         }
     } else {
         if (is_array($uids)) {
             $uids = join(',', $uids);
         } else {
             if (strpos($uids, ':')) {
                 $uids = join(',', rcube_imap_generic::uncompressMessageSet($uids));
             }
         }
         if (preg_match('/[^0-9,]/', $uids)) {
             $uids = '';
         }
     }
     return array($uids, (bool) $all);
 }
 /**
  * Synchronizes the mailbox.
  *
  * @param string $mailbox Folder name
  */
 function synchronize($mailbox)
 {
     // RFC4549: Synchronization Operations for Disconnected IMAP4 Clients
     // RFC4551: IMAP Extension for Conditional STORE Operation
     //          or Quick Flag Changes Resynchronization
     // RFC5162: IMAP Extensions for Quick Mailbox Resynchronization
     // @TODO: synchronize with other methods?
     $qresync = $this->imap->get_capability('QRESYNC');
     $condstore = $qresync ? true : $this->imap->get_capability('CONDSTORE');
     if (!$qresync && !$condstore) {
         return;
     }
     // Get stored index
     $index = $this->get_index_row($mailbox);
     // database is empty
     if (empty($index)) {
         // set the flag that DB was already queried for index
         // this way we'll be able to skip one SELECT in get_index()
         $this->icache[$mailbox]['index_queried'] = true;
         return;
     }
     $this->icache[$mailbox]['index'] = $index;
     // no last HIGHESTMODSEQ value
     if (empty($index['modseq'])) {
         return;
     }
     if (!$this->imap->check_connection()) {
         return;
     }
     // Enable QRESYNC
     $res = $this->imap->conn->enable($qresync ? 'QRESYNC' : 'CONDSTORE');
     if ($res === false) {
         return;
     }
     // Close mailbox if already selected to get most recent data
     if ($this->imap->conn->selected == $mailbox) {
         $this->imap->conn->close();
     }
     // Get mailbox data (UIDVALIDITY, HIGHESTMODSEQ, counters, etc.)
     $mbox_data = $this->imap->folder_data($mailbox);
     if (empty($mbox_data)) {
         return;
     }
     // Check UIDVALIDITY
     if ($index['validity'] != $mbox_data['UIDVALIDITY']) {
         $this->clear($mailbox);
         return;
     }
     // QRESYNC not supported on specified mailbox
     if (!empty($mbox_data['NOMODSEQ']) || empty($mbox_data['HIGHESTMODSEQ'])) {
         return;
     }
     // Nothing new
     if ($mbox_data['HIGHESTMODSEQ'] == $index['modseq']) {
         return;
     }
     $uids = array();
     $removed = array();
     // Get known UIDs
     if ($this->mode & self::MODE_MESSAGE) {
         $sql_result = $this->db->query("SELECT `uid`" . " FROM {$this->messages_table}" . " WHERE `user_id` = ?" . " AND `mailbox` = ?", $this->userid, $mailbox);
         while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
             $uids[] = $sql_arr['uid'];
         }
     }
     // Synchronize messages data
     if (!empty($uids)) {
         // Get modified flags and vanished messages
         // UID FETCH 1:* (FLAGS) (CHANGEDSINCE 0123456789 VANISHED)
         $result = $this->imap->conn->fetch($mailbox, $uids, true, array('FLAGS'), $index['modseq'], $qresync);
         if (!empty($result)) {
             foreach ($result as $msg) {
                 $uid = $msg->uid;
                 // Remove deleted message
                 if ($this->skip_deleted && !empty($msg->flags['DELETED'])) {
                     $removed[] = $uid;
                     // Invalidate index
                     $index['valid'] = false;
                     continue;
                 }
                 $flags = 0;
                 if (!empty($msg->flags)) {
                     foreach ($this->flags as $idx => $flag) {
                         if (!empty($msg->flags[$flag])) {
                             $flags += $idx;
                         }
                     }
                 }
                 $this->db->query("UPDATE {$this->messages_table}" . " SET `flags` = ?, `expires` = " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL') . " WHERE `user_id` = ?" . " AND `mailbox` = ?" . " AND `uid` = ?" . " AND `flags` <> ?", $flags, $this->userid, $mailbox, $uid, $flags);
             }
         }
         // VANISHED found?
         if ($qresync) {
             $mbox_data = $this->imap->folder_data($mailbox);
             // Removed messages found
             $uids = rcube_imap_generic::uncompressMessageSet($mbox_data['VANISHED']);
             if (!empty($uids)) {
                 $removed = array_merge($removed, $uids);
                 // Invalidate index
                 $index['valid'] = false;
             }
         }
         // remove messages from database
         if (!empty($removed)) {
             $this->remove_message($mailbox, $removed);
         }
     }
     $sort_field = $index['sort_field'];
     $sort_order = $index['object']->get_parameters('ORDER');
     $exists = true;
     // Validate index
     if (!$this->validate($mailbox, $index, $exists)) {
         // Invalidate (remove) thread index
         // if $exists=false it was already removed in validate()
         if ($exists) {
             $this->remove_thread($mailbox);
         }
         // Update index
         $data = $this->get_index_data($mailbox, $sort_field, $sort_order, $mbox_data);
     } else {
         $data = $index['object'];
     }
     // update index and/or HIGHESTMODSEQ value
     $this->add_index_row($mailbox, $sort_field, $data, $mbox_data, $exists);
     // update internal cache for get_index()
     $this->icache[$mailbox]['index']['object'] = $data;
 }
Example #5
0
 /**
  * Checks if the cache is up-to-date
  *
  * @param string $mailbox   Mailbox name
  * @param string $cache_key Internal cache key
  * @return int   Cache status: -3 = off, -2 = incomplete, -1 = dirty, 1 = OK
  */
 private function check_cache_status($mailbox, $cache_key)
 {
     if (!$this->caching_enabled) {
         return -3;
     }
     $cache_index = $this->get_message_cache_index($cache_key);
     $msg_count = $this->_messagecount($mailbox);
     $cache_count = count($cache_index);
     // empty mailbox
     if (!$msg_count) {
         return $cache_count ? -2 : 1;
     }
     if ($cache_count == $msg_count) {
         if ($this->skip_deleted) {
             if (!empty($this->icache['all_undeleted_idx'])) {
                 $uids = rcube_imap_generic::uncompressMessageSet($this->icache['all_undeleted_idx']);
                 $uids = array_flip($uids);
                 foreach ($cache_index as $uid) {
                     unset($uids[$uid]);
                 }
             } else {
                 // get all undeleted messages excluding cached UIDs
                 $uids = $this->search_once($mailbox, 'ALL UNDELETED NOT UID ' . rcube_imap_generic::compressMessageSet($cache_index));
             }
             if (empty($uids)) {
                 return 1;
             }
         } else {
             // get UID of the message with highest index
             $uid = $this->_id2uid($msg_count, $mailbox);
             $cache_uid = array_pop($cache_index);
             // uids of highest message matches -> cache seems OK
             if ($cache_uid == $uid) {
                 return 1;
             }
         }
         // cache is dirty
         return -1;
     }
     // if cache count differs less than 10% report as dirty
     return abs($msg_count - $cache_count) < $msg_count / 10 ? -1 : -2;
 }
Example #6
0
 /**
  * Synchronizes the mailbox.
  *
  * @param string $mailbox Folder name
  */
 function synchronize($mailbox)
 {
     // RFC4549: Synchronization Operations for Disconnected IMAP4 Clients
     // RFC4551: IMAP Extension for Conditional STORE Operation
     //          or Quick Flag Changes Resynchronization
     // RFC5162: IMAP Extensions for Quick Mailbox Resynchronization
     // @TODO: synchronize with other methods?
     $qresync = $this->imap->get_capability('QRESYNC');
     $condstore = $qresync ? true : $this->imap->get_capability('CONDSTORE');
     if (!$qresync && !$condstore) {
         return;
     }
     // Get stored index
     $index = $this->get_index_row($mailbox);
     // database is empty
     if (empty($index)) {
         // set the flag that DB was already queried for index
         // this way we'll be able to skip one SELECT in get_index()
         $this->icache[$mailbox]['index_queried'] = true;
         return;
     }
     $this->icache[$mailbox]['index'] = $index;
     // no last HIGHESTMODSEQ value
     if (empty($index['modseq'])) {
         return;
     }
     // NOTE: make sure the mailbox isn't selected, before
     // enabling QRESYNC and invoking SELECT
     if ($this->imap->conn->selected !== null) {
         $this->imap->conn->close();
     }
     // Enable QRESYNC
     $res = $this->imap->conn->enable($qresync ? 'QRESYNC' : 'CONDSTORE');
     if (!is_array($res)) {
         return;
     }
     // Get mailbox data (UIDVALIDITY, HIGHESTMODSEQ, counters, etc.)
     $mbox_data = $this->imap->mailbox_data($mailbox);
     if (empty($mbox_data)) {
         return;
     }
     // Check UIDVALIDITY
     if ($index['validity'] != $mbox_data['UIDVALIDITY']) {
         $this->clear($mailbox);
         return;
     }
     // QRESYNC not supported on specified mailbox
     if (!empty($mbox_data['NOMODSEQ']) || empty($mbox_data['HIGHESTMODSEQ'])) {
         return;
     }
     // Nothing new
     if ($mbox_data['HIGHESTMODSEQ'] == $index['modseq']) {
         return;
     }
     // Get known uids
     $uids = array();
     $sql_result = $this->db->query("SELECT uid" . " FROM " . get_table_name('cache_messages') . " WHERE user_id = ?" . " AND mailbox = ?", $this->userid, $mailbox);
     while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
         $uids[] = $sql_arr['uid'];
     }
     // No messages in database, nothing to sync
     if (empty($uids)) {
         return;
     }
     // Get modified flags and vanished messages
     // UID FETCH 1:* (FLAGS) (CHANGEDSINCE 0123456789 VANISHED)
     $result = $this->imap->conn->fetch($mailbox, !empty($uids) ? $uids : '1:*', true, array('FLAGS'), $index['modseq'], $qresync);
     $invalidated = false;
     if (!empty($result)) {
         foreach ($result as $id => $msg) {
             $uid = $msg->uid;
             // Remove deleted message
             if ($this->skip_deleted && !empty($msg->flags['DELETED'])) {
                 $this->remove_message($mailbox, $uid);
                 if (!$invalidated) {
                     $invalidated = true;
                     // Invalidate thread indexes (?)
                     $this->remove_thread($mailbox);
                     // Invalidate index
                     $index['valid'] = false;
                 }
                 continue;
             }
             $flags = 0;
             if (!empty($msg->flags)) {
                 foreach ($this->flags as $idx => $flag) {
                     if (!empty($msg->flags[$flag])) {
                         $flags += $idx;
                     }
                 }
             }
             $this->db->query("UPDATE " . get_table_name('cache_messages') . " SET flags = ?, changed = " . $this->db->now() . " WHERE user_id = ?" . " AND mailbox = ?" . " AND uid = ?" . " AND flags <> ?", $flags, $this->userid, $mailbox, $uid, $flags);
         }
     }
     // Get VANISHED
     if ($qresync) {
         $mbox_data = $this->imap->mailbox_data($mailbox);
         // Removed messages
         if (!empty($mbox_data['VANISHED'])) {
             $uids = rcube_imap_generic::uncompressMessageSet($mbox_data['VANISHED']);
             if (!empty($uids)) {
                 // remove messages from database
                 $this->remove_message($mailbox, $uids);
                 // Invalidate thread indexes (?)
                 $this->remove_thread($mailbox);
                 // Invalidate index
                 $index['valid'] = false;
             }
         }
     }
     $sort_field = $index['sort_field'];
     $sort_order = $index['sort_order'];
     $exists = true;
     // Validate index
     if (!$this->validate($mailbox, $index, $exists)) {
         // Update index
         $data = $this->get_index_data($mailbox, $sort_field, $sort_order, $mbox_data);
     } else {
         $data = array_combine($index['seq'], $index['uid']);
     }
     // update index and/or HIGHESTMODSEQ value
     $this->add_index_row($mailbox, $sort_field, $sort_order, $data, $mbox_data, $exists);
     // update internal cache for get_index()
     $this->icache[$mailbox]['index']['result'] = $data;
 }