Skip to content
Snippets Groups Projects
Commit 2f011093 authored by Hahn Axel (hahn)'s avatar Hahn Axel (hahn)
Browse files

initial comment of first lines

parents
Branches
No related tags found
No related merge requests found
data/emaildata.txt
<?php
class emailcatcher
{
protected string $sMailData='emaildata.txt';
protected string $sId='';
protected array $aSelectedEmail=[];
public function __construct()
{
$this->sMailData=dirname(__DIR__) . '/data/' . $this->sMailData;
// $this->_readEmails();
}
// ------------------------------------------------------------------
// STORE NEW EMAIL
// ------------------------------------------------------------------
/**
* Fetch email of a single email from stdin and store it.
* It returns the return value of file_put_contents().
*
* @return bool|int
*/
public function catchEmail(){
$fp = fopen('php://stdin', 'rb');
$sMaildata='';
while (!feof($fp)) {
$sMaildata.=fgets($fp, 4096);
}
fclose($fp);
return $this->storeEmail($sMaildata);
}
/**
* Store a new email.
* It returns the return value of file_put_contents().
*
* @param string $sMaildata maildata with header and body
* @return bool|int
*/
public function storeEmail($sMaildata): bool|int
{
$sLogentry=json_encode(["date"=>date("Y-m-d H:i:s"), "mail"=>$sMaildata])."\n";
return file_put_contents($this->sMailData, $sLogentry, FILE_APPEND | LOCK_EX);
}
// ------------------------------------------------------------------
// READ STORED DATA
// ------------------------------------------------------------------
protected function _readEmails(string $sEmail2Show=''): array
{
if(!file_exists($this->sMailData)){
return [];
}
foreach(file($this->sMailData) as $line) {
$aLinedata=json_decode($line, true);
[$sHead, $sBody] = explode("\r\n\r\n", $aLinedata['mail']);
$sHead="\n$sHead";
preg_match('/\nfrom: (.*)/i', $sHead, $aFrom);
preg_match('/\nto: (.*)/i', $sHead, $aTo);
preg_match('/\ncc: (.*)/i', $sHead, $aCc);
preg_match('/\nbcc: (.*)/i', $sHead, $aBcc);
preg_match('/\nsubject: (.*)/i', $sHead, $aSubject);
preg_match('/\Content-Type: (.*)/i', $sHead, $aContentType);
$aEntry=[
"date" => $aLinedata['date'],
"from" => $aFrom[1] ?? false,
"to" => $aTo[1] ?? false,
"cc" => $aCc[1] ?? false,
"bcc" => $aBcc[1] ?? false,
"subject" => $aSubject[1] ?? false,
"contentType" => $aContentType[1] ?? false,
];
$sId=md5($aEntry['date'].' - to '.$aEntry['to'].': '.$aEntry['subject']);
$aEntry['id']=$sId;
if($sId==$sEmail2Show){
$aEntry['head']=$sHead;
$aEntry['body']=$sBody;
return $aEntry;
}
$aEmails[$aLinedata['date']]=$aEntry;
}
krsort($aEmails);
return $aEmails;
}
/**
* Get a list of emails to render an inbox like selection.
* It doesn't contain header and body - just metadata
*
* @return array
*/
public function readEmails(): array
{
return $this->_readEmails();
}
// ------------------------------------------------------------------
// SINGLE EMAIL
// ------------------------------------------------------------------
/**
* Set a single email by id.
* It returns a bool for success: false = failed
*
* @param string $sId
* @return bool
*/
public function setId(string $sId): bool{
$this->sId=$sId;
$this->aSelectedEmail=$this->_readEmails($sId);
if(! isset($this->aSelectedEmail['id'])){
$this->sId='';
$this->aSelectedEmail=[];
return false;
}
return true;
}
/**
* Get hash for a single email with all metadata and body
* @param string $sEmail2Show optional: email id to show
* @return array
*/
public function getEmail($sEmail2Show=''): array
{
if($sEmail2Show){
$this->setId($sEmail2Show);
}
return $this->aSelectedEmail;
}
/**
* Get a Meta value of the selected email
* @param string $sField
* @return mixed
*/
public function getField(string $sField): mixed
{
return $this->aSelectedEmail[$sField] ?? null;
}
/**
* Get message body of the selected email
* @return mixed
*/
public function getBody(): mixed
{
return $this->getField('body');
}
/**
* get message header of the selected email
* @return mixed
*/
public function getHeader(): mixed
{
return $this->getField('head');
}
}
\ No newline at end of file
## Description
This is a helper for a development environment for your PHP projects.
It allows you to see sent emails and preview text only and html messages.
This project has a script to fetch emails sent by `mail()`. It works as replacement for a postfix or sendmail deamon of your live system. It stores messages it into a single textfile.
With a viewer you can browse fetched messages. You can view heder and body. For html messages there is an additional preview.
## Installation
### Get the source
Extract or git pull the sources below your webroot, eg. in subfolder `./vendor/emailcatcher/`
```text
cd [WEBROOT]
cd vendor
git clone [URL] emailcatcher
```
### Enable fetching emails
In the php.ini in section `[PHP]` add a value for sendmail_path:
```ini
[PHP]
...
sendmail_path = "[WEBROOT]/vendor/emailcatcher/php-sendmail.php"
```
### Test sending an email
If you have no php script that sends an email you can create a test file like this:
```php
<?php
$sTO="somebody@example.com";
$sSUBJECT="My subject - ".date("Y-m-d H:i:s");
$sBODY="Hello you,
I am just testing to fetch the email...
";
$ret = mail($sTO, $sSUBJECT, $sBODY);
var_dump($ret); // (bool)true
```
... and open it in the browser to "send" the email.
In `[WEBROOT]/vendor//emailcatcher/data/` it creates a textfile if the fetching was successful.
This will create a data file below `./vendor/emailcatcher/data/`.
### Exlude mail data file
If you extracted the sources (if no git clone was used):
Exclude the file `vendor/emailcatcher/data/emaildata.txt` from commit into your project (eg. for git add it in the .gitignore file).
## Usage
### Web ui
To browse emails and view each message open <http://localhost/vendor/emailcatcher/viewer.php>
You see a list of emails.
Click an email to view details of it including header and body.
If an html email was detected then you get an additional button to preview as html.
### Delete emails
There is no delete button in the web ui. You can delete single emails removing its line in the file `./data/emaildata.txt`.
To delete all emails you can delete `./data/emaildata.txt`.
<html>
<div class="hero">
<h2>PHP emailcatcher</h2>
Fetch emails sent by mail() and browse them
</div>
</html>
👤 Author: Axel Hahn; Institute for Medical Education; University of Bern \
📄 Source: <https://git-repo.iml.unibe.ch/iml-open-source/__NAME__> \
📜 License: GNU GPL 3.0 \
📗 Docs: <https://os-docs.iml.unibe.ch/__NAME__/>
{
"title": "PHP emailcatcher",
"author": "Axel Hahn",
"tagline": "Fetch emails sent by mail() and browse them",
"ignore": {
"files": ["30_PHP-client/Plugins/Checks/_skeleton.md"],
"folders": ["99_Not_Ready"]
},
"html": {
"auto_toc": true,
"auto_landing": false,
"date_modified": false,
"jump_buttons": true,
"edit_on_github_": "iml-it/__PROJECT__/tree/master/docs",
"edit_on_": {
"name": "Gitlab",
"basepath": "https://git-repo.iml.unibe.ch/iml-open-source/__PROJECT__/tree/master/docs"
},
"links": {
"Git Repo": "__GITURL__",
"IML Opensource": "https://os-docs.iml.unibe.ch/"
},
"theme": "daux-blue",
"search": true
}
}
/*
override css elements of daux.io blue theme
version 2023-11-10
*/
:root {
/* Axels Overrides */
--color-text: #234;
--link-color: #228;
--brand-color: var(--color-text);
--brand-background: var(--body-background);
--code-tag-border-color: #d8d8d8;
--hr-color: none;
--search-field-background: none;
--search-field-border-color: none;
--sidebar-background: var(--body-background);
--sidebar-border-color: none;
--sidebar-link-active-background: #e8f4f6;
--sidebar-link-active-background: #eee;
/* Axels custom values */
--axel_bg-toc: var(--body-background);
--axel_bg-toc-head: #f8f8f8;
--axel_brand-background: none;
--axel_brand-pre-background: rgb(255, 0, 51);
;
--axel_brand-pre-background-hover: rgb(255, 0, 51);
;
--axel_h1_header: none;
--axel_h1: #111;
--axel_h1-bg: none;
--axel_h1-bottom: 3px solid none;
--axel_h2: #222;
--axel_h2-bg: none;
--axel_h2-bottom: 0px solid #467;
--axel_h2-hero-bottom: 2px solid #912;
--axel_h3: #333;
--axel_h3-bottom: 0px solid #ddd;
--axel_h4: #444;
--axel_hero_bg: #f8f8f8;
--axel_img-border: 2px dashed #ccc;
--axel_nav-bg: #fcfcfc;
--axel_nav-buttomborder: #ddd;
--axel_pre-background: #f8f8f8;
--axel-th-background: #e0e4e8;
--axel-article-nav-border-top: 0px dotted #ddd;
}
.dark {
/* Axels Overrides */
--color-text: #c0c0c0;
--link-color: #88e;
--brand-color: var(--color-text);
--brand-background: var(--body-background);
--body-background: #101418;
--hr-color: none;
--code-tag-background-color_: #bcc;
--search-field-background: none;
--search-field-border-color: none;
--sidebar-background: var(--body-background);
--sidebar-border-color: none;
--sidebar-link-active-background: #333;
/* Axels custom values */
--axel_bg-toc: var(--body-background);
--axel_bg-toc-head: #333;
--axel_brand-background: none;
--axel_brand-pre-background: rgb(255, 0, 51);
;
--axel_brand-pre-background-hover: rgb(255, 0, 51);
;
--axel_h1_header: none;
--axel_h1: #578;
--axel_h1-bg: none;
--axel_h1-bottom: none;
--axel_h2: #467;
--axel_h2-bg: none;
--axel_h2-bottom: 0px solid #256;
--axel_h2-hero-bottom: 2px solid #712;
--axel_h3: #589;
--axel_h3-bottom: 0px solid #333;
--axel_hero_bg: #242424;
--axel_img-border: 2px dashed #555;
--axel_nav-bg: #242424;
--axel_nav-buttomborder: #555;
--axel_pre-background: #bcc;
--axel-th-background: #203038;
--axel-article-nav-border-top: 0px dotted #234;
}
/* ---------- left side ---------- */
a.Brand::before {
background: var(--axel_brand-pre-background);
color: #fff;
font-family: arial;
font-weight: bold;
padding: 0.5em 0.3em;
content: 'IML';
margin-right: 0.4em;
float: left;
}
a.Brand:hover::before {
background: var(--axel_brand-pre-background-hover);
}
a.Brand {
background: var(--axel_brand-background);
font-size: 200%;
height: 4em;
}
/* ---------- page header: breadcrumb ---------- */
.Page__header {
border: none;
}
.Page__header a {
color: var(--axel_h1_header);
}
.Page__header h1 {
font-size: 1.3em;
}
/* ---------- page content ---------- */
.s-content {
padding-top: 1em;
}
.s-content h1 {
background: var(--axel_h1-bg);
color: var(--axel_h1);
font-size: 200%;
font-weight: bold;
margin-bottom: 2em;
margin-top: 2em;
border-bottom: var(--axel_h1-bottom);
}
.s-content h2 {
background: var(--axel_h2-bg);
color: var(--axel_h2);
font-size: 190%;
font-weight: bold;
margin-top: 4em;
border-bottom: var(--axel_h2-bottom);
}
h1:first-of-type {
margin-top: 0em;
}
h2:first-of-type {
margin-top: 0em;
}
img{
border: var(--axel_img-border);
border-radius: 1.5em;
padding: 0.7em;
}
.s-content h3 {
background: var(--axel_h3-bg);
color: var(--axel_h3);
font-size: 150%;
font-weight: bold;
margin-top: 3em;
border-bottom: var(--axel_h3-bottom);
}
.s-content > h4 {
color: var(--axel_h4);
font-size: 135%;
font-weight: bold;
margin: 2em 0;
}
.s-content .TableOfContentsContainer h4 {
margin: 1em 0;
font-size: 110%;
text-align: center;
background-color: rgba(0, 0, 0, 0.1);
padding: 0.3em;
font-weight: bold;
font-family: Arial;
}
ul.TableOfContents a{
color: var(--color-text);
}
.s-content pre {
background: var(--axel_pre-background);
}
/* FIX smaller fnt size in tables */
.s-content table {
font-size: 1em;
}
.s-content table th {
background: var(--axel-th-background);
}
.s-content h3 code {
border: none;
background: none;
}
article nav {
border-top: var(--axel-article-nav-border-top);
margin: 8em 0 5em;
}
.Pager li>a {
padding: 1em 2em;
}
/* ---------- classes ---------- */
.required {
color: #a42;
}
.optional {
color: #888;
}
div.hero {
background: var(--axel_hero_bg);
border-radius: 2em;
padding: 5em 2em;
text-align: center;
margin-bottom: 1.5em;
}
div.hero h2 {
color: var(--color-text);
background: none;
border-bottom: var(--axel_h2-hero-bottom);
font-size: 300%;
margin: 0 0 2em;
}
/* ---------- TOC ---------- */
@media(min-width:1700px) {
.TableOfContentsContainer {
position: fixed;
right: 2em;
top: 1em;
}
}
.TableOfContentsContainer {
background-color: var(--axel_bg-toc);
padding: 0.5em;
}
.s-content .TableOfContentsContainer h4 {
background-color: var(--axel_bg-toc-head);
border-top-left-radius: 1em;
font-size: 1.1em;
margin: 0;
padding: 0;
}
.TableOfContentsContainer__content {
border-width: 1px;
font-size: 0.5em;
}
ul.TableOfContents ul {
list-style-type: none;
padding-left: 1em;
}
/* ----- Icons on links --- */
.EditOn a::before{
content: '✏️ ';
}
.Links a[href^="https://github.com/"]::before {
content: '🌐 ';
}
.Links a[href^="https://git-repo.iml.unibe.ch/"]::before {
content: '🌐 ';
}
.Links a[href^="https://os-docs.iml.unibe.ch"]::before {
content: '📗 ';
}
#!/usr/local/bin/php -q
<?php
require_once('classes/emailcatcher.class.php');
// you will likely need to handle additional arguments such as "-f"
// $args = $_SERVER['argv'];
$oMail=new emailcatcher();
$oMail->catchEmail();
// return 0 to indicate acceptance of the message (not necessarily delivery)
return 0;
# EMAIL CATCHER
A helper for a development environment: This PHP class fetches emails sent by mail() command and offers a viewer to browse sent messages.
Free software and Open Source from University of Bern :: IML - Institute of Medical Education
📄 Source: TODO \
📜 License: GNU GPL 3.0 \
📗 Docs: TODO
body{
background-color: #f8f8f8;
color: #333;
font-family: Arial, Helvetica, sans-serif;
font-size: 1.0m;
margin: 0;
}
a{
color: #44c;
}
a.button{
background-color: #cce;
background: linear-gradient(#ccd, #dde);
border: 3px solid rgba(0,0,0,0.05);
box-shadow: 0 0 1em #ccc;
color: #444;
text-decoration: none;
padding: 0.2em 0.4em;
border-radius: 0.5em;
transition: all 0.2s linear;
}
a.button.close{
background-color: #ebb;
background: linear-gradient(#ebb, #fca);
color: #633;
}
a.button:hover{
box-shadow: none;
border: 3px solid #fff;
}
footer{
background-color: #fff;
position: fixed; bottom: 0;
text-align: center;
width: 100%;
padding: 1em;
opacity: 0.6;
}
pre{
background-color: #e8ecec;
overflow: scroll;
padding: 1em;
border-radius: 1em;
}
.email{
border-bottom: 1px solid #ddd;
border-radius: 1em;
padding: 0.3em 1em;
}
.email.open{
background-color: #fff;
}
.email a{
color:#446;
text-decoration: none;
}
.email:hover{
background-color: #ddd;
}
.right{
float: right;
}
h1{
background-color: #ebb;
background: linear-gradient(to right, #9be, #fcc);
margin: 0;
padding-left: 0.5em;
border-bottom: 4px solid rgba(0,0,0,0.05);
}
h1 a{
color:#424;
text-decoration: none;
}
#messages{
background-color: #f0f0f0;
border: 2px solid rgba(0,0,0,0.1);
border-radius: 1em;
margin: 1em;
padding: 0.5em;
}
#singlemessage{
background-color: #f8f8f8;
box-shadow: 0 0 3em #555;
display: block;
padding: 0;
height: 100%;
overflow: scroll;
position: absolute;
right: 0;
top: 0em;
width: 50%;
border-bottom-left-radius: 2em;
}
#singlemessage table{
background-color: #cdc;
background: linear-gradient(to right, #cec, #cee);
width: 100%;
padding: 0.5em;
border-bottom: 4px solid rgba(0,0,0,0.05);
}
#singlemessage .content{
padding: 0.5em;
}
\ No newline at end of file
<?php
require_once('classes/emailcatcher.class.php');
// $sJsonFile='/tmp/mailin.txt.json';
$sOpen=$_GET['open'] ?? '';
$sShowHtml=$_GET['html'] ?? '';
// ----------------------------------------------------------------------
// FUNCTIONS
// ----------------------------------------------------------------------
function showEmail($sId)
{
$sReturn='';
$oMail=new emailcatcher();
if(!$oMail->setId($sId)){
$sReturn.="❌ ERROR: wrong email id: $sId<br>";
} else {
$bIsHtml=strstr( $oMail->getBody(), '<html>');
$sReturn.= '<div id="singlemessage">
<table>
<tr><td>🕜 DATE</td><td>'.$oMail->getField('date')
.'<span class="right"><a href="?" class="button close">❌ Close</a></span>
</td></tr>
<tr><td>👤 TO</td><td>'.$oMail->getField('to').'</td></tr>
<tr><td colspan=2><strong>'.$oMail->getField('subject').'</strong></td></tr>
</table>
<br>
<div class="content">
📜 Header:<br>
<pre>'.$oMail->getHeader().'</pre>
<br>🗨️ '
.($bIsHtml
? '<a href="?open='.$sId.'&html=1" class="button">👁️ Show message as HTML</a><br><br>'
: 'Text only:'
)
.'<pre>'.htmlentities($oMail->getBody()).'</pre>'
.'<br>
<span class="right"><a href="?" class="button close">❌ Close</a></span><br>
<br>'
.'</div>'
.'</div>'
;
}
return $sReturn;
}
function showHtmlEmail($sId): void
{
$oMail=new emailcatcher();
echo '<button onclick="history.back();return false;">back</button><br>';
if(!$oMail->setId($sId)){
echo "❌ ERROR: wrong email id: $sId<br>";
} else {
echo $oMail->getBody();
die();
}
}
// ----------------------------------------------------------------------
// MAIN
// ----------------------------------------------------------------------
$oMail=new emailcatcher();
$aEmails=$oMail->readEmails();
$sOut='';
$sMessage='';
if(!count($aEmails)){
$sOut='❌ No email was found.<br>';
} else {
// get a single email if id was given.
if ($sOpen){
if($sShowHtml=="1"){
showHtmlEmail($sOpen);
}
$sMessage=showEmail($sOpen);
}
$sOut='Messages: <strong>'.count($aEmails).'</strong><br>';
foreach($aEmails as $aEmail){
$sId=$aEmail['id'];
$sOut.='
<div id="'.$sId.'" class="email'.($sId==$sOpen ? ' open':'').'">
'.(
$sId!=$sOpen
? '✉️ <a href="?open='.$sId.'">'.$aEmail['date'].' - to '.$aEmail['to'].': '.$aEmail['subject'].'</a>'
: '🔶 '. $aEmail['date'].' - to '.$aEmail['to'].': '.$aEmail['subject']
)
.'</div>';
}
}
// write html page
echo "<!doctype html>
<html>
<head>
<title>Email catcher :: viewer</title>
<link rel=\"stylesheet\" href=\"viewer.css\">
</head>
<body>
<h1><a href=\"?\">🕶️ Email catcher :: viewer</a></h1>
<div id=\"messages\">$sOut</div>
<footer>
Email catcher
📄 <a href=\"\">source</a>
📗 <a href=\"\">docs</a>
</footer>
$sMessage
</body>
</html>
";
// ----------------------------------------------------------------------
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment