Skip to content

Commit

Permalink
Fetch email attachments
Browse files Browse the repository at this point in the history
  • Loading branch information
freescout-help-desk committed Aug 4, 2018
1 parent d93925c commit ea7e6be
Show file tree
Hide file tree
Showing 11 changed files with 341 additions and 16 deletions.
148 changes: 148 additions & 0 deletions app/Attachment.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Storage;

class Attachment extends Model
{
const TYPE_TEXT = 0;
const TYPE_MULTIPART = 1;
const TYPE_MESSAGE = 2;
const TYPE_APPLICATION = 3;
const TYPE_AUDIO = 4;
const TYPE_IMAGE = 5;
const TYPE_VIDEO = 6;
const TYPE_MODEL = 7;
const TYPE_OTHER = 8;

// https://github.com/Webklex/laravel-imap/blob/master/src/IMAP/Attachment.php
public static $types = [
'message' => self::TYPE_MESSAGE,
'application' => self::TYPE_APPLICATION,
'audio' => self::TYPE_AUDIO,
'image' => self::TYPE_IMAGE,
'video' => self::TYPE_VIDEO,
'model' => self::TYPE_MODEL,
'text' => self::TYPE_TEXT,
'multipart' => self::TYPE_MULTIPART,
'other' => self::TYPE_OTHER,
];

const DIRECTORY = 'attachment';

public $timestamps = false;

/**
* Get thread.
*/
public function thread()
{
return $this->belongsTo('App\Thread');
}

/**
* Save attachment to file and database.
*/
public static function create($name, $mime_type, $type, $content, $thread_id = null)
{
if (!$content) {
return false;
}

$file_name = $name;

$attachment = new Attachment();
$attachment->thread_id = $thread_id;
$attachment->name = $name;
$attachment->file_name = $file_name;
$attachment->mime_type = $mime_type;
$attachment->type = $type;
//$attachment->size = Storage::size($file_path);
$attachment->save();

$file_path = self::DIRECTORY.DIRECTORY_SEPARATOR.self::getPath($attachment->id).$file_name;
Storage::put($file_path, $content);

$attachment->size = Storage::size($file_path);
if ($attachment->size) {
$attachment->save();
}
return true;
}

/**
* Get file path by ID.
*
* @param integer $id
* @return string
*/
public static function getPath($id)
{
$hash = md5($id);

$first = -1;
$second = 0;

for ($i = 0; $i < strlen($hash); $i++) {
if (is_numeric($hash[$i])) {
if ($first == -1) {
$first = $hash[$i];
} else {
$second = $hash[$i];
break;
}
}
}
if ($first == -1) {
$first = 0;
}
return $first.DIRECTORY_SEPARATOR.$second.DIRECTORY_SEPARATOR;
}

/**
* Conver type name to integer.
*/
public static function typeNameToInt($type_name)
{
if (!empty(self::$types[$type_name])) {
return self::$types[$type_name];
} else {
return self::TYPE_OTHER;
}
}

/**
* Get attachment public URL.
*
* @return string
*/
public function getUrl()
{
return Storage::url(self::DIRECTORY.DIRECTORY_SEPARATOR.self::getPath($this->id).$this->file_name);
}

/**
* Convert size into human readable format.
*
* @return string
*/
public function getSizeName()
{
return self::formatBytes($this->size);
}

public static function formatBytes($size, $precision = 0)
{
$size = (int) $size;
if ($size > 0) {
$base = log($size) / log(1024);
$suffixes = array(' b', ' KB', ' MB', ' GB', ' TB');

return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)];
} else {
return $size;
}
}
}
81 changes: 69 additions & 12 deletions app/Console/Commands/FetchEmails.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Console\Commands;

use App\Attachment;
use App\Conversation;
use App\Customer;
use App\Email;
Expand All @@ -28,6 +29,13 @@ class FetchEmails extends Command
*/
protected $description = 'Fetch emails from mailboxes addresses';

/**
* Current mailbox.
*
* @var Mailbox
*/
public $mailbox;

