<?php /** * ======================================================================= * * PHP EMAIL CATCHER * Read emails sent by mail() and browse them * * 👤 Author: Axel Hahn, Institute for Medical Education, University of Bern * 📄 Source: <https://git-repo.iml.unibe.ch/iml-open-source/php-emailcatcher> * 📗 Docs: <https://os-docs.iml.unibe.ch/php-emailcatcher/> * 📜 License: GNU GPL 3.0 * * ---------------------------------------------------------------------- * 2024-10-08 v0.1 initial version * 2024-10-16 v0.2 detect parse error when reading email data * ======================================================================= */ class emailcatcher { /** * Filename for email data * @var string */ protected string $sMailData='emaildata.txt'; /** * Id of selected email * @var string */ protected string $sId=''; /** * Email data of the selected email * @var array */ protected array $aSelectedEmail=[]; // ------------------------------------------------------------------ public function __construct() { $this->sMailData=dirname(__DIR__) . '/data/' . $this->sMailData; } // ------------------------------------------------------------------ // STORE NEW EMAIL // ------------------------------------------------------------------ /** * Fetch email of a single email from stdin and store it. * It returns the return value of file_put_contents(). * used in php-sendmail.php * * @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 // ------------------------------------------------------------------ /** * Read all stored emails and return them as an array * @param string $sEmail2Show optional: id of email to show * @return array */ protected function _readEmails(string $sEmail2Show=''): array { if(!file_exists($this->sMailData)){ return []; } foreach(file($this->sMailData) as $line) { if (empty(trim($line))) { continue; } $aLinedata=json_decode($line, true); if(!is_array($aLinedata)){ // echo "ERROR: unable to parse line as single json object: <code>$line</code>"; continue; } [$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'); } }