Page MenuHomePhorge

D5072.1775294729.diff
No OneTemporary

Authored By
Unknown
Size
103 KB
Referenced Files
None
Subscribers
None

D5072.1775294729.diff

diff --git a/src/app/DataMigrator/DAV.php b/src/app/DataMigrator/Driver/DAV.php
rename from src/app/DataMigrator/DAV.php
rename to src/app/DataMigrator/Driver/DAV.php
--- a/src/app/DataMigrator/DAV.php
+++ b/src/app/DataMigrator/Driver/DAV.php
@@ -1,11 +1,13 @@
<?php
-namespace App\DataMigrator;
+namespace App\DataMigrator\Driver;
use App\Backends\DAV as DAVClient;
use App\Backends\DAV\Opaque as DAVOpaque;
use App\Backends\DAV\Folder as DAVFolder;
use App\Backends\DAV\Search as DAVSearch;
+use App\DataMigrator\Account;
+use App\DataMigrator\Engine;
use App\DataMigrator\Interface\Folder;
use App\DataMigrator\Interface\ExporterInterface;
use App\DataMigrator\Interface\ImporterInterface;
@@ -433,7 +435,7 @@
{
// When dealing with iRony DAV other user folders names have distinct names
// there's no other way to recognize them than by the name pattern.
- // ;et's hope that users do not have personal folders with names starting with a bracket.
+ // Let's hope that users do not have personal folders with names starting with a bracket.
if (preg_match('~\(.*\) .*~', $folder->name)) {
return true;
diff --git a/src/app/DataMigrator/EWS.php b/src/app/DataMigrator/Driver/EWS.php
rename from src/app/DataMigrator/EWS.php
rename to src/app/DataMigrator/Driver/EWS.php
--- a/src/app/DataMigrator/EWS.php
+++ b/src/app/DataMigrator/Driver/EWS.php
@@ -1,10 +1,15 @@
<?php
-namespace App\DataMigrator;
+namespace App\DataMigrator\Driver;
+use App\DataMigrator\Account;
+use App\DataMigrator\Engine;
+use App\DataMigrator\Interface\ExporterInterface;
+use App\DataMigrator\Interface\ImporterInterface;
use App\DataMigrator\Interface\Folder;
use App\DataMigrator\Interface\Item;
use App\DataMigrator\Interface\ItemSet;
+use App\DataMigrator\Queue;
use garethp\ews\API;
use garethp\ews\API\Type;
use Illuminate\Support\Facades\Http;
@@ -12,7 +17,7 @@
/**
* Data migration from Exchange (EWS)
*/
-class EWS implements Interface\ExporterInterface
+class EWS implements ExporterInterface
{
/** @const int Max number of items to migrate in one go */
protected const CHUNK_SIZE = 20;
@@ -307,7 +312,7 @@
/**
* Fetch a list of folder items
*/
- public function fetchItemList(Folder $folder, $callback, Interface\ImporterInterface $importer): void
+ public function fetchItemList(Folder $folder, $callback, ImporterInterface $importer): void
{
// Job processing - initialize environment
$this->initEnv($this->engine->queue);
diff --git a/src/app/DataMigrator/EWS/Appointment.php b/src/app/DataMigrator/Driver/EWS/Appointment.php
rename from src/app/DataMigrator/EWS/Appointment.php
rename to src/app/DataMigrator/Driver/EWS/Appointment.php
--- a/src/app/DataMigrator/EWS/Appointment.php
+++ b/src/app/DataMigrator/Driver/EWS/Appointment.php
@@ -1,6 +1,6 @@
<?php
-namespace App\DataMigrator\EWS;
+namespace App\DataMigrator\Driver\EWS;
use garethp\ews\API;
use garethp\ews\API\Type;
diff --git a/src/app/DataMigrator/EWS/Contact.php b/src/app/DataMigrator/Driver/EWS/Contact.php
rename from src/app/DataMigrator/EWS/Contact.php
rename to src/app/DataMigrator/Driver/EWS/Contact.php
--- a/src/app/DataMigrator/EWS/Contact.php
+++ b/src/app/DataMigrator/Driver/EWS/Contact.php
@@ -1,6 +1,6 @@
<?php
-namespace App\DataMigrator\EWS;
+namespace App\DataMigrator\Driver\EWS;
use garethp\ews\API\Type;
diff --git a/src/app/DataMigrator/EWS/DistList.php b/src/app/DataMigrator/Driver/EWS/DistList.php
rename from src/app/DataMigrator/EWS/DistList.php
rename to src/app/DataMigrator/Driver/EWS/DistList.php
--- a/src/app/DataMigrator/EWS/DistList.php
+++ b/src/app/DataMigrator/Driver/EWS/DistList.php
@@ -1,6 +1,6 @@
<?php
-namespace App\DataMigrator\EWS;
+namespace App\DataMigrator\Driver\EWS;
use garethp\ews\API\Type;
diff --git a/src/app/DataMigrator/EWS/Email.php b/src/app/DataMigrator/Driver/EWS/Email.php
rename from src/app/DataMigrator/EWS/Email.php
rename to src/app/DataMigrator/Driver/EWS/Email.php
--- a/src/app/DataMigrator/EWS/Email.php
+++ b/src/app/DataMigrator/Driver/EWS/Email.php
@@ -1,6 +1,6 @@
<?php
-namespace App\DataMigrator\EWS;
+namespace App\DataMigrator\Driver\EWS;
use garethp\ews\API\Type;
diff --git a/src/app/DataMigrator/EWS/Item.php b/src/app/DataMigrator/Driver/EWS/Item.php
rename from src/app/DataMigrator/EWS/Item.php
rename to src/app/DataMigrator/Driver/EWS/Item.php
--- a/src/app/DataMigrator/EWS/Item.php
+++ b/src/app/DataMigrator/Driver/EWS/Item.php
@@ -1,8 +1,8 @@
<?php
-namespace App\DataMigrator\EWS;
+namespace App\DataMigrator\Driver\EWS;
-use App\DataMigrator\EWS;
+use App\DataMigrator\Driver\EWS;
use App\DataMigrator\Engine;
use App\DataMigrator\Interface\Folder as FolderInterface;
use App\DataMigrator\Interface\Item as ItemInterface;
diff --git a/src/app/DataMigrator/EWS/Note.php b/src/app/DataMigrator/Driver/EWS/Note.php
rename from src/app/DataMigrator/EWS/Note.php
rename to src/app/DataMigrator/Driver/EWS/Note.php
--- a/src/app/DataMigrator/EWS/Note.php
+++ b/src/app/DataMigrator/Driver/EWS/Note.php
@@ -1,6 +1,6 @@
<?php
-namespace App\DataMigrator\EWS;
+namespace App\DataMigrator\Driver\EWS;
use garethp\ews\API\Type;
diff --git a/src/app/DataMigrator/EWS/ScheduleMeeting.php b/src/app/DataMigrator/Driver/EWS/ScheduleMeeting.php
rename from src/app/DataMigrator/EWS/ScheduleMeeting.php
rename to src/app/DataMigrator/Driver/EWS/ScheduleMeeting.php
--- a/src/app/DataMigrator/EWS/ScheduleMeeting.php
+++ b/src/app/DataMigrator/Driver/EWS/ScheduleMeeting.php
@@ -1,6 +1,6 @@
<?php
-namespace App\DataMigrator\EWS;
+namespace App\DataMigrator\Driver\EWS;
use garethp\ews\API\Type;
diff --git a/src/app/DataMigrator/EWS/Task.php b/src/app/DataMigrator/Driver/EWS/Task.php
rename from src/app/DataMigrator/EWS/Task.php
rename to src/app/DataMigrator/Driver/EWS/Task.php
--- a/src/app/DataMigrator/EWS/Task.php
+++ b/src/app/DataMigrator/Driver/EWS/Task.php
@@ -1,6 +1,6 @@
<?php
-namespace App\DataMigrator\EWS;
+namespace App\DataMigrator\Driver\EWS;
use garethp\ews\API\Type;
diff --git a/src/app/DataMigrator/IMAP.php b/src/app/DataMigrator/Driver/IMAP.php
rename from src/app/DataMigrator/IMAP.php
rename to src/app/DataMigrator/Driver/IMAP.php
--- a/src/app/DataMigrator/IMAP.php
+++ b/src/app/DataMigrator/Driver/IMAP.php
@@ -1,7 +1,9 @@
<?php
-namespace App\DataMigrator;
+namespace App\DataMigrator\Driver;
+use App\DataMigrator\Account;
+use App\DataMigrator\Engine;
use App\DataMigrator\Interface\Folder;
use App\DataMigrator\Interface\ExporterInterface;
use App\DataMigrator\Interface\ImporterInterface;
@@ -366,7 +368,7 @@
/**
* Initialize IMAP connection and authenticate the user
*/
- private static function initIMAP(array $config): \rcube_imap_generic
+ protected static function initIMAP(array $config): \rcube_imap_generic
{
$imap = new \rcube_imap_generic();
@@ -390,7 +392,7 @@
/**
* Get IMAP configuration
*/
- private static function getConfig(Account $account): array
+ protected static function getConfig(Account $account): array
{
$uri = \parse_url($account->uri);
$default_port = 143;
@@ -439,7 +441,7 @@
/**
* Limit IMAP flags to these that can be migrated
*/
- private function filterImapFlags($flags)
+ protected function filterImapFlags($flags)
{
// TODO: Support custom flags migration
@@ -454,7 +456,7 @@
/**
* Check if the folder should not be migrated
*/
- private function shouldSkip($folder): bool
+ protected function shouldSkip($folder): bool
{
// TODO: This should probably use NAMESPACE information
@@ -468,7 +470,7 @@
/**
* Return Message-Id, generate unique identifier if Message-Id does not exist
*/
- private function getMessageId($message, $folder): string
+ protected function getMessageId($message, $folder): string
{
if (!empty($message->messageID)) {
return $message->messageID;
diff --git a/src/app/DataMigrator/Driver/Kolab.php b/src/app/DataMigrator/Driver/Kolab.php
new file mode 100644
--- /dev/null
+++ b/src/app/DataMigrator/Driver/Kolab.php
@@ -0,0 +1,257 @@
+<?php
+
+namespace App\DataMigrator\Driver;
+
+use App\DataMigrator\Account;
+use App\DataMigrator\Engine;
+use App\DataMigrator\Interface\Folder;
+use App\DataMigrator\Interface\ImporterInterface;
+use App\DataMigrator\Interface\Item;
+use App\DataMigrator\Interface\ItemSet;
+
+/**
+ * Data migration from/to a Kolab server
+ */
+class Kolab extends IMAP
+{
+ protected const CTYPE_KEY = '/shared/vendor/kolab/folder-type';
+ protected const CTYPE_KEY_PRIVATE = '/private/vendor/kolab/folder-type';
+ protected const DAV_TYPES = [
+ Engine::TYPE_CONTACT,
+ Engine::TYPE_EVENT,
+ Engine::TYPE_TASK,
+ ];
+
+ /** @var DAV DAV importer/exporter engine */
+ protected $davDriver;
+
+ /**
+ * Object constructor
+ */
+ public function __construct(Account $account, Engine $engine)
+ {
+ // TODO: Right now we require IMAP server and DAV host names, but for Kolab we should be able
+ // to detect IMAP and DAV locations, e.g. so we can just provide "kolabnow.com" as an input
+ // Note: E.g. KolabNow uses different hostname for DAV, pass it as a query parameter
+ // 'dav_host' and 'dav_path'.
+
+ // Setup IMAP connection
+ $uri = (string) $account;
+ $uri = preg_replace('/^kolab:/', 'tls:', $uri);
+
+ parent::__construct(new Account($uri), $engine);
+
+ // Setup DAV connection
+ $uri = sprintf(
+ 'davs://%s:%s@%s/%s',
+ urlencode($account->username),
+ urlencode($account->password),
+ $account->params['dav_host'] ?? $account->host,
+ $account->params['dav_path'] ?? '', // e.g. 'dav'
+ );
+
+ $this->davDriver = new DAV(new Account($uri), $engine);
+ }
+
+ /**
+ * Authenticate
+ */
+ public function authenticate(): void
+ {
+ // IMAP
+ parent::authenticate();
+
+ // DAV
+ $this->davDriver->authenticate();
+ }
+
+ /**
+ * Create a folder.
+ *
+ * @param Folder $folder Folder data
+ *
+ * @throws \Exception on error
+ */
+ public function createFolder(Folder $folder): void
+ {
+ // IMAP
+ if ($folder->type == Engine::TYPE_MAIL) {
+ parent::createFolder($folder);
+ return;
+ }
+
+ // DAV
+ if (in_array($folder->type, self::DAV_TYPES)) {
+ $this->davDriver->createFolder($folder);
+ return;
+ }
+ }
+
+ /**
+ * Create an item in a folder.
+ *
+ * @param Item $item Item to import
+ *
+ * @throws \Exception
+ */
+ public function createItem(Item $item): void
+ {
+ // IMAP
+ if ($item->folder->type == Engine::TYPE_MAIL) {
+ parent::createItem($item);
+ return;
+ }
+
+ // DAV
+ if (in_array($item->folder->type, self::DAV_TYPES)) {
+ $this->davDriver->createItem($item);
+ return;
+ }
+
+ // TODO: Configuration (v3 tags)
+ }
+
+ /**
+ * Fetching an item
+ */
+ public function fetchItem(Item $item): void
+ {
+ // IMAP
+ if ($item->folder->type == Engine::TYPE_MAIL) {
+ parent::fetchItem($item);
+ return;
+ }
+
+ // DAV
+ if (in_array($item->folder->type, self::DAV_TYPES)) {
+ $this->davDriver->fetchItem($item);
+ return;
+ }
+
+ // TODO: Configuration (v3 tags)
+ }
+
+ /**
+ * Fetch a list of folder items
+ */
+ public function fetchItemList(Folder $folder, $callback, ImporterInterface $importer): void
+ {
+ // IMAP
+ if ($folder->type == Engine::TYPE_MAIL) {
+ parent::fetchItemList($folder, $callback, $importer);
+ return;
+ }
+
+ // DAV
+ if (in_array($folder->type, self::DAV_TYPES)) {
+ $this->davDriver->fetchItemList($folder, $callback, $importer);
+ return;
+ }
+
+ // TODO: Configuration (v3 tags)
+ }
+
+ /**
+ * Get folders hierarchy
+ */
+ public function getFolders($types = []): array
+ {
+ // Using only IMAP to get the list of all folders works with Kolab v3, but not v4.
+ // We could use IMAP, extract the XML, convert to iCal/vCard format and pass to DAV.
+ // But it will be easier to use DAV for contact/task/event folders migration.
+ $result = [];
+
+ // Get DAV folders
+ if (empty($types) || count(array_intersect($types, self::DAV_TYPES)) > 0) {
+ $result = $this->davDriver->getFolders($types);
+ }
+
+ if (!empty($types) && count(array_intersect($types, [Engine::TYPE_MAIL, Engine::TYPE_CONFIGURATION])) == 0) {
+ return $result;
+ }
+
+ // Get IMAP (mail and configuration) folders
+ $folders = $this->imap->listMailboxes('', '');
+
+ if ($folders === false) {
+ throw new \Exception("Failed to get list of IMAP folders");
+ }
+
+ $metadata = $this->imap->getMetadata('*', [self::CTYPE_KEY, self::CTYPE_KEY_PRIVATE]);
+
+ if ($metadata === null) {
+ throw new \Exception("Failed to get METADATA for IMAP folders. Not a Kolab server?");
+ }
+
+ $configuration_folders = [];
+
+ foreach ($folders as $folder) {
+ $type = 'mail';
+ if (!empty($metadata[$folder][self::CTYPE_KEY_PRIVATE])) {
+ $type = $metadata[$folder][self::CTYPE_KEY_PRIVATE];
+ } elseif (!empty($metadata[$folder][self::CTYPE_KEY])) {
+ $type = $metadata[$folder][self::CTYPE_KEY];
+ }
+
+ [$type] = explode('.', $type);
+
+ // These types we do not support
+ if ($type != Engine::TYPE_MAIL && $type != Engine::TYPE_CONFIGURATION) {
+ continue;
+ }
+
+ if (!empty($types) && !in_array($type, $types)) {
+ continue;
+ }
+
+ if ($this->shouldSkip($folder)) {
+ \Log::debug("Skipping folder {$folder}.");
+ continue;
+ }
+
+ $folder = Folder::fromArray([
+ 'fullname' => $folder,
+ 'type' => $type,
+ ]);
+
+ if ($type == Engine::TYPE_CONFIGURATION) {
+ $configuration_folders[] = $folder;
+ } else {
+ $result[] = $folder;
+ }
+ }
+
+ // Put configuration folders at the end of the list
+ // Migrating tags requires all members already migrated
+ if (!empty($configuration_folders)) {
+ $result = array_merge($result, $configuration_folders);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get a list of folder items, limited to their essential propeties
+ * used in incremental migration to skip unchanged items.
+ */
+ public function getItems(Folder $folder): array
+ {
+ // IMAP
+ if ($folder->type == Engine::TYPE_MAIL) {
+ return parent::getItems($folder);
+ }
+
+ // DAV
+ if (in_array($folder->type, self::DAV_TYPES)) {
+ return $this->davDriver->getItems($folder);
+ }
+
+ // Configuration folder (v3 tags)
+ if ($folder->type == Engine::TYPE_CONFIGURATION) {
+ // X-Kolab-Type: application/x-vnd.kolab.configuration.relation
+ // TODO
+ }
+
+ return [];
+ }
+}
diff --git a/src/app/DataMigrator/Test.php b/src/app/DataMigrator/Driver/Test.php
rename from src/app/DataMigrator/Test.php
rename to src/app/DataMigrator/Driver/Test.php
--- a/src/app/DataMigrator/Test.php
+++ b/src/app/DataMigrator/Driver/Test.php
@@ -1,7 +1,9 @@
<?php
-namespace App\DataMigrator;
+namespace App\DataMigrator\Driver;
+use App\DataMigrator\Account;
+use App\DataMigrator\Engine;
use App\DataMigrator\Interface\Folder;
use App\DataMigrator\Interface\ExporterInterface;
use App\DataMigrator\Interface\ImporterInterface;
diff --git a/src/app/DataMigrator/Engine.php b/src/app/DataMigrator/Engine.php
--- a/src/app/DataMigrator/Engine.php
+++ b/src/app/DataMigrator/Engine.php
@@ -21,6 +21,8 @@
public const TYPE_MAIL = 'mail';
public const TYPE_NOTE = 'note';
public const TYPE_TASK = 'task';
+ public const TYPE_CONFIGURATION = 'configuration';
+ public const TYPE_FILE = 'file';
/** @const int Max item size to handle in-memory, bigger will be handled with temp files */
public const MAX_ITEM_SIZE = 20 * 1024 * 1024;
@@ -364,23 +366,27 @@
{
switch ($account->scheme) {
case 'ews':
- $driver = new EWS($account, $this);
+ $driver = new Driver\EWS($account, $this);
break;
case 'dav':
case 'davs':
- $driver = new DAV($account, $this);
+ $driver = new Driver\DAV($account, $this);
break;
case 'imap':
case 'imaps':
case 'tls':
case 'ssl':
- $driver = new IMAP($account, $this);
+ $driver = new Driver\IMAP($account, $this);
+ break;
+
+ case 'kolab':
+ $driver = new Driver\Kolab($account, $this);
break;
case 'test':
- $driver = new Test($account, $this);
+ $driver = new Driver\Test($account, $this);
break;
default:
diff --git a/src/include/rcube_imap_generic.php b/src/include/rcube_imap_generic.php
--- a/src/include/rcube_imap_generic.php
+++ b/src/include/rcube_imap_generic.php
@@ -34,16 +34,16 @@
public $result;
public $resultcode;
public $selected;
- public $data = [];
+ public $data = [];
public $flags = [
- 'SEEN' => '\\Seen',
- 'DELETED' => '\\Deleted',
- 'ANSWERED' => '\\Answered',
- 'DRAFT' => '\\Draft',
- 'FLAGGED' => '\\Flagged',
+ 'SEEN' => '\Seen',
+ 'DELETED' => '\Deleted',
+ 'ANSWERED' => '\Answered',
+ 'DRAFT' => '\Draft',
+ 'FLAGGED' => '\Flagged',
'FORWARDED' => '$Forwarded',
- 'MDNSENT' => '$MDNSent',
- '*' => '\\*',
+ 'MDNSENT' => '$MDNSent',
+ '*' => '\*',
];
protected $fp;
@@ -52,30 +52,29 @@
protected $cmd_tag;
protected $cmd_num = 0;
protected $resourceid;
- protected $extensions_enabled;
- protected $prefs = [];
- protected $logged = false;
- protected $capability = [];
- protected $capability_read = false;
- protected $debug = false;
- protected $debug_handler = false;
-
- public const ERROR_OK = 0;
- public const ERROR_NO = -1;
- public const ERROR_BAD = -2;
- public const ERROR_BYE = -3;
- public const ERROR_UNKNOWN = -4;
- public const ERROR_COMMAND = -5;
+ protected $extensions_enabled = [];
+ protected $prefs = [];
+ protected $logged = false;
+ protected $capability = [];
+ protected $capability_read = false;
+ protected $debug = false;
+ protected $debug_handler = false;
+
+ public const ERROR_OK = 0;
+ public const ERROR_NO = -1;
+ public const ERROR_BAD = -2;
+ public const ERROR_BYE = -3;
+ public const ERROR_UNKNOWN = -4;
+ public const ERROR_COMMAND = -5;
public const ERROR_READONLY = -6;
public const COMMAND_NORESPONSE = 1;
public const COMMAND_CAPABILITY = 2;
- public const COMMAND_LASTLINE = 4;
+ public const COMMAND_LASTLINE = 4;
public const COMMAND_ANONYMIZED = 8;
public const DEBUG_LINE_LENGTH = 4098; // 4KB + 2B for \r\n
-
/**
* Send simple (one line) command to the connection stream
*
@@ -83,7 +82,7 @@
* @param bool $endln True if CRLF need to be added at the end of command
* @param bool $anonymized Don't write the given data to log but a placeholder
*
- * @return int Number of bytes sent, False on error
+ * @return int|false Number of bytes sent, False on error
*/
protected function putLine($string, $endln = true, $anonymized = false)
{
@@ -126,7 +125,7 @@
* @param bool $endln True if CRLF need to be added at the end of command
* @param bool $anonymized Don't write the given data to log but a placeholder
*
- * @return int|bool Number of bytes sent, False on error
+ * @return int|false Number of bytes sent, False on error
*/
protected function putLineC($string, $endln = true, $anonymized = false)
{
@@ -235,7 +234,7 @@
// include all string literals untile the real end of "line"
while (preg_match('/\{([0-9]+)\}\r\n$/', $line, $m)) {
$bytes = $m[1];
- $out = '';
+ $out = '';
while (strlen($out) < $bytes) {
$out = $this->readBytes($bytes);
@@ -265,8 +264,8 @@
{
$line = rtrim($line);
if (preg_match('/\{([0-9]+)\}$/', $line, $m)) {
- $out = '';
- $str = substr($line, 0, -strlen($m[0]));
+ $out = '';
+ $str = substr($line, 0, -strlen($m[0]));
$bytes = $m[1];
while (strlen($out) < $bytes) {
@@ -294,7 +293,7 @@
protected function readBytes($bytes)
{
$data = '';
- $len = 0;
+ $len = 0;
while ($len < $bytes && !$this->eof()) {
$d = fread($this->fp, $bytes - $len);
@@ -315,25 +314,25 @@
/**
* Reads complete response to the IMAP command
*
- * @param array $untagged Will be filled with untagged response lines
+ * @param string $untagged Will be filled with untagged response line(s)
*
* @return string Response text
*/
protected function readReply(&$untagged = null)
{
+ $untagged = []; // @phpstan-ignore-line
+
while (true) {
$line = trim($this->readLine(1024));
// store untagged response lines
if (isset($line[0]) && $line[0] == '*') {
- $untagged[] = $line;
+ $untagged[] = $line; // @phpstan-ignore-line
} else {
break;
}
}
- if ($untagged) {
- $untagged = implode("\n", $untagged);
- }
+ $untagged = count($untagged) > 0 ? implode("\n", $untagged) : null;
return $line;
}
@@ -366,17 +365,17 @@
if ($str) {
$str = trim($str);
// get response string and code (RFC5530)
- if (preg_match('/^\\[([a-z-]+)\\]/i', $str, $m)) {
+ if (preg_match('/^\[([a-z-]+)\]/i', $str, $m)) {
$this->resultcode = strtoupper($m[1]);
$str = trim(substr($str, strlen($m[1]) + 2));
} else {
$this->resultcode = null;
// parse response for [APPENDUID 1204196876 3456]
- if (preg_match('/^\\[APPENDUID [0-9]+ ([0-9]+)\\]/i', $str, $m)) {
+ if (preg_match('/^\[APPENDUID [0-9]+ ([0-9]+)\]/i', $str, $m)) {
$this->data['APPENDUID'] = $m[1];
}
// parse response for [COPYUID 1204196876 3456:3457 123:124]
- elseif (preg_match('/^\\[COPYUID [0-9]+ ([0-9,:]+) ([0-9,:]+)\\]/i', $str, $m)) {
+ elseif (preg_match('/^\[COPYUID [0-9]+ ([0-9,:]+) ([0-9,:]+)\]/i', $str, $m)) {
$this->data['COPYUID'] = [$m[1], $m[2]];
}
}
@@ -436,7 +435,7 @@
protected function setError($code, $msg = '')
{
$this->errornum = $code;
- $this->error = $msg;
+ $this->error = $msg;
return $code;
}
@@ -450,7 +449,7 @@
* @param bool $error Enables BYE/BAD checking
* @param bool $nonempty Enables empty response checking
*
- * @return bool True any check is true or connection is closed.
+ * @return bool true any check is true or connection is closed
*/
protected function startsWith($string, $match, $error = false, $nonempty = false)
{
@@ -466,6 +465,7 @@
if (strtoupper($m[1]) == 'BYE') {
$this->closeSocket();
}
+
return true;
}
@@ -537,7 +537,7 @@
*/
public function clearCapability()
{
- $this->capability = [];
+ $this->capability = [];
$this->capability_read = false;
}
@@ -558,7 +558,7 @@
'The Auth_SASL package is required for DIGEST-MD5 authentication');
}
- $this->putLine($this->nextTag() . " AUTHENTICATE $type");
+ $this->putLine($this->nextTag() . " AUTHENTICATE {$type}");
$line = trim($this->readReply());
if ($line[0] == '+') {
@@ -571,12 +571,13 @@
// RFC2195: CRAM-MD5
$ipad = '';
$opad = '';
- $xor = static function ($str1, $str2) {
+ $xor = static function ($str1, $str2) {
$result = '';
- $size = strlen($str1);
+ $size = strlen($str1);
for ($i = 0; $i < $size; $i++) {
$result .= chr(ord($str1[$i]) ^ ord($str2[$i]));
}
+
return $result;
};
@@ -590,7 +591,7 @@
$pass = str_pad($pass, 64, chr(0));
// generate hash
- $hash = md5($xor($pass, $opad) . pack('H*',
+ $hash = md5($xor($pass, $opad) . pack('H*',
md5($xor($pass, $ipad) . base64_decode($challenge))));
$reply = base64_encode($user . ' ' . $hash);
@@ -601,15 +602,15 @@
// proxy authorization
if (!empty($this->prefs['auth_cid'])) {
$authc = $this->prefs['auth_cid'];
- $pass = $this->prefs['auth_pw'];
+ $pass = $this->prefs['auth_pw'];
} else {
$authc = $user;
- $user = '';
+ $user = '';
}
$auth_sasl = new Auth_SASL();
$auth_sasl = $auth_sasl->factory('digestmd5');
- $reply = base64_encode($auth_sasl->getResponse($authc, $pass,
+ $reply = base64_encode($auth_sasl->getResponse($authc, $pass,
base64_decode($challenge), $this->host, 'imap', $user));
// send result
@@ -631,7 +632,7 @@
$this->putLine('');
}
- $line = $this->readReply();
+ $line = $this->readReply();
$result = $this->parseResult($line);
} elseif ($type == 'GSSAPI') {
if (!extension_loaded('krb5')) {
@@ -657,9 +658,9 @@
$gssapicontext = new GSSAPIContext();
$gssapicontext->acquireCredentials($ccache);
- $token = '';
- $success = $gssapicontext->initSecContext($this->prefs['gssapi_context'], null, null, null, $token);
- $token = base64_encode($token);
+ $token = '';
+ $success = $gssapicontext->initSecContext($this->prefs['gssapi_context'], '', 0, 0, $token);
+ $token = base64_encode($token);
} catch (Exception $e) {
trigger_error($e->getMessage(), \E_USER_WARNING);
return $this->setError(self::ERROR_BYE, 'GSSAPI authentication failed');
@@ -672,9 +673,10 @@
return $this->parseResult($line);
}
- try {
- $itoken = base64_decode(substr($line, 2));
+ $itoken = base64_decode(substr($line, 2));
+ $otoken = null;
+ try {
if (!$gssapicontext->unwrap($itoken, $itoken)) {
throw new Exception('GSSAPI SASL input token unwrap failed');
}
@@ -704,16 +706,16 @@
$this->putLine(base64_encode($otoken));
- $line = $this->readReply();
+ $line = $this->readReply();
$result = $this->parseResult($line);
} elseif ($type == 'PLAIN') {
// proxy authorization
if (!empty($this->prefs['auth_cid'])) {
$authc = $this->prefs['auth_cid'];
- $pass = $this->prefs['auth_pw'];
+ $pass = $this->prefs['auth_pw'];
} else {
$authc = $user;
- $user = '';
+ $user = '';
}
$reply = base64_encode($user . chr(0) . $authc . chr(0) . $pass);
@@ -732,7 +734,7 @@
// send result, get reply and process it
$this->putLine($reply, true, true);
- $line = $this->readReply();
+ $line = $this->readReply();
$result = $this->parseResult($line);
}
} elseif ($type == 'LOGIN') {
@@ -753,13 +755,13 @@
// send result, get reply and process it
$this->putLine(base64_encode($pass), true, true);
- $line = $this->readReply();
+ $line = $this->readReply();
$result = $this->parseResult($line);
} elseif (($type == 'XOAUTH2') || ($type == 'OAUTHBEARER')) {
$auth = ($type == 'XOAUTH2')
- ? base64_encode("user=$user\1auth=$pass\1\1") // XOAUTH: original extension, still widely used
- : base64_encode("n,a=$user,\1auth=$pass\1\1"); // OAUTHBEARER: official RFC 7628
- $this->putLine($this->nextTag() . " AUTHENTICATE $type $auth", true, true);
+ ? base64_encode("user={$user}\1auth={$pass}\1\1") // XOAUTH: original extension, still widely used
+ : base64_encode("n,a={$user},\1auth={$pass}\1\1"); // OAUTHBEARER: official RFC 7628
+ $this->putLine($this->nextTag() . " AUTHENTICATE {$type} {$auth}", true, true);
$line = trim($this->readReply());
@@ -771,7 +773,7 @@
$result = $this->parseResult($line);
} else {
- $line = 'not supported';
+ $line = 'not supported';
$result = self::ERROR_UNKNOWN;
}
@@ -784,7 +786,7 @@
return $this->fp;
}
- return $this->setError($result, "AUTHENTICATE $type: $line");
+ return $this->setError($result, "AUTHENTICATE {$type}: {$line}");
}
/**
@@ -820,7 +822,7 @@
/**
* Detects hierarchy delimiter
*
- * @return string The delimiter
+ * @return ?string The delimiter
*/
public function getHierarchyDelimiter()
{
@@ -839,12 +841,14 @@
return $this->prefs['delimiter'] = $delimiter;
}
}
+
+ return null;
}
/**
* NAMESPACE handler (RFC 2342)
*
- * @return array Namespace data hash (personal, other, shared)
+ * @return array|false Namespace data hash (personal, other, shared)
*/
public function getNamespace()
{
@@ -853,24 +857,24 @@
}
if (!$this->getCapability('NAMESPACE')) {
- return self::ERROR_BAD;
+ return false;
}
[$code, $response] = $this->execute('NAMESPACE');
if ($code == self::ERROR_OK && preg_match('/^\* NAMESPACE /', $response)) {
$response = substr($response, 11);
- $data = $this->tokenizeResponse($response);
+ $data = $this->tokenizeResponse($response);
}
if (!isset($data) || !is_array($data)) {
- return $code;
+ return false;
}
$this->prefs['namespace'] = [
'personal' => $data[0],
- 'other' => $data[1],
- 'shared' => $data[2],
+ 'other' => $data[1],
+ 'shared' => $data[2],
];
return $this->prefs['namespace'];
@@ -891,9 +895,9 @@
// configure
$this->set_prefs($options);
- $this->host = $host;
- $this->user = $user;
- $this->logged = false;
+ $this->host = $host;
+ $this->user = $user;
+ $this->logged = false;
$this->selected = null;
// check input
@@ -922,12 +926,12 @@
$this->data['ID'] = $this->id($this->prefs['preauth_ident']);
}
- $auth_method = $this->prefs['auth_type'];
+ $auth_method = $this->prefs['auth_type'];
$auth_methods = [];
- $result = null;
+ $result = null;
// check for supported auth methods
- if (!$auth_method || $auth_method == 'CHECK') {
+ if (!$auth_method || $auth_method === 'CHECK' || $auth_method === 'OAUTH') {
if ($auth_caps = $this->getCapability('AUTH')) {
$auth_methods = $auth_caps;
}
@@ -935,12 +939,18 @@
// Use best (for security) supported authentication method
$all_methods = ['DIGEST-MD5', 'CRAM-MD5', 'CRAM_MD5', 'PLAIN', 'LOGIN'];
+ // special case of OAUTH, use the supported method
+ if ($auth_method === 'OAUTH') {
+ $all_methods = ['OAUTHBEARER', 'XOAUTH2'];
+ }
+
if (!empty($this->prefs['gssapi_cn'])) {
array_unshift($all_methods, 'GSSAPI');
}
- foreach ($all_methods as $auth_method) {
- if (in_array($auth_method, $auth_methods)) {
+ foreach ($all_methods as $method) {
+ $auth_method = $method;
+ if (in_array($method, $auth_methods)) {
break;
}
}
@@ -967,13 +977,11 @@
case 'OAUTHBEARER':
$result = $this->authenticate($user, $password, $auth_method);
break;
-
case 'IMAP':
$result = $this->login($user, $password);
break;
-
default:
- $this->setError(self::ERROR_BAD, "Configuration error. Unknown auth method: $auth_method");
+ $this->setError(self::ERROR_BAD, "Configuration error. Unknown auth method: {$auth_method}");
}
// Connected and authenticated
@@ -1007,10 +1015,10 @@
protected function _connect($host)
{
// initialize connection
- $this->error = '';
+ $this->error = '';
$this->errornum = self::ERROR_OK;
- $port = empty($this->prefs['port']) ? 143 : $this->prefs['port'];
+ $port = empty($this->prefs['port']) ? 143 : $this->prefs['port'];
$ssl_mode = $this->prefs['ssl_mode'] ?? null;
// check for SSL
@@ -1027,12 +1035,12 @@
$this->resourceid = strtoupper(substr(md5(microtime() . $host . $this->user), 0, 4));
$_host = ($ssl_mode == 'tls' ? 'tls://' : '') . $host . ':' . $port;
- $this->debug("Connecting to $_host...");
+ $this->debug("Connecting to {$_host}...");
}
if (!empty($this->prefs['socket_options'])) {
- $options = array_intersect_key($this->prefs['socket_options'], ['ssl' => 1]);
- $context = stream_context_create($options);
+ $options = array_intersect_key($this->prefs['socket_options'], ['ssl' => 1, 'socket' => 1]);
+ $context = stream_context_create($options);
$this->fp = stream_socket_client($host . ':' . $port, $errno, $errstr,
$this->prefs['timeout'], \STREAM_CLIENT_CONNECT, $context);
} else {
@@ -1046,6 +1054,14 @@
return false;
}
+ // insert proxy protocol header, if enabled
+ if (!empty($this->prefs['socket_options'])) {
+ $proxy_protocol_header = rcube_utils::proxy_protocol_header($this->prefs['socket_options']);
+ if (strlen($proxy_protocol_header) > 0) {
+ fwrite($this->fp, $proxy_protocol_header);
+ }
+ }
+
if ($this->prefs['timeout'] > 0) {
stream_set_timeout($this->fp, $this->prefs['timeout']);
}
@@ -1140,7 +1156,7 @@
/**
* Checks connection status
*
- * @return bool True if connection is active and user is logged in, False otherwise.
+ * @return bool true if connection is active and user is logged in, False otherwise
*/
public function connected()
{
@@ -1202,9 +1218,9 @@
$response = explode("\r\n", $response);
foreach ($response as $line) {
if (preg_match('/^\* OK \[/i', $line)) {
- $pos = strcspn($line, ' ]', 6);
+ $pos = strcspn($line, ' ]', 6);
$token = strtoupper(substr($line, 6, $pos));
- $pos += 7;
+ $pos += 7;
switch ($token) {
case 'UIDNEXT':
@@ -1213,25 +1229,25 @@
if ($len = strspn($line, '0123456789', $pos)) {
$this->data[$token] = (int) substr($line, $pos, $len);
}
- break;
+ break;
case 'HIGHESTMODSEQ':
if ($len = strspn($line, '0123456789', $pos)) {
$this->data[$token] = (string) substr($line, $pos, $len);
}
- break;
+ break;
case 'NOMODSEQ':
$this->data[$token] = true;
break;
-
case 'PERMANENTFLAGS':
$start = strpos($line, '(', $pos);
- $end = strrpos($line, ')');
+ $end = strrpos($line, ')');
if ($start && $end) {
$flags = substr($line, $start + 1, $end - $start - 1);
$this->data[$token] = explode(' ', $flags);
}
+
break;
}
} elseif (preg_match('/^\* ([0-9]+) (EXISTS|RECENT|FETCH)/i', $line, $match)) {
@@ -1241,12 +1257,11 @@
case 'RECENT':
$this->data[$token] = (int) $match[1];
break;
-
case 'FETCH':
// QRESYNC FETCH response (RFC5162)
- $line = substr($line, strlen($match[0]));
+ $line = substr($line, strlen($match[0]));
$fetch_data = $this->tokenizeResponse($line, 1);
- $data = ['id' => $match[1]];
+ $data = ['id' => $match[1]];
for ($i = 0, $size = count($fetch_data); $i < $size; $i += 2) {
$data[strtolower($fetch_data[$i])] = $fetch_data[$i + 1];
@@ -1258,7 +1273,7 @@
}
// QRESYNC VANISHED response (RFC5162)
elseif (preg_match('/^\* VANISHED [()EARLIER]*/i', $line, $match)) {
- $line = substr($line, strlen($match[0]));
+ $line = substr($line, strlen($match[0]));
$v_data = $this->tokenizeResponse($line, 1);
$this->data['VANISHED'] = $v_data;
@@ -1282,7 +1297,7 @@
* MESSAGES and UNSEEN are requested. Other defined
* in RFC3501: UIDNEXT, UIDVALIDITY, RECENT
*
- * @return array Status item-value hash
+ * @return array|false Status item-value hash, False on error
*
* @since 0.5-beta
*/
@@ -1303,7 +1318,7 @@
[$this->escape($mailbox), '(' . implode(' ', $items) . ')'], 0, '/^\* STATUS /i');
if ($code == self::ERROR_OK && $response) {
- $result = [];
+ $result = [];
$response = substr($response, 9); // remove prefix "* STATUS "
[$mbox, $items] = $this->tokenizeResponse($response, 2);
@@ -1312,7 +1327,7 @@
// folder name with spaces. Let's try to handle this situation
if (!is_array($items) && ($pos = strpos($response, '(')) !== false) {
$response = substr($response, $pos);
- $items = $this->tokenizeResponse($response, 1);
+ $items = $this->tokenizeResponse($response, 1);
}
if (!is_array($items)) {
@@ -1355,7 +1370,7 @@
if (!empty($messages) && $messages != '*' && $this->hasCapability('UIDPLUS')) {
$messages = self::compressMessageSet($messages);
- $result = $this->execute('UID EXPUNGE', [$messages], self::COMMAND_NORESPONSE);
+ $result = $this->execute('UID EXPUNGE', [$messages], self::COMMAND_NORESPONSE);
} else {
$result = $this->execute('EXPUNGE', null, self::COMMAND_NORESPONSE);
}
@@ -1507,8 +1522,8 @@
* @param array $return_opts (see self::_listMailboxes)
* @param array $select_opts (see self::_listMailboxes)
*
- * @return array|bool List of mailboxes or hash of options if STATUS/MYRIGHTS response
- * is requested, False on error.
+ * @return array|bool list of mailboxes or hash of options if STATUS/MYRIGHTS response
+ * is requested, False on error
*/
public function listMailboxes($ref, $mailbox, $return_opts = [], $select_opts = [])
{
@@ -1522,8 +1537,8 @@
* @param string $mailbox Mailbox name
* @param array $return_opts (see self::_listMailboxes)
*
- * @return array|bool List of mailboxes or hash of options if STATUS/MYRIGHTS response
- * is requested, False on error.
+ * @return array|bool list of mailboxes or hash of options if STATUS/MYRIGHTS response
+ * is requested, False on error
*/
public function listSubscribed($ref, $mailbox, $return_opts = [])
{
@@ -1543,8 +1558,8 @@
* Possible: SUBSCRIBED, RECURSIVEMATCH, REMOTE,
* SPECIAL-USE (RFC6154)
*
- * @return array|bool List of mailboxes or hash of options if STATUS/MYRIGHTS response
- * is requested, False on error.
+ * @return array|bool list of mailboxes or hash of options if STATUS/MYRIGHTS response
+ * is requested, False on error
*/
protected function _listMailboxes($ref, $mailbox, $subscribed = false, $return_opts = [], $select_opts = [])
{
@@ -1553,8 +1568,8 @@
}
$lstatus = false;
- $args = [];
- $rets = [];
+ $args = [];
+ $rets = [];
if (!empty($select_opts) && $this->getCapability('LIST-EXTENDED')) {
$select_opts = (array) $select_opts;
@@ -1566,15 +1581,15 @@
$args[] = $this->escape($mailbox);
if (!empty($return_opts) && $this->getCapability('LIST-EXTENDED')) {
- $ext_opts = ['SUBSCRIBED', 'CHILDREN'];
- $rets = array_intersect($return_opts, $ext_opts);
+ $ext_opts = ['SUBSCRIBED', 'CHILDREN'];
+ $rets = array_intersect($return_opts, $ext_opts);
$return_opts = array_diff($return_opts, $rets);
}
if (!empty($return_opts) && $this->getCapability('LIST-STATUS')) {
- $lstatus = true;
+ $lstatus = true;
$status_opts = ['MESSAGES', 'RECENT', 'UIDNEXT', 'UIDVALIDITY', 'UNSEEN', 'SIZE'];
- $opts = array_diff($return_opts, $status_opts);
+ $opts = array_diff($return_opts, $status_opts);
$status_opts = array_diff($return_opts, $opts);
if (!empty($status_opts)) {
@@ -1593,9 +1608,9 @@
[$code, $response] = $this->execute($subscribed ? 'LSUB' : 'LIST', $args);
if ($code == self::ERROR_OK) {
- $folders = [];
- $last = 0;
- $pos = 0;
+ $folders = [];
+ $last = 0;
+ $pos = 0;
$response .= "\r\n";
while ($pos = strpos($response, "\r\n", $pos + 1)) {
@@ -1611,7 +1626,7 @@
continue;
}
- $cmd = strtoupper($m[1]);
+ $cmd = strtoupper($m[1]);
$line = substr($line, strlen($m[0]));
// * LIST (<options>) <delimiter> <mailbox>
@@ -1664,7 +1679,7 @@
}
// * MYRIGHTS <mailbox> <acl>
elseif ($cmd == 'MYRIGHTS') {
- [$mailbox, $acl] = $this->tokenizeResponse($line, 2);
+ [$mailbox, $acl] = $this->tokenizeResponse($line, 2);
$folders[$mailbox]['MYRIGHTS'] = $acl;
}
}
@@ -1681,7 +1696,7 @@
*
* @param string $mailbox Mailbox name
*
- * @return int Number of messages, False on error
+ * @return int|false Number of messages, False on error
*/
public function countMessages($mailbox)
{
@@ -1711,7 +1726,7 @@
*
* @param string $mailbox Mailbox name
*
- * @return int Number of messages, False on error
+ * @return int|false Number of messages, False on error
*/
public function countRecent($mailbox)
{
@@ -1739,7 +1754,7 @@
*
* @param string $mailbox Mailbox name
*
- * @return int Number of messages, False on error
+ * @return int|false Number of messages, False on error
*/
public function countUnseen($mailbox)
{
@@ -1777,7 +1792,8 @@
*/
public function id($items = [])
{
- if (is_array($items) && !empty($items)) {
+ $args = [];
+ if (!empty($items)) {
foreach ($items as $key => $value) {
$args[] = $this->escape($key, true);
$args[] = $this->escape($value, true);
@@ -1791,8 +1807,8 @@
if ($code == self::ERROR_OK && $response) {
$response = substr($response, 5); // remove prefix "* ID "
- $items = $this->tokenizeResponse($response, 1);
- $result = [];
+ $items = $this->tokenizeResponse($response, 1);
+ $result = [];
if (is_array($items)) {
for ($i = 0, $len = count($items); $i < $len; $i += 2) {
@@ -1847,9 +1863,9 @@
if ($code == self::ERROR_OK && $response) {
$response = substr($response, 10); // remove prefix "* ENABLED "
- $result = (array) $this->tokenizeResponse($response);
+ $result = (array) $this->tokenizeResponse($response);
- $this->extensions_enabled = array_unique(array_merge((array) $this->extensions_enabled, $result));
+ $this->extensions_enabled = array_unique(array_merge($this->extensions_enabled, $result));
return $this->extensions_enabled;
}
@@ -1870,9 +1886,9 @@
*/
public function sort($mailbox, $field = 'ARRIVAL', $criteria = '', $return_uid = false, $encoding = 'US-ASCII')
{
- $old_sel = $this->selected;
+ $old_sel = $this->selected;
$supported = ['ARRIVAL', 'CC', 'DATE', 'FROM', 'SIZE', 'SUBJECT', 'TO'];
- $field = strtoupper($field);
+ $field = strtoupper($field);
if ($field == 'INTERNALDATE') {
$field = 'ARRIVAL';
@@ -1900,7 +1916,7 @@
$criteria = $criteria ? 'ALL ' . trim($criteria) : 'ALL';
[$code, $response] = $this->execute($return_uid ? 'UID SORT' : 'SORT',
- ["($field)", $encoding, $criteria]);
+ ["({$field})", $encoding, $criteria]);
if ($code != self::ERROR_OK) {
$response = null;
@@ -1933,9 +1949,9 @@
return new rcube_result_thread($mailbox, '* THREAD');
}
- $encoding = $encoding ? trim($encoding) : 'US-ASCII';
+ $encoding = $encoding ? trim($encoding) : 'US-ASCII';
$algorithm = $algorithm ? trim($algorithm) : 'REFERENCES';
- $criteria = $criteria ? 'ALL ' . trim($criteria) : 'ALL';
+ $criteria = $criteria ? 'ALL ' . trim($criteria) : 'ALL';
[$code, $response] = $this->execute($return_uid ? 'UID THREAD' : 'THREAD',
[$algorithm, $encoding, $criteria]);
@@ -1976,9 +1992,9 @@
$items = ['ALL'];
}
- $esearch = empty($items) ? false : $this->getCapability('ESEARCH');
+ $esearch = empty($items) ? false : $this->getCapability('ESEARCH');
$criteria = trim($criteria);
- $params = '';
+ $params = '';
// RFC4731: ESEARCH
if (!empty($items) && $esearch) {
@@ -2046,7 +2062,8 @@
{
// Validate input
if (is_array($message_set)) {
- if (!($message_set = $this->compressMessageSet($message_set))) {
+ $message_set = $this->compressMessageSet($message_set);
+ if ($message_set === '' || $message_set === 'INVALID') {
return false;
}
} elseif (empty($message_set)) {
@@ -2061,20 +2078,20 @@
$index_field = empty($index_field) ? 'DATE' : strtoupper($index_field);
$supported = [
- 'DATE' => 1,
+ 'DATE' => 1,
'INTERNALDATE' => 4,
- 'ARRIVAL' => 4,
- 'FROM' => 1,
- 'REPLY-TO' => 1,
- 'SENDER' => 1,
- 'TO' => 1,
- 'CC' => 1,
- 'SUBJECT' => 1,
- 'UID' => 2,
- 'SIZE' => 2,
- 'SEEN' => 3,
- 'RECENT' => 3,
- 'DELETED' => 3,
+ 'ARRIVAL' => 4,
+ 'FROM' => 1,
+ 'REPLY-TO' => 1,
+ 'SENDER' => 1,
+ 'TO' => 1,
+ 'CC' => 1,
+ 'SUBJECT' => 1,
+ 'UID' => 2,
+ 'SIZE' => 2,
+ 'SEEN' => 3,
+ 'RECENT' => 3,
+ 'DELETED' => 3,
];
if (empty($supported[$index_field])) {
@@ -2089,8 +2106,8 @@
}
// build FETCH command string
- $key = $this->nextTag();
- $cmd = $uidfetch ? 'UID FETCH' : 'FETCH';
+ $key = $this->nextTag();
+ $cmd = $uidfetch ? 'UID FETCH' : 'FETCH';
$fields = [];
if ($return_uid) {
@@ -2104,7 +2121,7 @@
if ($index_field == 'DATE') {
$fields[] = 'INTERNALDATE';
}
- $fields[] = "BODY.PEEK[HEADER.FIELDS ($index_field)]";
+ $fields[] = "BODY.PEEK[HEADER.FIELDS ({$index_field})]";
} elseif ($mode == 2) {
if ($index_field == 'SIZE') {
$fields[] = 'RFC822.SIZE';
@@ -2117,10 +2134,10 @@
$fields[] = 'INTERNALDATE';
}
- $request = "$key $cmd $message_set (" . implode(' ', $fields) . ')';
+ $request = "{$key} {$cmd} {$message_set} (" . implode(' ', $fields) . ')';
if (!$this->putLine($request)) {
- $this->setError(self::ERROR_COMMAND, "Failed to send $cmd command");
+ $this->setError(self::ERROR_COMMAND, "Failed to send {$cmd} command");
return false;
}
@@ -2131,8 +2148,8 @@
$line = $this->multLine($line);
if (preg_match('/^\* ([0-9]+) FETCH/', $line, $m)) {
- $id = $m[1];
- $flags = null;
+ $id = $m[1];
+ $flags = null;
if ($return_uid) {
if (preg_match('/UID ([0-9]+)/', $line, $matches)) {
@@ -2144,7 +2161,7 @@
if ($skip_deleted && preg_match('/FLAGS \(([^)]+)\)/', $line, $matches)) {
$flags = explode(' ', strtoupper($matches[1]));
- if (in_array('\\DELETED', $flags)) {
+ if (in_array('\DELETED', $flags)) {
continue;
}
}
@@ -2200,18 +2217,20 @@
* @param string $mailbox Mailbox name
* @param int $uid Message unique identifier (UID)
*
- * @return int Message sequence identifier
+ * @return ?int Message sequence identifier
*/
public function UID2ID($mailbox, $uid)
{
if ($uid > 0) {
- $index = $this->search($mailbox, "UID $uid");
+ $index = $this->search($mailbox, "UID {$uid}");
if ($index->count() == 1) {
$arr = $index->get();
return (int) $arr[0];
}
}
+
+ return null;
}
/**
@@ -2220,7 +2239,7 @@
* @param string $mailbox Mailbox name
* @param int $id Message sequence identifier
*
- * @return int Message unique identifier
+ * @return ?int Message unique identifier
*/
public function ID2UID($mailbox, $id)
{
@@ -2246,6 +2265,8 @@
$arr = $index->get();
return $this->data['UID-MAP'][$id] = (int) $arr[0];
}
+
+ return null;
}
/**
@@ -2308,7 +2329,7 @@
// if PERMANENTFLAGS is not specified all flags are allowed
if (!empty($this->data['PERMANENTFLAGS'])
&& !in_array($flag, (array) $this->data['PERMANENTFLAGS'])
- && !in_array('\\*', (array) $this->data['PERMANENTFLAGS'])
+ && !in_array('\*', (array) $this->data['PERMANENTFLAGS'])
) {
return false;
}
@@ -2323,7 +2344,7 @@
}
$result = $this->execute('UID STORE',
- [$this->compressMessageSet($messages), $mod . 'FLAGS.SILENT', "($flag)"],
+ [$this->compressMessageSet($messages), $mod . 'FLAGS.SILENT', "({$flag})"],
self::COMMAND_NORESPONSE
);
@@ -2426,7 +2447,7 @@
* @param string $mod_seq Modification sequence for CHANGEDSINCE (RFC4551) query
* @param bool $vanished Enables VANISHED parameter (RFC5162) for CHANGEDSINCE query
*
- * @return array List of rcube_message_header elements, False on error
+ * @return array|false List of rcube_message_header elements, False on error
*
* @since 0.6
*/
@@ -2438,18 +2459,18 @@
}
$message_set = $this->compressMessageSet($message_set);
- $result = [];
+ $result = [];
- $key = $this->nextTag();
- $cmd = ($is_uid ? 'UID ' : '') . 'FETCH';
- $request = "$key $cmd $message_set (" . implode(' ', $query_items) . ')';
+ $key = $this->nextTag();
+ $cmd = ($is_uid ? 'UID ' : '') . 'FETCH';
+ $request = "{$key} {$cmd} {$message_set} (" . implode(' ', $query_items) . ')';
if ($mod_seq !== null && $this->hasCapability('CONDSTORE')) {
- $request .= " (CHANGEDSINCE $mod_seq" . ($vanished ? ' VANISHED' : '') . ')';
+ $request .= " (CHANGEDSINCE {$mod_seq}" . ($vanished ? ' VANISHED' : '') . ')';
}
if (!$this->putLine($request)) {
- $this->setError(self::ERROR_COMMAND, "Failed to send $cmd command");
+ $this->setError(self::ERROR_COMMAND, "Failed to send {$cmd} command");
return false;
}
@@ -2468,14 +2489,14 @@
if (preg_match('/^\* ([0-9]+) FETCH/', $line, $m)) {
$id = intval($m[1]);
- $result[$id] = new rcube_message_header();
- $result[$id]->id = $id;
- $result[$id]->subject = '';
+ $result[$id] = new rcube_message_header();
+ $result[$id]->id = $id;
+ $result[$id]->subject = '';
$result[$id]->messageID = 'mid:' . $id;
+ $result[$id]->folder = $mailbox;
$headers = null;
- $line = substr($line, strlen($m[0]) + 2);
-
+ $line = substr($line, strlen($m[0]) + 2);
// Tokenize response and assign to object properties
while (($tokens = $this->tokenizeResponse($line, 2)) && count($tokens) == 2) {
[$name, $value] = $tokens;
@@ -2487,8 +2508,8 @@
$result[$id]->body = $value;
} elseif ($name == 'INTERNALDATE') {
$result[$id]->internaldate = $value;
- $result[$id]->date = $value;
- $result[$id]->timestamp = rcube_utils::strtotime($value);
+ $result[$id]->date = $value;
+ $result[$id]->timestamp = rcube_utils::strtotime($value);
} elseif ($name == 'FLAGS') {
if (!empty($value)) {
foreach ((array) $value as $flag) {
@@ -2498,6 +2519,20 @@
$result[$id]->flags[$flag] = true;
}
}
+ } elseif ($name == 'ANNOTATION') {
+ $result[$id]->annotations = [];
+ if (!empty($value) && is_array($value)) {
+ $n = 0;
+ while (!empty($value[$n]) && is_string($value[$n])) {
+ $name = $value[$n++];
+ $list = $value[$n++];
+ $result[$id]->annotations[$name] = [];
+ $c = 0;
+ while (!empty($list[$c]) && is_string($list[$c])) {
+ $result[$id]->annotations[$name][$list[$c++]] = $list[$c++];
+ }
+ }
+ }
} elseif ($name == 'MODSEQ') {
$result[$id]->modseq = $value[0];
} elseif ($name == 'ENVELOPE') {
@@ -2527,8 +2562,8 @@
// create array with header field:data
if (!empty($headers)) {
$headers = explode("\n", trim($headers));
- $lines = [];
- $ln = 0;
+ $lines = [];
+ $ln = 0;
foreach ($headers as $resln) {
if (!isset($resln[0]) || ord($resln[0]) <= 32) {
@@ -2545,13 +2580,13 @@
[$field, $string] = explode(':', $str, 2);
- $field = strtolower($field);
+ $field = strtolower($field);
$string = preg_replace('/\n[\t\s]*/', ' ', trim($string));
switch ($field) {
case 'date':
- $string = substr($string, 0, 128);
- $result[$id]->date = $string;
+ $string = substr($string, 0, 128);
+ $result[$id]->date = $string;
$result[$id]->timestamp = rcube_utils::strtotime($string);
break;
case 'to':
@@ -2577,6 +2612,7 @@
if (preg_match('/charset\s*=\s*"?([a-z0-9\-\.\_]+)"?/i', $string, $regs)) {
$result[$id]->charset = $regs[1];
}
+
break;
case 'in-reply-to':
$result[$id]->in_reply_to = str_replace(["\n", '<', '>'], '', $string);
@@ -2592,6 +2628,7 @@
if (preg_match('/^(\d+)/', $string, $matches)) {
$result[$id]->priority = intval($matches[1]);
}
+
break;
default:
if (strlen($field) < 3) {
@@ -2608,7 +2645,7 @@
// VANISHED response (QRESYNC RFC5162)
// Sample: * VANISHED (EARLIER) 300:310,405,411
elseif (preg_match('/^\* VANISHED [()EARLIER]*/i', $line, $match)) {
- $line = substr($line, strlen($match[0]));
+ $line = substr($line, strlen($match[0]));
$v_data = $this->tokenizeResponse($line, 1);
$this->data['VANISHED'] = $v_data;
@@ -2629,19 +2666,20 @@
* @param mixed $message_set Message(s) sequence identifier(s) or UID(s)
* @param bool $is_uid True if $message_set contains UIDs
* @param bool $bodystr Enable to add BODYSTRUCTURE data to the result
- * @param array $add_headers List of additional headers
+ * @param array $add_headers List of additional headers to fetch
+ * @param array $query_items List of additional items to fetch
*
* @return bool|array List of rcube_message_header elements, False on error
*/
- public function fetchHeaders($mailbox, $message_set, $is_uid = false, $bodystr = false, $add_headers = [])
+ public function fetchHeaders($mailbox, $message_set, $is_uid = false, $bodystr = false, $add_headers = [], $query_items = [])
{
- $query_items = ['UID', 'RFC822.SIZE', 'FLAGS', 'INTERNALDATE'];
- $headers = ['DATE', 'FROM', 'TO', 'SUBJECT', 'CONTENT-TYPE', 'CC', 'REPLY-TO',
+ $query_items = array_unique(array_merge($query_items, ['UID', 'RFC822.SIZE', 'FLAGS', 'INTERNALDATE']));
+ $headers = ['DATE', 'FROM', 'TO', 'SUBJECT', 'CONTENT-TYPE', 'CC', 'REPLY-TO',
'LIST-POST', 'DISPOSITION-NOTIFICATION-TO', 'X-PRIORITY'];
if (!empty($add_headers)) {
$add_headers = array_map('strtoupper', $add_headers);
- $headers = array_unique(array_merge($headers, $add_headers));
+ $headers = array_unique(array_merge($headers, $add_headers));
}
if ($bodystr) {
@@ -2661,12 +2699,13 @@
* @param bool $is_uid True if $id is an UID
* @param bool $bodystr Enable to add BODYSTRUCTURE data to the result
* @param array $add_headers List of additional headers
+ * @param array $query_items List of additional items to fetch
*
* @return bool|rcube_message_header Message data, False on error
*/
- public function fetchHeader($mailbox, $id, $is_uid = false, $bodystr = false, $add_headers = [])
+ public function fetchHeader($mailbox, $id, $is_uid = false, $bodystr = false, $add_headers = [], $query_items = [])
{
- $a = $this->fetchHeaders($mailbox, $id, $is_uid, $bodystr, $add_headers);
+ $a = $this->fetchHeaders($mailbox, $id, $is_uid, $bodystr, $add_headers, $query_items);
if (is_array($a)) {
return array_first($a);
@@ -2707,7 +2746,6 @@
}
break;
-
default:
// @TODO: decode header value, convert to UTF-8
$value = $headers->{$field};
@@ -2743,8 +2781,8 @@
* @param array $parts Message part identifiers
* @param bool $mime Use MIME instead of HEADER
*
- * @return array|bool Array containing headers string for each specified body
- * False on failure.
+ * @return array|bool array containing headers string for each specified body
+ * False on failure
*/
public function fetchMIMEHeaders($mailbox, $uid, $parts, $mime = true)
{
@@ -2752,17 +2790,17 @@
return false;
}
- $parts = (array) $parts;
- $key = $this->nextTag();
- $peeks = [];
- $type = $mime ? 'MIME' : 'HEADER';
+ $parts = (array) $parts;
+ $key = $this->nextTag();
+ $peeks = [];
+ $type = $mime ? 'MIME' : 'HEADER';
// format request
foreach ($parts as $part) {
- $peeks[] = "BODY.PEEK[$part.$type]";
+ $peeks[] = "BODY.PEEK[{$part}.{$type}]";
}
- $request = "$key UID FETCH $uid (" . implode(' ', $peeks) . ')';
+ $request = "{$key} UID FETCH {$uid} (" . implode(' ', $peeks) . ')';
// send request
if (!$this->putLine($request)) {
@@ -2807,8 +2845,11 @@
return false;
}
- $binary = true;
+ $binary = true;
$initiated = false;
+ $result = false;
+ $found = false;
+ $mode = 0;
do {
if (!$initiated) {
@@ -2830,21 +2871,21 @@
}
// Use BINARY extension when possible (and safe)
- $binary = $binary && $mode && preg_match('/^[0-9.]+$/', (string) $part) && $this->hasCapability('BINARY');
+ $binary = $binary && $mode && preg_match('/^[0-9.]+$/', (string) $part) && $this->hasCapability('BINARY');
$fetch_mode = $binary ? 'BINARY' : 'BODY';
- $partial = $max_bytes ? sprintf('<0.%d>', $max_bytes) : '';
+ $partial = $max_bytes ? sprintf('<0.%d>', $max_bytes) : '';
// format request
- $key = $this->nextTag();
- $cmd = ($is_uid ? 'UID ' : '') . 'FETCH';
- $request = "$key $cmd $id ($fetch_mode.PEEK[$part]$partial)";
- $result = false;
- $found = false;
+ $key = $this->nextTag();
+ $cmd = ($is_uid ? 'UID ' : '') . 'FETCH';
+ $request = "{$key} {$cmd} {$id} ({$fetch_mode}.PEEK[{$part}]{$partial})";
+ $result = false;
+ $found = false;
$initiated = true;
// send request
if (!$this->putLine($request)) {
- $this->setError(self::ERROR_COMMAND, "Failed to send $cmd command");
+ $this->setError(self::ERROR_COMMAND, "Failed to send {$cmd} command");
return false;
}
@@ -2861,6 +2902,7 @@
}
// handle UNKNOWN-CTE response - RFC 3516, try again with standard BODY request
+ // @phpstan-ignore-next-line
if ($binary && !$found && preg_match('/^' . $key . ' NO \[(UNKNOWN-CTE|PARSE)\]/i', $line)) {
$binary = $initiated = false;
continue;
@@ -2883,7 +2925,7 @@
for ($i = 0; $i < count($tokens); $i += 2) {
if (preg_match('/^(BODY|BINARY)/i', $tokens[$i])) {
$result = $tokens[$i + 1];
- $found = true;
+ $found = true;
break;
}
}
@@ -2903,7 +2945,7 @@
// response with string literal
elseif (preg_match('/\{([0-9]+)\}$/', $line, $m)) {
$bytes = (int) $m[1];
- $prev = '';
+ $prev = '';
$found = true;
$chunkSize = 1024 * 1024;
@@ -2915,6 +2957,7 @@
elseif (!$mode && !$file && !$print) {
$result = $this->readBytes($bytes);
} else {
+ $result = '';
while ($bytes > 0) {
$chunk = $this->readBytes($bytes > $chunkSize ? $chunkSize : $bytes);
@@ -2944,12 +2987,14 @@
}
}
}
- } while (!$this->startsWith($line, $key, true) || !$initiated);
+ } while (!$initiated || !$this->startsWith($line, $key, true)); // @phpstan-ignore-line
if ($result !== false) {
if ($file) {
return is_string($result) ? fwrite($file, $result) !== false : true;
- } elseif ($print) {
+ }
+
+ if ($print) {
echo $result;
return true;
}
@@ -2987,7 +3032,14 @@
$prev = '';
}
- return base64_decode($chunk);
+ // There might be multiple base64 blocks in a single message part,
+ // we have to pass them separately to base64_decode() (#9290)
+ $result = '';
+ foreach (preg_split('|=+|', $chunk, -1, \PREG_SPLIT_NO_EMPTY) as $_chunk) {
+ $result .= base64_decode($_chunk);
+ }
+
+ return $result;
}
// QUOTED-PRINTABLE
@@ -3073,15 +3125,16 @@
{
unset($this->data['APPENDUID']);
+ // @phpstan-ignore-next-line
if ($mailbox === null || $mailbox === '') {
return false;
}
- $binary = $binary && $this->getCapability('BINARY');
+ $binary = $binary && $this->getCapability('BINARY');
$literal_plus = !$binary && !empty($this->prefs['literal+']);
- $len = 0;
- $msg = is_array($message) ? $message : [&$message];
- $chunk_size = 512000;
+ $len = 0;
+ $msg = is_array($message) ? $message : [&$message];
+ $chunk_size = 512000;
for ($i = 0, $cnt = count($msg); $i < $cnt; $i++) {
if (is_resource($msg[$i])) {
@@ -3106,7 +3159,7 @@
// build APPEND command
$key = $this->nextTag();
- $request = "$key APPEND " . $this->escape($mailbox) . ' (' . $this->flagsToStr($flags) . ')';
+ $request = "{$key} APPEND " . $this->escape($mailbox) . ' (' . $this->flagsToStr($flags) . ')';
if (!empty($date)) {
$request .= ' ' . $this->escape($date);
}
@@ -3195,7 +3248,7 @@
}
if (empty($fp)) {
- $this->setError(self::ERROR_UNKNOWN, "Couldn't open $path for reading");
+ $this->setError(self::ERROR_UNKNOWN, "Couldn't open {$path} for reading");
return false;
}
@@ -3233,13 +3286,13 @@
}
$min_free = \PHP_INT_MAX;
- $result = [];
- $all = [];
+ $result = [];
+ $all = [];
foreach (explode("\n", $response) as $line) {
- $tokens = $this->tokenizeResponse($line, 3);
+ $tokens = $this->tokenizeResponse($line, 3);
$quota_root = $tokens[2] ?? null;
- $quotas = $this->tokenizeResponse($line, 1);
+ $quotas = $this->tokenizeResponse($line, 1);
if (empty($quotas)) {
continue;
@@ -3250,7 +3303,7 @@
$type = strtolower($type);
if ($type && $total) {
- $all[$quota_root][$type]['used'] = intval($used);
+ $all[$quota_root][$type]['used'] = intval($used);
$all[$quota_root][$type]['total'] = intval($total);
}
}
@@ -3259,17 +3312,17 @@
continue;
}
- $used = $all[$quota_root]['storage']['used'];
+ $used = $all[$quota_root]['storage']['used'];
$total = $all[$quota_root]['storage']['total'];
- $free = $total - $used;
+ $free = $total - $used;
// calculate lowest available space from all storage quotas
if ($free < $min_free) {
- $min_free = $free;
- $result['used'] = $used;
- $result['total'] = $total;
+ $min_free = $free;
+ $result['used'] = $used;
+ $result['total'] = $total;
$result['percent'] = min(100, round(($used / max(1, $total)) * 100));
- $result['free'] = 100 - $result['percent'];
+ $result['free'] = 100 - $result['percent'];
}
}
@@ -3352,7 +3405,7 @@
*
* @param string $mailbox Mailbox name
*
- * @return array User-rights array on success, NULL on error
+ * @return ?array User-rights array on success, NULL on error
*
* @since 0.5-beta
*/
@@ -3363,7 +3416,7 @@
if ($code == self::ERROR_OK && $response) {
// Parse server response (remove "* ACL ")
$response = substr($response, 6);
- $ret = $this->tokenizeResponse($response);
+ $ret = $this->tokenizeResponse($response);
$mbox = array_shift($ret);
$size = count($ret);
@@ -3377,11 +3430,14 @@
unset($ret[$i - 1]);
unset($ret[$i]);
}
+
return $ret;
}
$this->setError(self::ERROR_COMMAND, 'Incomplete ACL response');
}
+
+ return null;
}
/**
@@ -3390,7 +3446,7 @@
* @param string $mailbox Mailbox name
* @param string $user User name
*
- * @return array List of user rights
+ * @return ?array List of user rights, NULL on error
*
* @since 0.5-beta
*/
@@ -3405,14 +3461,16 @@
$ret_mbox = $this->tokenizeResponse($response, 1);
$ret_user = $this->tokenizeResponse($response, 1);
- $granted = $this->tokenizeResponse($response, 1);
+ $granted = $this->tokenizeResponse($response, 1);
$optional = trim($response);
return [
- 'granted' => str_split($granted),
+ 'granted' => str_split($granted),
'optional' => explode(' ', $optional),
];
}
+
+ return null;
}
/**
@@ -3420,7 +3478,7 @@
*
* @param string $mailbox Mailbox name
*
- * @return array MYRIGHTS response on success, NULL on error
+ * @return ?array MYRIGHTS response on success, NULL on error
*
* @since 0.5-beta
*/
@@ -3433,10 +3491,12 @@
$response = substr($response, 11);
$ret_mbox = $this->tokenizeResponse($response, 1);
- $rights = $this->tokenizeResponse($response, 1);
+ $rights = $this->tokenizeResponse($response, 1);
return str_split($rights);
}
+
+ return null;
}
/**
@@ -3451,7 +3511,7 @@
*/
public function setMetadata($mailbox, $entries)
{
- if (!is_array($entries) || empty($entries)) {
+ if (empty($entries)) {
$this->setError(self::ERROR_COMMAND, 'Wrong argument for SETMETADATA command');
return false;
}
@@ -3472,8 +3532,8 @@
/**
* Send the SETMETADATA command with NIL values (RFC5464)
*
- * @param string $mailbox Mailbox name
- * @param array $entries Entry names array
+ * @param string $mailbox Mailbox name
+ * @param array|string $entries Entry names array (or space separated string)
*
* @return bool True on success, False on failure
*
@@ -3501,11 +3561,11 @@
/**
* Send the GETMETADATA command (RFC5464)
*
- * @param string $mailbox Mailbox name
- * @param array $entries Entries
- * @param array $options Command options (with MAXSIZE and DEPTH keys)
+ * @param string $mailbox Mailbox name
+ * @param array|string $entries Entry name(s)
+ * @param array $options Command options (with MAXSIZE and DEPTH keys)
*
- * @return array GETMETADATA result on success, NULL on error
+ * @return ?array GETMETADATA result on success, NULL on error
*
* @since 0.5-beta
*/
@@ -3518,9 +3578,9 @@
$args = [];
// create options string
- if (is_array($options)) {
+ if (!empty($options)) {
$options = array_change_key_case($options, \CASE_UPPER);
- $opts = [];
+ $opts = [];
if (!empty($options['MAXSIZE'])) {
$opts[] = 'MAXSIZE ' . intval($options['MAXSIZE']);
@@ -3542,17 +3602,18 @@
if ($code == self::ERROR_OK) {
$result = [];
- $data = $this->tokenizeResponse($response);
+ $data = $this->tokenizeResponse($response);
// The METADATA response can contain multiple entries in a single
// response or multiple responses for each entry or group of entries
for ($i = 0, $size = count($data); $i < $size; $i++) {
if ($data[$i] === '*'
&& $data[++$i] === 'METADATA'
- && is_string($mbox = $data[++$i])
- && is_array($data[++$i])
+ && is_string($mbox = $data[++$i]) // @phpstan-ignore-line
+ && is_array($data[++$i]) // @phpstan-ignore-line
) {
for ($x = 0, $size2 = count($data[$i]); $x < $size2; $x += 2) {
+ // @phpstan-ignore-next-line
if ($data[$i][$x + 1] !== null) {
$result[$mbox][$data[$i][$x]] = $data[$i][$x + 1];
}
@@ -3562,6 +3623,8 @@
return $result;
}
+
+ return null;
}
/**
@@ -3577,11 +3640,12 @@
*/
public function setAnnotation($mailbox, $data)
{
- if (!is_array($data) || empty($data)) {
+ if (empty($data)) {
$this->setError(self::ERROR_COMMAND, 'Wrong argument for SETANNOTATION command');
return false;
}
+ $entries = [];
foreach ($data as $entry) {
// ANNOTATEMORE drafts before version 08 require quoted parameters
$entries[] = sprintf('%s (%s %s)', $this->escape($entry[0], true),
@@ -3589,7 +3653,7 @@
}
$entries = implode(' ', $entries);
- $result = $this->execute('SETANNOTATION', [$this->escape($mailbox), $entries], self::COMMAND_NORESPONSE);
+ $result = $this->execute('SETANNOTATION', [$this->escape($mailbox), $entries], self::COMMAND_NORESPONSE);
return $result == self::ERROR_OK;
}
@@ -3607,22 +3671,17 @@
*/
public function deleteAnnotation($mailbox, $data)
{
- if (!is_array($data) || empty($data)) {
- $this->setError(self::ERROR_COMMAND, 'Wrong argument for SETANNOTATION command');
- return false;
- }
-
return $this->setAnnotation($mailbox, $data);
}
/**
* Send the GETANNOTATION command (draft-daboo-imap-annotatemore)
*
- * @param string $mailbox Mailbox name
- * @param array $entries Entries names
- * @param array $attribs Attribs names
+ * @param string $mailbox Mailbox name
+ * @param array|string $entries Entries name(s)
+ * @param array|string $attribs Attribs name(s)
*
- * @return array Annotations result on success, NULL on error
+ * @return ?array Annotations result on success, NULL on error
*
* @since 0.5-beta
*/
@@ -3652,8 +3711,8 @@
[$code, $response] = $this->execute('GETANNOTATION', [$this->escape($mailbox), $entries, $attribs]);
if ($code == self::ERROR_OK) {
- $result = [];
- $data = $this->tokenizeResponse($response);
+ $result = [];
+ $data = $this->tokenizeResponse($response);
$last_entry = null;
// Here we returns only data compatible with METADATA result format
@@ -3662,7 +3721,7 @@
$entry = $data[$i];
if (isset($mbox) && is_array($entry)) {
$attribs = $entry;
- $entry = $last_entry;
+ $entry = $last_entry;
} elseif ($entry == '*') {
if ($data[$i + 1] == 'ANNOTATION') {
$mbox = $data[$i + 2];
@@ -3675,6 +3734,7 @@
unset($mbox);
unset($data[$i]);
}
+
continue;
} elseif (isset($mbox)) {
$attribs = $data[++$i];
@@ -3685,7 +3745,7 @@
if (!empty($attribs)) {
for ($x = 0, $len = count($attribs); $x < $len;) {
- $attr = $attribs[$x++];
+ $attr = $attribs[$x++];
$value = $attribs[$x++];
if ($attr == 'value.priv' && $value !== null) {
$result[$mbox]['/private' . $entry] = $value;
@@ -3702,6 +3762,62 @@
return $result;
}
+
+ return null;
+ }
+
+ /**
+ * Send the STORE X ANNOTATION command (RFC5257)
+ *
+ * @param string $mailbox Mailbox name
+ * @param array $entries
+ *
+ * @return bool True on success, False on failure
+ *
+ * @since 1.6.10
+ */
+ public function storeMessageAnnotation($mailbox, $uids, $entries)
+ {
+ if (!$this->hasCapability('ANNOTATE-EXPERIMENT-1')) {
+ return false;
+ }
+
+ if (empty($entries) || empty($uids)) {
+ $this->setError(self::ERROR_COMMAND, 'Wrong argument for STORE ANNOTATION command');
+ return false;
+ }
+
+ if (!$this->select($mailbox)) {
+ return false;
+ }
+
+ /* Example input compatible with rcube_message_header::$annotations:
+ $entries = [
+ '/comment' => [
+ 'value.priv' => 'test1',
+ 'value.shared' => null,
+ ],
+ ];
+ */
+
+ $request = [];
+ foreach ($entries as $name => $annotation) {
+ if (!empty($annotation)) {
+ foreach ($annotation as $key => $value) {
+ $annotation[$key] = $this->escape($key) . ' ' . $this->escape($value, true);
+ }
+ $request[] = $this->escape($name);
+ $request[] = $annotation;
+ }
+ }
+
+ $result = $this->execute(
+ 'UID STORE',
+ [$this->compressMessageSet($uids), 'ANNOTATION', $request],
+ self::COMMAND_NORESPONSE
+ );
+
+ return $result == self::ERROR_OK;
}
/**
@@ -3711,7 +3827,7 @@
* @param int $id Message sequence number or UID
* @param bool $is_uid True if $id is an UID
*
- * @return array|bool Body structure array or False on error.
+ * @return array|bool body structure array or False on error
*
* @since 0.6
*/
@@ -3738,7 +3854,7 @@
public static function getStructurePartData($structure, $part)
{
$part_a = self::getStructurePartArray($structure, $part);
- $data = [];
+ $data = [];
if (empty($part_a)) {
return $data;
@@ -3748,8 +3864,8 @@
if (is_array($part_a[0])) {
$data['type'] = 'multipart';
} else {
- $data['type'] = strtolower($part_a[0]);
- $data['subtype'] = strtolower($part_a[1]);
+ $data['type'] = strtolower($part_a[0]);
+ $data['subtype'] = strtolower($part_a[1]);
$data['encoding'] = strtolower($part_a[5]);
// charset
@@ -3787,9 +3903,9 @@
if (strpos($part, '.') > 0) {
$orig_part = $part;
- $pos = strpos($part, '.');
- $rest = substr($orig_part, $pos + 1);
- $part = substr($orig_part, 0, $pos);
+ $pos = strpos($part, '.');
+ $rest = substr($orig_part, $pos + 1);
+ $part = (int) substr($orig_part, 0, $pos);
return self::getStructurePartArray($a[$part - 1], $rest);
} elseif ($part > 0) {
@@ -3826,9 +3942,9 @@
*/
public function execute($command, $arguments = [], $options = 0, $filter = null)
{
- $tag = $this->nextTag();
- $query = $tag . ' ' . $command;
- $noresp = ($options & self::COMMAND_NORESPONSE);
+ $tag = $this->nextTag();
+ $query = $tag . ' ' . $command;
+ $noresp = ($options & self::COMMAND_NORESPONSE);
$response = $noresp ? null : '';
if (!empty($arguments)) {
@@ -3840,8 +3956,8 @@
// Send command
if (!$this->putLineC($query, true, $options & self::COMMAND_ANONYMIZED)) {
preg_match('/^[A-Z0-9]+ ((UID )?[A-Z]+)/', $query, $matches);
- $cmd = $matches[1] ?: 'UNKNOWN';
- $this->setError(self::ERROR_COMMAND, "Failed to send $cmd command");
+ $cmd = $matches[1] ?? 'UNKNOWN';
+ $this->setError(self::ERROR_COMMAND, "Failed to send {$cmd} command");
return $noresp ? self::ERROR_COMMAND : [self::ERROR_COMMAND, ''];
}
@@ -3858,7 +3974,7 @@
// parse untagged response for [COPYUID 1204196876 3456:3457 123:124] (RFC6851)
if ($line && $command == 'UID MOVE') {
- if (preg_match('/^\\* OK \\[COPYUID [0-9]+ ([0-9,:]+) ([0-9,:]+)\\]/i', $line, $m)) {
+ if (preg_match('/^\* OK \[COPYUID [0-9]+ ([0-9,:]+) ([0-9,:]+)\]/i', $line, $m)) {
$this->data['COPYUID'] = [$m[1], $m[2]];
}
}
@@ -3885,7 +4001,7 @@
// return last line only (without command tag, result and response code)
if ($line && ($options & self::COMMAND_LASTLINE)) {
- $response = preg_replace("/^$tag (OK|NO|BAD|BYE|PREAUTH)?\\s*(\\[[a-z-]+\\])?\\s*/i", '', trim($line));
+ $response = preg_replace("/^{$tag} (OK|NO|BAD|BYE|PREAUTH)?\\s*(\\[[a-z-]+\\])?\\s*/i", '', trim($line));
}
return $noresp ? $code : [$code, $response];
@@ -3910,12 +4026,11 @@
$str = ltrim($str);
// empty string
- if ($str === '' || $str === null) {
+ if ($str === '') {
break;
}
switch ($str[0]) {
-
// String literal
case '{':
if (($epos = strpos($str, "}\r\n", 1)) == false) {
@@ -3925,10 +4040,10 @@
// error
}
+ $bytes = (int) $bytes;
$result[] = $bytes ? substr($str, $epos + 3, $bytes) : '';
- $str = substr($str, $epos + 3 + $bytes);
+ $str = substr($str, $epos + 3 + $bytes);
break;
-
// Quoted string (<< reindent once https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues/7179 is fixed)
case '"':
$len = strlen($str);
@@ -3946,26 +4061,23 @@
// we need to strip slashes for a quoted string
$result[] = stripslashes(substr($str, 1, $pos - 1));
- $str = substr($str, $pos + 1);
+ $str = substr($str, $pos + 1);
break;
-
// Parenthesized list (<< reindent once https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues/7179 is fixed)
case '(':
- $str = substr($str, 1);
+ $str = substr($str, 1);
$result[] = self::tokenizeResponse($str);
break;
-
case ')':
$str = substr($str, 1);
return $result;
-
// String atom, number, astring, NIL, *, % (<< reindent once https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues/7179 is fixed)
default:
// excluded chars: SP, CTL, ), DEL
// we do not exclude [ and ] (#1489223)
if (preg_match('/^([^\x00-\x20\x29\x7F]+)/', $str, $m)) {
$result[] = $m[1] == 'NIL' ? null : $m[1];
- $str = substr($str, strlen($m[1]));
+ $str = substr($str, strlen($m[1]));
}
break;
@@ -3998,10 +4110,10 @@
/**
* Converts message identifiers array into sequence-set syntax
*
- * @param array $messages Message identifiers
- * @param bool $force Forces compression of any size
+ * @param array|string $messages Message identifiers
+ * @param bool $force Forces compression of any size
*
- * @return string Compressed sequence-set
+ * @return string Compressed sequence-set or 'INVALID' on invalid input
*/
public static function compressMessageSet($messages, $force = false)
{
@@ -4022,10 +4134,14 @@
$messages = explode(',', $messages);
}
+ if (empty($messages)) {
+ return '';
+ }
+
sort($messages);
$result = [];
- $start = $prev = $messages[0];
+ $start = $prev = $messages[0];
foreach ($messages as $id) {
$incr = $id - $prev;
@@ -4066,7 +4182,7 @@
return [];
}
- $result = [];
+ $result = [];
$messages = explode(',', $messages);
foreach ($messages as $idx => $part) {
@@ -4162,8 +4278,8 @@
/**
* Escapes a string when it contains special characters (RFC3501)
*
- * @param string $string IMAP string
- * @param bool $force_quotes Forces string quoting (for atoms)
+ * @param ?string $string IMAP string
+ * @param bool $force_quotes Forces string quoting (for atoms)
*
* @return string String atom, quoted-string or string literal
*
@@ -4186,7 +4302,7 @@
// quoted-string
if (!preg_match('/[\r\n\x00\x80-\xFF]/', $string)) {
- return '"' . addcslashes($string, '\\"') . '"';
+ return '"' . addcslashes($string, '\"') . '"';
}
// literal-string
@@ -4196,30 +4312,30 @@
/**
* Set the value of the debugging flag.
*
- * @param bool $debug New value for the debugging flag.
+ * @param bool $debug new value for the debugging flag
* @param callable $handler Logging handler function
*
* @since 0.5-stable
*/
public function setDebug($debug, $handler = null)
{
- $this->debug = $debug;
+ $this->debug = $debug;
$this->debug_handler = $handler;
}
/**
* Write the given debug text to the current debug output handler.
*
- * @param string $message Debug message text.
+ * @param string $message debug message text
*
* @since 0.5-stable
*/
protected function debug($message)
{
if (($len = strlen($message)) > self::DEBUG_LINE_LENGTH) {
- $diff = $len - self::DEBUG_LINE_LENGTH;
+ $diff = $len - self::DEBUG_LINE_LENGTH;
$message = substr($message, 0, self::DEBUG_LINE_LENGTH)
- . "... [truncated $diff bytes]";
+ . "... [truncated {$diff} bytes]";
}
if ($this->resourceid) {
@@ -4229,7 +4345,7 @@
if ($this->debug_handler) {
call_user_func_array($this->debug_handler, [$this, $message]);
} else {
- echo "DEBUG: $message\n";
+ echo "DEBUG: {$message}\n";
}
}
}
diff --git a/src/include/rcube_message_header.php b/src/include/rcube_message_header.php
--- a/src/include/rcube_message_header.php
+++ b/src/include/rcube_message_header.php
@@ -1,6 +1,6 @@
<?php
-/**
+/*
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
@@ -20,135 +20,153 @@
/**
* Struct representing an e-mail message header
- *
- * @package Framework
- * @subpackage Storage
*/
class rcube_message_header
{
/**
* Message sequence number
*
- * @var int
+ * @var ?int
*/
public $id;
/**
* Message unique identifier
*
- * @var int
+ * @var int|string|null
*/
public $uid;
/**
* Message subject
*
- * @var string
+ * @var ?string
*/
public $subject;
/**
* Message sender (From)
*
- * @var string
+ * @var ?string
*/
public $from;
/**
* Message recipient (To)
*
- * @var string
+ * @var ?string
*/
public $to;
/**
* Message additional recipients (Cc)
*
- * @var string
+ * @var ?string
*/
public $cc;
+ /**
+ * Message hidden recipients (Bcc)
+ *
+ * @var ?string
+ */
+ public $bcc;
+
/**
* Message Reply-To header
*
- * @var string
+ * @var ?string
*/
public $replyto;
/**
* Message In-Reply-To header
*
- * @var string
+ * @var ?string
*/
public $in_reply_to;
/**
* Message date (Date)
*
- * @var string
+ * @var ?string
*/
public $date;
/**
* Message identifier (Message-ID)
*
- * @var string
+ * @var ?string
*/
public $messageID;
/**
* Message size
*
- * @var int
+ * @var ?int
*/
public $size;
/**
* Message encoding
*
- * @var string
+ * @var ?string
*/
public $encoding;
/**
* Message charset
*
- * @var string
+ * @var ?string
*/
public $charset;
/**
* Message Content-type
*
- * @var string
+ * @var ?string
*/
public $ctype;
/**
* Message timestamp (based on message date)
*
- * @var int
+ * @var ?int
*/
public $timestamp;
/**
* IMAP bodystructure string
*
- * @var string
+ * @var ?array
*/
public $bodystructure;
+ /**
+ * IMAP body (RFC822.TEXT)
+ *
+ * @var ?string
+ */
+ public $body;
+
+ /**
+ * IMAP part bodies
+ *
+ * @var array
+ */
+ public $bodypart = [];
+
/**
* IMAP internal date
*
- * @var string
+ * @var ?string
*/
public $internaldate;
/**
* Message References header
*
- * @var string
+ * @var ?string
*/
public $references;
@@ -162,14 +180,14 @@
/**
* Message receipt recipient
*
- * @var string
+ * @var ?string
*/
public $mdn_to;
/**
* IMAP folder this message is stored in
*
- * @var string
+ * @var ?string
*/
public $folder;
@@ -187,30 +205,109 @@
*/
public $flags = [];
+ /**
+ * Message annotations (RFC 5257)
+ *
+ * @var ?array
+ */
+ public $annotations;
+
+ /**
+ * Extra flags (for the messages list)
+ *
+ * @var array
+ *
+ * @deprecated Use $flags
+ */
+ public $list_flags = [];
+
+ /**
+ * Extra columns content (for the messages list)
+ *
+ * @var array
+ */
+ public $list_cols = [];
+
+ /**
+ * Message structure
+ *
+ * @var ?rcube_message_part
+ */
+ public $structure;
+
+ /**
+ * Message thread depth
+ *
+ * @var int
+ */
+ public $depth = 0;
+
+ /**
+ * Whether the message has references in the thread
+ *
+ * @var bool
+ */
+ public $has_children = false;
+
+ /**
+ * Number of flagged children (in a thread)
+ *
+ * @var int
+ */
+ public $flagged_children = 0;
+
+ /**
+ * Number of unread children (in a thread)
+ *
+ * @var int
+ */
+ public $unread_children = 0;
+
+ /**
+ * UID of the message parent (in a thread)
+ *
+ * @var int|string|null
+ */
+ public $parent_uid;
+
+ /**
+ * IMAP MODSEQ value
+ *
+ * @var ?int
+ */
+ public $modseq;
+
+ /**
+ * IMAP ENVELOPE
+ *
+ * @var ?string
+ */
+ public $envelope;
+
/**
* Header name to rcube_message_header object property map
*
* @var array
*/
private $obj_headers = [
- 'date' => 'date',
- 'from' => 'from',
- 'to' => 'to',
- 'subject' => 'subject',
- 'reply-to' => 'replyto',
- 'cc' => 'cc',
- 'bcc' => 'bcc',
- 'mbox' => 'folder',
- 'folder' => 'folder',
+ 'date' => 'date',
+ 'from' => 'from',
+ 'to' => 'to',
+ 'subject' => 'subject',
+ 'reply-to' => 'replyto',
+ 'cc' => 'cc',
+ 'bcc' => 'bcc',
+ 'mbox' => 'folder',
+ 'folder' => 'folder',
'content-transfer-encoding' => 'encoding',
- 'in-reply-to' => 'in_reply_to',
- 'content-type' => 'ctype',
- 'charset' => 'charset',
- 'references' => 'references',
+ 'in-reply-to' => 'in_reply_to',
+ 'content-type' => 'ctype',
+ 'charset' => 'charset',
+ 'references' => 'references',
'disposition-notification-to' => 'mdn_to',
- 'x-confirm-reading-to' => 'mdn_to',
- 'message-id' => 'messageID',
- 'x-priority' => 'priority',
+ 'x-confirm-reading-to' => 'mdn_to',
+ 'message-id' => 'messageID',
+ 'x-priority' => 'priority',
];
/**
@@ -219,28 +316,26 @@
* @param string $name Header name
* @param bool $decode Decode the header content
*
- * @param string|null Header content
+ * @return array|string|int|null Header content
*/
public function get($name, $decode = true)
{
- $name = strtolower($name);
+ $name = strtolower($name);
$value = null;
if (isset($this->obj_headers[$name]) && isset($this->{$this->obj_headers[$name]})) {
$value = $this->{$this->obj_headers[$name]};
- }
- else if (isset($this->others[$name])) {
+ } elseif (isset($this->others[$name])) {
$value = $this->others[$name];
}
if ($decode && $value !== null) {
if (is_array($value)) {
foreach ($value as $key => $val) {
- $val = rcube_mime::decode_header($val, $this->charset);
+ $val = rcube_mime::decode_header($val, $this->charset);
$value[$key] = rcube_charset::clean($val);
}
- }
- else {
+ } else {
$value = rcube_mime::decode_header($value, $this->charset);
$value = rcube_charset::clean($value);
}
@@ -261,8 +356,7 @@
if (isset($this->obj_headers[$name])) {
$this->{$this->obj_headers[$name]} = $value;
- }
- else {
+ } else {
$this->others[$name] = $value;
}
}
@@ -276,7 +370,7 @@
*/
public static function from_array($arr)
{
- $obj = new rcube_message_header;
+ $obj = new self();
foreach ($arr as $k => $v) {
$obj->set($k, $v);
}
@@ -285,25 +379,20 @@
}
}
-
/**
* Class for sorting an array of rcube_message_header objects in a predetermined order.
- *
- * @package Framework
- * @subpackage Storage
*/
class rcube_message_header_sorter
{
/** @var array Message UIDs */
private $uids = [];
-
/**
* Set the predetermined sort order.
*
- * @param array $index Numerically indexed array of IMAP UIDs
+ * @param array $index Numerically indexed array of IMAP UIDs
*/
- function set_index($index)
+ public function set_index($index)
{
$index = array_flip($index);
@@ -315,9 +404,9 @@
*
* @param array $headers Array of rcube_message_header objects indexed by UID
*/
- function sort_headers(&$headers)
+ public function sort_headers(&$headers)
{
- uksort($headers, [$this, "compare_uids"]);
+ uksort($headers, [$this, 'compare_uids']);
}
/**
@@ -326,7 +415,7 @@
* @param int $a Array key (UID)
* @param int $b Array key (UID)
*/
- function compare_uids($a, $b)
+ public function compare_uids($a, $b)
{
// then find each sequence number in my ordered list
$posa = isset($this->uids[$a]) ? intval($this->uids[$a]) : -1;
diff --git a/src/tests/Feature/DataMigrator/EngineTest.php b/src/tests/Feature/DataMigrator/EngineTest.php
--- a/src/tests/Feature/DataMigrator/EngineTest.php
+++ b/src/tests/Feature/DataMigrator/EngineTest.php
@@ -7,7 +7,7 @@
use App\DataMigrator\Jobs\FolderJob;
use App\DataMigrator\Jobs\ItemJob;
use App\DataMigrator\Queue as MigratorQueue;
-use App\DataMigrator\Test;
+use App\DataMigrator\Driver\Test;
use Illuminate\Support\Facades\Queue;
use Tests\TestCase;
diff --git a/src/tests/TestCase.php b/src/tests/TestCase.php
--- a/src/tests/TestCase.php
+++ b/src/tests/TestCase.php
@@ -11,6 +11,8 @@
{
use TestCaseTrait;
+ public const BASE_DIR = __DIR__;
+
/**
* {@inheritDoc}
*/
diff --git a/src/tests/Unit/DataMigrator/EWS/AppointmentTest.php b/src/tests/Unit/DataMigrator/Driver/EWS/AppointmentTest.php
rename from src/tests/Unit/DataMigrator/EWS/AppointmentTest.php
rename to src/tests/Unit/DataMigrator/Driver/EWS/AppointmentTest.php
--- a/src/tests/Unit/DataMigrator/EWS/AppointmentTest.php
+++ b/src/tests/Unit/DataMigrator/Driver/EWS/AppointmentTest.php
@@ -1,11 +1,11 @@
<?php
-namespace Tests\Unit\DataMigrator\EWS;
+namespace Tests\Unit\DataMigrator\Driver\EWS;
use App\Backends\DAV\Vevent;
use App\DataMigrator\Account;
+use App\DataMigrator\Driver\EWS;
use App\DataMigrator\Engine;
-use App\DataMigrator\EWS;
use App\DataMigrator\Interface\Folder;
use App\DataMigrator\Interface\Item;
use garethp\ews\API\Type;
@@ -25,7 +25,7 @@
$targetItem = Item::fromArray(['id' => 'test']);
$appointment = new EWS\Appointment($ews, $folder);
- $ical = file_get_contents(__DIR__ . '/../../../data/ews/event/1.ics');
+ $ical = file_get_contents(self::BASE_DIR . '/data/ews/event/1.ics');
$ical = preg_replace('/\r?\n/', "\r\n", $ical);
// FIXME: I haven't found a way to convert xml content into a Type instance
diff --git a/src/tests/Unit/DataMigrator/EWS/ContactTest.php b/src/tests/Unit/DataMigrator/Driver/EWS/ContactTest.php
rename from src/tests/Unit/DataMigrator/EWS/ContactTest.php
rename to src/tests/Unit/DataMigrator/Driver/EWS/ContactTest.php
--- a/src/tests/Unit/DataMigrator/EWS/ContactTest.php
+++ b/src/tests/Unit/DataMigrator/Driver/EWS/ContactTest.php
@@ -1,11 +1,11 @@
<?php
-namespace Tests\Unit\DataMigrator\EWS;
+namespace Tests\Unit\DataMigrator\Driver\EWS;
use App\Backends\DAV\Vcard;
use App\DataMigrator\Account;
+use App\DataMigrator\Driver\EWS;
use App\DataMigrator\Engine;
-use App\DataMigrator\EWS;
use App\DataMigrator\Interface\Folder;
use App\DataMigrator\Interface\Item;
use garethp\ews\API\Type;
@@ -25,7 +25,7 @@
$targetItem = Item::fromArray(['id' => 'test']);
$contact = new EWS\Contact($ews, $folder);
- $vcard = file_get_contents(__DIR__ . '/../../../data/ews/contact/1.vcf');
+ $vcard = file_get_contents(self::BASE_DIR . '/data/ews/contact/1.vcf');
$vcard = preg_replace('/\r?\n/', "\r\n", $vcard);
// FIXME: I haven't found a way to convert xml content into a Type instance
diff --git a/src/tests/Unit/DataMigrator/EWS/DistListTest.php b/src/tests/Unit/DataMigrator/Driver/EWS/DistListTest.php
rename from src/tests/Unit/DataMigrator/EWS/DistListTest.php
rename to src/tests/Unit/DataMigrator/Driver/EWS/DistListTest.php
--- a/src/tests/Unit/DataMigrator/EWS/DistListTest.php
+++ b/src/tests/Unit/DataMigrator/Driver/EWS/DistListTest.php
@@ -1,11 +1,11 @@
<?php
-namespace Tests\Unit\DataMigrator\EWS;
+namespace Tests\Unit\DataMigrator\Driver\EWS;
use App\Backends\DAV\Vcard;
use App\DataMigrator\Account;
+use App\DataMigrator\Driver\EWS;
use App\DataMigrator\Engine;
-use App\DataMigrator\EWS;
use App\DataMigrator\Interface\Folder;
use App\DataMigrator\Interface\Item;
use garethp\ews\API\Type;
diff --git a/src/tests/Unit/DataMigrator/EWS/TaskTest.php b/src/tests/Unit/DataMigrator/Driver/EWS/TaskTest.php
rename from src/tests/Unit/DataMigrator/EWS/TaskTest.php
rename to src/tests/Unit/DataMigrator/Driver/EWS/TaskTest.php
--- a/src/tests/Unit/DataMigrator/EWS/TaskTest.php
+++ b/src/tests/Unit/DataMigrator/Driver/EWS/TaskTest.php
@@ -1,11 +1,11 @@
<?php
-namespace Tests\Unit\DataMigrator\EWS;
+namespace Tests\Unit\DataMigrator\Driver\EWS;
use App\Backends\DAV\Vtodo;
use App\DataMigrator\Account;
+use App\DataMigrator\Driver\EWS;
use App\DataMigrator\Engine;
-use App\DataMigrator\EWS;
use App\DataMigrator\Interface\Folder;
use App\DataMigrator\Interface\Item;
use garethp\ews\API\Type;

File Metadata

Mime Type
text/plain
Expires
Sat, Apr 4, 9:25 AM (9 h, 57 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18825375
Default Alt Text
D5072.1775294729.diff (103 KB)

Event Timeline