whoami7 - Manager
:
/
home
/
kckglobal
/
www
/
portal
/
vendor
/
webklex
/
php-imap
/
src
/
Connection
/
Protocols
/
Upload File:
files >> //home/kckglobal/www/portal/vendor/webklex/php-imap/src/Connection/Protocols/ImapProtocol.php
<?php /* * File: ImapProtocol.php * Category: Protocol * Author: M.Goldenbaum * Created: 16.09.20 18:27 * Updated: - * * Description: * - */ namespace Webklex\PHPIMAP\Connection\Protocols; use Exception; use Webklex\PHPIMAP\Exceptions\AuthFailedException; use Webklex\PHPIMAP\Exceptions\ConnectionFailedException; use Webklex\PHPIMAP\Exceptions\ImapBadRequestException; use Webklex\PHPIMAP\Exceptions\ImapServerErrorException; use Webklex\PHPIMAP\Exceptions\InvalidMessageDateException; use Webklex\PHPIMAP\Exceptions\MessageNotFoundException; use Webklex\PHPIMAP\Exceptions\ResponseException; use Webklex\PHPIMAP\Exceptions\RuntimeException; use Webklex\PHPIMAP\Header; use Webklex\PHPIMAP\IMAP; /** * Class ImapProtocol * * @package Webklex\PHPIMAP\Connection\Protocols * * @reference https://www.rfc-editor.org/rfc/rfc2087.txt */ class ImapProtocol extends Protocol { /** * Request noun * @var int */ protected int $noun = 0; /** * Imap constructor. * @param bool $cert_validation set to false to skip SSL certificate validation * @param mixed $encryption Connection encryption method */ public function __construct(bool $cert_validation = true, mixed $encryption = false) { $this->setCertValidation($cert_validation); $this->encryption = $encryption; } /** * Handle the class destruction / tear down */ public function __destruct() { $this->logout(); } /** * Open connection to IMAP server * @param string $host hostname or IP address of IMAP server * @param int|null $port of IMAP server, default is 143 and 993 for ssl * * @throws ConnectionFailedException */ public function connect(string $host, int $port = null): bool { $transport = 'tcp'; $encryption = ''; if ($this->encryption) { $encryption = strtolower($this->encryption); if (in_array($encryption, ['ssl', 'tls'])) { $transport = $encryption; $port = $port === null ? 993 : $port; } } $port = $port === null ? 143 : $port; try { $response = new Response(0, $this->debug); $this->stream = $this->createStream($transport, $host, $port, $this->connection_timeout); if (!$this->stream || !$this->assumedNextLine($response, '* OK')) { throw new ConnectionFailedException('connection refused'); } if ($encryption == 'starttls') { $this->enableStartTls(); } } catch (Exception $e) { throw new ConnectionFailedException('connection failed', 0, $e); } return true; } /** * Enable tls on the current connection * * @throws ConnectionFailedException * @throws ImapBadRequestException * @throws ImapServerErrorException * @throws RuntimeException */ protected function enableStartTls() { $response = $this->requestAndResponse('STARTTLS'); $result = $response->successful() && stream_socket_enable_crypto($this->stream, true, $this->getCryptoMethod()); if (!$result) { throw new ConnectionFailedException('failed to enable TLS'); } } /** * Get the next line from stream * * @return string next line * @throws RuntimeException */ public function nextLine(Response $response): string { $line = ""; while (($next_char = fread($this->stream, 1)) !== false && !in_array($next_char, ["","\n"])) { $line .= $next_char; } if ($line === "" && ($next_char === false || $next_char === "")) { throw new RuntimeException('empty response'); } $line .= "\n"; $response->addResponse($line); if ($this->debug) echo "<< " . $line; return $line; } /** * Get the next line and check if it starts with a given string * @param Response $response * @param string $start * * @return bool * @throws RuntimeException */ protected function assumedNextLine(Response $response, string $start): bool { return str_starts_with($this->nextLine($response), $start); } /** * Get the next line and split the tag * @param string|null $tag reference tag * * @return string next line * @throws RuntimeException */ protected function nextTaggedLine(Response $response, ?string &$tag): string { $line = $this->nextLine($response); if (str_contains($line, ' ')) { list($tag, $line) = explode(' ', $line, 2); } return $line ?? ''; } /** * Get the next line and check if it contains a given string and split the tag * @param Response $response * @param string $start * @param $tag * * @return bool * @throws RuntimeException */ protected function assumedNextTaggedLine(Response $response, string $start, &$tag): bool { return str_contains($this->nextTaggedLine($response, $tag), $start); } /** * Split a given line in values. A value is literal of any form or a list * @param Response $response * @param string $line * * @return array * @throws RuntimeException */ protected function decodeLine(Response $response, string $line): array { $tokens = []; $stack = []; // replace any trailing <NL> including spaces with a single space $line = rtrim($line) . ' '; while (($pos = strpos($line, ' ')) !== false) { $token = substr($line, 0, $pos); if (!strlen($token)) { $line = substr($line, $pos + 1); continue; } while ($token[0] == '(') { $stack[] = $tokens; $tokens = []; $token = substr($token, 1); } if ($token[0] == '"') { if (preg_match('%^\(*\"((.|\\\|\")*?)\"( |$)%', $line, $matches)) { $tokens[] = $matches[1]; $line = substr($line, strlen($matches[0])); continue; } } if ($token[0] == '{') { $endPos = strpos($token, '}'); $chars = substr($token, 1, $endPos - 1); if (is_numeric($chars)) { $token = ''; while (strlen($token) < $chars) { $token .= $this->nextLine($response); } $line = ''; if (strlen($token) > $chars) { $line = substr($token, $chars); $token = substr($token, 0, $chars); } else { $line .= $this->nextLine($response); } $tokens[] = $token; $line = trim($line) . ' '; continue; } } if ($stack && $token[strlen($token) - 1] == ')') { // closing braces are not separated by spaces, so we need to count them $braces = strlen($token); $token = rtrim($token, ')'); // only count braces if more than one $braces -= strlen($token) + 1; // only add if token had more than just closing braces if (rtrim($token) != '') { $tokens[] = rtrim($token); } $token = $tokens; $tokens = array_pop($stack); // special handling if more than one closing brace while ($braces-- > 0) { $tokens[] = $token; $token = $tokens; $tokens = array_pop($stack); } } $tokens[] = $token; $line = substr($line, $pos + 1); } // maybe the server forgot to send some closing braces while ($stack) { $child = $tokens; $tokens = array_pop($stack); $tokens[] = $child; } return $tokens; } /** * Read abd decode a response "line" * @param Response $response * @param array|string $tokens to decode * @param string $wantedTag targeted tag * @param bool $dontParse if true only the unparsed line is returned in $tokens * * @return bool * @throws RuntimeException */ public function readLine(Response $response, array|string &$tokens = [], string $wantedTag = '*', bool $dontParse = false): bool { $line = $this->nextTaggedLine($response, $tag); // get next tag if (!$dontParse) { $tokens = $this->decodeLine($response, $line); } else { $tokens = $line; } // if tag is wanted tag we might be at the end of a multiline response return $tag == $wantedTag; } /** * Read all lines of response until given tag is found * @param Response $response * @param string $tag request tag * @param bool $dontParse if true every line is returned unparsed instead of the decoded tokens * * @return array * * @throws ImapBadRequestException * @throws ImapServerErrorException * @throws RuntimeException */ public function readResponse(Response $response, string $tag, bool $dontParse = false): array { $lines = []; $tokens = ""; // define $tokens variable before first use do { $readAll = $this->readLine($response, $tokens, $tag, $dontParse); $lines[] = $tokens; } while (!$readAll); $original = $tokens; if ($dontParse) { // First two chars are still needed for the response code $tokens = [trim(substr($tokens, 0, 3))]; } $original = is_array($original)?$original : [$original]; // last line has response code if ($tokens[0] == 'OK') { return $lines ?: [true]; } elseif ($tokens[0] == 'NO' || $tokens[0] == 'BAD' || $tokens[0] == 'BYE') { throw new ImapServerErrorException(implode("\n", $original)); } throw new ImapBadRequestException(implode("\n", $original)); } /** * Send a new request * @param string $command * @param array $tokens additional parameters to command, use escapeString() to prepare * @param string|null $tag provide a tag otherwise an autogenerated is returned * * @return Response * @throws RuntimeException */ public function sendRequest(string $command, array $tokens = [], string &$tag = null): Response { if (!$tag) { $this->noun++; $tag = 'TAG' . $this->noun; } $line = $tag . ' ' . $command; $response = new Response($this->noun, $this->debug); foreach ($tokens as $token) { if (is_array($token)) { $this->write($response, $line . ' ' . $token[0]); if (!$this->assumedNextLine($response, '+ ')) { throw new RuntimeException('failed to send literal string'); } $line = $token[1]; } else { $line .= ' ' . $token; } } $this->write($response, $line); return $response; } /** * Write data to the current stream * @param Response $response * @param string $data * * @return void * @throws RuntimeException */ public function write(Response $response, string $data): void { $command = $data . "\r\n"; if ($this->debug) echo ">> " . $command . "\n"; $response->addCommand($command); if (fwrite($this->stream, $command) === false) { throw new RuntimeException('failed to write - connection closed?'); } } /** * Send a request and get response at once * * @param string $command * @param array $tokens parameters as in sendRequest() * @param bool $dontParse if true unparsed lines are returned instead of tokens * * @return Response response as in readResponse() * @throws ImapBadRequestException * @throws ImapServerErrorException * @throws RuntimeException */ public function requestAndResponse(string $command, array $tokens = [], bool $dontParse = false): Response { $response = $this->sendRequest($command, $tokens, $tag); $response->setResult($this->readResponse($response, $tag, $dontParse)); return $response; } /** * Escape one or more literals i.e. for sendRequest * @param array|string $string the literal/-s * * @return string|array escape literals, literals with newline ar returned * as array('{size}', 'string'); */ public function escapeString(array|string $string): array|string { if (func_num_args() < 2) { if (str_contains($string, "\n")) { return ['{' . strlen($string) . '}', $string]; } else { return '"' . str_replace(['\\', '"'], ['\\\\', '\\"'], $string) . '"'; } } $result = []; foreach (func_get_args() as $string) { $result[] = $this->escapeString($string); } return $result; } /** * Escape a list with literals or lists * @param array $list list with literals or lists as PHP array * * @return string escaped list for imap */ public function escapeList(array $list): string { $result = []; foreach ($list as $v) { if (!is_array($v)) { $result[] = $v; continue; } $result[] = $this->escapeList($v); } return '(' . implode(' ', $result) . ')'; } /** * Login to a new session. * * @param string $user username * @param string $password password * * @return Response * @throws AuthFailedException * @throws ImapBadRequestException * @throws ImapServerErrorException */ public function login(string $user, string $password): Response { try { $command = 'LOGIN'; $params = $this->escapeString($user, $password); return $this->requestAndResponse($command, $params, true); } catch (RuntimeException $e) { throw new AuthFailedException("failed to authenticate", 0, $e); } } /** * Authenticate your current IMAP session. * @param string $user username * @param string $token access token * * @return Response * @throws AuthFailedException */ public function authenticate(string $user, string $token): Response { try { $authenticateParams = ['XOAUTH2', base64_encode("user=$user\1auth=Bearer $token\1\1")]; $response = $this->sendRequest('AUTHENTICATE', $authenticateParams); while (true) { $tokens = ""; $is_plus = $this->readLine($response, $tokens, '+', true); if ($is_plus) { // try to log the challenge somewhere where it can be found error_log("got an extra server challenge: $tokens"); // respond with an empty response. $response->stack($this->sendRequest('')); } else { if (preg_match('/^NO /i', $tokens) || preg_match('/^BAD /i', $tokens)) { error_log("got failure response: $tokens"); return $response->addError("got failure response: $tokens"); } else if (preg_match("/^OK /i", $tokens)) { return $response->setResult(is_array($tokens) ? $tokens : [$tokens]); } } } } catch (RuntimeException $e) { throw new AuthFailedException("failed to authenticate", 0, $e); } } /** * Logout of imap server * * @return Response */ public function logout(): Response { if (!$this->stream) { $this->reset(); return new Response(0, $this->debug); }elseif ($this->meta()["timed_out"]) { $this->reset(); return new Response(0, $this->debug); } $result = null; try { $result = $this->requestAndResponse('LOGOUT', [], true); fclose($this->stream); } catch (\Throwable) {} $this->reset(); return $result ?? new Response(0, $this->debug); } /** * Reset the current stream and uid cache * * @return void */ public function reset(): void { $this->stream = null; $this->uid_cache = []; } /** * Get an array of available capabilities * * @return Response list of capabilities * * @throws ImapBadRequestException * @throws ImapServerErrorException * @throws RuntimeException * @throws ResponseException */ public function getCapabilities(): Response { $response = $this->requestAndResponse('CAPABILITY'); if (!$response->getResponse()) return $response; return $response->setResult($response->validatedData()[0]); } /** * Examine and select have the same response. * @param string $command can be 'EXAMINE' or 'SELECT' * @param string $folder target folder * * @return Response * @throws RuntimeException */ public function examineOrSelect(string $command = 'EXAMINE', string $folder = 'INBOX'): Response { $response = $this->sendRequest($command, [$this->escapeString($folder)], $tag); $result = []; $tokens = []; // define $tokens variable before first use while (!$this->readLine($response, $tokens, $tag, false)) { if ($tokens[0] == 'FLAGS') { array_shift($tokens); $result['flags'] = $tokens; continue; } switch ($tokens[1]) { case 'EXISTS': case 'RECENT': $result[strtolower($tokens[1])] = (int)$tokens[0]; break; case '[UIDVALIDITY': $result['uidvalidity'] = (int)$tokens[2]; break; case '[UIDNEXT': $result['uidnext'] = (int)$tokens[2]; break; case '[UNSEEN': $result['unseen'] = (int)$tokens[2]; break; case '[NONEXISTENT]': throw new RuntimeException("folder doesn't exist"); default: // ignore break; } } $response->setResult($result); if ($tokens[0] != 'OK') { $response->addError("request failed"); } return $response; } /** * Change the current folder * @param string $folder change to this folder * * @return Response see examineOrSelect() * @throws RuntimeException */ public function selectFolder(string $folder = 'INBOX'): Response { $this->uid_cache = []; return $this->examineOrSelect('SELECT', $folder); } /** * Examine a given folder * @param string $folder examine this folder * * @return Response see examineOrSelect() * @throws RuntimeException */ public function examineFolder(string $folder = 'INBOX'): Response { return $this->examineOrSelect('EXAMINE', $folder); } /** * Fetch one or more items of one or more messages * @param array|string $items items to fetch [RFC822.HEADER, FLAGS, RFC822.TEXT, etc] * @param array|int $from message for items or start message if $to !== null * @param int|null $to if null only one message ($from) is fetched, else it's the * last message, INF means last message available * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use * message numbers instead. * * @return Response if only one item of one message is fetched it's returned as string * if items of one message are fetched it's returned as (name => value) * if one item of messages are fetched it's returned as (msgno => value) * if items of messages are fetched it's returned as (msgno => (name => value)) * @throws RuntimeException */ public function fetch(array|string $items, array|int $from, mixed $to = null, int|string $uid = IMAP::ST_UID): Response { if (is_array($from)) { $set = implode(',', $from); } elseif ($to === null) { $set = $from; } elseif ($to == INF) { $set = $from . ':*'; } else { $set = $from . ':' . (int)$to; } $items = (array)$items; $itemList = $this->escapeList($items); $response = $this->sendRequest($this->buildUIDCommand("FETCH", $uid), [$set, $itemList], $tag); $result = []; $tokens = []; // define $tokens variable before first use while (!$this->readLine($response, $tokens, $tag)) { // ignore other responses if ($tokens[1] != 'FETCH') { continue; } $uidKey = 0; $data = []; // find array key of UID value; try the last elements, or search for it if ($uid === IMAP::ST_UID) { $count = count($tokens[2]); if ($tokens[2][$count - 2] == 'UID') { $uidKey = $count - 1; } else if ($tokens[2][0] == 'UID') { $uidKey = 1; } else { $found = array_search('UID', $tokens[2]); if ($found === false || $found === -1) { continue; } $uidKey = $found + 1; } } // ignore other messages if ($to === null && !is_array($from) && ($uid === IMAP::ST_UID ? $tokens[2][$uidKey] != $from : $tokens[0] != $from)) { continue; } // if we only want one item we return that one directly if (count($items) == 1) { if ($tokens[2][0] == $items[0]) { $data = $tokens[2][1]; } elseif ($uid === IMAP::ST_UID && $tokens[2][2] == $items[0]) { $data = $tokens[2][3]; } else { $expectedResponse = 0; // maybe the server send another field we didn't wanted $count = count($tokens[2]); // we start with 2, because 0 was already checked for ($i = 2; $i < $count; $i += 2) { if ($tokens[2][$i] != $items[0]) { continue; } $data = $tokens[2][$i + 1]; $expectedResponse = 1; break; } if (!$expectedResponse) { continue; } } } else { while (key($tokens[2]) !== null) { $data[current($tokens[2])] = next($tokens[2]); next($tokens[2]); } } // if we want only one message we can ignore everything else and just return if ($to === null && !is_array($from) && ($uid === IMAP::ST_UID ? $tokens[2][$uidKey] == $from : $tokens[0] == $from)) { // we still need to read all lines if (!$this->readLine($response, $tokens, $tag)) return $response->setResult($data); } if ($uid === IMAP::ST_UID) { $result[$tokens[2][$uidKey]] = $data; } else { $result[$tokens[0]] = $data; } } if ($to === null && !is_array($from)) { throw new RuntimeException('the single id was not found in response'); } return $response->setResult($result); } /** * Fetch message headers * @param int|array $uids * @param string $rfc * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use * message numbers instead. * * @return Response * @throws RuntimeException */ public function content(int|array $uids, string $rfc = "RFC822", int|string $uid = IMAP::ST_UID): Response { return $this->fetch(["$rfc.TEXT"], is_array($uids)?$uids:[$uids], null, $uid); } /** * Fetch message headers * @param int|array $uids * @param string $rfc * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use * message numbers instead. * * @return Response * @throws RuntimeException */ public function headers(int|array $uids, string $rfc = "RFC822", int|string $uid = IMAP::ST_UID): Response { return $this->fetch(["$rfc.HEADER"], is_array($uids)?$uids:[$uids], null, $uid); } /** * Fetch message flags * @param int|array $uids * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use * message numbers instead. * * @return Response * @throws RuntimeException */ public function flags(int|array $uids, int|string $uid = IMAP::ST_UID): Response { return $this->fetch(["FLAGS"], is_array($uids)?$uids:[$uids], null, $uid); } /** * Fetch message sizes * @param int|array $uids * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use * message numbers instead. * * @return Response * @throws RuntimeException */ public function sizes(int|array $uids, int|string $uid = IMAP::ST_UID): Response { return $this->fetch(["RFC822.SIZE"], is_array($uids)?$uids:[$uids], null, $uid); } /** * Get uid for a given id * @param int|null $id message number * * @return Response message number for given message or all messages as array * @throws MessageNotFoundException */ public function getUid(?int $id = null): Response { if (!$this->enable_uid_cache || empty($this->uid_cache) || count($this->uid_cache) <= 0) { try { $this->setUidCache((array)$this->fetch('UID', 1, INF)->data()); // set cache for this folder } catch (RuntimeException) { } } $uids = $this->uid_cache; if ($id == null) { return Response::empty($this->debug)->setResult($uids); } foreach ($uids as $k => $v) { if ($k == $id) { return Response::empty($this->debug)->setResult($v); } } // clear uid cache and run method again if ($this->enable_uid_cache && $this->uid_cache) { $this->setUidCache(null); return $this->getUid($id); } throw new MessageNotFoundException('unique id not found'); } /** * Get a message number for a uid * @param string $id uid * * @return Response message number * @throws MessageNotFoundException */ public function getMessageNumber(string $id): Response { foreach ($this->getUid()->data() as $k => $v) { if ($v == $id) { return Response::empty($this->debug)->setResult((int)$k); } } throw new MessageNotFoundException('message number not found: ' . $id); } /** * Get a list of available folders * * @param string $reference mailbox reference for list * @param string $folder mailbox name match with wildcards * * @return Response folders that matched $folder as array(name => array('delimiter' => .., 'flags' => ..)) * * @throws ImapBadRequestException * @throws ImapServerErrorException * @throws RuntimeException */ public function folders(string $reference = '', string $folder = '*'): Response { $response = $this->requestAndResponse('LIST', $this->escapeString($reference, $folder))->setCanBeEmpty(true); $list = $response->data(); $result = []; if ($list[0] !== true) { foreach ($list as $item) { if (count($item) != 4 || $item[0] != 'LIST') { continue; } $item[3] = str_replace("\\\\", "\\", str_replace("\\\"", "\"", $item[3])); $result[$item[3]] = ['delimiter' => $item[2], 'flags' => $item[1]]; } } return $response->setResult($result); } /** * Manage flags * * @param array|string $flags flags to set, add or remove - see $mode * @param int $from message for items or start message if $to !== null * @param int|null $to if null only one message ($from) is fetched, else it's the * last message, INF means last message available * @param string|null $mode '+' to add flags, '-' to remove flags, everything else sets the flags as given * @param bool $silent if false the return values are the new flags for the wanted messages * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use * message numbers instead. * @param string|null $item command used to store a flag * * @return Response new flags if $silent is false, else true or false depending on success * @throws ImapBadRequestException * @throws ImapServerErrorException * @throws RuntimeException */ public function store( array|string $flags, int $from, int $to = null, string $mode = null, bool $silent = true, int|string $uid = IMAP::ST_UID, string $item = null ): Response { $flags = $this->escapeList(is_array($flags) ? $flags : [$flags]); $set = $this->buildSet($from, $to); $command = $this->buildUIDCommand("STORE", $uid); $item = ($mode == '-' ? "-" : "+") . ($item === null ? "FLAGS" : $item) . ($silent ? '.SILENT' : ""); $response = $this->requestAndResponse($command, [$set, $item, $flags], $silent); if ($silent) { return $response; } $result = []; foreach ($response as $token) { if ($token[1] != 'FETCH' || $token[2][0] != 'FLAGS') { continue; } $result[$token[0]] = $token[2][1]; } return $response->setResult($result); } /** * Append a new message to given folder * * @param string $folder name of target folder * @param string $message full message content * @param array|null $flags flags for new message * @param string|null $date date for new message * * @return Response * * @throws ImapBadRequestException * @throws ImapServerErrorException * @throws RuntimeException */ public function appendMessage(string $folder, string $message, array $flags = null, string $date = null): Response { $tokens = []; $tokens[] = $this->escapeString($folder); if ($flags !== null) { $tokens[] = $this->escapeList($flags); } if ($date !== null) { $tokens[] = $this->escapeString($date); } $tokens[] = $this->escapeString($message); return $this->requestAndResponse('APPEND', $tokens, true); } /** * Copy a message set from current folder to another folder * * @param string $folder destination folder * @param $from * @param int|null $to if null only one message ($from) is fetched, else it's the * last message, INF means last message available * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use * message numbers instead. * * @return Response * * @throws ImapBadRequestException * @throws ImapServerErrorException * @throws RuntimeException */ public function copyMessage(string $folder, $from, int $to = null, int|string $uid = IMAP::ST_UID): Response { $set = $this->buildSet($from, $to); $command = $this->buildUIDCommand("COPY", $uid); return $this->requestAndResponse($command, [$set, $this->escapeString($folder)], true); } /** * Copy multiple messages to the target folder * * @param array $messages List of message identifiers * @param string $folder Destination folder * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use * message numbers instead. * * @return Response Tokens if operation successful, false if an error occurred * * @throws ImapBadRequestException * @throws ImapServerErrorException * @throws RuntimeException */ public function copyManyMessages(array $messages, string $folder, int|string $uid = IMAP::ST_UID): Response { $command = $this->buildUIDCommand("COPY", $uid); $set = implode(',', $messages); $tokens = [$set, $this->escapeString($folder)]; return $this->requestAndResponse($command, $tokens, true); } /** * Move a message set from current folder to another folder * * @param string $folder destination folder * @param $from * @param int|null $to if null only one message ($from) is fetched, else it's the * last message, INF means last message available * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use * message numbers instead. * * @return Response * * @throws ImapBadRequestException * @throws ImapServerErrorException * @throws RuntimeException */ public function moveMessage(string $folder, $from, int $to = null, int|string $uid = IMAP::ST_UID): Response { $set = $this->buildSet($from, $to); $command = $this->buildUIDCommand("MOVE", $uid); return $this->requestAndResponse($command, [$set, $this->escapeString($folder)], true); } /** * Move multiple messages to the target folder * * @param array $messages List of message identifiers * @param string $folder Destination folder * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use * message numbers instead. * * @return Response * * @throws ImapBadRequestException * @throws ImapServerErrorException * @throws RuntimeException */ public function moveManyMessages(array $messages, string $folder, int|string $uid = IMAP::ST_UID): Response { $command = $this->buildUIDCommand("MOVE", $uid); $set = implode(',', $messages); $tokens = [$set, $this->escapeString($folder)]; return $this->requestAndResponse($command, $tokens, true); } /** * Exchange identification information * Ref.: https://datatracker.ietf.org/doc/html/rfc2971 * * @param array|null $ids * @return Response * * @throws ImapBadRequestException * @throws ImapServerErrorException * @throws RuntimeException */ public function ID($ids = null): Response { $token = "NIL"; if (is_array($ids) && !empty($ids)) { $token = "("; foreach ($ids as $id) { $token .= '"' . $id . '" '; } $token = rtrim($token) . ")"; } return $this->requestAndResponse("ID", [$token], true); } /** * Create a new folder (and parent folders if needed) * * @param string $folder folder name * @return Response * * @throws ImapBadRequestException * @throws ImapServerErrorException * @throws RuntimeException */ public function createFolder(string $folder): Response { return $this->requestAndResponse('CREATE', [$this->escapeString($folder)], true); } /** * Rename an existing folder * * @param string $old old name * @param string $new new name * * @return Response * * @throws ImapBadRequestException * @throws ImapServerErrorException * @throws RuntimeException */ public function renameFolder(string $old, string $new): Response { return $this->requestAndResponse('RENAME', $this->escapeString($old, $new), true); } /** * Delete a folder * * @param string $folder folder name * @return Response * * @throws ImapBadRequestException * @throws ImapServerErrorException * @throws RuntimeException */ public function deleteFolder(string $folder): Response { return $this->requestAndResponse('DELETE', [$this->escapeString($folder)], true); } /** * Subscribe to a folder * * @param string $folder folder name * @return Response * * @throws ImapBadRequestException * @throws ImapServerErrorException * @throws RuntimeException */ public function subscribeFolder(string $folder): Response { return $this->requestAndResponse('SUBSCRIBE', [$this->escapeString($folder)], true); } /** * Unsubscribe from a folder * * @param string $folder folder name * @return Response * * @throws ImapBadRequestException * @throws ImapServerErrorException * @throws RuntimeException */ public function unsubscribeFolder(string $folder): Response { return $this->requestAndResponse('UNSUBSCRIBE', [$this->escapeString($folder)], true); } /** * Apply session saved changes to the server * * @return Response * @throws ImapBadRequestException * @throws ImapServerErrorException * @throws RuntimeException */ public function expunge(): Response { $this->uid_cache = []; return $this->requestAndResponse('EXPUNGE'); } /** * Send noop command * * @return Response * @throws ImapBadRequestException * @throws ImapServerErrorException * @throws RuntimeException */ public function noop(): Response { return $this->requestAndResponse('NOOP'); } /** * Retrieve the quota level settings, and usage statics per mailbox * * @param $username * @return Response * * @throws ImapBadRequestException * @throws ImapServerErrorException * @throws RuntimeException * * @Doc https://www.rfc-editor.org/rfc/rfc2087.txt */ public function getQuota($username): Response { $command = "GETQUOTA"; $params = ['"#user/' . $username . '"']; return $this->requestAndResponse($command, $params); } /** * Retrieve the quota settings per user * * @param string $quota_root * @return Response * * @throws ImapBadRequestException * @throws ImapServerErrorException * @throws RuntimeException * * @Doc https://www.rfc-editor.org/rfc/rfc2087.txt */ public function getQuotaRoot(string $quota_root = 'INBOX'): Response { $command = "GETQUOTAROOT"; $params = [$quota_root]; return $this->requestAndResponse($command, $params); } /** * Send idle command * * @throws RuntimeException */ public function idle() { $response = $this->sendRequest("IDLE"); if (!$this->assumedNextLine($response, '+ ')) { throw new RuntimeException('idle failed'); } } /** * Send done command * @throws RuntimeException */ public function done(): bool { $response = new Response($this->noun, $this->debug); $this->write($response, "DONE"); if (!$this->assumedNextTaggedLine($response, 'OK', $tags)) { throw new RuntimeException('done failed'); } return true; } /** * Search for matching messages * * @param array $params * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use * message numbers instead. * * @return Response message ids * @throws ImapBadRequestException * @throws ImapServerErrorException * @throws RuntimeException */ public function search(array $params, int|string $uid = IMAP::ST_UID): Response { $command = $this->buildUIDCommand("SEARCH", $uid); $response = $this->requestAndResponse($command, $params)->setCanBeEmpty(true); foreach ($response->data() as $ids) { if ($ids[0] === 'SEARCH') { array_shift($ids); return $response->setResult($ids); } } return $response; } /** * Get a message overview * @param string $sequence * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use * message numbers instead. * * @return Response * @throws RuntimeException * @throws MessageNotFoundException * @throws InvalidMessageDateException */ public function overview(string $sequence, int|string $uid = IMAP::ST_UID): Response { $result = []; list($from, $to) = explode(":", $sequence); $response = $this->getUid(); $ids = []; foreach ($response->data() as $msgn => $v) { $id = $uid === IMAP::ST_UID ? $v : $msgn; if (($to >= $id && $from <= $id) || ($to === "*" && $from <= $id)) { $ids[] = $id; } } if (!empty($ids)) { $headers = $this->headers($ids, "RFC822", $uid); $response->stack($headers); foreach ($headers->data() as $id => $raw_header) { $result[$id] = (new Header($raw_header, false))->getAttributes(); } } return $response->setResult($result)->setCanBeEmpty(true); } /** * Enable the debug mode * * @return void */ public function enableDebug(): void { $this->debug = true; } /** * Disable the debug mode * * @return void */ public function disableDebug(): void { $this->debug = false; } /** * Build a valid UID number set * @param $from * @param null $to * * @return int|string */ public function buildSet($from, $to = null): int|string { $set = (int)$from; if ($to !== null) { $set .= ':' . ($to == INF ? '*' : (int)$to); } return $set; } }
Copyright ©2021 || Defacer Indonesia