/**
* Create a new command instance.
*
Expand Down Expand Up @@ -56,17 +64,12 @@ public function handle()
foreach ($mailboxes as $mailbox) {
$this->info('['.date('Y-m-d H:i:s').'] Mailbox: '.$mailbox->name);

$this->mailbox = $mailbox;

try {
$this->fetch($mailbox);
} catch (\Exception $e) {
$this->error('['.date('Y-m-d H:i:s').'] Error: '.$e->getMessage().'; Line: '.$e->getLine());
activity()
->withProperties([
'error' => $e->getMessage(),
'mailbox' => $mailbox->name,
])
->useLog(\App\ActivityLog::NAME_EMAILS_FETCHING)
->log(\App\ActivityLog::DESCRIPTION_EMAILS_FETCHING_ERROR);
$this->logError('Error: '.$e->getMessage().'; Line: '.$e->getLine());
}
}
}
Expand Down Expand Up @@ -96,6 +99,10 @@ public function fetch($mailbox)
// Get unseen messages for a period
$messages = $folder->query()->unseen()->since(now()->subDays(1))->leaveUnread()->get();

if ($client->getLastError()) {
$this->logError($client->getLastError());
}

$this->line('['.date('Y-m-d H:i:s').'] Fetched: '.count($messages));

$message_index = 1;
Expand All @@ -119,7 +126,7 @@ public function fetch($mailbox)
$body = $this->separateReply($body, false);
}
if (!$body) {
$this->error('['.date('Y-m-d H:i:s').'] Message body is empty');
$this->logError('Message body is empty');
$message->setFlag(['Seen']);
continue;
}
Expand All @@ -130,7 +137,7 @@ public function fetch($mailbox)
$from = $message->getFrom();
}
if (!$from) {
$this->error('['.date('Y-m-d H:i:s').'] From is empty');
$this->logError('From is empty');
$message->setFlag(['Seen']);
continue;
} else {
Expand Down Expand Up @@ -158,11 +165,29 @@ public function fetch($mailbox)
$message->setFlag(['Seen']);
$this->line('['.date('Y-m-d H:i:s').'] Processed');
} else {
$this->error('['.date('Y-m-d H:i:s').'] Error occured processing message');
$this->logError('Error occured processing message');
}
}
}

public function logError($message)
{
$this->error('['.date('Y-m-d H:i:s').'] '.$message);

$mailbox_name = '';
if ($this->mailbox) {
$mailbox_name = $this->mailbox->name;
}

activity()
->withProperties([
'error' => $message,
'mailbox' => $mailbox_name,
])
->useLog(\App\ActivityLog::NAME_EMAILS_FETCHING)
->log(\App\ActivityLog::DESCRIPTION_EMAILS_FETCHING_ERROR);
}

/**
* Save email as thread.
*/
Expand Down Expand Up @@ -195,7 +220,6 @@ public function saveThread($mailbox_id, $message_id, $in_reply_to, $references,

$conversation = new Conversation();
$conversation->type = Conversation::TYPE_EMAIL;
$conversation->status = Conversation::STATUS_ACTIVE;
$conversation->state = Conversation::STATE_PUBLISHED;
$conversation->subject = $subject;
$conversation->setCc($cc);
Expand All @@ -210,6 +234,8 @@ public function saveThread($mailbox_id, $message_id, $in_reply_to, $references,
$conversation->source_via = Conversation::PERSON_CUSTOMER;
$conversation->source_type = Conversation::SOURCE_TYPE_EMAIL;
}
// Reply from customer makes conversation active
$conversation->status = Conversation::STATUS_ACTIVE;
$conversation->last_reply_at = $now;
$conversation->last_reply_from = Conversation::PERSON_USER;
// Set folder id
Expand All @@ -233,11 +259,42 @@ public function saveThread($mailbox_id, $message_id, $in_reply_to, $references,
$thread->created_by_customer_id = $customer->id;
$thread->save();

$has_attachments = $this->saveAttachments($attachments, $thread->id);
if ($has_attachments) {
$thread->has_attachments = true;
$thread->save();
}

event(new CustomerReplied($conversation, $thread, $new));

return true;
}

/**
* Save attachments from email.
*
* @param array $attachments
* @param integer $thread_id
* @return bool
*/
public function saveAttachments($email_attachments, $thread_id)
{
$has_attachments = false;
foreach ($email_attachments as $email_attachment) {
$create_result = Attachment::create(
$email_attachment->getName(),
$email_attachment->getMimeType(),
Attachment::typeNameToInt($email_attachment->getType()),
$email_attachment->getContent(),
$thread_id
);
if ($create_result) {
$has_attachments = true;
}
}
return $has_attachments;
}

/**
* Separate reply in the body.
*
Expand Down
12 changes: 12 additions & 0 deletions app/Conversation.php
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,18 @@ public function setPreview($text = '')
}

$text = strip_tags($text);
$text = trim(preg_replace('/\s+/', ' ', $text));

// Remove "undetectable" whitespaces
$whitespaces = ["%81", "%7F", "%C5%8D", "%8D", "%8F", "%C2%90", "%C2", "%90", "%9D", "%C2%A0", "%A0", "%C2%AD", "%AD", "%08", "%09", "%0A", "%0D"];
$text = urlencode($text);
foreach($whitespaces as $char){
$text = str_replace($char, ' ', $text);
}
$text = urldecode($text);

$text = trim(preg_replace('/[ ]+/', ' ', $text));

$this->preview = mb_substr($text, 0, self::PREVIEW_MAXLENGTH);

return $this->preview;
Expand Down
8 changes: 8 additions & 0 deletions app/Thread.php
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,14 @@ public function conversation()
return $this->belongsTo('App\Conversation');
}

/**
* Get thread attachmets.
*/
public function attachments()
{
return $this->hasMany('App\Attachment');
}

/**
* Get user who created the thread.
*/
Expand Down
6 changes: 4 additions & 2 deletions config/filesystems.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@
'disks' => [

'local' => [
'driver' => 'local',
'root' => storage_path('app'),
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
],

'public' => [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public function up()
$table->text('to')->nullable(); // JSON
$table->text('cc')->nullable(); // JSON
$table->text('bcc')->nullable(); // JSON
$table->boolean('has_attachments')->default(false);
// Email Message-ID header for email received from customer
$table->string('message_id', 998)->nullable();
// source.via - Originating source of the thread - user or customer
Expand Down
Loading

0 comments on commit ea7e6be

Please sign in to comment.