mirror of
https://github.com/itflow-org/itflow
synced 2026-02-28 19:04:52 +00:00
Migrated away from PHP Mail Parser to the new WebKlex PHP IMAP Mail Parser this will open the way to support OAUTH2 for Mail servers such as Microsoft 365 and Google Workspaces
This commit is contained in:
1378
plugins/php-imap/src/Connection/Protocols/ImapProtocol.php
Normal file
1378
plugins/php-imap/src/Connection/Protocols/ImapProtocol.php
Normal file
File diff suppressed because it is too large
Load Diff
825
plugins/php-imap/src/Connection/Protocols/LegacyProtocol.php
Normal file
825
plugins/php-imap/src/Connection/Protocols/LegacyProtocol.php
Normal file
@@ -0,0 +1,825 @@
|
||||
<?php
|
||||
/*
|
||||
* File: LegacyProtocol.php
|
||||
* Category: Protocol
|
||||
* Author: M.Goldenbaum
|
||||
* Created: 16.09.20 18:27
|
||||
* Updated: -
|
||||
*
|
||||
* Description:
|
||||
* -
|
||||
*/
|
||||
|
||||
namespace Webklex\PHPIMAP\Connection\Protocols;
|
||||
|
||||
use Webklex\PHPIMAP\ClientManager;
|
||||
use Webklex\PHPIMAP\Config;
|
||||
use Webklex\PHPIMAP\Exceptions\AuthFailedException;
|
||||
use Webklex\PHPIMAP\Exceptions\ImapBadRequestException;
|
||||
use Webklex\PHPIMAP\Exceptions\MethodNotSupportedException;
|
||||
use Webklex\PHPIMAP\Exceptions\RuntimeException;
|
||||
use Webklex\PHPIMAP\IMAP;
|
||||
|
||||
/**
|
||||
* Class LegacyProtocol
|
||||
*
|
||||
* @package Webklex\PHPIMAP\Connection\Protocols
|
||||
*/
|
||||
class LegacyProtocol extends Protocol {
|
||||
|
||||
protected string $protocol = "imap";
|
||||
protected string $host = "localhost";
|
||||
protected int $port = 993;
|
||||
|
||||
/**
|
||||
* Imap constructor.
|
||||
* @param Config $config
|
||||
* @param bool $cert_validation set to false to skip SSL certificate validation
|
||||
* @param mixed $encryption Connection encryption method
|
||||
*/
|
||||
public function __construct(Config $config, bool $cert_validation = true, mixed $encryption = false) {
|
||||
$this->config = $config;
|
||||
$this->setCertValidation($cert_validation);
|
||||
$this->encryption = $encryption;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public destructor
|
||||
*/
|
||||
public function __destruct() {
|
||||
$this->logout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the information for a nw connection
|
||||
* @param string $host
|
||||
* @param int|null $port
|
||||
*/
|
||||
public function connect(string $host, int $port = null): void {
|
||||
if ($this->encryption) {
|
||||
$encryption = strtolower($this->encryption);
|
||||
if ($encryption == "ssl") {
|
||||
$port = $port === null ? 993 : $port;
|
||||
}
|
||||
}
|
||||
$port = $port === null ? 143 : $port;
|
||||
$this->host = $host;
|
||||
$this->port = $port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Login to a new session.
|
||||
* @param string $user username
|
||||
* @param string $password password
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function login(string $user, string $password): Response {
|
||||
return $this->response()->wrap(function($response) use ($user, $password) {
|
||||
/** @var Response $response */
|
||||
try {
|
||||
$this->stream = \imap_open(
|
||||
$this->getAddress(),
|
||||
$user,
|
||||
$password,
|
||||
0,
|
||||
$attempts = 3,
|
||||
$this->config->get('options.open')
|
||||
);
|
||||
$response->addCommand("imap_open");
|
||||
} catch (\ErrorException $e) {
|
||||
$errors = \imap_errors();
|
||||
$message = $e->getMessage() . '. ' . implode("; ", (is_array($errors) ? $errors : array()));
|
||||
throw new AuthFailedException($message);
|
||||
}
|
||||
|
||||
if (!$this->stream) {
|
||||
$errors = \imap_errors();
|
||||
$message = implode("; ", (is_array($errors) ? $errors : array()));
|
||||
throw new AuthFailedException($message);
|
||||
}
|
||||
|
||||
$errors = \imap_errors();
|
||||
$response->addCommand("imap_errors");
|
||||
if (is_array($errors)) {
|
||||
$status = $this->examineFolder();
|
||||
$response->stack($status);
|
||||
if ($status->data()['exists'] !== 0) {
|
||||
$message = implode("; ", $errors);
|
||||
throw new RuntimeException($message);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->stream !== false) {
|
||||
return ["TAG" . $response->Noun() . " OK [] Logged in\r\n"];
|
||||
}
|
||||
|
||||
$response->addError("failed to login");
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate your current session.
|
||||
* @param string $user username
|
||||
* @param string $token access token
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function authenticate(string $user, string $token): Response {
|
||||
return $this->login($user, $token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get full address of mailbox.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getAddress(): string {
|
||||
$address = "{" . $this->host . ":" . $this->port . "/" . $this->protocol;
|
||||
if (!$this->cert_validation) {
|
||||
$address .= '/novalidate-cert';
|
||||
}
|
||||
if (in_array($this->encryption, ['tls', 'notls', 'ssl'])) {
|
||||
$address .= '/' . $this->encryption;
|
||||
} elseif ($this->encryption === "starttls") {
|
||||
$address .= '/tls';
|
||||
}
|
||||
|
||||
$address .= '}';
|
||||
|
||||
return $address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout of the current session
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function logout(): Response {
|
||||
return $this->response()->wrap(function($response) {
|
||||
/** @var Response $response */
|
||||
if ($this->stream) {
|
||||
$this->uid_cache = [];
|
||||
$response->addCommand("imap_close");
|
||||
if (\imap_close($this->stream, IMAP::CL_EXPUNGE)) {
|
||||
$this->stream = false;
|
||||
return [
|
||||
0 => "BYE Logging out\r\n",
|
||||
1 => "TAG" . $response->Noun() . " OK Logout completed (0.001 + 0.000 secs).\r\n",
|
||||
];
|
||||
}
|
||||
$this->stream = false;
|
||||
}
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of available capabilities
|
||||
*
|
||||
* @throws MethodNotSupportedException
|
||||
*/
|
||||
public function getCapabilities(): Response {
|
||||
throw new MethodNotSupportedException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the current folder
|
||||
* @param string $folder change to this folder
|
||||
*
|
||||
* @return Response see examineOrselect()
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function selectFolder(string $folder = 'INBOX'): Response {
|
||||
$flags = IMAP::OP_READONLY;
|
||||
if (in_array($this->protocol, ["pop3", "nntp"])) {
|
||||
$flags = IMAP::NIL;
|
||||
}
|
||||
if ($this->stream === false) {
|
||||
throw new RuntimeException("failed to reopen stream.");
|
||||
}
|
||||
|
||||
return $this->response("imap_reopen")->wrap(function($response) use ($folder, $flags) {
|
||||
/** @var Response $response */
|
||||
\imap_reopen($this->stream, $this->getAddress() . $folder, $flags, 3);
|
||||
$this->uid_cache = [];
|
||||
|
||||
$status = $this->examineFolder($folder);
|
||||
$response->stack($status);
|
||||
|
||||
return $status->data();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Examine a given folder
|
||||
* @param string $folder examine this folder
|
||||
*
|
||||
* @return Response
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function examineFolder(string $folder = 'INBOX'): Response {
|
||||
if (str_starts_with($folder, ".")) {
|
||||
throw new RuntimeException("Segmentation fault prevented. Folders starts with an illegal char '.'.");
|
||||
}
|
||||
return $this->response("imap_status")->wrap(function($response) use ($folder) {
|
||||
/** @var Response $response */
|
||||
$status = \imap_status($this->stream, $this->getAddress() . $folder, IMAP::SA_ALL);
|
||||
|
||||
return $status ? [
|
||||
"flags" => [],
|
||||
"exists" => $status->messages,
|
||||
"recent" => $status->recent,
|
||||
"unseen" => $status->unseen,
|
||||
"uidnext" => $status->uidnext,
|
||||
] : [];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the status of a given folder
|
||||
*
|
||||
* @return Response list of STATUS items
|
||||
* @throws MethodNotSupportedException
|
||||
*/
|
||||
public function folderStatus(string $folder = 'INBOX', $arguments = ['MESSAGES', 'UNSEEN', 'RECENT', 'UIDNEXT', 'UIDVALIDITY']): Response {
|
||||
throw new MethodNotSupportedException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch message content
|
||||
* @param int|array $uids
|
||||
* @param string $rfc
|
||||
* @param int|string $uid set to IMAP::ST_UID if you pass message unique identifiers instead of numbers.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function content(int|array $uids, string $rfc = "RFC822", int|string $uid = IMAP::ST_UID): Response {
|
||||
return $this->response()->wrap(function($response) use ($uids, $uid) {
|
||||
/** @var Response $response */
|
||||
|
||||
$result = [];
|
||||
$uids = is_array($uids) ? $uids : [$uids];
|
||||
foreach ($uids as $id) {
|
||||
$response->addCommand("imap_fetchbody");
|
||||
$result[$id] = \imap_fetchbody($this->stream, $id, "", $uid === IMAP::ST_UID ? IMAP::ST_UID : IMAP::NIL);
|
||||
}
|
||||
|
||||
return $result;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch message headers
|
||||
* @param int|array $uids
|
||||
* @param string $rfc
|
||||
* @param int|string $uid set to IMAP::ST_UID if you pass message unique identifiers instead of numbers.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function headers(int|array $uids, string $rfc = "RFC822", int|string $uid = IMAP::ST_UID): Response {
|
||||
return $this->response()->wrap(function($response) use ($uids, $uid) {
|
||||
/** @var Response $response */
|
||||
|
||||
$result = [];
|
||||
$uids = is_array($uids) ? $uids : [$uids];
|
||||
foreach ($uids as $id) {
|
||||
$response->addCommand("imap_fetchheader");
|
||||
$result[$id] = \imap_fetchheader($this->stream, $id, $uid ? IMAP::ST_UID : IMAP::NIL);
|
||||
}
|
||||
|
||||
return $result;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch message flags
|
||||
* @param int|array $uids
|
||||
* @param int|string $uid set to IMAP::ST_UID if you pass message unique identifiers instead of numbers.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function flags(int|array $uids, int|string $uid = IMAP::ST_UID): Response {
|
||||
return $this->response()->wrap(function($response) use ($uids, $uid) {
|
||||
/** @var Response $response */
|
||||
|
||||
$result = [];
|
||||
$uids = is_array($uids) ? $uids : [$uids];
|
||||
foreach ($uids as $id) {
|
||||
$response->addCommand("imap_fetch_overview");
|
||||
$raw_flags = \imap_fetch_overview($this->stream, $id, $uid ? IMAP::ST_UID : IMAP::NIL);
|
||||
$flags = [];
|
||||
if (is_array($raw_flags) && isset($raw_flags[0])) {
|
||||
$raw_flags = (array)$raw_flags[0];
|
||||
foreach ($raw_flags as $flag => $value) {
|
||||
if ($value === 1 && in_array($flag, ["size", "uid", "msgno", "update"]) === false) {
|
||||
$flags[] = "\\" . ucfirst($flag);
|
||||
}
|
||||
}
|
||||
}
|
||||
$result[$id] = $flags;
|
||||
}
|
||||
|
||||
return $result;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch message sizes
|
||||
* @param int|array $uids
|
||||
* @param int|string $uid set to IMAP::ST_UID if you pass message unique identifiers instead of numbers.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function sizes(int|array $uids, int|string $uid = IMAP::ST_UID): Response {
|
||||
return $this->response()->wrap(function($response) use ($uids, $uid) {
|
||||
/** @var Response $response */
|
||||
$result = [];
|
||||
$uids = is_array($uids) ? $uids : [$uids];
|
||||
$uid_text = implode("','", $uids);
|
||||
$response->addCommand("imap_fetch_overview");
|
||||
if ($uid == IMAP::ST_UID) {
|
||||
$raw_overview = \imap_fetch_overview($this->stream, $uid_text, IMAP::FT_UID);
|
||||
} else {
|
||||
$raw_overview = \imap_fetch_overview($this->stream, $uid_text);
|
||||
}
|
||||
if ($raw_overview !== false) {
|
||||
foreach ($raw_overview as $overview_element) {
|
||||
$overview_element = (array)$overview_element;
|
||||
$result[$overview_element[$uid == IMAP::ST_UID ? 'uid' : 'msgno']] = $overview_element['size'];
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get uid for a given id
|
||||
* @param int|null $id message number
|
||||
*
|
||||
* @return Response message number for given message or all messages as array
|
||||
*/
|
||||
public function getUid(int $id = null): Response {
|
||||
return $this->response()->wrap(function($response) use ($id) {
|
||||
/** @var Response $response */
|
||||
if ($id === null) {
|
||||
if ($this->enable_uid_cache && $this->uid_cache) {
|
||||
return $this->uid_cache;
|
||||
}
|
||||
|
||||
$overview = $this->overview("1:*");
|
||||
$response->stack($overview);
|
||||
$uids = [];
|
||||
foreach ($overview->data() as $set) {
|
||||
$uids[$set->msgno] = $set->uid;
|
||||
}
|
||||
|
||||
$this->setUidCache($uids);
|
||||
return $uids;
|
||||
}
|
||||
|
||||
$response->addCommand("imap_uid");
|
||||
$uid = \imap_uid($this->stream, $id);
|
||||
if ($uid) {
|
||||
return $uid;
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message number of a given uid
|
||||
* @param string $id uid
|
||||
*
|
||||
* @return Response message number
|
||||
*/
|
||||
public function getMessageNumber(string $id): Response {
|
||||
return $this->response("imap_msgno")->wrap(function($response) use ($id) {
|
||||
/** @var Response $response */
|
||||
return \imap_msgno($this->stream, $id);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a message overview
|
||||
* @param string $sequence uid sequence
|
||||
* @param int|string $uid set to IMAP::ST_UID if you pass message unique identifiers instead of numbers.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function overview(string $sequence, int|string $uid = IMAP::ST_UID): Response {
|
||||
return $this->response("imap_fetch_overview")->wrap(function($response) use ($sequence, $uid) {
|
||||
/** @var Response $response */
|
||||
return \imap_fetch_overview($this->stream, $sequence, $uid ? IMAP::ST_UID : IMAP::NIL) ?: [];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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' => ..))
|
||||
*/
|
||||
public function folders(string $reference = '', string $folder = '*'): Response {
|
||||
return $this->response("imap_getmailboxes")->wrap(function($response) use ($reference, $folder) {
|
||||
/** @var Response $response */
|
||||
$result = [];
|
||||
|
||||
$items = \imap_getmailboxes($this->stream, $this->getAddress(), $reference . $folder);
|
||||
if (is_array($items)) {
|
||||
foreach ($items as $item) {
|
||||
$name = $this->decodeFolderName($item->name);
|
||||
$result[$name] = ['delimiter' => $item->delimiter, 'flags' => []];
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException(\imap_last_error());
|
||||
}
|
||||
|
||||
return $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 if you pass message unique identifiers instead of numbers.
|
||||
* @param string|null $item unused attribute
|
||||
*
|
||||
* @return Response new flags if $silent is false, else true or false depending on success
|
||||
*/
|
||||
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 {
|
||||
$flag = trim(is_array($flags) ? implode(" ", $flags) : $flags);
|
||||
|
||||
return $this->response()->wrap(function($response) use ($mode, $from, $flag, $uid, $silent) {
|
||||
/** @var Response $response */
|
||||
|
||||
if ($mode == "+") {
|
||||
$response->addCommand("imap_setflag_full");
|
||||
$status = \imap_setflag_full($this->stream, $from, $flag, $uid ? IMAP::ST_UID : IMAP::NIL);
|
||||
} else {
|
||||
$response->addCommand("imap_clearflag_full");
|
||||
$status = \imap_clearflag_full($this->stream, $from, $flag, $uid ? IMAP::ST_UID : IMAP::NIL);
|
||||
}
|
||||
|
||||
if ($silent === true) {
|
||||
if ($status) {
|
||||
return [
|
||||
"TAG" . $response->Noun() . " OK Store completed (0.001 + 0.000 secs).\r\n"
|
||||
];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->flags($from);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 mixed $date date for new message
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function appendMessage(string $folder, string $message, array $flags = null, mixed $date = null): Response {
|
||||
return $this->response("imap_append")->wrap(function($response) use ($folder, $message, $flags, $date) {
|
||||
/** @var Response $response */
|
||||
if ($date != null) {
|
||||
if ($date instanceof \Carbon\Carbon) {
|
||||
$date = $date->format('d-M-Y H:i:s O');
|
||||
}
|
||||
if (\imap_append($this->stream, $this->getAddress() . $folder, $message, $flags, $date)) {
|
||||
return [
|
||||
"OK Append completed (0.001 + 0.000 secs).\r\n"
|
||||
];
|
||||
}
|
||||
} else if (\imap_append($this->stream, $this->getAddress() . $folder, $message, $flags)) {
|
||||
return [
|
||||
"OK Append completed (0.001 + 0.000 secs).\r\n"
|
||||
];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy message set from current folder to other 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 if you pass message unique identifiers instead of numbers.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function copyMessage(string $folder, $from, int $to = null, int|string $uid = IMAP::ST_UID): Response {
|
||||
return $this->response("imap_mail_copy")->wrap(function($response) use ($from, $folder, $uid) {
|
||||
/** @var Response $response */
|
||||
|
||||
if (\imap_mail_copy($this->stream, $from, $this->getAddress() . $folder, $uid ? IMAP::ST_UID : IMAP::NIL)) {
|
||||
return [
|
||||
"TAG" . $response->Noun() . " OK Copy completed (0.001 + 0.000 secs).\r\n"
|
||||
];
|
||||
}
|
||||
throw new ImapBadRequestException("Invalid ID $from");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 if you pass message unique identifiers instead of numbers.
|
||||
*
|
||||
* @return Response Tokens if operation successful, false if an error occurred
|
||||
*/
|
||||
public function copyManyMessages(array $messages, string $folder, int|string $uid = IMAP::ST_UID): Response {
|
||||
return $this->response()->wrap(function($response) use ($messages, $folder, $uid) {
|
||||
/** @var Response $response */
|
||||
foreach ($messages as $msg) {
|
||||
$copy_response = $this->copyMessage($folder, $msg, null, $uid);
|
||||
$response->stack($copy_response);
|
||||
if (empty($copy_response->data())) {
|
||||
return [
|
||||
"TAG" . $response->Noun() . " BAD Copy failed (0.001 + 0.000 secs).\r\n",
|
||||
"Invalid ID $msg\r\n"
|
||||
];
|
||||
}
|
||||
}
|
||||
return [
|
||||
"TAG" . $response->Noun() . " OK Copy completed (0.001 + 0.000 secs).\r\n"
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 if you pass message unique identifiers instead of numbers.
|
||||
*
|
||||
* @return Response success
|
||||
*/
|
||||
public function moveMessage(string $folder, $from, int $to = null, int|string $uid = IMAP::ST_UID): Response {
|
||||
return $this->response("imap_mail_move")->wrap(function($response) use ($from, $folder, $uid) {
|
||||
if (\imap_mail_move($this->stream, $from, $this->getAddress() . $folder, $uid ? IMAP::ST_UID : IMAP::NIL)) {
|
||||
return [
|
||||
"TAG" . $response->Noun() . " OK Move completed (0.001 + 0.000 secs).\r\n"
|
||||
];
|
||||
}
|
||||
throw new ImapBadRequestException("Invalid ID $from");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 if you pass message unique identifiers instead of numbers.
|
||||
*
|
||||
* @return Response Tokens if operation successful, false if an error occurred
|
||||
* @throws ImapBadRequestException
|
||||
*/
|
||||
public function moveManyMessages(array $messages, string $folder, int|string $uid = IMAP::ST_UID): Response {
|
||||
return $this->response()->wrap(function($response) use ($messages, $folder, $uid) {
|
||||
foreach ($messages as $msg) {
|
||||
$move_response = $this->moveMessage($folder, $msg, null, $uid);
|
||||
$response = $response->include($response);
|
||||
if (empty($move_response->data())) {
|
||||
return [
|
||||
"TAG" . $response->Noun() . " BAD Move failed (0.001 + 0.000 secs).\r\n",
|
||||
"Invalid ID $msg\r\n"
|
||||
];
|
||||
}
|
||||
}
|
||||
return [
|
||||
"TAG" . $response->Noun() . " OK Move completed (0.001 + 0.000 secs).\r\n"
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Exchange identification information
|
||||
* Ref.: https://datatracker.ietf.org/doc/html/rfc2971
|
||||
*
|
||||
* @param null $ids
|
||||
* @return Response
|
||||
*
|
||||
* @throws MethodNotSupportedException
|
||||
*/
|
||||
public function ID($ids = null): Response {
|
||||
throw new MethodNotSupportedException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new folder (and parent folders if needed)
|
||||
* @param string $folder folder name
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function createFolder(string $folder): Response {
|
||||
return $this->response("imap_createmailbox")->wrap(function($response) use ($folder) {
|
||||
return \imap_createmailbox($this->stream, $this->getAddress() . $folder) ? [
|
||||
0 => "TAG" . $response->Noun() . " OK Create completed (0.004 + 0.000 + 0.003 secs).\r\n",
|
||||
] : [];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename an existing folder
|
||||
* @param string $old old name
|
||||
* @param string $new new name
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function renameFolder(string $old, string $new): Response {
|
||||
return $this->response("imap_renamemailbox")->wrap(function($response) use ($old, $new) {
|
||||
return \imap_renamemailbox($this->stream, $this->getAddress() . $old, $this->getAddress() . $new) ? [
|
||||
0 => "TAG" . $response->Noun() . " OK Move completed (0.004 + 0.000 + 0.003 secs).\r\n",
|
||||
] : [];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a folder
|
||||
* @param string $folder folder name
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function deleteFolder(string $folder): Response {
|
||||
return $this->response("imap_deletemailbox")->wrap(function($response) use ($folder) {
|
||||
return \imap_deletemailbox($this->stream, $this->getAddress() . $folder) ? [
|
||||
0 => "OK Delete completed (0.004 + 0.000 + 0.003 secs).\r\n",
|
||||
] : [];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to a folder
|
||||
* @param string $folder folder name
|
||||
*
|
||||
* @throws MethodNotSupportedException
|
||||
*/
|
||||
public function subscribeFolder(string $folder): Response {
|
||||
throw new MethodNotSupportedException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe from a folder
|
||||
* @param string $folder folder name
|
||||
*
|
||||
* @throws MethodNotSupportedException
|
||||
*/
|
||||
public function unsubscribeFolder(string $folder): Response {
|
||||
throw new MethodNotSupportedException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply session saved changes to the server
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function expunge(): Response {
|
||||
return $this->response("imap_expunge")->wrap(function($response) {
|
||||
return \imap_expunge($this->stream) ? [
|
||||
0 => "TAG" . $response->Noun() . " OK Expunge completed (0.001 + 0.000 secs).\r\n",
|
||||
] : [];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send noop command
|
||||
*
|
||||
* @throws MethodNotSupportedException
|
||||
*/
|
||||
public function noop(): Response {
|
||||
throw new MethodNotSupportedException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send idle command
|
||||
*
|
||||
* @throws MethodNotSupportedException
|
||||
*/
|
||||
public function idle() {
|
||||
throw new MethodNotSupportedException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send done command
|
||||
*
|
||||
* @throws MethodNotSupportedException
|
||||
*/
|
||||
public function done() {
|
||||
throw new MethodNotSupportedException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for matching messages
|
||||
* @param array $params
|
||||
* @param int|string $uid set to IMAP::ST_UID if you pass message unique identifiers instead of numbers.
|
||||
*
|
||||
* @return Response message ids
|
||||
*/
|
||||
public function search(array $params, int|string $uid = IMAP::ST_UID): Response {
|
||||
return $this->response("imap_search")->wrap(function($response) use ($params, $uid) {
|
||||
$response->setCanBeEmpty(true);
|
||||
$result = \imap_search($this->stream, $params[0], $uid ? IMAP::ST_UID : IMAP::NIL);
|
||||
return $result ?: [];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the debug mode
|
||||
*/
|
||||
public function enableDebug() {
|
||||
$this->debug = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the debug mode
|
||||
*/
|
||||
public function disableDebug() {
|
||||
$this->debug = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode name.
|
||||
* It converts UTF7-IMAP encoding to UTF-8.
|
||||
*
|
||||
* @param $name
|
||||
*
|
||||
* @return array|false|string|string[]|null
|
||||
*/
|
||||
protected function decodeFolderName($name): array|bool|string|null {
|
||||
preg_match('#\{(.*)}(.*)#', $name, $preg);
|
||||
return mb_convert_encoding($preg[2], "UTF-8", "UTF7-IMAP");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getProtocol(): string {
|
||||
return $this->protocol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the quota level settings, and usage statics per mailbox
|
||||
* @param $username
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function getQuota($username): Response {
|
||||
return $this->response("imap_get_quota")->wrap(function($response) use ($username) {
|
||||
$result = \imap_get_quota($this->stream, 'user.' . $username);
|
||||
return $result ?: [];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the quota settings per user
|
||||
* @param string $quota_root
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function getQuotaRoot(string $quota_root = 'INBOX'): Response {
|
||||
return $this->response("imap_get_quotaroot")->wrap(function($response) use ($quota_root) {
|
||||
$result = \imap_get_quotaroot($this->stream, $this->getAddress() . $quota_root);
|
||||
return $result ?: [];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $protocol
|
||||
* @return LegacyProtocol
|
||||
*/
|
||||
public function setProtocol(string $protocol): LegacyProtocol {
|
||||
if (($pos = strpos($protocol, "legacy")) > 0) {
|
||||
$protocol = substr($protocol, 0, ($pos + 2) * -1);
|
||||
}
|
||||
$this->protocol = $protocol;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Response instance
|
||||
* @param string|null $command
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
protected function response(?string $command = ""): Response {
|
||||
return Response::make(0, $command == "" ? [] : [$command], [], $this->debug);
|
||||
}
|
||||
}
|
||||
381
plugins/php-imap/src/Connection/Protocols/Protocol.php
Normal file
381
plugins/php-imap/src/Connection/Protocols/Protocol.php
Normal file
@@ -0,0 +1,381 @@
|
||||
<?php
|
||||
/*
|
||||
* File: ImapProtocol.php
|
||||
* Category: Protocol
|
||||
* Author: M.Goldenbaum
|
||||
* Created: 16.09.20 18:27
|
||||
* Updated: -
|
||||
*
|
||||
* Description:
|
||||
* -
|
||||
*/
|
||||
|
||||
namespace Webklex\PHPIMAP\Connection\Protocols;
|
||||
|
||||
use Webklex\PHPIMAP\Config;
|
||||
use Webklex\PHPIMAP\Exceptions\ConnectionFailedException;
|
||||
use Webklex\PHPIMAP\IMAP;
|
||||
|
||||
/**
|
||||
* Class Protocol
|
||||
*
|
||||
* @package Webklex\PHPIMAP\Connection\Protocols
|
||||
*/
|
||||
abstract class Protocol implements ProtocolInterface {
|
||||
|
||||
/**
|
||||
* Default connection timeout in seconds
|
||||
*/
|
||||
protected int $connection_timeout = 30;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
protected bool $debug = false;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
protected bool $enable_uid_cache = true;
|
||||
|
||||
/**
|
||||
* @var resource|mixed|boolean|null $stream
|
||||
*/
|
||||
public $stream = false;
|
||||
|
||||
/**
|
||||
* @var Config $config
|
||||
*/
|
||||
protected Config $config;
|
||||
|
||||
/**
|
||||
* Connection encryption method
|
||||
* @var string $encryption
|
||||
*/
|
||||
protected string $encryption = "";
|
||||
|
||||
/**
|
||||
* Set to false to ignore SSL certificate validation
|
||||
* @var bool
|
||||
*/
|
||||
protected bool $cert_validation = true;
|
||||
|
||||
/**
|
||||
* Proxy settings
|
||||
* @var array
|
||||
*/
|
||||
protected array $proxy = [
|
||||
'socket' => null,
|
||||
'request_fulluri' => false,
|
||||
'username' => null,
|
||||
'password' => null,
|
||||
];
|
||||
|
||||
/**
|
||||
* Cache for uid of active folder.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected array $uid_cache = [];
|
||||
|
||||
/**
|
||||
* Get an available cryptographic method
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getCryptoMethod(): int {
|
||||
// Allow the best TLS version(s) we can
|
||||
$cryptoMethod = STREAM_CRYPTO_METHOD_TLS_CLIENT;
|
||||
|
||||
// PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT
|
||||
// so add them back in manually if we can
|
||||
if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
|
||||
$cryptoMethod = STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
|
||||
}elseif (defined('STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT')) {
|
||||
$cryptoMethod = STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
|
||||
}
|
||||
|
||||
return $cryptoMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable SSL certificate validation
|
||||
*
|
||||
* @return Protocol
|
||||
*/
|
||||
public function enableCertValidation(): Protocol {
|
||||
$this->cert_validation = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable SSL certificate validation
|
||||
* @return Protocol
|
||||
*/
|
||||
public function disableCertValidation(): Protocol {
|
||||
$this->cert_validation = false;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set SSL certificate validation
|
||||
* @var int $cert_validation
|
||||
*
|
||||
* @return Protocol
|
||||
*/
|
||||
public function setCertValidation(int $cert_validation): Protocol {
|
||||
$this->cert_validation = $cert_validation;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should we validate SSL certificate?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getCertValidation(): bool {
|
||||
return $this->cert_validation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set connection proxy settings
|
||||
* @var array $options
|
||||
*
|
||||
* @return Protocol
|
||||
*/
|
||||
public function setProxy(array $options): Protocol {
|
||||
foreach ($this->proxy as $key => $val) {
|
||||
if (isset($options[$key])) {
|
||||
$this->proxy[$key] = $options[$key];
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current proxy settings
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getProxy(): array {
|
||||
return $this->proxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare socket options
|
||||
* @return array
|
||||
*@var string $transport
|
||||
*
|
||||
*/
|
||||
private function defaultSocketOptions(string $transport): array {
|
||||
$options = [];
|
||||
if ($this->encryption) {
|
||||
$options["ssl"] = [
|
||||
'verify_peer_name' => $this->getCertValidation(),
|
||||
'verify_peer' => $this->getCertValidation(),
|
||||
];
|
||||
}
|
||||
|
||||
if ($this->proxy["socket"] != null) {
|
||||
$options[$transport]["proxy"] = $this->proxy["socket"];
|
||||
$options[$transport]["request_fulluri"] = $this->proxy["request_fulluri"];
|
||||
|
||||
if ($this->proxy["username"] != null) {
|
||||
$auth = base64_encode($this->proxy["username"].':'.$this->proxy["password"]);
|
||||
|
||||
$options[$transport]["header"] = [
|
||||
"Proxy-Authorization: Basic $auth"
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new resource stream
|
||||
* @param $transport
|
||||
* @param string $host hostname or IP address of IMAP server
|
||||
* @param int $port of IMAP server, default is 143 (993 for ssl)
|
||||
* @param int $timeout timeout in seconds for initiating session
|
||||
*
|
||||
* @return resource The socket created.
|
||||
* @throws ConnectionFailedException
|
||||
*/
|
||||
public function createStream($transport, string $host, int $port, int $timeout) {
|
||||
$socket = "$transport://$host:$port";
|
||||
$stream = stream_socket_client($socket, $errno, $errstr, $timeout,
|
||||
STREAM_CLIENT_CONNECT,
|
||||
stream_context_create($this->defaultSocketOptions($transport))
|
||||
);
|
||||
|
||||
if (!$stream) {
|
||||
throw new ConnectionFailedException($errstr, $errno);
|
||||
}
|
||||
|
||||
if (false === stream_set_timeout($stream, $timeout)) {
|
||||
throw new ConnectionFailedException('Failed to set stream timeout');
|
||||
}
|
||||
|
||||
return $stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current connection timeout
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getConnectionTimeout(): int {
|
||||
return $this->connection_timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the connection timeout
|
||||
* @param int $connection_timeout
|
||||
*
|
||||
* @return Protocol
|
||||
*/
|
||||
public function setConnectionTimeout(int $connection_timeout): Protocol {
|
||||
$this->connection_timeout = $connection_timeout;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the UID key string
|
||||
* @param int|string $uid
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUIDKey(int|string $uid): string {
|
||||
if ($uid == IMAP::ST_UID || $uid == IMAP::FT_UID) {
|
||||
return "UID";
|
||||
}
|
||||
if (strlen($uid) > 0 && !is_numeric($uid)) {
|
||||
return (string)$uid;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a UID / MSGN command
|
||||
* @param string $command
|
||||
* @param int|string $uid
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buildUIDCommand(string $command, int|string $uid): string {
|
||||
return trim($this->getUIDKey($uid)." ".$command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the uid cache of current active folder
|
||||
*
|
||||
* @param array|null $uids
|
||||
*/
|
||||
public function setUidCache(?array $uids): void {
|
||||
if (is_null($uids)) {
|
||||
$this->uid_cache = [];
|
||||
return;
|
||||
}
|
||||
|
||||
$messageNumber = 1;
|
||||
|
||||
$uid_cache = [];
|
||||
foreach ($uids as $uid) {
|
||||
$uid_cache[$messageNumber++] = (int)$uid;
|
||||
}
|
||||
|
||||
$this->uid_cache = $uid_cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the uid cache
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enableUidCache(): void {
|
||||
$this->enable_uid_cache = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the uid cache
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function disableUidCache(): void {
|
||||
$this->enable_uid_cache = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the encryption method
|
||||
* @param string $encryption
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setEncryption(string $encryption): void {
|
||||
$this->encryption = $encryption;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the encryption method
|
||||
* @return string
|
||||
*/
|
||||
public function getEncryption(): string {
|
||||
return $this->encryption;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current session is connected
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function connected(): bool {
|
||||
return (bool)$this->stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves header/metadata from the resource stream
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function meta(): array {
|
||||
if (!$this->stream) {
|
||||
return [
|
||||
"crypto" => [
|
||||
"protocol" => "",
|
||||
"cipher_name" => "",
|
||||
"cipher_bits" => 0,
|
||||
"cipher_version" => "",
|
||||
],
|
||||
"timed_out" => true,
|
||||
"blocked" => true,
|
||||
"eof" => true,
|
||||
"stream_type" => "tcp_socket/unknown",
|
||||
"mode" => "c",
|
||||
"unread_bytes" => 0,
|
||||
"seekable" => false,
|
||||
];
|
||||
}
|
||||
return stream_get_meta_data($this->stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resource stream
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getStream(): mixed {
|
||||
return $this->stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Config instance
|
||||
*
|
||||
* @return Config
|
||||
*/
|
||||
public function getConfig(): Config {
|
||||
return $this->config;
|
||||
}
|
||||
}
|
||||
458
plugins/php-imap/src/Connection/Protocols/ProtocolInterface.php
Normal file
458
plugins/php-imap/src/Connection/Protocols/ProtocolInterface.php
Normal file
@@ -0,0 +1,458 @@
|
||||
<?php
|
||||
/*
|
||||
* File: ImapProtocol.php
|
||||
* Category: Protocol
|
||||
* Author: M.Goldenbaum
|
||||
* Created: 16.09.20 18:27
|
||||
* Updated: -
|
||||
*
|
||||
* Description:
|
||||
* -
|
||||
*/
|
||||
|
||||
namespace Webklex\PHPIMAP\Connection\Protocols;
|
||||
|
||||
use ErrorException;
|
||||
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\RuntimeException;
|
||||
use Webklex\PHPIMAP\IMAP;
|
||||
|
||||
/**
|
||||
* Interface ProtocolInterface
|
||||
*
|
||||
* @package Webklex\PHPIMAP\Connection\Protocols
|
||||
*/
|
||||
interface ProtocolInterface {
|
||||
|
||||
/**
|
||||
* Public destructor
|
||||
*
|
||||
* @throws ImapBadRequestException
|
||||
* @throws ImapServerErrorException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function __destruct();
|
||||
|
||||
/**
|
||||
* Open a new connection / session
|
||||
* @param string $host hostname or IP address of IMAP server
|
||||
* @param int|null $port of service server
|
||||
*
|
||||
* @throws ErrorException
|
||||
* @throws ConnectionFailedException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function connect(string $host, ?int $port = null);
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Authenticate your current session.
|
||||
* @param string $user username
|
||||
* @param string $token access token
|
||||
*
|
||||
* @return Response
|
||||
* @throws AuthFailedException
|
||||
*/
|
||||
public function authenticate(string $user, string $token): Response;
|
||||
|
||||
/**
|
||||
* Logout of the current server session
|
||||
*
|
||||
* @return Response
|
||||
*
|
||||
* @throws ImapBadRequestException
|
||||
* @throws ImapServerErrorException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function logout(): Response;
|
||||
|
||||
/**
|
||||
* Check if the current session is connected
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function connected(): bool;
|
||||
|
||||
/**
|
||||
* Get an array of available capabilities
|
||||
*
|
||||
* @return Response containing a list of capabilities
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function getCapabilities(): 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;
|
||||
|
||||
/**
|
||||
* Examine a given folder
|
||||
* @param string $folder
|
||||
*
|
||||
* @return Response
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function examineFolder(string $folder = 'INBOX'): Response;
|
||||
|
||||
/**
|
||||
* Get the status of a given folder
|
||||
*
|
||||
* @return Response list of STATUS items
|
||||
*
|
||||
* @throws ImapBadRequestException
|
||||
* @throws ImapServerErrorException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function folderStatus(string $folder = 'INBOX', $arguments = ['MESSAGES', 'UNSEEN', 'RECENT', 'UIDNEXT', 'UIDVALIDITY']): Response;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Get uid for a given id
|
||||
* @param int|null $id message number
|
||||
*
|
||||
* @return Response containing a message number for given message or all messages as array
|
||||
* @throws MessageNotFoundException
|
||||
*/
|
||||
public function getUid(?int $id = null): Response;
|
||||
|
||||
/**
|
||||
* Get a message number for a uid
|
||||
* @param string $id uid
|
||||
*
|
||||
* @return Response containing the message number
|
||||
* @throws MessageNotFoundException
|
||||
*/
|
||||
public function getMessageNumber(string $id): Response;
|
||||
|
||||
/**
|
||||
* Get a list of available folders
|
||||
* @param string $reference mailbox reference for list
|
||||
* @param string $folder mailbox / folder name match with wildcards
|
||||
*
|
||||
* @return Response containing mailboxes that matched $folder as array(globalName => array('delim' => .., 'flags' => ..))
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function folders(string $reference = '', string $folder = '*'): Response;
|
||||
|
||||
/**
|
||||
* Set message flags
|
||||
* @param array|string $flags flags to set, add or remove
|
||||
* @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 containing the new flags if $silent is false, else true or false depending on success
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* 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 RuntimeException
|
||||
*/
|
||||
public function appendMessage(string $folder, string $message, ?array $flags = null, ?string $date = null): Response;
|
||||
|
||||
/**
|
||||
* Copy message set from current folder to other 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 RuntimeException
|
||||
*/
|
||||
public function copyMessage(string $folder, $from, ?int $to = null, int|string $uid = IMAP::ST_UID): Response;
|
||||
|
||||
/**
|
||||
* Copy multiple messages to the target folder
|
||||
* @param array<string> $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 RuntimeException
|
||||
*/
|
||||
public function copyManyMessages(array $messages, string $folder, int|string $uid = IMAP::ST_UID): Response;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public function moveMessage(string $folder, $from, ?int $to = null, int|string $uid = IMAP::ST_UID): Response;
|
||||
|
||||
/**
|
||||
* Move multiple messages to the target folder
|
||||
*
|
||||
* @param array<string> $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 RuntimeException
|
||||
*/
|
||||
public function moveManyMessages(array $messages, string $folder, int|string $uid = IMAP::ST_UID): Response;
|
||||
|
||||
/**
|
||||
* Exchange identification information
|
||||
* Ref.: https://datatracker.ietf.org/doc/html/rfc2971
|
||||
*
|
||||
* @param null $ids
|
||||
* @return Response
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function ID($ids = null): Response;
|
||||
|
||||
/**
|
||||
* Create a new folder
|
||||
*
|
||||
* @param string $folder folder name
|
||||
* @return Response
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function createFolder(string $folder): Response;
|
||||
|
||||
/**
|
||||
* Rename an existing folder
|
||||
*
|
||||
* @param string $old old name
|
||||
* @param string $new new name
|
||||
* @return Response
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function renameFolder(string $old, string $new): Response;
|
||||
|
||||
/**
|
||||
* Delete a folder
|
||||
*
|
||||
* @param string $folder folder name
|
||||
* @return Response
|
||||
*
|
||||
* @throws ImapBadRequestException
|
||||
* @throws ImapServerErrorException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function deleteFolder(string $folder): Response;
|
||||
|
||||
/**
|
||||
* Subscribe to a folder
|
||||
*
|
||||
* @param string $folder folder name
|
||||
* @return Response
|
||||
*
|
||||
* @throws ImapBadRequestException
|
||||
* @throws ImapServerErrorException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function subscribeFolder(string $folder): Response;
|
||||
|
||||
/**
|
||||
* Unsubscribe from a folder
|
||||
*
|
||||
* @param string $folder folder name
|
||||
* @return Response
|
||||
*
|
||||
* @throws ImapBadRequestException
|
||||
* @throws ImapServerErrorException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function unsubscribeFolder(string $folder): Response;
|
||||
|
||||
/**
|
||||
* Send idle command
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function idle();
|
||||
|
||||
/**
|
||||
* Send done command
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function done();
|
||||
|
||||
/**
|
||||
* Apply session saved changes to the server
|
||||
*
|
||||
* @return Response
|
||||
*
|
||||
* @throws ImapBadRequestException
|
||||
* @throws ImapServerErrorException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function expunge(): Response;
|
||||
|
||||
/**
|
||||
* Retrieve the quota level settings, and usage statics per mailbox
|
||||
* @param $username
|
||||
*
|
||||
* @return Response
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function getQuota($username): Response;
|
||||
|
||||
/**
|
||||
* Retrieve the quota settings per user
|
||||
*
|
||||
* @param string $quota_root
|
||||
*
|
||||
* @return Response
|
||||
* @throws ConnectionFailedException
|
||||
*/
|
||||
public function getQuotaRoot(string $quota_root = 'INBOX'): Response;
|
||||
|
||||
/**
|
||||
* Send noop command
|
||||
*
|
||||
* @return Response
|
||||
*
|
||||
* @throws ImapBadRequestException
|
||||
* @throws ImapServerErrorException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function noop(): Response;
|
||||
|
||||
/**
|
||||
* Do a search request
|
||||
*
|
||||
* @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 containing the message ids
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function search(array $params, int|string $uid = IMAP::ST_UID): Response;
|
||||
|
||||
/**
|
||||
* Get a message overview
|
||||
* @param string $sequence uid 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;
|
||||
|
||||
/**
|
||||
* Enable the debug mode
|
||||
*/
|
||||
public function enableDebug();
|
||||
|
||||
/**
|
||||
* Disable the debug mode
|
||||
*/
|
||||
public function disableDebug();
|
||||
|
||||
/**
|
||||
* Enable uid caching
|
||||
*/
|
||||
public function enableUidCache();
|
||||
|
||||
/**
|
||||
* Disable uid caching
|
||||
*/
|
||||
public function disableUidCache();
|
||||
|
||||
/**
|
||||
* Set the uid cache of current active folder
|
||||
*
|
||||
* @param array|null $uids
|
||||
*/
|
||||
public function setUidCache(?array $uids);
|
||||
}
|
||||
417
plugins/php-imap/src/Connection/Protocols/Response.php
Normal file
417
plugins/php-imap/src/Connection/Protocols/Response.php
Normal file
@@ -0,0 +1,417 @@
|
||||
<?php
|
||||
/*
|
||||
* File: Response.php
|
||||
* Category: -
|
||||
* Author: M.Goldenbaum
|
||||
* Created: 30.12.22 19:46
|
||||
* Updated: -
|
||||
*
|
||||
* Description:
|
||||
* -
|
||||
*/
|
||||
|
||||
|
||||
namespace Webklex\PHPIMAP\Connection\Protocols;
|
||||
|
||||
use Webklex\PHPIMAP\Exceptions\ResponseException;
|
||||
|
||||
/**
|
||||
* Class Response
|
||||
*
|
||||
* @package Webklex\PHPIMAP\Connection\Protocols
|
||||
*/
|
||||
class Response {
|
||||
|
||||
/**
|
||||
* The commands used to fetch or manipulate data
|
||||
* @var array $command
|
||||
*/
|
||||
protected array $commands = [];
|
||||
|
||||
/**
|
||||
* The original response received
|
||||
* @var array $response
|
||||
*/
|
||||
protected array $response = [];
|
||||
|
||||
/**
|
||||
* Errors that have occurred while fetching or parsing the response
|
||||
* @var array $errors
|
||||
*/
|
||||
protected array $errors = [];
|
||||
|
||||
/**
|
||||
* Result to be returned
|
||||
* @var mixed|null $result
|
||||
*/
|
||||
protected mixed $result = null;
|
||||
|
||||
/**
|
||||
* Noun to identify the request / response
|
||||
* @var int $noun
|
||||
*/
|
||||
protected int $noun = 0;
|
||||
|
||||
/**
|
||||
* Other related responses
|
||||
* @var array $response_stack
|
||||
*/
|
||||
protected array $response_stack = [];
|
||||
|
||||
/**
|
||||
* Debug flag
|
||||
* @var bool $debug
|
||||
*/
|
||||
protected bool $debug = false;
|
||||
|
||||
/**
|
||||
* Can the response be empty?
|
||||
* @var bool $can_be_empty
|
||||
*/
|
||||
protected bool $can_be_empty = false;
|
||||
|
||||
/**
|
||||
* Create a new Response instance
|
||||
*/
|
||||
public function __construct(int $noun, bool $debug = false) {
|
||||
$this->debug = $debug;
|
||||
$this->noun = $noun > 0 ? $noun : (int)str_replace(".", "", (string)microtime(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a new response instance
|
||||
* @param int $noun
|
||||
* @param array $commands
|
||||
* @param array $responses
|
||||
* @param bool $debug
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public static function make(int $noun, array $commands = [], array $responses = [], bool $debug = false): Response {
|
||||
return (new self($noun, $debug))->setCommands($commands)->setResponse($responses);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new empty response
|
||||
* @param bool $debug
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public static function empty(bool $debug = false): Response {
|
||||
return (new self(0, $debug));
|
||||
}
|
||||
|
||||
/**
|
||||
* Stack another response
|
||||
* @param Response $response
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function stack(Response $response): void {
|
||||
$this->response_stack[] = $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the associated response stack
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getStack(): array {
|
||||
return $this->response_stack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all assigned commands
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getCommands(): array {
|
||||
return $this->commands;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new command
|
||||
* @param string $command
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function addCommand(string $command): Response {
|
||||
$this->commands[] = $command;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set and overwrite all commands
|
||||
* @param array $commands
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function setCommands(array $commands): Response {
|
||||
$this->commands = $commands;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all set errors
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getErrors(): array {
|
||||
$errors = $this->errors;
|
||||
foreach($this->getStack() as $response) {
|
||||
$errors = array_merge($errors, $response->getErrors());
|
||||
}
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set and overwrite all existing errors
|
||||
* @param array $errors
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function setErrors(array $errors): Response {
|
||||
$this->errors = $errors;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the response
|
||||
* @param string $error
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function addError(string $error): Response {
|
||||
$this->errors[] = $error;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the response
|
||||
* @param array $response
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function addResponse(mixed $response): Response {
|
||||
$this->response[] = $response;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the response
|
||||
* @param array $response
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function setResponse(array $response): Response {
|
||||
$this->response = $response;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the assigned response
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getResponse(): array {
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the result data
|
||||
* @param mixed $result
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function setResult(mixed $result): Response {
|
||||
$this->result = $result;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap a result bearing action
|
||||
* @param callable $callback
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function wrap(callable $callback): Response {
|
||||
$this->result = call_user_func($callback, $this);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the response data
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function data(): mixed {
|
||||
if ($this->result !== null) {
|
||||
return $this->result;
|
||||
}
|
||||
return $this->getResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the response data as array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function array(): array {
|
||||
$data = $this->data();
|
||||
if(is_array($data)){
|
||||
return $data;
|
||||
}
|
||||
return [$data];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the response data as string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function string(): string {
|
||||
$data = $this->data();
|
||||
if(is_array($data)){
|
||||
return implode(" ", $data);
|
||||
}
|
||||
return (string)$data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the response data as integer
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function integer(): int {
|
||||
$data = $this->data();
|
||||
if(is_array($data) && isset($data[0])){
|
||||
return (int)$data[0];
|
||||
}
|
||||
return (int)$data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the response data as boolean
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function boolean(): bool {
|
||||
return (bool)$this->data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and retrieve the response data
|
||||
*
|
||||
* @throws ResponseException
|
||||
*/
|
||||
public function validatedData(): mixed {
|
||||
return $this->validate()->data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the response date
|
||||
*
|
||||
* @throws ResponseException
|
||||
*/
|
||||
public function validate(): Response {
|
||||
if ($this->failed()) {
|
||||
throw ResponseException::make($this, $this->debug);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the Response can be considered successful
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function successful(): bool {
|
||||
foreach(array_merge($this->getResponse(), $this->array()) as $data) {
|
||||
if (!$this->verify_data($data)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
foreach($this->getStack() as $response) {
|
||||
if (!$response->successful()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return ($this->boolean() || $this->canBeEmpty()) && !$this->getErrors();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if the Response can be considered failed
|
||||
* @param mixed $data
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function verify_data(mixed $data): bool {
|
||||
if (is_array($data)) {
|
||||
foreach ($data as $line) {
|
||||
if (is_array($line)) {
|
||||
if(!$this->verify_data($line)){
|
||||
return false;
|
||||
}
|
||||
}else{
|
||||
if (!$this->verify_line((string)$line)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
if (!$this->verify_line((string)$data)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a single line
|
||||
* @param string $line
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function verify_line(string $line): bool {
|
||||
return !str_starts_with($line, "TAG".$this->noun." BAD ") && !str_starts_with($line, "TAG".$this->noun." NO ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the Response can be considered failed
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function failed(): bool {
|
||||
return !$this->successful();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Response noun
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function Noun(): int {
|
||||
return $this->noun;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Response to be allowed to be empty
|
||||
* @param bool $can_be_empty
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setCanBeEmpty(bool $can_be_empty): Response {
|
||||
$this->can_be_empty = $can_be_empty;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the Response can be empty
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canBeEmpty(): bool {
|
||||
return $this->can_be_empty;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user