Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117811897
D5072.1775279154.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
103 KB
Referenced Files
None
Subscribers
None
D5072.1775279154.diff
View Options
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
Details
Attached
Mime Type
text/plain
Expires
Sat, Apr 4, 5:05 AM (8 h, 9 s)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18825375
Default Alt Text
D5072.1775279154.diff (103 KB)
Attached To
Mode
D5072: DataMigrator drivers refactoring, (WIP) Kolab driver added
Attached
Detach File
Event Timeline