inclure une version simplifiée de nulib/mail
This commit is contained in:
		
							parent
							
								
									ee33699608
								
							
						
					
					
						commit
						6b7d4b683d
					
				
							
								
								
									
										17
									
								
								.idea/php.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										17
									
								
								.idea/php.xml
									
									
									
										generated
									
									
									
								
							| @ -55,6 +55,23 @@ | ||||
|       <path value="$PROJECT_DIR$/php/vendor/myclabs/deep-copy" /> | ||||
|       <path value="$PROJECT_DIR$/php/vendor/nikic/php-parser" /> | ||||
|       <path value="$PROJECT_DIR$/php/vendor/composer" /> | ||||
|       <path value="$PROJECT_DIR$/php/vendor/league/config" /> | ||||
|       <path value="$PROJECT_DIR$/php/vendor/nette/schema" /> | ||||
|       <path value="$PROJECT_DIR$/php/vendor/league/commonmark" /> | ||||
|       <path value="$PROJECT_DIR$/php/vendor/dflydev/dot-access-data" /> | ||||
|       <path value="$PROJECT_DIR$/php/vendor/psr/event-dispatcher" /> | ||||
|       <path value="$PROJECT_DIR$/php/vendor/nette/utils" /> | ||||
|       <path value="$PROJECT_DIR$/php/vendor/psr/container" /> | ||||
|       <path value="$PROJECT_DIR$/php/vendor/psr/log" /> | ||||
|       <path value="$PROJECT_DIR$/php/vendor/psr/cache" /> | ||||
|       <path value="$PROJECT_DIR$/php/vendor/phpmailer/phpmailer" /> | ||||
|       <path value="$PROJECT_DIR$/php/vendor/symfony/expression-language" /> | ||||
|       <path value="$PROJECT_DIR$/php/vendor/symfony/cache" /> | ||||
|       <path value="$PROJECT_DIR$/php/vendor/symfony/cache-contracts" /> | ||||
|       <path value="$PROJECT_DIR$/php/vendor/symfony/polyfill-php80" /> | ||||
|       <path value="$PROJECT_DIR$/php/vendor/symfony/service-contracts" /> | ||||
|       <path value="$PROJECT_DIR$/php/vendor/symfony/polyfill-php73" /> | ||||
|       <path value="$PROJECT_DIR$/php/vendor/symfony/var-exporter" /> | ||||
|     </include_path> | ||||
|   </component> | ||||
|   <component name="PhpProjectSharedConfiguration" php_language_level="7.4"> | ||||
|  | ||||
| @ -19,6 +19,9 @@ | ||||
| 	}, | ||||
| 	"require": { | ||||
| 		"symfony/yaml": "^5.0", | ||||
| 		"phpmailer/phpmailer": "^6.8", | ||||
| 		"symfony/expression-language": "^5.4", | ||||
| 		"league/commonmark": "^2.4", | ||||
| 		"ext-json": "*", | ||||
| 		"php": "^7.4" | ||||
| 	}, | ||||
|  | ||||
							
								
								
									
										1377
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1377
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										120
									
								
								php/src/mail/MailTemplate.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								php/src/mail/MailTemplate.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,120 @@ | ||||
| <?php | ||||
| namespace nulib\mail; | ||||
| 
 | ||||
| use nulib\cv; | ||||
| use nulib\str; | ||||
| use nulib\ValueException; | ||||
| use Symfony\Component\ExpressionLanguage\ExpressionLanguage; | ||||
| 
 | ||||
