1<?php
2
3/*
4 * This file is part of MailSo.
5 *
6 * (c) 2014 Usenko Timur
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace MailSo\Mail;
13
14/**
15 * @category MailSo
16 * @package Mail
17 */
18class MailClient
19{
20	/**
21	 * @var \MailSo\Log\Logger
22	 */
23	private $oLogger;
24
25	/**
26	 * @var \MailSo\Imap\ImapClient
27	 */
28	private $oImapClient;
29
30	/**
31	 * @access private
32	 */
33	private function __construct()
34	{
35		$this->oLogger = null;
36
37		$this->oImapClient = \MailSo\Imap\ImapClient::NewInstance();
38		$this->oImapClient->SetTimeOuts(10, \MailSo\Config::$ImapTimeout);
39	}
40
41	/**
42	 * @return \MailSo\Mail\MailClient
43	 */
44	public static function NewInstance()
45	{
46		return new self();
47	}
48
49	/**
50	 * @return \MailSo\Imap\ImapClient
51	 */
52	public function ImapClient()
53	{
54		return $this->oImapClient;
55	}
56
57	/**
58	 * @param string $sServerName
59	 * @param int $iPort = 143
60	 * @param int $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT
61	 * @param bool $bVerifySsl = false
62	 * @param string $sClientCert = ""
63	 *
64	 * @return \MailSo\Mail\MailClient
65	 *
66	 * @throws \MailSo\Base\Exceptions\InvalidArgumentException
67	 * @throws \MailSo\Net\Exceptions\Exception
68	 * @throws \MailSo\Imap\Exceptions\Exception
69	 */
70	public function Connect($sServerName, $iPort = 143,
71		$iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT, $bVerifySsl = false, $bAllowSelfSigned = false, $sClientCert = '')
72	{
73		$this->oImapClient->Connect($sServerName, $iPort, $iSecurityType, $bVerifySsl, $bAllowSelfSigned, $sClientCert);
74		return $this;
75	}
76
77	/**
78	 * @param string $sLogin
79	 * @param string $sPassword
80	 * @param string $sProxyAuthUser = ''
81	 * @param bool $bUseAuthPlainIfSupported = true
82	 * @param bool $bUseAuthCramMd5IfSupported = true
83	 *
84	 * @return \MailSo\Mail\MailClient
85	 *
86	 * @throws \MailSo\Base\Exceptions\InvalidArgumentException
87	 * @throws \MailSo\Net\Exceptions\Exception
88	 * @throws \MailSo\Imap\Exceptions\LoginException
89	 */
90	public function Login($sLogin, $sPassword, $sProxyAuthUser = '',
91		$bUseAuthPlainIfSupported = true, $bUseAuthCramMd5IfSupported = true)
92	{
93		$this->oImapClient->Login($sLogin, $sPassword, $sProxyAuthUser, $bUseAuthPlainIfSupported, $bUseAuthCramMd5IfSupported);
94		return $this;
95	}
96
97	/**
98	 * @param string $sXOAuth2Token
99	 *
100	 * @return \MailSo\Mail\MailClient
101	 *
102	 * @throws \MailSo\Base\Exceptions\InvalidArgumentException
103	 * @throws \MailSo\Net\Exceptions\Exception
104	 * @throws \MailSo\Imap\Exceptions\LoginException
105	 */
106	public function LoginWithXOauth2($sXOAuth2Token)
107	{
108		$this->oImapClient->LoginWithXOauth2($sXOAuth2Token);
109		return $this;
110	}
111
112	/**
113	 * @return \MailSo\Mail\MailClient
114	 *
115	 * @throws \MailSo\Net\Exceptions\Exception
116	 */
117	public function Logout()
118	{
119		$this->oImapClient->Logout();
120		return $this;
121	}
122
123	/**
124	 * @return \MailSo\Mail\MailClient
125	 *
126	 * @throws \MailSo\Net\Exceptions\Exception
127	 */
128	public function Disconnect()
129	{
130		$this->oImapClient->Disconnect();
131		return $this;
132	}
133
134	/**
135	 * @return \MailSo\Mail\MailClient
136	 *
137	 * @throws \MailSo\Net\Exceptions\Exception
138	 */
139	public function LogoutAndDisconnect()
140	{
141		return $this->Logout()->Disconnect();
142	}
143
144	/**
145	 * @return bool
146	 */
147	public function IsConnected()
148	{
149		return $this->oImapClient->IsConnected();
150	}
151
152	/**
153	 * @return bool
154	 */
155	public function IsLoggined()
156	{
157		return $this->oImapClient->IsLoggined();
158	}
159
160	/**
161	 * @return string
162	 */
163	private function getEnvelopeOrHeadersRequestStringForSimpleList()
164	{
165		return \MailSo\Imap\Enumerations\FetchType::BuildBodyCustomHeaderRequest(array(
166			\MailSo\Mime\Enumerations\Header::RETURN_PATH,
167			\MailSo\Mime\Enumerations\Header::RECEIVED,
168			\MailSo\Mime\Enumerations\Header::MIME_VERSION,
169			\MailSo\Mime\Enumerations\Header::FROM_,
170			\MailSo\Mime\Enumerations\Header::TO_,
171			\MailSo\Mime\Enumerations\Header::CC,
172			\MailSo\Mime\Enumerations\Header::SENDER,
173			\MailSo\Mime\Enumerations\Header::REPLY_TO,
174			\MailSo\Mime\Enumerations\Header::DATE,
175			\MailSo\Mime\Enumerations\Header::SUBJECT,
176			\MailSo\Mime\Enumerations\Header::CONTENT_TYPE,
177			\MailSo\Mime\Enumerations\Header::LIST_UNSUBSCRIBE,
178		), true);
179	}
180
181	/**
182	 * @return string
183	 */
184	private function getEnvelopeOrHeadersRequestString()
185	{
186		if (\MailSo\Config::$MessageAllHeaders)
187		{
188			return \MailSo\Imap\Enumerations\FetchType::BODY_HEADER_PEEK;
189		}
190
191		return \MailSo\Imap\Enumerations\FetchType::BuildBodyCustomHeaderRequest(array(
192			\MailSo\Mime\Enumerations\Header::RETURN_PATH,
193			\MailSo\Mime\Enumerations\Header::RECEIVED,
194			\MailSo\Mime\Enumerations\Header::MIME_VERSION,
195			\MailSo\Mime\Enumerations\Header::MESSAGE_ID,
196			\MailSo\Mime\Enumerations\Header::CONTENT_TYPE,
197			\MailSo\Mime\Enumerations\Header::FROM_,
198			\MailSo\Mime\Enumerations\Header::TO_,
199			\MailSo\Mime\Enumerations\Header::CC,
200			\MailSo\Mime\Enumerations\Header::BCC,
201			\MailSo\Mime\Enumerations\Header::SENDER,
202			\MailSo\Mime\Enumerations\Header::REPLY_TO,
203			\MailSo\Mime\Enumerations\Header::DELIVERED_TO,
204			\MailSo\Mime\Enumerations\Header::IN_REPLY_TO,
205			\MailSo\Mime\Enumerations\Header::REFERENCES,
206			\MailSo\Mime\Enumerations\Header::DATE,
207			\MailSo\Mime\Enumerations\Header::SUBJECT,
208			\MailSo\Mime\Enumerations\Header::SENSITIVITY,
209			\MailSo\Mime\Enumerations\Header::X_MSMAIL_PRIORITY,
210			\MailSo\Mime\Enumerations\Header::IMPORTANCE,
211			\MailSo\Mime\Enumerations\Header::X_PRIORITY,
212			\MailSo\Mime\Enumerations\Header::X_DRAFT_INFO,
213			\MailSo\Mime\Enumerations\Header::RETURN_RECEIPT_TO,
214			\MailSo\Mime\Enumerations\Header::DISPOSITION_NOTIFICATION_TO,
215			\MailSo\Mime\Enumerations\Header::X_CONFIRM_READING_TO,
216			\MailSo\Mime\Enumerations\Header::AUTHENTICATION_RESULTS,
217			\MailSo\Mime\Enumerations\Header::X_DKIM_AUTHENTICATION_RESULTS,
218			\MailSo\Mime\Enumerations\Header::LIST_UNSUBSCRIBE,
219		), true);
220//
221//		return \MailSo\Imap\Enumerations\FetchType::ENVELOPE;
222	}
223
224	/**
225	 * @param string $sFolderName
226	 * @param string $sMessageFlag
227	 * @param bool $bSetAction = true
228	 * @param bool $sSkipUnsupportedFlag = false
229	 * @param array $aCustomUids = null
230	 *
231	 * @throws \MailSo\Base\Exceptions\InvalidArgumentException
232	 * @throws \MailSo\Net\Exceptions\Exception
233	 * @throws \MailSo\Imap\Exceptions\Exception
234	 * @throws \MailSo\Mail\Exceptions\Exception
235	 */
236	public function MessageSetFlagToAll($sFolderName, $sMessageFlag, $bSetAction = true, $sSkipUnsupportedFlag = false, $aCustomUids = null)
237	{
238		$this->oImapClient->FolderSelect($sFolderName);
239
240		$oFolderInfo = $this->oImapClient->FolderCurrentInformation();
241		if (!$oFolderInfo || !$oFolderInfo->IsFlagSupported($sMessageFlag))
242		{
243			if (!$sSkipUnsupportedFlag)
244			{
245				throw new \MailSo\Mail\Exceptions\RuntimeException('Message flag "'.$sMessageFlag.'" is not supported.');
246			}
247		}
248
249		if ($oFolderInfo && 0 < $oFolderInfo->Exists)
250		{
251			$sStoreAction = $bSetAction
252				? \MailSo\Imap\Enumerations\StoreAction::ADD_FLAGS_SILENT
253				: \MailSo\Imap\Enumerations\StoreAction::REMOVE_FLAGS_SILENT
254			;
255
256			if (is_array($aCustomUids))
257			{
258				if (0 < count($aCustomUids))
259				{
260					$this->oImapClient->MessageStoreFlag(implode(',', $aCustomUids), true, array($sMessageFlag), $sStoreAction);
261				}
262			}
263			else
264			{
265				$this->oImapClient->MessageStoreFlag('1:*', false, array($sMessageFlag), $sStoreAction);
266			}
267		}
268	}
269
270	/**
271	 * @param string $sFolderName
272	 * @param array $aIndexRange
273	 * @param bool $bIndexIsUid
274	 * @param string $sMessageFlag
275	 * @param bool $bSetAction = true
276	 * @param bool $sSkipUnsupportedFlag = false
277	 *
278	 * @throws \MailSo\Base\Exceptions\InvalidArgumentException
279	 * @throws \MailSo\Net\Exceptions\Exception
280	 * @throws \MailSo\Imap\Exceptions\Exception
281	 * @throws \MailSo\Mail\Exceptions\Exception
282	 */
283	public function MessageSetFlag($sFolderName, $aIndexRange, $bIndexIsUid, $sMessageFlag, $bSetAction = true, $sSkipUnsupportedFlag = false)
284	{
285		$this->oImapClient->FolderSelect($sFolderName);
286
287		$oFolderInfo = $this->oImapClient->FolderCurrentInformation();
288		if (!$oFolderInfo || !$oFolderInfo->IsFlagSupported($sMessageFlag))
289		{
290			if (!$sSkipUnsupportedFlag)
291			{
292				throw new \MailSo\Mail\Exceptions\RuntimeException('Message flag "'.$sMessageFlag.'" is not supported.');
293			}
294		}
295		else
296		{
297			$sStoreAction = $bSetAction
298				? \MailSo\Imap\Enumerations\StoreAction::ADD_FLAGS_SILENT
299				: \MailSo\Imap\Enumerations\StoreAction::REMOVE_FLAGS_SILENT
300			;
301
302			$this->oImapClient->MessageStoreFlag(\MailSo\Base\Utils::PrepearFetchSequence($aIndexRange),
303				$bIndexIsUid, array($sMessageFlag), $sStoreAction);
304		}
305	}
306
307	/**
308	 * @param string $sFolderName
309	 * @param array $aIndexRange
310	 * @param bool $bIndexIsUid
311	 * @param bool $bSetAction = true
312	 * @param bool $sSkipUnsupportedFlag = false
313	 *
314	 * @throws \MailSo\Base\Exceptions\InvalidArgumentException
315	 * @throws \MailSo\Net\Exceptions\Exception
316	 * @throws \MailSo\Imap\Exceptions\Exception
317	 */
318	public function MessageSetFlagged($sFolderName, $aIndexRange, $bIndexIsUid, $bSetAction = true, $sSkipUnsupportedFlag = false)
319	{
320		$this->MessageSetFlag($sFolderName, $aIndexRange, $bIndexIsUid,
321			\MailSo\Imap\Enumerations\MessageFlag::FLAGGED, $bSetAction, $sSkipUnsupportedFlag);
322	}
323
324	/**
325	 * @param string $sFolderName
326	 * @param bool $bSetAction = true
327	 * @param array $aCustomUids = null
328	 *
329	 * @throws \MailSo\Base\Exceptions\InvalidArgumentException
330	 * @throws \MailSo\Net\Exceptions\Exception
331	 * @throws \MailSo\Imap\Exceptions\Exception
332	 */
333	public function MessageSetSeenToAll($sFolderName, $bSetAction = true, $aCustomUids = null)
334	{
335		$this->MessageSetFlagToAll($sFolderName, \MailSo\Imap\Enumerations\MessageFlag::SEEN, $bSetAction, true, $aCustomUids);
336	}
337
338	/**
339	 * @param string $sFolderName
340	 * @param array $aIndexRange
341	 * @param bool $bIndexIsUid
342	 * @param bool $bSetAction = true
343	 *
344	 * @throws \MailSo\Base\Exceptions\InvalidArgumentException
345	 * @throws \MailSo\Net\Exceptions\Exception
346	 * @throws \MailSo\Imap\Exceptions\Exception
347	 */
348	public function MessageSetSeen($sFolderName, $aIndexRange, $bIndexIsUid, $bSetAction = true)
349	{
350		$this->MessageSetFlag($sFolderName, $aIndexRange, $bIndexIsUid,
351			\MailSo\Imap\Enumerations\MessageFlag::SEEN, $bSetAction, true);
352	}
353
354	/**
355	 * @param string $sFolderName
356	 * @param int $iIndex
357	 * @param bool $bIndexIsUid = true
358	 * @param \MailSo\Cache\CacheClient $oCacher = null
359	 * @param int $iBodyTextLimit = null
360	 *
361	 * @return \MailSo\Mail\Message|false
362	 *
363	 * @throws \MailSo\Base\Exceptions\InvalidArgumentException
364	 * @throws \MailSo\Net\Exceptions\Exception
365	 * @throws \MailSo\Imap\Exceptions\Exception
366	 */
367	public function Message($sFolderName, $iIndex, $bIndexIsUid = true, $oCacher = null, $iBodyTextLimit = null)
368	{
369		if (!\MailSo\Base\Validator::RangeInt($iIndex, 1))
370		{
371			throw new \MailSo\Base\Exceptions\InvalidArgumentException();
372		}
373
374		$this->oImapClient->FolderSelect($sFolderName);
375
376		$oBodyStructure = null;
377		$oMessage = false;
378
379		$aBodyPeekMimeIndexes = array();
380		$aSignatureMimeIndexes = array();
381
382		$aFetchResponse = $this->oImapClient->Fetch(array(\MailSo\Imap\Enumerations\FetchType::BODYSTRUCTURE), $iIndex, $bIndexIsUid);
383		if (0 < \count($aFetchResponse) && isset($aFetchResponse[0]))
384		{
385			$oBodyStructure = $aFetchResponse[0]->GetFetchBodyStructure();
386			if ($oBodyStructure)
387			{
388				$aTextParts = $oBodyStructure->SearchHtmlOrPlainParts();
389				if (is_array($aTextParts) && 0 < \count($aTextParts))
390				{
391					foreach ($aTextParts as $oPart)
392					{
393						$aBodyPeekMimeIndexes[] = array($oPart->PartID(), $oPart->Size());
394					}
395				}
396
397				$aSignatureParts = $oBodyStructure->SearchByContentType('application/pgp-signature');
398				if (is_array($aSignatureParts) && 0 < \count($aSignatureParts))
399				{
400					foreach ($aSignatureParts as $oPart)
401					{
402						$aSignatureMimeIndexes[] = $oPart->PartID();
403					}
404				}
405			}
406		}
407
408		$aFetchItems = array(
409			\MailSo\Imap\Enumerations\FetchType::INDEX,
410			\MailSo\Imap\Enumerations\FetchType::UID,
411			\MailSo\Imap\Enumerations\FetchType::RFC822_SIZE,
412			\MailSo\Imap\Enumerations\FetchType::INTERNALDATE,
413			\MailSo\Imap\Enumerations\FetchType::FLAGS,
414			$this->getEnvelopeOrHeadersRequestString()
415		);
416
417		if (0 < \count($aBodyPeekMimeIndexes))
418		{
419			foreach ($aBodyPeekMimeIndexes as $aTextMimeData)
420			{
421				$sLine = \MailSo\Imap\Enumerations\FetchType::BODY_PEEK.'['.$aTextMimeData[0].']';
422				if (\is_numeric($iBodyTextLimit) && 0 < $iBodyTextLimit && $iBodyTextLimit < $aTextMimeData[1])
423				{
424					$sLine .= '<0.'.((int) $iBodyTextLimit).'>';
425				}
426
427				$aFetchItems[] = $sLine;
428			}
429		}
430
431		if (0 < \count($aSignatureMimeIndexes))
432		{
433			foreach ($aSignatureMimeIndexes as $sTextMimeIndex)
434			{
435				$aFetchItems[] = \MailSo\Imap\Enumerations\FetchType::BODY_PEEK.'['.$sTextMimeIndex.']';
436			}
437		}
438
439		if (!$oBodyStructure)
440		{
441			$aFetchItems[] = \MailSo\Imap\Enumerations\FetchType::BODYSTRUCTURE;
442		}
443
444		$aFetchResponse = $this->oImapClient->Fetch($aFetchItems, $iIndex, $bIndexIsUid);
445		if (0 < \count($aFetchResponse))
446		{
447			$oMessage = \MailSo\Mail\Message::NewFetchResponseInstance(
448				$sFolderName, $aFetchResponse[0], $oBodyStructure);
449		}
450
451		return $oMessage;
452	}
453
454	/**
455	 * @param mixed $mCallback
456	 * @param string $sFolderName
457	 * @param int $iIndex
458	 * @param bool $bIndexIsUid = true,
459	 * @param string $sMimeIndex = ''
460	 *
461	 * @return bool
462	 *
463	 * @throws \MailSo\Base\Exceptions\InvalidArgumentException
464	 * @throws \MailSo\Net\Exceptions\Exception
465	 * @throws \MailSo\Imap\Exceptions\Exception
466	 */
467	public function MessageMimeStream($mCallback, $sFolderName, $iIndex, $bIndexIsUid = true, $sMimeIndex = '')
468	{
469		if (!is_callable($mCallback))
470		{
471			throw new \MailSo\Base\Exceptions\InvalidArgumentException();
472		}
473
474		$this->oImapClient->FolderSelect($sFolderName);
475
476		$sFileName = '';
477		$sContentType = '';
478		$sMailEncodingName = '';
479
480		$sMimeIndex = trim($sMimeIndex);
481		$aFetchResponse = $this->oImapClient->Fetch(array(
482			0 === \strlen($sMimeIndex)
483				? \MailSo\Imap\Enumerations\FetchType::BODY_HEADER_PEEK
484				: \MailSo\Imap\Enumerations\FetchType::BODY_PEEK.'['.$sMimeIndex.'.MIME]'
485		), $iIndex, $bIndexIsUid);
486
487		if (0 < \count($aFetchResponse))
488		{
489			$sMime = $aFetchResponse[0]->GetFetchValue(
490				0 === \strlen($sMimeIndex)
491					? \MailSo\Imap\Enumerations\FetchType::BODY_HEADER
492					: \MailSo\Imap\Enumerations\FetchType::BODY.'['.$sMimeIndex.'.MIME]'
493			);
494
495			if (0 < \strlen($sMime))
496			{
497				$oHeaders = \MailSo\Mime\HeaderCollection::NewInstance()->Parse($sMime);
498
499				if (0 < \strlen($sMimeIndex))
500				{
501					$sFileName = $oHeaders->ParameterValue(
502						\MailSo\Mime\Enumerations\Header::CONTENT_DISPOSITION,
503						\MailSo\Mime\Enumerations\Parameter::FILENAME);
504
505					if (0 === \strlen($sFileName))
506					{
507						$sFileName = $oHeaders->ParameterValue(
508							\MailSo\Mime\Enumerations\Header::CONTENT_TYPE,
509							\MailSo\Mime\Enumerations\Parameter::NAME);
510					}
511
512					$sMailEncodingName = $oHeaders->ValueByName(
513						\MailSo\Mime\Enumerations\Header::CONTENT_TRANSFER_ENCODING);
514
515					$sContentType = $oHeaders->ValueByName(
516						\MailSo\Mime\Enumerations\Header::CONTENT_TYPE);
517				}
518				else
519				{
520					$sSubject = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::SUBJECT);
521
522					$sFileName = 0 === \strlen($sSubject) ? (string) $iIndex : $sSubject;
523					$sFileName .= '.eml';
524
525					$sContentType = 'message/rfc822';
526				}
527			}
528		}
529
530		$aFetchResponse = $this->oImapClient->Fetch(array(
531			array(\MailSo\Imap\Enumerations\FetchType::BODY_PEEK.'['.$sMimeIndex.']',
532				function ($sParent, $sLiteralAtomUpperCase, $rImapLiteralStream) use ($mCallback, $sMimeIndex, $sMailEncodingName, $sContentType, $sFileName)
533				{
534					if (0 < \strlen($sLiteralAtomUpperCase))
535					{
536						if (is_resource($rImapLiteralStream) && 'FETCH' === $sParent)
537						{
538							$rMessageMimeIndexStream = (0 === \strlen($sMailEncodingName))
539								? $rImapLiteralStream
540								: \MailSo\Base\StreamWrappers\Binary::CreateStream($rImapLiteralStream,
541									\MailSo\Base\StreamWrappers\Binary::GetInlineDecodeOrEncodeFunctionName(
542										$sMailEncodingName, true));
543
544							\call_user_func($mCallback, $rMessageMimeIndexStream, $sContentType, $sFileName, $sMimeIndex);
545						}
546					}
547				}
548			)), $iIndex, $bIndexIsUid);
549
550		return ($aFetchResponse && 1 === \count($aFetchResponse));
551	}
552
553	/**
554	 * @param string $sFolder
555	 * @param array $aIndexRange
556	 * @param bool $bIndexIsUid
557	 * @param bool $bUseExpunge = true
558	 * @param bool $bExpungeAll = false
559	 *
560	 * @return \MailSo\Mail\MailClient
561	 *
562	 * @throws \MailSo\Base\Exceptions\InvalidArgumentException
563	 * @throws \MailSo\Net\Exceptions\Exception
564	 * @throws \MailSo\Imap\Exceptions\Exception
565	 */
566	public function MessageDelete($sFolder, $aIndexRange, $bIndexIsUid, $bUseExpunge = true, $bExpungeAll = false)
567	{
568		if (0 === \strlen($sFolder) || !\is_array($aIndexRange) || 0 === \count($aIndexRange))
569		{
570			throw new \MailSo\Base\Exceptions\InvalidArgumentException();
571		}
572
573		$this->oImapClient->FolderSelect($sFolder);
574
575		$sIndexRange = \MailSo\Base\Utils::PrepearFetchSequence($aIndexRange);
576
577		$this->oImapClient->MessageStoreFlag($sIndexRange, $bIndexIsUid,
578			array(\MailSo\Imap\Enumerations\MessageFlag::DELETED),
579			\MailSo\Imap\Enumerations\StoreAction::ADD_FLAGS_SILENT
580		);
581
582		if ($bUseExpunge)
583		{
584			$this->oImapClient->MessageExpunge($bIndexIsUid ? $sIndexRange : '', $bIndexIsUid, $bExpungeAll);
585		}
586
587		return $this;
588	}
589
590	/**
591	 * @param string $sFromFolder
592	 * @param string $sToFolder
593	 * @param array $aIndexRange
594	 * @param bool $bIndexIsUid
595	 * @param bool $bUseMoveSupported = false
596	 * @param bool $bExpungeAll = false
597	 *
598	 * @return \MailSo\Mail\MailClient
599	 *
600	 * @throws \MailSo\Base\Exceptions\InvalidArgumentException
601	 * @throws \MailSo\Net\Exceptions\Exception
602	 * @throws \MailSo\Imap\Exceptions\Exception
603	 */
604	public function MessageMove($sFromFolder, $sToFolder, $aIndexRange, $bIndexIsUid, $bUseMoveSupported = false, $bExpungeAll = false)
605	{
606		if (0 === \strlen($sFromFolder) || 0 === \strlen($sToFolder) ||
607			!\is_array($aIndexRange) || 0 === \count($aIndexRange))
608		{
609			throw new \MailSo\Base\Exceptions\InvalidArgumentException();
610		}
611
612		$this->oImapClient->FolderSelect($sFromFolder);
613
614		if ($bUseMoveSupported && $this->oImapClient->IsSupported('MOVE'))
615		{
616			$this->oImapClient->MessageMove($sToFolder,
617				\MailSo\Base\Utils::PrepearFetchSequence($aIndexRange), $bIndexIsUid);
618		}
619		else
620		{
621			$this->oImapClient->MessageCopy($sToFolder,
622				\MailSo\Base\Utils::PrepearFetchSequence($aIndexRange), $bIndexIsUid);
623
624			$this->MessageDelete($sFromFolder, $aIndexRange, $bIndexIsUid, true, $bExpungeAll);
625		}
626
627		return $this;
628	}
629
630	/**
631	 * @param string $sFromFolder
632	 * @param string $sToFolder
633	 * @param array $aIndexRange
634	 * @param bool $bIndexIsUid
635	 *
636	 * @return \MailSo\Mail\MailClient
637	 *
638	 * @throws \MailSo\Base\Exceptions\InvalidArgumentException
639	 * @throws \MailSo\Net\Exceptions\Exception
640	 * @throws \MailSo\Imap\Exceptions\Exception
641	 */
642	public function MessageCopy($sFromFolder, $sToFolder, $aIndexRange, $bIndexIsUid)
643	{
644		if (0 === \strlen($sFromFolder) || 0 === \strlen($sToFolder) ||
645			!\is_array($aIndexRange) || 0 === \count($aIndexRange))
646		{
647			throw new \MailSo\Base\Exceptions\InvalidArgumentException();
648		}
649
650		$this->oImapClient->FolderSelect($sFromFolder);
651		$this->oImapClient->MessageCopy($sToFolder,
652			\MailSo\Base\Utils::PrepearFetchSequence($aIndexRange), $bIndexIsUid);
653
654		return $this;
655	}
656
657	/**
658	 * @return \MailSo\Mail\MailClient
659	 *
660	 * @throws \MailSo\Net\Exceptions\Exception
661	 * @throws \MailSo\Imap\Exceptions\Exception
662	 */
663	public function FolderUnSelect()
664	{
665		if ($this->oImapClient->IsSelected())
666		{
667			$this->oImapClient->FolderUnSelect();
668		}
669
670		return $this;
671	}
672
673	/**
674	 * @param resource $rMessageStream
675	 * @param int $iMessageStreamSize
676	 * @param string $sFolderToSave
677	 * @param array $aAppendFlags = null
678	 * @param int $iUid = null
679	 *
680	 * @return \MailSo\Mail\MailClient
681	 */
682	public function MessageAppendStream($rMessageStream, $iMessageStreamSize, $sFolderToSave, $aAppendFlags = null, &$iUid = null)
683	{
684		if (!\is_resource($rMessageStream) || 0 === \strlen($sFolderToSave))
685		{
686			throw new \MailSo\Base\Exceptions\InvalidArgumentException();
687		}
688
689		$this->oImapClient->MessageAppendStream(
690			$sFolderToSave, $rMessageStream, $iMessageStreamSize, $aAppendFlags, $iUid);
691
692		return $this;
693	}
694
695	/**
696	 * @param string $sMessageFileName
697	 * @param string $sFolderToSave
698	 * @param array $aAppendFlags = null
699	 * @param int &$iUid = null
700	 *
701	 * @return \MailSo\Mail\MailClient
702	 */
703	public function MessageAppendFile($sMessageFileName, $sFolderToSave, $aAppendFlags = null, &$iUid = null)
704	{
705		if (!@\is_file($sMessageFileName) || !@\is_readable($sMessageFileName))
706		{
707			throw new \MailSo\Base\Exceptions\InvalidArgumentException();
708		}
709
710		$iMessageStreamSize = \filesize($sMessageFileName);
711		$rMessageStream = \fopen($sMessageFileName, 'rb');
712
713		$this->MessageAppendStream($rMessageStream, $iMessageStreamSize, $sFolderToSave, $aAppendFlags, $iUid);
714
715		if (\is_resource($rMessageStream))
716		{
717			@fclose($rMessageStream);
718		}
719
720		return $this;
721	}
722
723	/**
724	 * @param string $sFolderName
725	 * @param int $iCount
726	 * @param int $iUnseenCount
727	 * @param string $sUidNext
728	 * @param string $sHighestModSeq
729	 *
730	 * @return void
731	 */
732	protected function initFolderValues($sFolderName, &$iCount, &$iUnseenCount,
733		&$sUidNext, &$sHighestModSeq = '')
734	{
735		$aTypes = array(
736			\MailSo\Imap\Enumerations\FolderResponseStatus::MESSAGES,
737			\MailSo\Imap\Enumerations\FolderResponseStatus::UNSEEN,
738			\MailSo\Imap\Enumerations\FolderResponseStatus::UIDNEXT
739		);
740
741		if ($this->oImapClient->IsSupported('CONDSTORE'))
742		{
743			$aTypes[] = \MailSo\Imap\Enumerations\FolderResponseStatus::HIGHESTMODSEQ;
744		}
745
746		$aFolderStatus = $this->oImapClient->FolderStatus($sFolderName, $aTypes);
747
748		$iCount = isset($aFolderStatus[\MailSo\Imap\Enumerations\FolderResponseStatus::MESSAGES])
749			? (int) $aFolderStatus[\MailSo\Imap\Enumerations\FolderResponseStatus::MESSAGES] : 0;
750
751		$iUnseenCount = isset($aFolderStatus[\MailSo\Imap\Enumerations\FolderResponseStatus::UNSEEN])
752			? (int) $aFolderStatus[\MailSo\Imap\Enumerations\FolderResponseStatus::UNSEEN] : 0;
753
754		$sUidNext = isset($aFolderStatus[\MailSo\Imap\Enumerations\FolderResponseStatus::UIDNEXT])
755			? (string) $aFolderStatus[\MailSo\Imap\Enumerations\FolderResponseStatus::UIDNEXT] : '0';
756
757		$sHighestModSeq = isset($aFolderStatus[\MailSo\Imap\Enumerations\FolderResponseStatus::HIGHESTMODSEQ])
758			? (string) $aFolderStatus[\MailSo\Imap\Enumerations\FolderResponseStatus::HIGHESTMODSEQ] : '';
759
760		if ($this->IsGmail() &&
761			('INBOX' === $sFolderName || '[gmail]' === \strtolower(\substr($sFolderName, 0, 7))))
762		{
763			$oFolder = $this->oImapClient->FolderCurrentInformation();
764			if ($oFolder && null !== $oFolder->Exists && $oFolder->FolderName === $sFolderName)
765			{
766				$iSubCount = (int) $oFolder->Exists;
767				if (0 < $iSubCount && $iSubCount < $iCount)
768				{
769					$iCount = $iSubCount;
770				}
771			}
772		}
773	}
774
775	/**
776	 * @return string
777	 */
778	public function GenerateImapClientHash()
779	{
780		return \md5('ImapClientHash/'.
781			$this->oImapClient->GetLogginedUser().'@'.
782			$this->oImapClient->GetConnectedHost().':'.
783			$this->oImapClient->GetConnectedPort()
784		);
785	}
786
787	/**
788	 * @param string $sFolder
789	 * @param int $iCount
790	 * @param int $iUnseenCount
791	 * @param string $sUidNext
792	 * @param string $sHighestModSeq = ''
793	 *
794	 * @return string
795	 */
796	public function GenerateFolderHash($sFolder, $iCount, $iUnseenCount, $sUidNext, $sHighestModSeq = '')
797	{
798		$iUnseenCount = 0; // unneccessery
799		return \md5('FolderHash/'.$sFolder.'-'.$iCount.'-'.$iUnseenCount.'-'.$sUidNext.'-'.
800			$sHighestModSeq.'-'.$this->GenerateImapClientHash().'-'.
801			\MailSo\Config::$MessageListPermanentFilter
802		);
803	}
804
805	/**
806	 * @param string $sFolderName
807	 * @param string $sPrevUidNext
808	 * @param string $sCurrentUidNext
809	 *
810	 * @return array
811	 */
812	private function getFolderNextMessageInformation($sFolderName, $sPrevUidNext, $sCurrentUidNext)
813	{
814		$aNewMessages = array();
815
816		if (0 < \strlen($sPrevUidNext) && (string) $sPrevUidNext !== (string) $sCurrentUidNext)
817		{
818			$this->oImapClient->FolderSelect($sFolderName);
819
820			$aFetchResponse = $this->oImapClient->Fetch(array(
821				\MailSo\Imap\Enumerations\FetchType::INDEX,
822				\MailSo\Imap\Enumerations\FetchType::UID,
823				\MailSo\Imap\Enumerations\FetchType::FLAGS,
824				\MailSo\Imap\Enumerations\FetchType::BuildBodyCustomHeaderRequest(array(
825					\MailSo\Mime\Enumerations\Header::FROM_,
826					\MailSo\Mime\Enumerations\Header::SUBJECT,
827					\MailSo\Mime\Enumerations\Header::CONTENT_TYPE
828				))
829			), $sPrevUidNext.':*', true);
830
831			if (\is_array($aFetchResponse) && 0 < \count($aFetchResponse))
832			{
833				foreach ($aFetchResponse as /* @var $oFetchResponse \MailSo\Imap\FetchResponse */ $oFetchResponse)
834				{
835					$aFlags = \array_map('strtolower', $oFetchResponse->GetFetchValue(
836						\MailSo\Imap\Enumerations\FetchType::FLAGS));
837
838					if (!\in_array(\strtolower(\MailSo\Imap\Enumerations\MessageFlag::SEEN), $aFlags))
839					{
840						$sUid = $oFetchResponse->GetFetchValue(\MailSo\Imap\Enumerations\FetchType::UID);
841						$sHeaders = $oFetchResponse->GetHeaderFieldsValue();
842
843						$oHeaders = \MailSo\Mime\HeaderCollection::NewInstance()->Parse($sHeaders);
844
845						$sContentTypeCharset = $oHeaders->ParameterValue(
846							\MailSo\Mime\Enumerations\Header::CONTENT_TYPE,
847							\MailSo\Mime\Enumerations\Parameter::CHARSET
848						);
849
850						$sCharset = '';
851						if (0 < \strlen($sContentTypeCharset))
852						{
853							$sCharset = $sContentTypeCharset;
854						}
855
856						if (0 < \strlen($sCharset))
857						{
858							$oHeaders->SetParentCharset($sCharset);
859						}
860
861						$aNewMessages[] = array(
862							'Folder' => $sFolderName,
863							'Uid' => $sUid,
864							'Subject' => $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::SUBJECT, 0 === \strlen($sCharset)),
865							'From' => $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::FROM_, 0 === \strlen($sCharset))
866						);
867					}
868				}
869			}
870		}
871
872		return $aNewMessages;
873	}
874
875	/**
876	 * @param string $sFolderName
877	 * @param string $sPrevUidNext = ''
878	 * @param array $aUids = ''
879	 *
880	 * @return string
881	 *
882	 * @throws \MailSo\Base\Exceptions\InvalidArgumentException
883	 * @throws \MailSo\Net\Exceptions\Exception
884	 * @throws \MailSo\Imap\Exceptions\Exception
885	 */
886	public function FolderInformation($sFolderName, $sPrevUidNext = '', $aUids = array())
887	{
888		$aFlags = array();
889
890		$bSelect = false;
891		if ($this->IsGmail() &&
892			('INBOX' === $sFolderName || '[gmail]' === \strtolower(\substr($sFolderName, 0, 7))))
893		{
894			$this->oImapClient->FolderSelect($sFolderName);
895			$bSelect = true;
896		}
897
898		if (\is_array($aUids) && 0 < \count($aUids))
899		{
900			if (!$bSelect)
901			{
902				$this->oImapClient->FolderSelect($sFolderName);
903			}
904
905			$aFetchResponse = $this->oImapClient->Fetch(array(
906				\MailSo\Imap\Enumerations\FetchType::INDEX,
907				\MailSo\Imap\Enumerations\FetchType::UID,
908				\MailSo\Imap\Enumerations\FetchType::FLAGS
909			), \MailSo\Base\Utils::PrepearFetchSequence($aUids), true);
910
911			if (\is_array($aFetchResponse) && 0 < \count($aFetchResponse))
912			{
913				foreach ($aFetchResponse as $oFetchResponse)
914				{
915					$sUid = $oFetchResponse->GetFetchValue(\MailSo\Imap\Enumerations\FetchType::UID);
916					$aFlags[(\is_numeric($sUid) ? (int) $sUid : 0)] =
917						$oFetchResponse->GetFetchValue(\MailSo\Imap\Enumerations\FetchType::FLAGS);
918				}
919			}
920		}
921
922		$iCount = 0;
923		$iUnseenCount = 0;
924		$sUidNext = '0';
925		$sHighestModSeq = '';
926
927		$this->initFolderValues($sFolderName, $iCount, $iUnseenCount, $sUidNext, $sHighestModSeq);
928
929		$aResult = array(
930			'Folder' => $sFolderName,
931			'Hash' => $this->GenerateFolderHash($sFolderName, $iCount, $iUnseenCount, $sUidNext, $sHighestModSeq),
932			'MessageCount' => $iCount,
933			'MessageUnseenCount' => $iUnseenCount,
934			'UidNext' => $sUidNext,
935			'Flags' => $aFlags,
936			'HighestModSeq' => $sHighestModSeq,
937			'NewMessages' => 'INBOX' === $sFolderName && \MailSo\Config::$CheckNewMessages ?
938				$this->getFolderNextMessageInformation($sFolderName, $sPrevUidNext, $sUidNext) : array()
939		);
940
941		return $aResult;
942	}
943
944	/**
945	 * @param string $sFolderName
946	 *
947	 * @return string
948	 *
949	 * @throws \MailSo\Base\Exceptions\InvalidArgumentException
950	 * @throws \MailSo\Net\Exceptions\Exception
951	 * @throws \MailSo\Imap\Exceptions\Exception
952	 */
953	public function FolderHash($sFolderName)
954	{
955		$iCount = 0;
956		$iUnseenCount = 0;
957		$sUidNext = '0';
958		$sHighestModSeq = '';
959
960		$this->initFolderValues($sFolderName, $iCount, $iUnseenCount, $sUidNext, $sHighestModSeq);
961
962		return $this->GenerateFolderHash($sFolderName, $iCount, $iUnseenCount, $sUidNext, $sHighestModSeq);
963	}
964
965	/**
966	 * @return int
967	 *
968	 * @throws \MailSo\Net\Exceptions\Exception
969	 * @throws \MailSo\Imap\Exceptions\Exception
970	 */
971	public function InboxUnreadCount()
972	{
973		$aFolderStatus = $this->oImapClient->FolderStatus('INBOX', array(
974			\MailSo\Imap\Enumerations\FolderResponseStatus::UNSEEN
975		));
976
977		$iResult = isset($aFolderStatus[\MailSo\Imap\Enumerations\FolderResponseStatus::UNSEEN]) ?
978			(int) $aFolderStatus[\MailSo\Imap\Enumerations\FolderResponseStatus::UNSEEN] : 0;
979
980		return 0 < $iResult ? $iResult : 0;
981	}
982
983	/**
984	 * @return bool
985	 */
986	public function IsGmail()
987	{
988		return 'ssl://imap.gmail.com' === \strtolower($this->oImapClient->GetConnectedHost());
989	}
990
991	/**
992	 * @param string $sSearch
993	 * @param bool $bDetectGmail = true
994	 *
995	 * @return string
996	 */
997	private function escapeSearchString($sSearch, $bDetectGmail = true)
998	{
999		return !\MailSo\Base\Utils::IsAscii($sSearch)
1000			? '{'.\strlen($sSearch).'}'."\r\n".$sSearch : $this->oImapClient->EscapeString($sSearch);
1001//		return ($bDetectGmail && !\MailSo\Base\Utils::IsAscii($sSearch) && $this->IsGmail())
1002//			? '{'.\strlen($sSearch).'+}'."\r\n".$sSearch : $this->oImapClient->EscapeString($sSearch);
1003	}
1004
1005	/**
1006	 * @param string $sDate
1007	 * @param int $iTimeZoneOffset
1008	 *
1009	 * @return int
1010	 */
1011	private function parseSearchDate($sDate, $iTimeZoneOffset)
1012	{
1013		$iResult = 0;
1014		if (0 < \strlen($sDate))
1015		{
1016			$oDateTime = \DateTime::createFromFormat('Y.m.d', $sDate, \MailSo\Base\DateTimeHelper::GetUtcTimeZoneObject());
1017			return $oDateTime ? $oDateTime->getTimestamp() - $iTimeZoneOffset : 0;
1018		}
1019
1020		return $iResult;
1021	}
1022
1023	/**
1024	 * @param string $sSize
1025	 *
1026	 * @return int
1027	 */
1028	private function parseFriendlySize($sSize)
1029	{
1030		$sSize = preg_replace('/[^0-9bBkKmM]/', '', $sSize);
1031
1032		$iResult = 0;
1033		$aMatch = array();
1034
1035		if (\preg_match('/([\d]+)(B|KB|M|MB|G|GB)$/i', $sSize, $aMatch) && isset($aMatch[1], $aMatch[2]))
1036		{
1037			$iResult = (int) $aMatch[1];
1038			switch (\strtoupper($aMatch[2]))
1039			{
1040				case 'K':
1041				case 'KB':
1042					$iResult *= 1024;
1043				case 'M':
1044				case 'MB':
1045					$iResult *= 1024;
1046				case 'G':
1047				case 'GB':
1048					$iResult *= 1024;
1049			}
1050		}
1051		else
1052		{
1053			$iResult = (int) $sSize;
1054		}
1055
1056		return $iResult;
1057	}
1058
1059	/**
1060	 * @param string $sSearch
1061	 *
1062	 * @return array
1063	 */
1064	private function parseSearchString($sSearch)
1065	{
1066		$aResult = array(
1067			'OTHER' => ''
1068		);
1069
1070		$aCache = array();
1071
1072		$sReg = 'e?mail|from|to|subject|has|is|date|text|body|size|larger|bigger|smaller|maxsize|minsize';
1073
1074		$sSearch = \MailSo\Base\Utils::StripSpaces($sSearch);
1075		$sSearch = \trim(\preg_replace('/('.$sReg.'): /i', '\\1:', $sSearch));
1076
1077		$mMatch = array();
1078		\preg_match_all('/".*?(?<!\\\)"/', $sSearch, $mMatch);
1079		if (\is_array($mMatch) && isset($mMatch[0]) && \is_array($mMatch[0]) && 0 < \count($mMatch[0]))
1080		{
1081			foreach ($mMatch[0] as $sItem)
1082			{
1083				do
1084				{
1085					$sKey = \MailSo\Base\Utils::Md5Rand();
1086				}
1087				while (isset($aCache[$sKey]));
1088
1089				$aCache[$sKey] = \stripcslashes($sItem);
1090				$sSearch = \str_replace($sItem, $sKey, $sSearch);
1091			}
1092		}
1093
1094		\preg_match_all('/\'.*?(?<!\\\)\'/', $sSearch, $mMatch);
1095		if (\is_array($mMatch) && isset($mMatch[0]) && \is_array($mMatch[0]) && 0 < \count($mMatch[0]))
1096		{
1097			foreach ($mMatch[0] as $sItem)
1098			{
1099				do
1100				{
1101					$sKey = \MailSo\Base\Utils::Md5Rand();
1102				}
1103				while (isset($aCache[$sKey]));
1104
1105				$aCache[$sKey] = \stripcslashes($sItem);
1106				$sSearch = \str_replace($sItem, $sKey, $sSearch);
1107			}
1108		}
1109
1110		$mMatch = array();
1111		\preg_match_all('/('.$sReg.'):([^\s]*)/i', $sSearch, $mMatch);
1112		if (\is_array($mMatch) && isset($mMatch[1]) && \is_array($mMatch[1]) && 0 < \count($mMatch[1]))
1113		{
1114			if (\is_array($mMatch[0]))
1115			{
1116				foreach ($mMatch[0] as $sToken)
1117				{
1118					$sSearch = \str_replace($sToken, '', $sSearch);
1119				}
1120
1121				$sSearch = \MailSo\Base\Utils::StripSpaces($sSearch);
1122			}
1123
1124			foreach ($mMatch[1] as $iIndex => $sName)
1125			{
1126				if (isset($mMatch[2][$iIndex]) && 0 < \strlen($mMatch[2][$iIndex]))
1127				{
1128					$sName = \strtoupper($sName);
1129					$sValue = $mMatch[2][$iIndex];
1130					switch ($sName)
1131					{
1132						case 'TEXT':
1133						case 'BODY':
1134						case 'EMAIL':
1135						case 'MAIL':
1136						case 'FROM':
1137						case 'TO':
1138						case 'SUBJECT':
1139						case 'IS':
1140						case 'HAS':
1141						case 'SIZE':
1142						case 'SMALLER':
1143						case 'LARGER':
1144						case 'BIGGER':
1145						case 'MAXSIZE':
1146						case 'MINSIZE':
1147						case 'DATE':
1148							if ('MAIL' === $sName)
1149							{
1150								$sName = 'EMAIL';
1151							}
1152							if ('BODY' === $sName)
1153							{
1154								$sName = 'TEXT';
1155							}
1156							if ('SIZE' === $sName || 'BIGGER' === $sName || 'MINSIZE' === $sName)
1157							{
1158								$sName = 'LARGER';
1159							}
1160							if ('MAXSIZE' === $sName)
1161							{
1162								$sName = 'SMALLER';
1163							}
1164							$aResult[$sName] = $sValue;
1165							break;
1166					}
1167				}
1168			}
1169		}
1170
1171		$aResult['OTHER'] = $sSearch;
1172		foreach ($aResult as $sName => $sValue)
1173		{
1174			if (isset($aCache[$sValue]))
1175			{
1176				$aResult[$sName] = \trim($aCache[$sValue], '"\' ');
1177			}
1178		}
1179
1180		return $aResult;
1181	}
1182
1183	/**
1184	 * @param string $sSearch
1185	 * @param string $sFilter
1186	 * @param int $iTimeZoneOffset = 0
1187	 * @param bool $bUseCache = true
1188	 *
1189	 * @return string
1190	 */
1191	private function getImapSearchCriterias($sSearch, $sFilter, $iTimeZoneOffset = 0, &$bUseCache = true)
1192	{
1193		$bUseCache = true;
1194		$iTimeFilter = 0;
1195		$aCriteriasResult = array();
1196
1197		if (0 < \MailSo\Config::$MessageListDateFilter)
1198		{
1199			$iD = \time() - 3600 * 24 * 30 * \MailSo\Config::$MessageListDateFilter;
1200			$iTimeFilter = \gmmktime(1, 1, 1, \gmdate('n', $iD), 1, \gmdate('Y', $iD));
1201		}
1202
1203		if (0 < \strlen(\trim($sSearch)))
1204		{
1205			$sGmailRawSearch = '';
1206			$sResultBodyTextSearch = '';
1207
1208			$aLines = $this->parseSearchString($sSearch);
1209			$bIsGmail = $this->oImapClient->IsSupported('X-GM-EXT-1');
1210
1211			if (1 === \count($aLines) && isset($aLines['OTHER']))
1212			{
1213				$sValue = $this->escapeSearchString($aLines['OTHER']);
1214
1215				if (\MailSo\Config::$MessageListFastSimpleSearch)
1216				{
1217					$aCriteriasResult[] = 'OR OR OR';
1218					$aCriteriasResult[] = 'FROM';
1219					$aCriteriasResult[] = $sValue;
1220					$aCriteriasResult[] = 'TO';
1221					$aCriteriasResult[] = $sValue;
1222					$aCriteriasResult[] = 'CC';
1223					$aCriteriasResult[] = $sValue;
1224					$aCriteriasResult[] = 'SUBJECT';
1225					$aCriteriasResult[] = $sValue;
1226				}
1227				else
1228				{
1229					$aCriteriasResult[] = 'TEXT';
1230					$aCriteriasResult[] = $sValue;
1231				}
1232			}
1233			else
1234			{
1235				if (isset($aLines['EMAIL']))
1236				{
1237					$sValue = $this->escapeSearchString($aLines['EMAIL']);
1238
1239					$aCriteriasResult[] = 'OR OR';
1240					$aCriteriasResult[] = 'FROM';
1241					$aCriteriasResult[] = $sValue;
1242					$aCriteriasResult[] = 'TO';
1243					$aCriteriasResult[] = $sValue;
1244					$aCriteriasResult[] = 'CC';
1245					$aCriteriasResult[] = $sValue;
1246
1247					unset($aLines['EMAIL']);
1248				}
1249
1250				if (isset($aLines['TO']))
1251				{
1252					$sValue = $this->escapeSearchString($aLines['TO']);
1253
1254					$aCriteriasResult[] = 'OR';
1255					$aCriteriasResult[] = 'TO';
1256					$aCriteriasResult[] = $sValue;
1257					$aCriteriasResult[] = 'CC';
1258					$aCriteriasResult[] = $sValue;
1259
1260					unset($aLines['TO']);
1261				}
1262
1263				$sMainText = '';
1264				foreach ($aLines as $sName => $sRawValue)
1265				{
1266					if ('' === \trim($sRawValue))
1267					{
1268						continue;
1269					}
1270
1271					$sValue = $this->escapeSearchString($sRawValue);
1272					switch ($sName)
1273					{
1274						case 'FROM':
1275							$aCriteriasResult[] = 'FROM';
1276							$aCriteriasResult[] = $sValue;
1277							break;
1278						case 'SUBJECT':
1279							$aCriteriasResult[] = 'SUBJECT';
1280							$aCriteriasResult[] = $sValue;
1281							break;
1282						case 'OTHER':
1283						case 'TEXT':
1284							$sMainText .= ' '.$sRawValue;
1285							break;
1286						case 'HAS':
1287							$aValue = \explode(',', \strtolower($sRawValue));
1288							$aValue = \array_map('trim', $aValue);
1289
1290							$aCompareArray = array('file', 'files', 'attach', 'attachs', 'attachment', 'attachments');
1291							if (\count($aCompareArray) > \count(\array_diff($aCompareArray, $aValue)))
1292							{
1293								if ($bIsGmail)
1294								{
1295									$sGmailRawSearch .= ' has:attachment';
1296								}
1297								else
1298								{
1299									// Simple, is not detailed search (Sometimes doesn't work)
1300									$aCriteriasResult[] = 'OR OR OR';
1301									$aCriteriasResult[] = 'HEADER Content-Type application/';
1302									$aCriteriasResult[] = 'HEADER Content-Type multipart/m';
1303									$aCriteriasResult[] = 'HEADER Content-Type multipart/signed';
1304									$aCriteriasResult[] = 'HEADER Content-Type multipart/report';
1305								}
1306							}
1307
1308						case 'IS':
1309							$aValue = \explode(',', \strtolower($sRawValue));
1310							$aValue = \array_map('trim', $aValue);
1311
1312							$aCompareArray = array('flag', 'flagged', 'star', 'starred', 'pinned');
1313							$aCompareArray2 = array('unflag', 'unflagged', 'unstar', 'unstarred', 'unpinned');
1314							if (\count($aCompareArray) > \count(\array_diff($aCompareArray, $aValue)))
1315							{
1316								$aCriteriasResult[] = 'FLAGGED';
1317								$bUseCache = false;
1318							}
1319							else if (\count($aCompareArray2) > \count(\array_diff($aCompareArray2, $aValue)))
1320							{
1321								$aCriteriasResult[] = 'UNFLAGGED';
1322								$bUseCache = false;
1323							}
1324
1325							$aCompareArray = array('unread', 'unseen');
1326							$aCompareArray2 = array('read', 'seen');
1327							if (\count($aCompareArray) > \count(\array_diff($aCompareArray, $aValue)))
1328							{
1329								$aCriteriasResult[] = 'UNSEEN';
1330								$bUseCache = false;
1331							}
1332							else if (\count($aCompareArray2) > \count(\array_diff($aCompareArray2, $aValue)))
1333							{
1334								$aCriteriasResult[] = 'SEEN';
1335								$bUseCache = false;
1336							}
1337							break;
1338
1339						case 'LARGER':
1340							$aCriteriasResult[] = 'LARGER';
1341							$aCriteriasResult[] =  $this->parseFriendlySize($sRawValue);
1342							break;
1343						case 'SMALLER':
1344							$aCriteriasResult[] = 'SMALLER';
1345							$aCriteriasResult[] =  $this->parseFriendlySize($sRawValue);
1346							break;
1347						case 'DATE':
1348							$iDateStampFrom = $iDateStampTo = 0;
1349
1350							$sDate = $sRawValue;
1351							$aDate = \explode('/', $sDate);
1352
1353							if (\is_array($aDate) && 2 === \count($aDate))
1354							{
1355								if (0 < \strlen($aDate[0]))
1356								{
1357									$iDateStampFrom = $this->parseSearchDate($aDate[0], $iTimeZoneOffset);
1358								}
1359
1360								if (0 < \strlen($aDate[1]))
1361								{
1362									$iDateStampTo = $this->parseSearchDate($aDate[1], $iTimeZoneOffset);
1363									$iDateStampTo += 60 * 60 * 24;
1364								}
1365							}
1366							else
1367							{
1368								if (0 < \strlen($sDate))
1369								{
1370									$iDateStampFrom = $this->parseSearchDate($sDate, $iTimeZoneOffset);
1371									$iDateStampTo = $iDateStampFrom + 60 * 60 * 24;
1372								}
1373							}
1374
1375							if (0 < $iDateStampFrom)
1376							{
1377								$aCriteriasResult[] = 'SINCE';
1378								$aCriteriasResult[] = \gmdate('j-M-Y', $iTimeFilter > $iDateStampFrom ?
1379									$iTimeFilter : $iDateStampFrom);
1380
1381								$iTimeFilter = 0;
1382							}
1383
1384							if (0 < $iDateStampTo)
1385							{
1386								$aCriteriasResult[] = 'BEFORE';
1387								$aCriteriasResult[] = \gmdate('j-M-Y', $iDateStampTo);
1388							}
1389							break;
1390					}
1391				}
1392
1393				if ('' !== \trim($sMainText))
1394				{
1395					$sMainText = \trim(\MailSo\Base\Utils::StripSpaces($sMainText), '"');
1396					if ($bIsGmail)
1397					{
1398						$sGmailRawSearch .= ' '.$sMainText;
1399					}
1400					else
1401					{
1402						$sResultBodyTextSearch .= ' '.$sMainText;
1403					}
1404				}
1405			}
1406
1407			$sGmailRawSearch = \trim($sGmailRawSearch);
1408			if ($bIsGmail && 0 < \strlen($sGmailRawSearch))
1409			{
1410				$aCriteriasResult[] = 'X-GM-RAW';
1411				$aCriteriasResult[] = $this->escapeSearchString($sGmailRawSearch, false);
1412			}
1413
1414			$sResultBodyTextSearch = \trim($sResultBodyTextSearch);
1415			if (0 < \strlen($sResultBodyTextSearch))
1416			{
1417				$aCriteriasResult[] = 'BODY';
1418				$aCriteriasResult[] = $this->escapeSearchString($sResultBodyTextSearch);
1419			}
1420		}
1421
1422		$sCriteriasResult = \trim(\implode(' ', $aCriteriasResult));
1423
1424		if (0 < $iTimeFilter)
1425		{
1426			$sCriteriasResult .= ' SINCE '.\gmdate('j-M-Y', $iTimeFilter);
1427		}
1428
1429		$sCriteriasResult = \trim($sCriteriasResult);
1430		if (\MailSo\Config::$MessageListUndeletedOnly)
1431		{
1432			$sCriteriasResult = \trim($sCriteriasResult.' UNDELETED');
1433		}
1434
1435		$sFilter = \trim($sFilter);
1436		if ('' !== $sFilter)
1437		{
1438			$sCriteriasResult .= ' '.$sFilter;
1439		}
1440
1441		$sCriteriasResult = \trim($sCriteriasResult);
1442		if ('' !== \MailSo\Config::$MessageListPermanentFilter)
1443		{
1444			$sCriteriasResult = \trim($sCriteriasResult.' '.\MailSo\Config::$MessageListPermanentFilter);
1445		}
1446
1447		$sCriteriasResult = \trim($sCriteriasResult);
1448		if ('' === $sCriteriasResult)
1449		{
1450			$sCriteriasResult = 'ALL';
1451		}
1452
1453		return $sCriteriasResult;
1454	}
1455
1456	/**
1457	 * @param array $aThreads
1458	 * @return array
1459	 */
1460	private function threadArrayMap($aThreads)
1461	{
1462		$aNew = array();
1463		foreach ($aThreads as $mItem)
1464		{
1465			if (!\is_array($mItem))
1466			{
1467				$aNew[] = $mItem;
1468			}
1469			else
1470			{
1471				$mMap = $this->threadArrayMap($mItem);
1472				if (\is_array($mMap) && 0 < \count($mMap))
1473				{
1474					$aNew = \array_merge($aNew, $mMap);
1475				}
1476			}
1477		}
1478
1479		return $aNew;
1480	}
1481
1482	/**
1483	 * @param array $aThreads
1484	 *
1485	 * @return array
1486	 */
1487	private function compileThreadArray($aThreads)
1488	{
1489		$aResult = array();
1490		foreach ($aThreads as $mItem)
1491		{
1492			if (\is_array($mItem))
1493			{
1494				$aMap = $this->threadArrayMap($mItem);
1495				if (\is_array($aMap))
1496				{
1497					if (1 < \count($aMap))
1498					{
1499						$aResult[] = $aMap;
1500					}
1501					else if (0 < \count($aMap))
1502					{
1503						$aResult[] = $aMap[0];
1504					}
1505				}
1506			}
1507			else
1508			{
1509				$aResult[] = $mItem;
1510			}
1511		}
1512
1513		return $aResult;
1514	}
1515
1516	/**
1517	 * @param string $sFolderName
1518	 * @param string $sFolderHash
1519	 * @param array $aIndexOrUids
1520	 * @param \MailSo\Cache\CacheClient $oCacher
1521	 * @param bool $bCacheOnly = false
1522	 *
1523	 * @return array
1524	 *
1525	 * @throws \MailSo\Base\Exceptions\InvalidArgumentException
1526	 * @throws \MailSo\Net\Exceptions\Exception
1527	 * @throws \MailSo\Imap\Exceptions\Exception
1528	 */
1529	public function MessageListThreadsMap($sFolderName, $sFolderHash, $aIndexOrUids, $oCacher, $bCacheOnly = false)
1530	{
1531		$iThreadLimit = \MailSo\Config::$LargeThreadLimit;
1532
1533		$sSearchHash = '';
1534		if (0 < \MailSo\Config::$MessageListDateFilter)
1535		{
1536			$iD = \time() - 3600 * 24 * 30 * \MailSo\Config::$MessageListDateFilter;
1537			$iTimeFilter = \gmmktime(1, 1, 1, \gmdate('n', $iD), 1, \gmdate('Y', $iD));
1538
1539			$sSearchHash .= ' SINCE '.\gmdate('j-M-Y', $iTimeFilter);
1540		}
1541
1542		if ('' === \trim($sSearchHash))
1543		{
1544			$sSearchHash = 'ALL';
1545		}
1546
1547		if ($oCacher && $oCacher->IsInited())
1548		{
1549			$sSerializedHashKey =
1550				'ThreadsMapSorted/'.$sSearchHash.'/'.
1551				'Limit='.$iThreadLimit.'/'.$sFolderName.'/'.$sFolderHash;
1552
1553			if ($this->oLogger)
1554			{
1555				$this->oLogger->Write($sSerializedHashKey);
1556			}
1557
1558			$sSerializedUids = $oCacher->Get($sSerializedHashKey);
1559			if (!empty($sSerializedUids))
1560			{
1561				$aSerializedUids = @\json_decode($sSerializedUids, true);
1562				if (isset($aSerializedUids['ThreadsUids']) && \is_array($aSerializedUids['ThreadsUids']))
1563				{
1564					if ($this->oLogger)
1565					{
1566						$this->oLogger->Write('Get Serialized Thread UIDS from cache ("'.$sFolderName.'" / '.$sSearchHash.') [count:'.\count($aSerializedUids['ThreadsUids']).']');
1567					}
1568
1569					return $aSerializedUids['ThreadsUids'];
1570				}
1571			}
1572		}
1573
1574		if ($bCacheOnly)
1575		{
1576			return null;
1577		}
1578
1579		$this->oImapClient->FolderExamine($sFolderName);
1580
1581		$aThreadUids = array();
1582		try
1583		{
1584			$aThreadUids = $this->oImapClient->MessageSimpleThread($sSearchHash);
1585		}
1586		catch (\MailSo\Imap\Exceptions\RuntimeException $oException)
1587		{
1588			unset($oException);
1589			$aThreadUids = array();
1590		}
1591
1592		$aResult = array();
1593		$aCompiledThreads = $this->compileThreadArray($aThreadUids);
1594
1595		foreach ($aCompiledThreads as $mData)
1596		{
1597			if (\is_array($mData))
1598			{
1599				foreach ($mData as $mSubData)
1600				{
1601					$aResult[(int) $mSubData] =
1602						\array_diff($mData, array((int) $mSubData));
1603				}
1604			}
1605			else if (\is_int($mData) || \is_string($mData))
1606			{
1607				$aResult[(int) $mData] = (int) $mData;
1608			}
1609		}
1610
1611		$aParentsMap = array();
1612		foreach ($aIndexOrUids as $iUid)
1613		{
1614			if (isset($aResult[$iUid]) && \is_array($aResult[$iUid]))
1615			{
1616				foreach ($aResult[$iUid] as $iTempUid)
1617				{
1618					$aParentsMap[$iTempUid] = $iUid;
1619					if (isset($aResult[$iTempUid]))
1620					{
1621						unset($aResult[$iTempUid]);
1622					}
1623				}
1624			}
1625		}
1626
1627		$aSortedThreads = array();
1628		foreach ($aIndexOrUids as $iUid)
1629		{
1630			if (isset($aResult[$iUid]))
1631			{
1632				$aSortedThreads[$iUid] = $iUid;
1633			}
1634		}
1635
1636		foreach ($aIndexOrUids as $iUid)
1637		{
1638			if (!isset($aSortedThreads[$iUid]) &&
1639				isset($aParentsMap[$iUid]) &&
1640				isset($aSortedThreads[$aParentsMap[$iUid]]))
1641			{
1642				if (!\is_array($aSortedThreads[$aParentsMap[$iUid]]))
1643				{
1644					$aSortedThreads[$aParentsMap[$iUid]] = array();
1645				}
1646
1647				$aSortedThreads[$aParentsMap[$iUid]][] = $iUid;
1648			}
1649		}
1650
1651		$aResult = $aSortedThreads;
1652		unset($aParentsMap, $aSortedThreads);
1653
1654		$aTemp = array();
1655		foreach ($aResult as $iUid => $mValue)
1656		{
1657			if (0 < $iThreadLimit && \is_array($mValue) && $iThreadLimit < \count($mValue))
1658			{
1659				$aParts = \array_chunk($mValue, $iThreadLimit);
1660				if (0 < count($aParts))
1661				{
1662					foreach ($aParts as $iIndex => $aItem)
1663					{
1664						if (0 === $iIndex)
1665						{
1666							$aResult[$iUid] = $aItem;
1667						}
1668						else if (0 < $iIndex && \is_array($aItem))
1669						{
1670							$mFirst = \array_shift($aItem);
1671							if (!empty($mFirst))
1672							{
1673								$aTemp[$mFirst] = 0 < \count($aItem) ? $aItem : $mFirst;
1674							}
1675						}
1676					}
1677				}
1678			}
1679		}
1680
1681		foreach ($aTemp as $iUid => $mValue)
1682		{
1683			$aResult[$iUid] = $mValue;
1684		}
1685
1686		unset($aTemp);
1687
1688		$aLastResult = array();
1689		foreach ($aIndexOrUids as $iUid)
1690		{
1691			if (isset($aResult[$iUid]))
1692			{
1693				$aLastResult[$iUid] = $aResult[$iUid];
1694			}
1695		}
1696
1697		$aResult = $aLastResult;
1698		unset($aLastResult);
1699
1700		if ($oCacher && $oCacher->IsInited() && !empty($sSerializedHashKey))
1701		{
1702			$oCacher->Set($sSerializedHashKey, @\json_encode(array(
1703				'ThreadsUids' => $aResult
1704			)));
1705
1706			if ($this->oLogger)
1707			{
1708				$this->oLogger->Write('Save Serialized Thread UIDS to cache ("'.$sFolderName.'" / '.$sSearchHash.') [count:'.\count($aResult).']');
1709			}
1710		}
1711
1712		return $aResult;
1713	}
1714
1715	/**
1716	 * @param \MailSo\Mail\MessageCollection &$oMessageCollection
1717	 * @param array $aRequestIndexOrUids
1718	 * @param bool $bIndexAsUid
1719	 * @param bool $bSimple = false
1720	 *
1721	 * @throws \MailSo\Net\Exceptions\Exception
1722	 * @throws \MailSo\Imap\Exceptions\Exception
1723	 */
1724	public function MessageListByRequestIndexOrUids(&$oMessageCollection, $aRequestIndexOrUids, $bIndexAsUid, $bSimple = false)
1725	{
1726		if (\is_array($aRequestIndexOrUids) && 0 < \count($aRequestIndexOrUids))
1727		{
1728			$aFetchResponse = $this->oImapClient->Fetch(array(
1729				\MailSo\Imap\Enumerations\FetchType::INDEX,
1730				\MailSo\Imap\Enumerations\FetchType::UID,
1731				\MailSo\Imap\Enumerations\FetchType::RFC822_SIZE,
1732				\MailSo\Imap\Enumerations\FetchType::INTERNALDATE,
1733				\MailSo\Imap\Enumerations\FetchType::FLAGS,
1734				\MailSo\Imap\Enumerations\FetchType::BODYSTRUCTURE,
1735				$bSimple ?
1736					$this->getEnvelopeOrHeadersRequestStringForSimpleList() :
1737					$this->getEnvelopeOrHeadersRequestString()
1738			), \MailSo\Base\Utils::PrepearFetchSequence($aRequestIndexOrUids), $bIndexAsUid);
1739
1740			if (\is_array($aFetchResponse) && 0 < \count($aFetchResponse))
1741			{
1742				$aFetchIndexArray = array();
1743				$oFetchResponseItem = null;
1744				foreach ($aFetchResponse as /* @var $oFetchResponseItem \MailSo\Imap\FetchResponse */ &$oFetchResponseItem)
1745				{
1746					$aFetchIndexArray[($bIndexAsUid)
1747						? $oFetchResponseItem->GetFetchValue(\MailSo\Imap\Enumerations\FetchType::UID)
1748						: $oFetchResponseItem->GetFetchValue(\MailSo\Imap\Enumerations\FetchType::INDEX)] =& $oFetchResponseItem;
1749
1750					unset($oFetchResponseItem);
1751				}
1752
1753				foreach ($aRequestIndexOrUids as $iFUid)
1754				{
1755					if (isset($aFetchIndexArray[$iFUid]))
1756					{
1757						$oMessageCollection->Add(
1758							Message::NewFetchResponseInstance(
1759								$oMessageCollection->FolderName, $aFetchIndexArray[$iFUid]));
1760					}
1761				}
1762			}
1763		}
1764	}
1765
1766	/**
1767	 * @return bool
1768	 *
1769	 * @throws \MailSo\Net\Exceptions\Exception
1770	 */
1771	public function IsThreadsSupported()
1772	{
1773		return $this->oImapClient->IsSupported('THREAD=REFS') ||
1774			$this->oImapClient->IsSupported('THREAD=REFERENCES') ||
1775			$this->oImapClient->IsSupported('THREAD=ORDEREDSUBJECT');
1776	}
1777
1778	/**
1779	 * @param string $sFolderName
1780	 * @param array $aUids
1781	 *
1782	 * @return \MailSo\Mail\MessageCollection
1783	 *
1784	 * @throws \MailSo\Base\Exceptions\InvalidArgumentException
1785	 * @throws \MailSo\Net\Exceptions\Exception
1786	 * @throws \MailSo\Imap\Exceptions\Exception
1787	 */
1788	public function MessageListSimple($sFolderName, $aUids)
1789	{
1790		if (0 === \strlen($sFolderName) || !\MailSo\Base\Validator::NotEmptyArray($aUids))
1791		{
1792			throw new \MailSo\Base\Exceptions\InvalidArgumentException();
1793		}
1794
1795		$this->oImapClient->FolderExamine($sFolderName);
1796
1797		$oMessageCollection = \MailSo\Mail\MessageCollection::NewInstance();
1798		$oMessageCollection->FolderName = $sFolderName;
1799
1800		$this->MessageListByRequestIndexOrUids($oMessageCollection, $aUids, true, true);
1801
1802		return $oMessageCollection->GetAsArray();
1803	}
1804
1805	/**
1806	 * @param \MailSo\Cache\CacheClient|null $oCacher
1807	 * @param string $sSearch
1808	 * @param string $sFilter
1809	 * @param string $sFolderName
1810	 * @param string $sFolderHash
1811	 * @param bool $bUseSortIfSupported = false
1812	 *
1813	 * @return array
1814	 *
1815	 * @throws \MailSo\Base\Exceptions\InvalidArgumentException
1816	 * @throws \MailSo\Net\Exceptions\Exception
1817	 * @throws \MailSo\Imap\Exceptions\Exception
1818	 */
1819	public function GetUids($oCacher, $sSearch, $sFilter, $sFolderName, $sFolderHash, $bUseSortIfSupported = false)
1820	{
1821		$aResultUids = false;
1822		$bUidsFromCacher = false;
1823		$bUseCacheAfterSearch = true;
1824
1825		$sSerializedHash = '';
1826		$sSerializedLog = '';
1827
1828		$bUseSortIfSupported = $bUseSortIfSupported ? !!$this->oImapClient->IsSupported('SORT') : false;
1829
1830		if (0 < \strlen($sSearch))
1831		{
1832			$bUseSortIfSupported = false;
1833		}
1834
1835		$sSearchCriterias = $this->getImapSearchCriterias($sSearch, $sFilter, 0, $bUseCacheAfterSearch);
1836		if ($bUseCacheAfterSearch && $oCacher && $oCacher->IsInited())
1837		{
1838			$sSerializedHash = 'GetUids/'.
1839				($bUseSortIfSupported ? 'S': 'N').'/'.
1840				$this->GenerateImapClientHash().'/'.
1841				$sFolderName.'/'.$sSearchCriterias;
1842
1843			$sSerializedLog = '"'.$sFolderName.'" / '.$sSearchCriterias.'';
1844
1845			$sSerialized = $oCacher->Get($sSerializedHash);
1846			if (!empty($sSerialized))
1847			{
1848				$aSerialized = @\json_decode($sSerialized, true);
1849				if (\is_array($aSerialized) && isset($aSerialized['FolderHash'], $aSerialized['Uids']) &&
1850					$sFolderHash === $aSerialized['FolderHash'] &&
1851					\is_array($aSerialized['Uids'])
1852				)
1853				{
1854					if ($this->oLogger)
1855					{
1856						$this->oLogger->Write('Get Serialized UIDS from cache ('.$sSerializedLog.') [count:'.\count($aSerialized['Uids']).']');
1857					}
1858
1859					$aResultUids = $aSerialized['Uids'];
1860					$bUidsFromCacher = true;
1861				}
1862			}
1863		}
1864
1865		if (!\is_array($aResultUids))
1866		{
1867			$aResultUids = $bUseSortIfSupported ?
1868				$this->oImapClient->MessageSimpleSort(array('REVERSE DATE'), $sSearchCriterias, true) :
1869				$this->oImapClient->MessageSimpleSearch($sSearchCriterias, true, \MailSo\Base\Utils::IsAscii($sSearchCriterias) ? '' : 'UTF-8')
1870			;
1871
1872			if (!$bUidsFromCacher && $bUseCacheAfterSearch && \is_array($aResultUids) && $oCacher && $oCacher->IsInited() && 0 < \strlen($sSerializedHash))
1873			{
1874				$oCacher->Set($sSerializedHash, @\json_encode(array(
1875					'FolderHash' => $sFolderHash,
1876					'Uids' => $aResultUids
1877				)));
1878
1879				if ($this->oLogger)
1880				{
1881					$this->oLogger->Write('Save Serialized UIDS to cache ('.$sSerializedLog.') [count:'.\count($aResultUids).']');
1882				}
1883			}
1884		}
1885
1886		return \is_array($aResultUids) ? $aResultUids : array();
1887	}
1888
1889	/**
1890	 * @param string $sFolderName
1891	 * @param int $iOffset = 0
1892	 * @param int $iLimit = 10
1893	 * @param string $sSearch = ''
1894	 * @param string $sPrevUidNext = ''
1895	 * @param \MailSo\Cache\CacheClient|null $oCacher = null
1896	 * @param bool $bUseSortIfSupported = false
1897	 * @param bool $bUseThreadSortIfSupported = false
1898	 * @param bool $bUseESearchOrESortRequest = false
1899	 * @param string $sThreadUid = ''
1900	 * @param string $sFilter = ''
1901	 *
1902	 * @return \MailSo\Mail\MessageCollection
1903	 *
1904	 * @throws \MailSo\Base\Exceptions\InvalidArgumentException
1905	 * @throws \MailSo\Net\Exceptions\Exception
1906	 * @throws \MailSo\Imap\Exceptions\Exception
1907	 */
1908	public function MessageList($sFolderName, $iOffset = 0, $iLimit = 10, $sSearch = '', $sPrevUidNext = '', $oCacher = null,
1909		$bUseSortIfSupported = false, $bUseThreadSortIfSupported = false, $sThreadUid = '', $sFilter = '')
1910	{
1911		$sFilter = \trim($sFilter);
1912		$sSearch = \trim($sSearch);
1913		if (!\MailSo\Base\Validator::RangeInt($iOffset, 0) ||
1914			!\MailSo\Base\Validator::RangeInt($iLimit, 0, 999))
1915		{
1916			throw new \MailSo\Base\Exceptions\InvalidArgumentException();
1917		}
1918
1919		$bUseFilter = '' !== $sFilter;
1920
1921		$this->oImapClient->FolderSelect($sFolderName);
1922
1923		$oMessageCollection = MessageCollection::NewInstance();
1924		$oMessageCollection->FolderName = $sFolderName;
1925		$oMessageCollection->Offset = $iOffset;
1926		$oMessageCollection->Limit = $iLimit;
1927		$oMessageCollection->Search = $sSearch;
1928		$oMessageCollection->ThreadUid = $sThreadUid;
1929		$oMessageCollection->Filtered = '' !== \MailSo\Config::$MessageListPermanentFilter;
1930
1931		$aUids = array();
1932		$mAllSortedUids = null;
1933		$mAllThreads = null;
1934
1935		$iThreadUid = empty($sThreadUid) ? 0 : (int) $sThreadUid;
1936
1937		$iMessageRealCount = 0;
1938		$iMessageUnseenCount = 0;
1939		$sUidNext = '0';
1940		$sHighestModSeq = '';
1941
1942		$bUseSortIfSupported = $bUseSortIfSupported ? $this->oImapClient->IsSupported('SORT') : false;
1943
1944		$bUseThreadSortIfSupported = $bUseThreadSortIfSupported ?
1945			($this->oImapClient->IsSupported('THREAD=REFS') || $this->oImapClient->IsSupported('THREAD=REFERENCES') || $this->oImapClient->IsSupported('THREAD=ORDEREDSUBJECT')) : false;
1946
1947		if (!empty($sThreadUid) && !$bUseThreadSortIfSupported)
1948		{
1949			throw new \MailSo\Base\Exceptions\InvalidArgumentException();
1950		}
1951
1952		if (!$oCacher || !($oCacher instanceof \MailSo\Cache\CacheClient))
1953		{
1954			$oCacher = null;
1955		}
1956
1957		$this->initFolderValues($sFolderName, $iMessageRealCount, $iMessageUnseenCount, $sUidNext, $sHighestModSeq);
1958
1959		if ($bUseFilter)
1960		{
1961			$iMessageUnseenCount = 0;
1962		}
1963
1964		$oMessageCollection->FolderHash = $this->GenerateFolderHash(
1965			$sFolderName, $iMessageRealCount, $iMessageUnseenCount, $sUidNext, $sHighestModSeq);
1966
1967		$oMessageCollection->UidNext = $sUidNext;
1968
1969		if (empty($sThreadUid) && 0 < \strlen($sPrevUidNext) && 'INBOX' === $sFolderName)
1970		{
1971			$oMessageCollection->NewMessages = $this->getFolderNextMessageInformation(
1972				$sFolderName, $sPrevUidNext, $sUidNext);
1973		}
1974
1975		$bSearch = false;
1976		$bMessageListOptimization = 0 < \MailSo\Config::$MessageListCountLimitTrigger &&
1977			\MailSo\Config::$MessageListCountLimitTrigger < $iMessageRealCount;
1978
1979		if ($bMessageListOptimization)
1980		{
1981			$bUseSortIfSupported = false;
1982			$bUseThreadSortIfSupported = false;
1983		}
1984
1985		if (0 < $iMessageRealCount && !$bMessageListOptimization)
1986		{
1987			$mAllSortedUids = $this->GetUids($oCacher, '', $sFilter,
1988				$oMessageCollection->FolderName, $oMessageCollection->FolderHash, $bUseSortIfSupported);
1989
1990			$mAllThreads = $bUseThreadSortIfSupported ? $this->MessageListThreadsMap(
1991				$oMessageCollection->FolderName, $oMessageCollection->FolderHash, $mAllSortedUids, $oCacher) : null;
1992
1993			if ($bUseThreadSortIfSupported && 0 < $iThreadUid && \is_array($mAllThreads))
1994			{
1995				$aUids = array();
1996				$iResultRootUid = 0;
1997
1998				if (isset($mAllThreads[$iThreadUid]))
1999				{
2000					$iResultRootUid = $iThreadUid;
2001					if (\is_array($mAllThreads[$iThreadUid]))
2002					{
2003						$aUids = $mAllThreads[$iThreadUid];
2004					}
2005				}
2006				else
2007				{
2008					foreach ($mAllThreads as $iRootUid => $mSubUids)
2009					{
2010						if (\is_array($mSubUids) && \in_array($iThreadUid, $mSubUids))
2011						{
2012							$iResultRootUid = $iRootUid;
2013							$aUids = $mSubUids;
2014							continue;
2015						}
2016					}
2017				}
2018
2019				if (0 < $iResultRootUid && \in_array($iResultRootUid, $mAllSortedUids))
2020				{
2021					\array_unshift($aUids, $iResultRootUid);
2022				}
2023			}
2024			else if ($bUseThreadSortIfSupported && \is_array($mAllThreads))
2025			{
2026				$aUids = \array_keys($mAllThreads);
2027			}
2028			else
2029			{
2030				$bUseThreadSortIfSupported = false;
2031				$aUids = $mAllSortedUids;
2032			}
2033
2034			if (0 < \strlen($sSearch) && \is_array($aUids))
2035			{
2036				$aSearchedUids = $this->GetUids($oCacher, $sSearch, $sFilter,
2037					$oMessageCollection->FolderName, $oMessageCollection->FolderHash);
2038
2039				if (\is_array($aSearchedUids) && 0 < \count($aSearchedUids))
2040				{
2041					$aFlippedSearchedUids = \array_flip($aSearchedUids);
2042
2043					$bSearch = true;
2044					$aNewUids = array();
2045
2046					foreach ($aUids as $iUid)
2047					{
2048						if (isset($aFlippedSearchedUids[$iUid]))
2049						{
2050							$aNewUids[] = $iUid;
2051						}
2052						else if ($bUseThreadSortIfSupported && 0 === $iThreadUid && isset($mAllThreads[$iUid]) && \is_array($mAllThreads[$iUid]))
2053						{
2054							foreach ($mAllThreads[$iUid] as $iSubUid)
2055							{
2056								if (isset($aFlippedSearchedUids[$iSubUid]))
2057								{
2058									$aNewUids[] = $iUid;
2059									continue;
2060								}
2061							}
2062						}
2063					}
2064
2065					$aUids = \array_unique($aNewUids);
2066					unset($aNewUids);
2067				}
2068				else
2069				{
2070					$aUids = array();
2071				}
2072			}
2073
2074			if (\is_array($aUids))
2075			{
2076				$oMessageCollection->MessageCount = $iMessageRealCount;
2077				$oMessageCollection->MessageUnseenCount = $iMessageUnseenCount;
2078				$oMessageCollection->MessageResultCount = \count($aUids);
2079
2080				if (0 < \count($aUids))
2081				{
2082					$aRequestUids = \array_slice($aUids, $iOffset, $iLimit);
2083					$this->MessageListByRequestIndexOrUids($oMessageCollection, $aRequestUids, true);
2084				}
2085			}
2086		}
2087		else if (0 < $iMessageRealCount)
2088		{
2089			if ($this->oLogger)
2090			{
2091				$this->oLogger->Write('List optimization (count: '.$iMessageRealCount.
2092					', limit:'.\MailSo\Config::$MessageListCountLimitTrigger.')');
2093			}
2094
2095			$oMessageCollection->MessageCount = $iMessageRealCount;
2096			$oMessageCollection->MessageUnseenCount = $iMessageUnseenCount;
2097
2098			if (0 < \strlen($sSearch) || $bUseFilter)
2099			{
2100				$aUids = $this->GetUids($oCacher, $sSearch, $sFilter,
2101					$oMessageCollection->FolderName, $oMessageCollection->FolderHash);
2102
2103				if (0 < \count($aUids))
2104				{
2105					$oMessageCollection->MessageResultCount = \count($aUids);
2106
2107					$aRequestUids = \array_slice($aUids, $iOffset, $iLimit);
2108					$this->MessageListByRequestIndexOrUids($oMessageCollection, $aRequestUids, true);
2109				}
2110				else
2111				{
2112					$oMessageCollection->MessageResultCount = 0;
2113				}
2114			}
2115			else
2116			{
2117				$oMessageCollection->MessageResultCount = $iMessageRealCount;
2118
2119				if (1 < $iMessageRealCount)
2120				{
2121					$aRequestIndexes = \array_slice(array_reverse(range(1, $iMessageRealCount)), $iOffset, $iLimit);
2122				}
2123				else
2124				{
2125					$aRequestIndexes = \array_slice(array(1), $iOffset, $iLimit);
2126				}
2127
2128				$this->MessageListByRequestIndexOrUids($oMessageCollection, $aRequestIndexes, false);
2129			}
2130		}
2131
2132		if ($bUseThreadSortIfSupported && 0 === $iThreadUid && \is_array($mAllThreads) && 0 < \count($mAllThreads))
2133		{
2134			$oMessageCollection->ForeachList(function (/* @var $oMessage \MailSo\Mail\Message */ $oMessage) use ($mAllThreads) {
2135
2136				$iUid = $oMessage->Uid();
2137				if (isset($mAllThreads[$iUid]) && \is_array($mAllThreads[$iUid]) && 0 < \count($mAllThreads[$iUid]))
2138				{
2139					$aSubThreads = $mAllThreads[$iUid];
2140					\array_unshift($aSubThreads, $iUid);
2141
2142					$oMessage->SetThreads(\array_map('trim', $aSubThreads));
2143					unset($aSubThreads);
2144				}
2145			});
2146		}
2147
2148		return $oMessageCollection;
2149	}
2150
2151	/**
2152	 * @return array|false
2153	 */
2154	public function Quota()
2155	{
2156		return $this->oImapClient->Quota();
2157	}
2158
2159	/**
2160	 * @param string $sFolderName
2161	 * @param string $sMessageId
2162	 *
2163	 * @return int|null
2164	 */
2165	public function FindMessageUidByMessageId($sFolderName, $sMessageId)
2166	{
2167		if (0 === \strlen($sMessageId))
2168		{
2169			throw new \MailSo\Base\Exceptions\InvalidArgumentException();
2170		}
2171
2172		$this->oImapClient->FolderExamine($sFolderName);
2173
2174		$aUids = $this->oImapClient->MessageSimpleSearch(
2175			'HEADER Message-ID '.$sMessageId, true);
2176
2177		return \is_array($aUids) && 1 === \count($aUids) && \is_numeric($aUids[0]) ? (int) $aUids[0] : null;
2178	}
2179
2180	/**
2181	 * @param array $aMailFoldersHelper
2182	 * @param int $iOptimizationLimit = 0
2183	 *
2184	 * @return array
2185	 */
2186	public function folderListOptimization($aMailFoldersHelper, $iOptimizationLimit = 0)
2187	{
2188		// optimization
2189		if (10 < $iOptimizationLimit && \is_array($aMailFoldersHelper) && $iOptimizationLimit < \count($aMailFoldersHelper))
2190		{
2191			if ($this->oLogger)
2192			{
2193				$this->oLogger->Write('Start optimization (limit:'.$iOptimizationLimit.') for '.\count($aMailFoldersHelper).' folders');
2194			}
2195
2196			$iForeachLimit = 1;
2197
2198			$aFilteredNames = array(
2199				'inbox',
2200				'sent', 'send', 'outbox', 'sentmail', 'sendmail',
2201				'drafts', 'draft',
2202				'junk', 'spam', 'spambucket',
2203				'trash', 'bin', 'deleted',
2204				'archives', 'archive', 'allmail', 'all',
2205				'starred', 'flagged', 'important',
2206				'contacts', 'chats'
2207			);
2208
2209			$aNewMailFoldersHelper = array();
2210
2211			$iCountLimit = $iForeachLimit;
2212
2213			foreach ($aMailFoldersHelper as $iIndex => /* @var $oImapFolder \MailSo\Mail\Folder */ $oFolder)
2214			{
2215				// mandatory folders
2216				if ($oFolder && \in_array(\str_replace(' ', '', \strtolower($oFolder->NameRaw())), $aFilteredNames))
2217				{
2218					$aNewMailFoldersHelper[] = $oFolder;
2219					$aMailFoldersHelper[$iIndex] = null;
2220				}
2221			}
2222
2223			foreach ($aMailFoldersHelper as $iIndex => /* @var $oImapFolder \MailSo\Mail\Folder */ $oFolder)
2224			{
2225				// subscribed folders
2226				if ($oFolder && $oFolder->IsSubscribed())
2227				{
2228					$aNewMailFoldersHelper[] = $oFolder;
2229
2230					$aMailFoldersHelper[$iIndex] = null;
2231					$iCountLimit--;
2232				}
2233
2234				if (0 > $iCountLimit)
2235				{
2236					if ($iOptimizationLimit < \count($aNewMailFoldersHelper))
2237					{
2238						break;
2239					}
2240					else
2241					{
2242						$iCountLimit = $iForeachLimit;
2243					}
2244				}
2245			}
2246
2247			$iCountLimit = $iForeachLimit;
2248			if ($iOptimizationLimit >= \count($aNewMailFoldersHelper))
2249			{
2250				// name filter
2251				foreach ($aMailFoldersHelper as $iIndex => /* @var $oImapFolder \MailSo\Mail\Folder */ $oFolder)
2252				{
2253					if ($oFolder && !\preg_match('/[{}\[\]]/', $oFolder->NameRaw()))
2254					{
2255						$aNewMailFoldersHelper[] = $oFolder;
2256
2257						$aMailFoldersHelper[$iIndex] = null;
2258						$iCountLimit--;
2259					}
2260
2261					if (0 > $iCountLimit)
2262					{
2263						if ($iOptimizationLimit < \count($aNewMailFoldersHelper))
2264						{
2265							break;
2266						}
2267						else
2268						{
2269							$iCountLimit = $iForeachLimit;
2270						}
2271					}
2272				}
2273			}
2274
2275			$iCountLimit = $iForeachLimit;
2276			if ($iOptimizationLimit >= \count($aNewMailFoldersHelper))
2277			{
2278				// other
2279				foreach ($aMailFoldersHelper as $iIndex => /* @var $oImapFolder \MailSo\Mail\Folder */ $oFolder)
2280				{
2281					if ($oFolder)
2282					{
2283						$aNewMailFoldersHelper[] = $oFolder;
2284
2285						$aMailFoldersHelper[$iIndex] = null;
2286						$iCountLimit--;
2287					}
2288
2289					if (0 > $iCountLimit)
2290					{
2291						if ($iOptimizationLimit < \count($aNewMailFoldersHelper))
2292						{
2293							break;
2294						}
2295						else
2296						{
2297							$iCountLimit = $iForeachLimit;
2298						}
2299					}
2300				}
2301			}
2302
2303			$aMailFoldersHelper = $aNewMailFoldersHelper;
2304
2305			if ($this->oLogger)
2306			{
2307				$this->oLogger->Write('Result optimization: '.\count($aMailFoldersHelper).' folders');
2308			}
2309		}
2310
2311		return $aMailFoldersHelper;
2312	}
2313
2314	/**
2315	 * @param string $sParent = ''
2316	 * @param string $sListPattern = '*'
2317	 * @param bool $bUseListSubscribeStatus = false
2318	 * @param int $iOptimizationLimit = 0
2319	 *
2320	 * @return \MailSo\Mail\FolderCollection|false
2321	 */
2322	public function Folders($sParent = '', $sListPattern = '*', $bUseListSubscribeStatus = true, $iOptimizationLimit = 0)
2323	{
2324		$oFolderCollection = false;
2325
2326		$aSubscribedFolders = null;
2327		if ($bUseListSubscribeStatus)
2328		{
2329			try
2330			{
2331				$aSubscribedFolders = $this->oImapClient->FolderSubscribeList($sParent, $sListPattern);
2332			}
2333			catch (\Exception $oException)
2334			{
2335				unset($oException);
2336			}
2337		}
2338
2339		$aImapSubscribedFoldersHelper = null;
2340		if (\is_array($aSubscribedFolders))
2341		{
2342			$aImapSubscribedFoldersHelper = array();
2343			foreach ($aSubscribedFolders as /* @var $oImapFolder \MailSo\Imap\Folder */ $oImapFolder)
2344			{
2345				$aImapSubscribedFoldersHelper[] = $oImapFolder->FullNameRaw();
2346			}
2347		}
2348
2349		$aFolders = $this->oImapClient->FolderList($sParent, $sListPattern);
2350
2351		$bOptimized = false;
2352		$aMailFoldersHelper = null;
2353
2354		if (\is_array($aFolders))
2355		{
2356			$aMailFoldersHelper = array();
2357
2358			foreach ($aFolders as /* @var $oImapFolder \MailSo\Imap\Folder */ $oImapFolder)
2359			{
2360				$aMailFoldersHelper[] = Folder::NewInstance($oImapFolder,
2361					(null === $aImapSubscribedFoldersHelper || \in_array($oImapFolder->FullNameRaw(), $aImapSubscribedFoldersHelper)) ||
2362					$oImapFolder->IsInbox()
2363				);
2364			}
2365
2366			$iCount = \count($aMailFoldersHelper);
2367			$aMailFoldersHelper = $this->folderListOptimization($aMailFoldersHelper, $iOptimizationLimit);
2368
2369			$bOptimized = $iCount !== \count($aMailFoldersHelper);
2370		}
2371
2372		if (\is_array($aMailFoldersHelper))
2373		{
2374			$oFolderCollection = FolderCollection::NewInstance();
2375			$oFolderCollection->InitByUnsortedMailFolderArray($aMailFoldersHelper);
2376
2377			$oFolderCollection->Optimized = $bOptimized;
2378		}
2379
2380		if ($oFolderCollection)
2381		{
2382			$oFolderCollection->SortByCallback(function ($oFolderA, $oFolderB) {
2383				$sA = \strtoupper($oFolderA->FullNameRaw());
2384				$sB = \strtoupper($oFolderB->FullNameRaw());
2385				switch (true)
2386				{
2387					case 'INBOX' === $sA:
2388						return -1;
2389					case 'INBOX' === $sB:
2390						return 1;
2391					case '[GMAIL]' === $sA:
2392						return -1;
2393					case '[GMAIL]' === $sB:
2394						return 1;
2395				}
2396
2397				return \strnatcasecmp($oFolderA->FullName(), $oFolderB->FullName());
2398			});
2399
2400			$oNamespace = $this->oImapClient->GetNamespace();
2401			if ($oNamespace)
2402			{
2403				$oFolderCollection->SetNamespace($oNamespace->GetPersonalNamespace());
2404			}
2405
2406			$oFolderCollection->IsThreadsSupported = $this->IsThreadsSupported();
2407		}
2408
2409		return $oFolderCollection;
2410	}
2411
2412	/**
2413	 * @param string $sFolderNameInUtf8
2414	 * @param string $sFolderParentFullNameRaw = ''
2415	 * @param bool $bSubscribeOnCreation = true
2416	 * @param string $sDelimiter = ''
2417	 *
2418	 * @return \MailSo\Mail\MailClient
2419	 *
2420	 * @throws \MailSo\Base\Exceptions\InvalidArgumentException
2421	 */
2422	public function FolderCreate($sFolderNameInUtf8, $sFolderParentFullNameRaw = '', $bSubscribeOnCreation = true, $sDelimiter = '')
2423	{
2424		if (!\MailSo\Base\Validator::NotEmptyString($sFolderNameInUtf8, true) ||
2425			!\is_string($sFolderParentFullNameRaw))
2426		{
2427			throw new \MailSo\Base\Exceptions\InvalidArgumentException();
2428		}
2429
2430		$sFolderNameInUtf8 = \trim($sFolderNameInUtf8);
2431
2432		if (0 === \strlen($sDelimiter) || 0 < \strlen(\trim($sFolderParentFullNameRaw)))
2433		{
2434			$aFolders = $this->oImapClient->FolderList('', 0 === \strlen(\trim($sFolderParentFullNameRaw)) ? 'INBOX' : $sFolderParentFullNameRaw);
2435			if (!\is_array($aFolders) || !isset($aFolders[0]))
2436			{
2437				// TODO
2438				throw new \MailSo\Mail\Exceptions\RuntimeException(
2439					0 === \strlen(trim($sFolderParentFullNameRaw))
2440						? 'Cannot get folder delimiter'
2441						: 'Cannot create folder in non-existen parent folder');
2442			}
2443
2444			$sDelimiter = $aFolders[0]->Delimiter();
2445			if (0 < \strlen($sDelimiter) && 0 < \strlen(\trim($sFolderParentFullNameRaw)))
2446			{
2447				$sFolderParentFullNameRaw .= $sDelimiter;
2448			}
2449		}
2450
2451		$sFullNameRawToCreate = \MailSo\Base\Utils::ConvertEncoding($sFolderNameInUtf8,
2452			\MailSo\Base\Enumerations\Charset::UTF_8,
2453			\MailSo\Base\Enumerations\Charset::UTF_7_IMAP);
2454
2455		if (0 < \strlen($sDelimiter) && false !== \strpos($sFullNameRawToCreate, $sDelimiter))
2456		{
2457			// TODO
2458			throw new \MailSo\Mail\Exceptions\RuntimeException(
2459				'New folder name contains delimiter');
2460		}
2461
2462		$sFullNameRawToCreate = $sFolderParentFullNameRaw.$sFullNameRawToCreate;
2463
2464		$this->oImapClient->FolderCreate($sFullNameRawToCreate);
2465
2466		if ($bSubscribeOnCreation)
2467		{
2468			$this->oImapClient->FolderSubscribe($sFullNameRawToCreate);
2469		}
2470
2471		return $this;
2472	}
2473
2474	/**
2475	 * @param string $sPrevFolderFullNameRaw
2476	 * @param string $sNextFolderFullNameInUtf
2477	 * @param bool $bSubscribeOnMove = true
2478	 *
2479	 * @return \MailSo\Mail\MailClient
2480	 *
2481	 * @throws \MailSo\Base\Exceptions\InvalidArgumentException
2482	 */
2483	public function FolderMove($sPrevFolderFullNameRaw, $sNextFolderFullNameInUtf, $bSubscribeOnMove = true)
2484	{
2485		return $this->folderModify($sPrevFolderFullNameRaw, $sNextFolderFullNameInUtf, false, $bSubscribeOnMove);
2486	}
2487
2488	/**
2489	 * @param string $sPrevFolderFullNameRaw
2490	 * @param string $sNewTopFolderNameInUtf
2491	 * @param bool $bSubscribeOnRename = true
2492	 *
2493	 * @return \MailSo\Mail\MailClient
2494	 *
2495	 * @throws \MailSo\Base\Exceptions\InvalidArgumentException
2496	 */
2497	public function FolderRename($sPrevFolderFullNameRaw, $sNewTopFolderNameInUtf, $bSubscribeOnRename = true)
2498	{
2499		return $this->folderModify($sPrevFolderFullNameRaw, $sNewTopFolderNameInUtf, true, $bSubscribeOnRename);
2500	}
2501
2502	/**
2503	 * @param string $sPrevFolderFullNameRaw
2504	 * @param string $sNextFolderNameInUtf
2505	 * @param bool $bRenameOrMove
2506	 * @param bool $bSubscribeOnModify
2507	 *
2508	 * @return \MailSo\Mail\MailClient
2509	 *
2510	 * @throws \MailSo\Base\Exceptions\InvalidArgumentException
2511	 */
2512	public function folderModify($sPrevFolderFullNameRaw, $sNextFolderNameInUtf, $bRenameOrMove, $bSubscribeOnModify)
2513	{
2514		if (0 === \strlen($sPrevFolderFullNameRaw) || 0 === \strlen($sNextFolderNameInUtf))
2515		{
2516			throw new \MailSo\Base\Exceptions\InvalidArgumentException();
2517		}
2518
2519		$aFolders = $this->oImapClient->FolderList('', $sPrevFolderFullNameRaw);
2520		if (!\is_array($aFolders) || !isset($aFolders[0]))
2521		{
2522			// TODO
2523			throw new \MailSo\Mail\Exceptions\RuntimeException('Cannot rename non-existen folder');
2524		}
2525
2526		$sDelimiter = $aFolders[0]->Delimiter();
2527		$iLast = \strrpos($sPrevFolderFullNameRaw, $sDelimiter);
2528
2529		$mSubscribeFolders = null;
2530		if ($bSubscribeOnModify)
2531		{
2532			$mSubscribeFolders = $this->oImapClient->FolderSubscribeList($sPrevFolderFullNameRaw, '*');
2533			if (\is_array($mSubscribeFolders) && 0 < count($mSubscribeFolders))
2534			{
2535				foreach ($mSubscribeFolders as /* @var $oFolder \MailSo\Imap\Folder */ $oFolder)
2536				{
2537					$this->oImapClient->FolderUnSubscribe($oFolder->FullNameRaw());
2538				}
2539			}
2540		}
2541
2542        $sNewFolderFullNameRaw = \MailSo\Base\Utils::ConvertEncoding($sNextFolderNameInUtf,
2543            \MailSo\Base\Enumerations\Charset::UTF_8,
2544            \MailSo\Base\Enumerations\Charset::UTF_7_IMAP);
2545
2546        if($bRenameOrMove)
2547        {
2548            if (0 < \strlen($sDelimiter) && false !== \strpos($sNewFolderFullNameRaw, $sDelimiter))
2549            {
2550                // TODO
2551                throw new \MailSo\Mail\Exceptions\RuntimeException('New folder name contains delimiter');
2552            }
2553
2554            $sFolderParentFullNameRaw = false === $iLast ? '' : \substr($sPrevFolderFullNameRaw, 0, $iLast + 1);
2555            $sNewFolderFullNameRaw = $sFolderParentFullNameRaw.$sNewFolderFullNameRaw;
2556        }
2557
2558		$this->oImapClient->FolderRename($sPrevFolderFullNameRaw, $sNewFolderFullNameRaw);
2559
2560		if (\is_array($mSubscribeFolders) && 0 < count($mSubscribeFolders))
2561		{
2562			foreach ($mSubscribeFolders as /* @var $oFolder \MailSo\Imap\Folder */ $oFolder)
2563			{
2564				$sFolderFullNameRawForResubscrine = $oFolder->FullNameRaw();
2565				if (0 === \strpos($sFolderFullNameRawForResubscrine, $sPrevFolderFullNameRaw))
2566				{
2567					$sNewFolderFullNameRawForResubscrine = $sNewFolderFullNameRaw.
2568						\substr($sFolderFullNameRawForResubscrine, \strlen($sPrevFolderFullNameRaw));
2569
2570					$this->oImapClient->FolderSubscribe($sNewFolderFullNameRawForResubscrine);
2571				}
2572			}
2573		}
2574
2575		return $this;
2576	}
2577
2578	/**
2579	 * @param string $sFolderFullNameRaw
2580	 * @param bool $bUnsubscribeOnDeletion = true
2581	 *
2582	 * @return \MailSo\Mail\MailClient
2583	 *
2584	 * @throws \MailSo\Base\Exceptions\InvalidArgumentException
2585	 * @throws \MailSo\Mail\Exceptions\RuntimeException
2586	 */
2587	public function FolderDelete($sFolderFullNameRaw, $bUnsubscribeOnDeletion = true)
2588	{
2589		if (0 === \strlen($sFolderFullNameRaw) || 'INBOX' === $sFolderFullNameRaw)
2590		{
2591			throw new \MailSo\Base\Exceptions\InvalidArgumentException();
2592		}
2593
2594		$this->oImapClient->FolderExamine($sFolderFullNameRaw);
2595
2596		$aIndexOrUids = $this->oImapClient->MessageSimpleSearch('ALL');
2597		if (0 < \count($aIndexOrUids))
2598		{
2599			throw new \MailSo\Mail\Exceptions\NonEmptyFolder();
2600		}
2601
2602		$this->oImapClient->FolderExamine('INBOX');
2603
2604		if ($bUnsubscribeOnDeletion)
2605		{
2606			$this->oImapClient->FolderUnSubscribe($sFolderFullNameRaw);
2607		}
2608
2609		$this->oImapClient->FolderDelete($sFolderFullNameRaw);
2610
2611		return $this;
2612	}
2613
2614	/**
2615	 * @param string $sFolderFullNameRaw
2616	 *
2617	 * @return \MailSo\Mail\MailClient
2618	 *
2619	 * @throws \MailSo\Base\Exceptions\InvalidArgumentException
2620	 */
2621	public function FolderClear($sFolderFullNameRaw)
2622	{
2623		$this->oImapClient->FolderSelect($sFolderFullNameRaw);
2624
2625		$oFolderInformation = $this->oImapClient->FolderCurrentInformation();
2626		if ($oFolderInformation && $oFolderInformation->Exists && 0 < $oFolderInformation->Exists) // STATUS?
2627		{
2628			$this->oImapClient->MessageStoreFlag('1:*', false,
2629				array(\MailSo\Imap\Enumerations\MessageFlag::DELETED),
2630				\MailSo\Imap\Enumerations\StoreAction::ADD_FLAGS_SILENT
2631			);
2632
2633			$this->oImapClient->MessageExpunge();
2634		}
2635
2636		return $this;
2637	}
2638
2639	/**
2640	 * @param string $sFolderFullNameRaw
2641	 * @param bool $bSubscribe
2642	 *
2643	 * @return \MailSo\Mail\MailClient
2644	 *
2645	 * @throws \MailSo\Base\Exceptions\InvalidArgumentException
2646	 */
2647	public function FolderSubscribe($sFolderFullNameRaw, $bSubscribe)
2648	{
2649		if (0 === \strlen($sFolderFullNameRaw))
2650		{
2651			throw new \MailSo\Base\Exceptions\InvalidArgumentException();
2652		}
2653
2654		$this->oImapClient->{($bSubscribe) ? 'FolderSubscribe' : 'FolderUnSubscribe'}($sFolderFullNameRaw);
2655
2656		return $this;
2657	}
2658
2659	/**
2660	 * @param \MailSo\Log\Logger $oLogger
2661	 *
2662	 * @return \MailSo\Mail\MailClient
2663	 *
2664	 * @throws \MailSo\Base\Exceptions\InvalidArgumentException
2665	 */
2666	public function SetLogger($oLogger)
2667	{
2668		if (!($oLogger instanceof \MailSo\Log\Logger))
2669		{
2670			throw new \MailSo\Base\Exceptions\InvalidArgumentException();
2671		}
2672
2673		$this->oLogger = $oLogger;
2674		$this->oImapClient->SetLogger($this->oLogger);
2675
2676		return $this;
2677	}
2678}
2679