- Work done on the Bug Email component

This commit is contained in:
Dave Mc Nicoll 2019-08-21 15:54:17 -04:00
parent ec873145d2
commit a7dcdf4ee7
8 changed files with 286 additions and 139 deletions

View File

@ -0,0 +1,26 @@
<?php
namespace TheBugs\Email;
class EmailConfiguration
{
const AUTH_TYPE_SMTP = 1;
protected $type;
public $smtpHost = "";
public $smtpUsername = "";
public $smtpPassword = "";
public $smtpPort = 25;
public $smtpUseTLS = false;
public function __construct($type = self::AUTH_TYPE_SMTP)
{
$this->type = $type;
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace TheBugs\Email;
interface MailerInterface
{
public function send(string $subject, string $message, bool $html = true) : bool;
public function setFrom($from) : self;
public function setTo($to) : self;
}

45
src/Email/SwiftMailer.php Normal file
View File

@ -0,0 +1,45 @@
<?php
namespace TheBugs\Email;
class SwiftMailer implements MailerInterface
{
protected $emailConfiguration;
protected $transport;
protected $from;
protected $to;
public function __construct(EmailConfiguration $configuration) {
$this->emailConfiguration = $configuration;
$this->transport = ( new \Swift_SmtpTransport($this->emailConfiguration->smtpHost, $this->emailConfiguration->smtpPort) )
->setUsername($this->emailConfiguration->smtpUsername)
->setPassword($this->emailConfiguration->smtpPassword);
}
public function send(string $subject, string $message, bool $html = true) : bool
{
$swiftObj = ( new \Swift_Message($subject) )
->setFrom( $this->from )
->setTo( $this->to )
->setBody( $message, $html ? 'text/html' : 'text/plain' );
return ( new \Swift_Mailer($this->transport) )->send($swiftObj);
}
public function setFrom($from) : MailerInterface
{
$this->from = $from;
return $this;
}
public function setTo($to) : MailerInterface
{
$this->to = $to;
return $this;
}
}

View File

@ -0,0 +1,53 @@
<?php
namespace TheBugs;
use Psr\Http\Message\ResponseFactoryInterface,
Psr\Container\ContainerInterface,
Psr\Http\Message\ResponseInterface,
Psr\Http\Message\ServerRequestInterface,
Psr\Http\Server\MiddlewareInterface,
Psr\Http\Server\RequestHandlerInterface;
use Zend\Diactoros\Response,
Zend\Diactoros\ServerRequest,
Zend\Diactoros\Stream,
Zend\Diactoros\Uri;
class EmailErrorMiddleware implements MiddlewareInterface
{
protected /* EmailConfiguration */ $emailConfiguration;
protected /* Callable */ $callable;
protected /* Mailer */ $mailer;
public function __construct(Email\EmailConfiguration $emailConfiguration, Email\MailerInterface $mailer, Callable $callable)
{
$this->emailConfiguration = $emailConfiguration;
$this->callable = $callable;
$this->mailer = $mailer;
}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
{
$response = $handler->handle($request);
if ( $response->getStatusCode() === 500 ) {
$this->mailer->setTo('dave.mcnicoll@cslsj.qc.ca');
$this->mailer->setFrom(['test@johndoe.com' => 'John Doe']);
$bugReport = $response->getBody()->__toString();
if ( false === ( $this->mailer->send(error_get_last()['message'], $bugReport, true) ) ) {
error_log("Impossile to send an email bug report from " . static::class);
}
return $this->callable->call($this);
}
return $response;
}
}

View File

@ -1,70 +1,70 @@
<?php <?php
namespace TheBugs; namespace TheBugs;
use Psr\Http\Message\ResponseFactoryInterface, use Psr\Http\Message\ResponseFactoryInterface,
Psr\Container\ContainerInterface, Psr\Container\ContainerInterface,
Psr\Http\Message\ResponseInterface, Psr\Http\Message\ResponseInterface,
Psr\Http\Message\ServerRequestInterface, Psr\Http\Message\ServerRequestInterface,
Psr\Http\Server\MiddlewareInterface, Psr\Http\Server\MiddlewareInterface,
Psr\Http\Server\RequestHandlerInterface; Psr\Http\Server\RequestHandlerInterface;
use Zend\Diactoros\Response, use Zend\Diactoros\Response,
Zend\Diactoros\ServerRequest, Zend\Diactoros\ServerRequest,
Zend\Diactoros\Stream, Zend\Diactoros\Stream,
Zend\Diactoros\Uri; Zend\Diactoros\Uri;
class JavascriptMiddleware implements MiddlewareInterface class JavascriptMiddleware implements MiddlewareInterface
{ {
const FILTERS = [ const FILTERS = [
"code" => null, "code" => null,
"headers" => [ "headers" => [
"User-Agent" => "TheBugs/1.0", "User-Agent" => "TheBugs/1.0",
], ],
# Callable functions # Callable functions
"functions" => [] "functions" => []
]; ];
/** /**
* Filter list, allows custom invokable too * Filter list, allows custom invokable too
* @var array * @var array
*/ */
protected $filters = []; protected $filters = [];
public function __construct($options = []) public function __construct($options = [])
{ {
foreach(static::FILTERS as $key => $default) { foreach(static::FILTERS as $key => $default) {
$this->filters[$key] = ( $options[$key] ?? false ) ? $options[$key] : $default; $this->filters[$key] = ( $options[$key] ?? false ) ? $options[$key] : $default;
} }
} }
public function throwError($errorDetails) public function throwError($errorDetails)
{ {
throw new Exception\JavascriptException($errorDetails['message'], 0, null, $errorDetails['location'], $errorDetails['line'], $errorDetails['url'], (array) $errorDetails['stack']); throw new Exception\JavascriptException($errorDetails['message'], 0, null, $errorDetails['location'], $errorDetails['line'], $errorDetails['url'], (array) $errorDetails['stack']);
} }
/** /**
* Process a server request and return a response. * Process a server request and return a response.
*/ */
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{ {
if ( ( $this->filters['code'] ?? false ) && ($this->filters['code'] !== $request->getCode())) { # if ( ( $this->filters['code'] ?? false ) && ($this->filters['code'] !== $request->getCode())) {
return $handler->handle($request); # return $handler->handle($request);
} # }
foreach($this->filters['headers'] as $key => $value) { foreach($this->filters['headers'] as $key => $value) {
if ( ! in_array($value, $request->getHeader($key)) ) { if ( ! in_array($value, $request->getHeader($key)) ) {
return $handler->handle($request); return $handler->handle($request);
} }
} }
foreach($this->filters['functions'] as $func) { foreach($this->filters['functions'] as $func) {
if ( $func->bindTo($this, $this)() ) { if ( $func->call($this) ) {
return $handler->handle($request); return $handler->handle($request);
} }
} }
$this->throwError(json_decode($request->getBody(), true)); $this->throwError(json_decode($request->getBody(), true));
} }
} }

View File

@ -1,15 +0,0 @@
<?php
use Whoops\Run as Whoops;
use Whoops\Handler\PrettyPageHandler as Handler;
$whoops = new Whoops();
$whoops->allowQuit(false);
$whoops->writeToOutput(false);
$whoops->pushHandler(new Handler());
IF CLI {
$handler->handleUnconditionally(true);
}
$body = $whoops->handleException($this->exception);

46
templates/js_debug.js Normal file
View File

@ -0,0 +1,46 @@
class ErrorHandler
{
constructor(options) {
if ( options ) {
if ( "url" in options ) {
this.url = options['url'];
}
}
this.catchError();
}
catchError() {
window.onerror = function(message, url, line, column, error) {
fetch(this.url ? this.url : window.location.href, {
method: "post",
headers: {
'Accept': "application/json",
'Content-Type': "application/json",
'User-Agent': "TheBugs/1.0"
},
body: JSON.stringify({
message: message,
url: url,
line: line,
column: column,
stack: error.stack,
location: window.location.toString()
})
}).then( response => response ).then(data => {
console.info("Error reported", data);
});
return false;
}.bind(this);
}
get url() {
return this._url;
}
set url(set) {
return this._url = set;
}
}

View File

@ -1,54 +1,34 @@
<script> <script>
(function(send) { (send => {
XMLHttpRequest.prototype.send = function(data) { XMLHttpRequest.prototype.send = function() {
console.log("Hey ! Something was sent !"); this.addEventListener('load', function(e) {
send.call(this, data); switch(this.status) {
}; case 500:
})(XMLHttpRequest.prototype.send); let response = JSON.parse(this.response),
content = JSON.stringify(response.error.trace, null, 2),
class ErrorHandler element = document.createElement('div'),
{ body = document.querySelector("body");
constructor(options) {
if ( options ) { body.appendChild(element);
if ( "url" in options ) { element.outerHTML = `
this.url = options['url']; <div ondblclick="this.parentNode.removeChild(this);">
} <dialog style="top:10vh;width:75%;z-index:1500;background:#444;color:#444;border:0px solid #2f2a2a;padding:2px;position:fixed; max-height:80vh;">
} <div style="background:#fff">
<div style="padding:15px; color:#fff; border-bottom:2px solid #444;background:#c61f1f;font-weight:bold">${response.error.message}</div>
this.catchError(); <pre style="overflow-y:auto;max-height:65vh;padding:15px;">${content}</pre>
} <div style="padding:8px 15px; color:#fff; border-top:2px solid #444;text-align:right;background:#c61f1f;font-weight:bold">${response.error.file}:${response.error.line}</div>
</div>
catchError() { </dialog>
window.onerror = function(message, url, line, column, error) { <div style="position:fixed;top:0;left:0;width:100vw;height:100vh;background:#000;opacity:0.5"></div>
fetch(this.url ? this.url : window.location.href, { </div>
method: "post", `.trim();
headers: {
'Accept': "application/json", body.querySelector("dialog").showModal();
'Content-Type': "application/json", break;
'User-Agent': "TheBugs/1.0" }
}, });
body: JSON.stringify({
'message': message, send.apply(this, arguments);
'url': url, };
'line': line, })(XMLHttpRequest.prototype.send);
'column': column, </script>
'stack': error.stack,
'location': window.location.toString()
})
}).then( response => response ).then(data => {
console.info("Error reported", data);
});
return false;
}.bind(this);
}
get url() {
return this._url;
}
set url(set) {
return this._url = set;
}
}
</script>