| class MailTemplate { | ||||
|   const SCHEMA = [ | ||||
|     "subject" => "string", | ||||
|     "body" => "string", | ||||
|     "exprs" => "array", | ||||
|   ]; | ||||
| 
 | ||||
|   function __construct(array $mail) { | ||||
|     $tsubject = $mail["subject"] ?? null; | ||||
|     $tbody = $mail["body"] ?? null; | ||||
|     $texprs = $mail["exprs"] ?? []; | ||||
| 
 | ||||
|     $this->el = new ExpressionLanguage(); | ||||
|     ValueException::check_null($this->subject = $tsubject, "subject"); | ||||
|     ValueException::check_null($this->body = $tbody, "body"); | ||||
|     $exprs = []; | ||||
|     # Commencer par extraire les expressions de la forme {name}
 | ||||
|     if (preg_match_all('/\{([a-zA-Z_][a-zA-Z0-9_.-]*)}/', $this->body, $mss, PREG_SET_ORDER)) { | ||||
|       foreach ($mss as $ms) { | ||||
|         $key = $ms[0]; | ||||
|         $expr = str_replace("'", "\\'", $ms[1]); | ||||
|         $expr = "_helper.value('$expr')"; | ||||
|         $exprs[$key] = $expr; | ||||
|       } | ||||
|     } | ||||
|     $index = 0; | ||||
|     foreach ($texprs as $key => $expr) { | ||||
|       $prefix = null; | ||||
|       $orig = $expr; | ||||
|       if (preg_match('/^\[([^]]*)]/', $expr, $ms)) { | ||||
|         # un préfixe spécifié de la forme [prefix]expr permet de reconnaitre les
 | ||||
|         # formes spéciales de expr (+, *, .) qui sont précédées de prefix
 | ||||
|         # exemple: [https://]+app.url permettra d'utiliser un texte markdown
 | ||||
|         # de la forme <https://+app.url> qui est correctement reconnu comme un
 | ||||
|         # url
 | ||||
|         $prefix = $ms[1]; | ||||
|         $expr = substr($expr, strlen($ms[0])); | ||||
|       } | ||||
|       $mapKey = false; | ||||
|       if (str::del_prefix($expr, "+")) { | ||||
|         # config
 | ||||
|         $mapKey = "$prefix+$expr"; | ||||
|         $expr = str_replace("'", "\\'", $expr); | ||||
|         $expr = "_helper.config('$expr')"; | ||||
|       } elseif (str::del_prefix($expr, "*")) { | ||||
|         # session
 | ||||
|         $mapKey = "$prefix*$expr"; | ||||
|         $expr = str_replace("'", "\\'", $expr); | ||||
|         $expr = "_helper.session('$expr')"; | ||||
|       } elseif (str::del_prefix($expr, ".")) { | ||||
|         # session
 | ||||
|         $mapKey = "$prefix.$expr"; | ||||
|         $expr = str_replace("'", "\\'", $expr); | ||||
|         $expr = "_helper.value('$expr')"; | ||||
|       } elseif ($prefix !== null) { | ||||
|         # sinon remettre le préfixe
 | ||||
|         $expr = $orig; | ||||
|       } | ||||
| 
 | ||||
|       if ($key === $index) { | ||||
|         $index++; | ||||
|         if ($mapKey !== false) { | ||||
|           $exprs[$mapKey] = $expr; | ||||
|         } else { | ||||
|           # clé normale: la correspondance est en minuscule
 | ||||
|           $exprs[$expr] = strtolower($expr); | ||||
|         } | ||||
|       } else { | ||||
|         $exprs[$key] = $expr; | ||||
|       } | ||||
|     } | ||||
|     uksort($exprs, function ($a, $b) { | ||||
|       return -cv::complen($a, $b); | ||||
|     }); | ||||
|     $this->exprs = $exprs; | ||||
|   } | ||||
| 
 | ||||
|   /** @var ExpressionLanguage */ | ||||
|   protected $el; | ||||
| 
 | ||||
|   protected $subject; | ||||
| 
 | ||||
|   protected $body; | ||||
| 
 | ||||
|   protected $exprs; | ||||
| 
 | ||||
|   protected function _eval(string $template, ?array $data): string { | ||||
|     if ($data === null) return $template; | ||||
|     $el = $this->el; | ||||
|     foreach ($this->exprs as $key => $expr) { | ||||
|       $value = $el->evaluate($expr, $data); | ||||
|       if (is_array($value)) $value = str::join(" ", $value); | ||||
|       elseif (!is_string($value)) $value = strval($value); | ||||
|       $template = str_replace($key, $value, $template); | ||||
|     } | ||||
|     return $template; | ||||
|   } | ||||
| 
 | ||||
|   function eval(?array $data, $convertMd=true): array { | ||||
|     if ($data !== null) { | ||||
|       $data["_helper"] = new MailTemplateHelper($data); | ||||
|     } | ||||
|     $subject = $this->_eval($this->subject, $data); | ||||
|     $body = $this->body; | ||||
|     if ($convertMd) $body = mdc::convert($body); | ||||
|     $body = $this->_eval($body, $data); | ||||
|     return [ | ||||
|       "subject" => $subject, | ||||
|       "body" => $body, | ||||
|     ]; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										22
									
								
								php/src/mail/MailTemplateHelper.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								php/src/mail/MailTemplateHelper.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | ||||
| <?php | ||||
| namespace nulib\mail; | ||||
| 
 | ||||
| use nulib\cl; | ||||
| 
 | ||||
| class MailTemplateHelper { | ||||
|   function __construct(?array $data) { | ||||
|     $this->data = $data; | ||||
|   } | ||||
| 
 | ||||
|   function value(string $pkey) { | ||||
|     return cl::pget($this->data, $pkey); | ||||
|   } | ||||
| 
 | ||||
|   function config(string $pkey) { | ||||
|     return config::get($pkey); | ||||
|   } | ||||
| 
 | ||||
|   function session(string $pkey) { | ||||
|     return session::pget($pkey); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										7
									
								
								php/src/mail/MailerException.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								php/src/mail/MailerException.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| <?php | ||||
| namespace nulib\mail; | ||||
| 
 | ||||
| use nulib\UserException; | ||||
| 
 | ||||
| class MailerException extends UserException { | ||||
| } | ||||
							
								
								
									
										188
									
								
								php/src/mail/mailer.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								php/src/mail/mailer.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,188 @@ | ||||
| <?php | ||||
| namespace nulib\mail; | ||||
| 
 | ||||
| use nulib\cl; | ||||
| use nulib\cv; | ||||
| use nulib\output\msg; | ||||
| use nulib\str; | ||||
| use nulib\ValueException; | ||||
| use PHPMailer\PHPMailer\PHPMailer; | ||||
| use PHPMailer\PHPMailer\SMTP; | ||||
| 
 | ||||
| class mailer { | ||||
|   private static function is_bool(&$value): bool { | ||||
|     if ($value === null) { | ||||
|       return false; | ||||
|     } elseif (is_bool($value)) { | ||||
|       return true; | ||||
|     } elseif (is_int($value)) { | ||||
|       $value = boolval($value); | ||||
|       return true; | ||||
|     } else { | ||||
|       switch (strval($value)) { | ||||
|       case "0": | ||||
|       case "no": | ||||
|       case "off": | ||||
|       case "false": | ||||
|         $value = false; | ||||
|         return true; | ||||
|       case "1": | ||||
|       case "yes": | ||||
|       case "on": | ||||
|       case "true": | ||||
|         $value = true; | ||||
|         return true; | ||||
|       default: | ||||
|         return false; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private static function get_bool($value): bool { | ||||
|     if (self::is_bool($value)) return $value; | ||||
|     else return false; | ||||
|   } | ||||
| 
 | ||||
|   const FROM = "no-reply@univ-reunion.fr"; | ||||
| 
 | ||||
|   const SCHEMA = [ | ||||
|     "backend" => ["string", "smtp"], | ||||
|     "debug" => ["int", SMTP::DEBUG_OFF], | ||||
|     "host" => ["?string", "smtp.univ.run"], | ||||
|     "port" => ["?int", 25], | ||||
|     "auth" => "?bool", | ||||
|     "username" => "?string", | ||||
|     "password" => "?string", | ||||
|     "secure" => "?string", | ||||
|   ]; | ||||
| 
 | ||||
|   static function get(?array $params=null, ?bool $exceptions=null): PHPMailer { | ||||
|     $mailer = new PHPMailer($exceptions); | ||||
|     $mailer->setLanguage("fr"); | ||||
|     $mailer->CharSet = PHPMailer::CHARSET_UTF8; | ||||
|     # backend
 | ||||
|     $backend = $params["backend"] ?? null; | ||||
|     $backend ??= cv::vn(getenv("NULIB_MAIL_BACKEND")); | ||||
|     $backend ??= "smtp"; | ||||
|     switch ($backend) { | ||||
|     case "smtp": | ||||
|       # host
 | ||||
|       $host = $params["host"] ?? null; | ||||
|       $host ??= cv::vn(getenv("NULIB_MAIL_HOST")); | ||||
|       # port
 | ||||
|       $port = $params["port"] ?? null; | ||||
|       $port ??= cv::vn(getenv("NULIB_MAIL_PORT")); | ||||
|       $port ??= 25; | ||||
|       if ($host === null) { | ||||
|         throw new ValueException("mail host is required"); | ||||
|       } | ||||
|       msg::debug("new PHPMailer using SMTP to $host:$port"); | ||||
|       $mailer->isSMTP(); | ||||
|       $mailer->Host = $host; | ||||
|       $mailer->Port = $port; | ||||
|       break; | ||||
|     case "phpmail": | ||||
|       msg::debug("new PHPMailer using PHPmail"); | ||||
|       $mailer->isMail(); | ||||
|       break; | ||||
|     case "sendmail": | ||||
|       msg::debug("new PHPMailer using sendmail"); | ||||
|       $mailer->isSendmail(); | ||||
|       break; | ||||
|     default: | ||||
|       throw ValueException::invalid_value($backend, "mailer backend"); | ||||
|     } | ||||
|     # debug
 | ||||
|     $debug = $params["debug"] ?? null; | ||||
|     $debug ??= cv::vn(getenv("NULIB_MAIL_DEBUG")); | ||||
|     $debug ??= SMTP::DEBUG_OFF; | ||||
|     if (is_int($debug)) { | ||||
|       if ($debug < SMTP::DEBUG_OFF) $debug = SMTP::DEBUG_OFF; | ||||
|       elseif ($debug > SMTP::DEBUG_LOWLEVEL) $debug = SMTP::DEBUG_LOWLEVEL; | ||||
|     } elseif (!self::is_bool($debug)) { | ||||
|       throw ValueException::invalid_value($debug, "debug mode"); | ||||
|     } | ||||
|     $mailer->SMTPDebug = $debug; | ||||
|     # auth, username, password
 | ||||
|     $username = $params["username"] ?? null; | ||||
|     $username ??= cv::vn(getenv("NULIB_MAIL_USERNAME")); | ||||
|     $password = $params["password"] ?? null; | ||||
|     $password ??= cv::vn(getenv("NULIB_MAIL_PASSWORD")); | ||||
|     $auth = $params["auth"] ?? null; | ||||
|     $auth ??= cv::vn(getenv("NULIB_MAIL_AUTH")); | ||||
|     $auth ??= $username !== null && $password !== null; | ||||
|     $mailer->SMTPAuth = self::get_bool($auth); | ||||
|     $mailer->Username = $username; | ||||
|     $mailer->Password = $password; | ||||
|     # secure
 | ||||
|     $secure = $params["secure"] ?? null; | ||||
|     $secure ??= cv::vn(getenv("NULIB_MAIL_SECURE")); | ||||
|     $secure ??= false; | ||||
|     if (self::is_bool($secure)) { | ||||
|       if (!$secure) { | ||||
|         $mailer->SMTPSecure = ""; | ||||
|         $mailer->SMTPAutoTLS = false; | ||||
|       } | ||||
|     } else { | ||||
|       switch ($secure) { | ||||
|       case PHPMailer::ENCRYPTION_SMTPS: | ||||
|       case PHPMailer::ENCRYPTION_STARTTLS: | ||||
|         $mailer->SMTPSecure = $secure; | ||||
|         break; | ||||
|       default: | ||||
|         throw ValueException::invalid_value($secure, "encryption mode"); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return $mailer; | ||||
|   } | ||||
| 
 | ||||
|   static function build($to, string $subject, string $body, $cc=null, $bcc=null, ?string $from=null, ?PHPMailer $mailer=null): PHPMailer { | ||||
|     if ($mailer === null) $mailer = self::get(); | ||||
|     $mailer->clearAllRecipients(); | ||||
| 
 | ||||
|     if ($from === null) $from = static::FROM; | ||||
|     $mailer->setFrom($from); | ||||
|     foreach (cl::with($to) as $tos) { | ||||
|       foreach (preg_split('/\s*[,;]\s*/', trim($tos)) as $to) { | ||||
|         $mailer->addAddress($to); | ||||
|       } | ||||
|     } | ||||
|     foreach (cl::with($cc) as $ccs) { | ||||
|       foreach (preg_split('/\s*[,;]\s*/', trim($ccs)) as $cc) { | ||||
|         $mailer->addCC($cc); | ||||
|       } | ||||
|     } | ||||
|     foreach (cl::with($bcc) as $bccs) { | ||||
|       foreach (preg_split('/\s*[,;]\s*/', trim($bccs)) as $bcc) { | ||||
|         $mailer->addBCC($bcc); | ||||
|       } | ||||
|     } | ||||
|     $mailer->isHTML(); | ||||
|     $mailer->Subject = $subject; | ||||
|     $mailer->Body = $body; | ||||
|     return $mailer; | ||||
|   } | ||||
| 
 | ||||
|   static function _send(PHPMailer $mailer): void { | ||||
|     $tos = []; | ||||
|     foreach ($mailer->getToAddresses() as $to) { | ||||
|       $tos[] = $to[0]; | ||||
|     } | ||||
|     $tos = str::join(",", $tos); | ||||
|     msg::debug("Sending to $tos"); | ||||
|     if (!$mailer->send()) { | ||||
|       throw new MailerException("Une erreur s'est produite pendant l'envoi du mail", $mailer->ErrorInfo); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   static function send($to, string $subject, string $body, $cc=null, $bcc=null, ?string $from=null, ?PHPMailer $mailer=null): void { | ||||
|     self::_send(self::build($to, $subject, $body, $cc, $bcc, $from, $mailer)); | ||||
|   } | ||||
| 
 | ||||
|   static function tsend(array $template, array $data, $to, $cc=null, $bcc=null, ?string $from=null): void { | ||||
|     $template = new MailTemplate($template); | ||||
|     $mail = $template->eval($data); | ||||
|     self::send($to, $mail["subject"], $mail["body"], $cc, $bcc, $from); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										22
									
								
								php/src/mail/mdc.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								php/src/mail/mdc.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | ||||
| <?php | ||||
| namespace nulib\mail; | ||||
| 
 | ||||
| use League\CommonMark\GithubFlavoredMarkdownConverter; | ||||
| use League\CommonMark\MarkdownConverter; | ||||
| 
 | ||||
| class mdc { | ||||
|   private static $mdc; | ||||
| 
 | ||||
|   static function mdc(): MarkdownConverter { | ||||
|     if (self::$mdc === null) { | ||||
|       self::$mdc = new GithubFlavoredMarkdownConverter([ | ||||
|         "allow_unsafe_links" => false, | ||||
|       ]); | ||||
|     } | ||||
|     return self::$mdc; | ||||
|   } | ||||
| 
 | ||||
|   static function convert(string $text): string { | ||||
|     return self::mdc()->convert($text); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										26
									
								
								php/tbin/mail.php
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										26
									
								
								php/tbin/mail.php
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,26 @@ | ||||
| #!/usr/bin/php
 | ||||
| <?php | ||||
| require __DIR__.'/../vendor/autoload.php'; | ||||
| 
 | ||||
| use lib\mail\mailer; | ||||
| use nur\cli\Application; | ||||
| 
 | ||||
| Application::run(new class extends Application { | ||||
|   const ARGS = [ | ||||
|     "merge" => parent::ARGS, | ||||
|     ["-t", "--to", "args" => 1, "action" => "--add", "name" => "to"], | ||||
|     ["-c", "--cc", "args" => 1, "action" => "--add", "name" => "cc"], | ||||
|     ["-b", "--bcc", "args" => 1, "action" => "--add", "name" => "bcc"], | ||||
|     ["-F", "--from", "args" => 1, "name" => "from"], | ||||
|     ["args" => 2, "name" => "args"], | ||||
|   ]; | ||||
| 
 | ||||
|   protected $to, $cc, $bcc, $from; | ||||
|   protected $args; | ||||
| 
 | ||||
|   function main() { | ||||
|     $subject = $this->args[0]; | ||||
|     $body = $this->args[1]; | ||||
|     mailer::send($this->to, $subject, $body, $this->cc, $this->bcc, $this->from); | ||||
|   } | ||||
| }); | ||||
							
								
								
									
										19
									
								
								php/tbin/test_mail.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								php/tbin/test_mail.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | ||||
| <?php | ||||
| require __DIR__.'/../vendor/autoload.php'; | ||||
| 
 | ||||
| use nulib\mail\mailer; | ||||
| 
 | ||||
| putenv("NULIB_MAIL_HOST=maildev.devel.self"); | ||||
| 
 | ||||
| $template = [ | ||||
|   "subject" => "test de mail", | ||||
|   "body" => <<<EOF | ||||
| bonjour, | ||||
| 
 | ||||
| ceci est un test de mail pour {dest} | ||||
| EOF | ||||
| ]; | ||||
| $data = [ | ||||
|   "dest" => "moi même", | ||||
| ]; | ||||
| mailer::tsend($template, $data, "jephte.clain@gmail.com"); | ||||
							
								
								
									
										34
									
								
								php/tests/mail/MailTemplateTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								php/tests/mail/MailTemplateTest.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | ||||
| <?php | ||||
| namespace nulib\mail; | ||||
| 
 | ||||
| use nur\t\TestCase; | ||||
| 
 | ||||
| class MailTemplateTest extends TestCase { | ||||
|   function testTemplate() { | ||||
|     $mail = [ | ||||
|       "subject" => "infos pour NOM PRENOM", | ||||
|       "body" => <<<EOT | ||||
| bonjour PRENOM NOM, | ||||
| 
 | ||||
| vous avez AGE ans | ||||
| EOT, | ||||
|       "exprs" => [ | ||||
|         "PRENOM" => "prenom", | ||||
|         "NOM" => "nom", | ||||
|         "AGE" => "age", | ||||
|       ], | ||||
|     ]; | ||||
| 
 | ||||
|     $tpl = new MailTemplate($mail); | ||||
|     [ | ||||
|       "subject" => $subject, | ||||
|       "body" => $body, | ||||
|     ] = $tpl->eval([ | ||||
|       "nom" => "Clain", | ||||
|       "prenom" => "Jephté", | ||||
|       "age" => 47, | ||||
|     ]); | ||||
|     self::assertSame("infos pour Clain Jephté", $subject); | ||||
|     self::assertSame("<p>bonjour Jephté Clain,</p>\n<p>vous avez 47 ans</p>\n", $body); | ||||
|   } | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user