457 lines
14 KiB
PHP
457 lines
14 KiB
PHP
|
<?php
|
||
|
|
||
|
namespace App;
|
||
|
|
||
|
use Illuminate\Database\Eloquent\Model;
|
||
|
|
||
|
class Folder extends Model
|
||
|
{
|
||
|
/**
|
||
|
* Folders types (ids from HelpScout interface).
|
||
|
*/
|
||
|
const TYPE_UNASSIGNED = 1;
|
||
|
// User specific
|
||
|
const TYPE_MINE = 20;
|
||
|
// User specific
|
||
|
const TYPE_STARRED = 25;
|
||
|
const TYPE_DRAFTS = 30;
|
||
|
const TYPE_ASSIGNED = 40;
|
||
|
const TYPE_CLOSED = 60;
|
||
|
const TYPE_DELETED = 70;
|
||
|
const TYPE_SPAM = 80;
|
||
|
|
||
|
public static $types = [
|
||
|
self::TYPE_UNASSIGNED => 'Unassigned',
|
||
|
self::TYPE_MINE => 'Mine',
|
||
|
self::TYPE_STARRED => 'Starred',
|
||
|
self::TYPE_DRAFTS => 'Drafts',
|
||
|
self::TYPE_ASSIGNED => 'Assigned',
|
||
|
self::TYPE_CLOSED => 'Closed',
|
||
|
self::TYPE_SPAM => 'Spam',
|
||
|
self::TYPE_DELETED => 'Deleted',
|
||
|
];
|
||
|
|
||
|
/**
|
||
|
* https://glyphicons.bootstrapcheatsheets.com/.
|
||
|
*/
|
||
|
public static $type_icons = [
|
||
|
self::TYPE_UNASSIGNED => 'folder-open',
|
||
|
self::TYPE_MINE => 'hand-right',
|
||
|
self::TYPE_DRAFTS => 'duplicate',
|
||
|
self::TYPE_ASSIGNED => 'user',
|
||
|
self::TYPE_CLOSED => 'lock', // lock
|
||
|
self::TYPE_SPAM => 'ban-circle',
|
||
|
self::TYPE_DELETED => 'trash',
|
||
|
self::TYPE_STARRED => 'star',
|
||
|
];
|
||
|
|
||
|
// Public non-user specific mailbox types
|
||
|
public static $public_types = [
|
||
|
self::TYPE_UNASSIGNED,
|
||
|
self::TYPE_DRAFTS,
|
||
|
self::TYPE_ASSIGNED,
|
||
|
self::TYPE_CLOSED,
|
||
|
self::TYPE_SPAM,
|
||
|
self::TYPE_DELETED,
|
||
|
];
|
||
|
|
||
|
// Folder types which belong to specific user.
|
||
|
// These folders has user_id specified.
|
||
|
public static $personal_types = [
|
||
|
self::TYPE_MINE,
|
||
|
self::TYPE_STARRED,
|
||
|
];
|
||
|
|
||
|
// Folder types to which conversations are added via conversation_folder table.
|
||
|
public static $indirect_types = [
|
||
|
self::TYPE_DRAFTS,
|
||
|
self::TYPE_STARRED,
|
||
|
];
|
||
|
|
||
|
// Counter mode.
|
||
|
const COUNTER_ACTIVE = 1;
|
||
|
const COUNTER_TOTAL = 2;
|
||
|
|
||
|
public $timestamps = false;
|
||
|
|
||
|
protected $casts = [
|
||
|
'meta' => 'array',
|
||
|
];
|
||
|
|
||
|
/**
|
||
|
* Get the mailbox to which folder belongs.
|
||
|
*/
|
||
|
public function mailbox()
|
||
|
{
|
||
|
return $this->belongsTo('App\Mailbox');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the user to which folder belongs.
|
||
|
*/
|
||
|
public function user()
|
||
|
{
|
||
|
return $this->belongsTo('App\User');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get starred conversations.
|
||
|
*/
|
||
|
public function conversations()
|
||
|
{
|
||
|
return $this->hasMany('App\Conversation');
|
||
|
}
|
||
|
|
||
|
public function getTypeName()
|
||
|
{
|
||
|
// To make name translatable.
|
||
|
switch ($this->type) {
|
||
|
case self::TYPE_UNASSIGNED:
|
||
|
return __('Unassigned');
|
||
|
case self::TYPE_MINE:
|
||
|
return __('Mine');
|
||
|
case self::TYPE_DRAFTS:
|
||
|
return __('Drafts');
|
||
|
case self::TYPE_ASSIGNED:
|
||
|
return __('Assigned');
|
||
|
case self::TYPE_CLOSED:
|
||
|
return __('Closed');
|
||
|
case self::TYPE_SPAM:
|
||
|
return __('Spam');
|
||
|
case self::TYPE_DELETED:
|
||
|
return __('Deleted');
|
||
|
case self::TYPE_STARRED:
|
||
|
return __('Starred');
|
||
|
default:
|
||
|
return __(\Eventy::filter('folder.type_name', self::$types[$this->type] ?? '', $this));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public function getTypeIcon()
|
||
|
{
|
||
|
return \Eventy::filter('folder.type_icon', self::$type_icons[$this->type] ?? '', $this);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get order by array.
|
||
|
*
|
||
|
* @return array
|
||
|
*/
|
||
|
public function getOrderByArray()
|
||
|
{
|
||
|
$order_by = [];
|
||
|
|
||
|
switch ($this->type) {
|
||
|
case self::TYPE_UNASSIGNED:
|
||
|
case self::TYPE_MINE:
|
||
|
case self::TYPE_ASSIGNED:
|
||
|
$order_by[] = ['status' => 'asc'];
|
||
|
$order_by[] = ['last_reply_at' => 'desc'];
|
||
|
break;
|
||
|
|
||
|
case self::TYPE_STARRED:
|
||
|
$order_by[] = ['status' => 'asc'];
|
||
|
$order_by[] = ['last_reply_at' => 'desc'];
|
||
|
break;
|
||
|
|
||
|
case self::TYPE_DRAFTS:
|
||
|
$order_by = [['updated_at' => 'desc']];
|
||
|
break;
|
||
|
|
||
|
case self::TYPE_CLOSED:
|
||
|
$order_by = [['closed_at' => 'desc']];
|
||
|
break;
|
||
|
|
||
|
case self::TYPE_SPAM:
|
||
|
$order_by = [['last_reply_at' => 'desc']];
|
||
|
break;
|
||
|
|
||
|
case self::TYPE_DELETED:
|
||
|
$order_by = [['user_updated_at' => 'desc']];
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
$order_by = \Eventy::filter('folder.conversations_order_by', $order_by, $this->type);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Process columns sorting.
|
||
|
$sorting = Conversation::getConvTableSorting();
|
||
|
if ($sorting['sort_by'] == 'date') {
|
||
|
if ($sorting['order'] != 'desc') {
|
||
|
foreach ($order_by as $block_i => $block) {
|
||
|
foreach ($block as $field => $order) {
|
||
|
if ($field == 'status') {
|
||
|
unset($order_by[$block_i][$field]);
|
||
|
} else {
|
||
|
if ($order == 'desc') {
|
||
|
$order_by[$block_i][$field] = 'asc';
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
$order_by = [[$sorting['sort_by'] => $sorting['order']]];
|
||
|
}
|
||
|
|
||
|
return $order_by;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Add order by to the query.
|
||
|
*/
|
||
|
public function queryAddOrderBy($query)
|
||
|
{
|
||
|
$order_bys = $this->getOrderByArray();
|
||
|
foreach ($order_bys as $order_by) {
|
||
|
foreach ($order_by as $field => $sort_order) {
|
||
|
$query->orderBy($field, $sort_order);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $query;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Is this folder accumulates conversations via conversation_folder table.
|
||
|
*/
|
||
|
public function isIndirect()
|
||
|
{
|
||
|
return in_array($this->type, self::$indirect_types);
|
||
|
}
|
||
|
|
||
|
public function updateCounters()
|
||
|
{
|
||
|
if (config('app.update_folder_counters_in_background')) {
|
||
|
\App\Jobs\UpdateFolderCounters::dispatch($this);
|
||
|
} else {
|
||
|
$this->updateCountersNow();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public function updateCountersNow()
|
||
|
{
|
||
|
if (\Eventy::filter('folder.update_counters', false, $this)) {
|
||
|
return;
|
||
|
}
|
||
|
if ($this->type == self::TYPE_MINE && $this->user_id) {
|
||
|
$this->active_count = Conversation::where('user_id', $this->user_id)
|
||
|
->where('mailbox_id', $this->mailbox_id)
|
||
|
->where('state', Conversation::STATE_PUBLISHED)
|
||
|
->where('status', Conversation::STATUS_ACTIVE)
|
||
|
->count();
|
||
|
$this->total_count = Conversation::where('user_id', $this->user_id)
|
||
|
->where('mailbox_id', $this->mailbox_id)
|
||
|
->where('state', Conversation::STATE_PUBLISHED)
|
||
|
->count();
|
||
|
} elseif ($this->type == self::TYPE_STARRED) {
|
||
|
$this->active_count = count(Conversation::getUserStarredConversationIds($this->mailbox_id, $this->user_id));
|
||
|
$this->total_count = $this->active_count;
|
||
|
} elseif ($this->type == self::TYPE_DELETED) {
|
||
|
$this->active_count = $this->conversations()->where('state', Conversation::STATE_DELETED)
|
||
|
->count();
|
||
|
$this->total_count = $this->active_count;
|
||
|
} elseif ($this->isIndirect()) {
|
||
|
// Conversation are connected to folder via conversation_folder table.
|
||
|
// Drafts.
|
||
|
$this->active_count = ConversationFolder::where('conversation_folder.folder_id', $this->id)
|
||
|
->join('conversations', 'conversations.id', '=', 'conversation_folder.conversation_id')
|
||
|
//->where('state', Conversation::STATE_PUBLISHED)
|
||
|
->count();
|
||
|
$this->total_count = $this->active_count;
|
||
|
} else {
|
||
|
$this->active_count = $this->conversations()
|
||
|
->where('state', Conversation::STATE_PUBLISHED)
|
||
|
->where('status', Conversation::STATUS_ACTIVE)
|
||
|
->count();
|
||
|
$this->total_count = $this->conversations()
|
||
|
->where('state', Conversation::STATE_PUBLISHED)
|
||
|
->count();
|
||
|
}
|
||
|
$this->save();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get count to display in folders list.
|
||
|
*
|
||
|
* @param array $folders [description]
|
||
|
*
|
||
|
* @return [type] [description]
|
||
|
*/
|
||
|
public function getCount($folders = [])
|
||
|
{
|
||
|
$counter = \Eventy::filter('folder.counter', self::COUNTER_ACTIVE, $this, $folders);
|
||
|
|
||
|
$count = \Eventy::filter('folder.count', false, $this, $counter, $folders);
|
||
|
if ($count !== false) {
|
||
|
return $count;
|
||
|
}
|
||
|
|
||
|
if ($counter == self::COUNTER_TOTAL || $this->type == self::TYPE_STARRED || $this->type == self::TYPE_DRAFTS) {
|
||
|
return $this->total_count;
|
||
|
} else {
|
||
|
return $this->getActiveCount($folders);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get calculated number of active conversations.
|
||
|
*/
|
||
|
public function getActiveCount($folders = [])
|
||
|
{
|
||
|
$active_count = $this->active_count;
|
||
|
if ($this->type == self::TYPE_ASSIGNED) {
|
||
|
$mine_folder = \Eventy::filter('folder.active_count_mine_folder', null, $this, $folders);
|
||
|
|
||
|
if (!$mine_folder) {
|
||
|
if ($folders) {
|
||
|
$mine_folder = $folders->firstWhere('type', self::TYPE_MINE);
|
||
|
} elseif ($this->mailbox_id) {
|
||
|
$mine_folder = $this->mailbox->folders()->where('type', self::TYPE_MINE)->first();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ($mine_folder) {
|
||
|
$active_count = $active_count - $mine_folder->active_count;
|
||
|
if ($active_count < 0) {
|
||
|
$active_count = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $active_count;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Query for waiting since.
|
||
|
*/
|
||
|
public function getWaitingSinceQuery()
|
||
|
{
|
||
|
$query = null;
|
||
|
|
||
|
if ($this->type == self::TYPE_MINE) {
|
||
|
// Assigned to user.
|
||
|
$query = Conversation::where('user_id', $this->user_id)
|
||
|
->where('mailbox_id', $this->mailbox_id);
|
||
|
} elseif ($this->isIndirect()) {
|
||
|
// Via intermediate table.
|
||
|
$query = Conversation::join('conversation_folder', 'conversations.id', '=', 'conversation_folder.conversation_id')
|
||
|
->where('conversation_folder.folder_id', $this->id);
|
||
|
} else {
|
||
|
// All other conversations.
|
||
|
$query = $this->conversations();
|
||
|
}
|
||
|
|
||
|
return \Eventy::filter('folder.waiting_since_query', $query, $this);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Works for main folder only for now.
|
||
|
*
|
||
|
* @return [type] [description]
|
||
|
*/
|
||
|
public function getWaitingSince()
|
||
|
{
|
||
|
// Get oldest active conversation.
|
||
|
$conversation = $this->getWaitingSinceQuery()
|
||
|
->where('state', Conversation::STATE_PUBLISHED)
|
||
|
->where('status', Conversation::STATUS_ACTIVE)
|
||
|
->orderBy($this->getWaitingSinceField(), 'asc')
|
||
|
->first();
|
||
|
if ($conversation) {
|
||
|
return $conversation->getWaitingSince($this);
|
||
|
} else {
|
||
|
return '';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get conversation field used to detect waiting since time.
|
||
|
*
|
||
|
* @return [type] [description]
|
||
|
*/
|
||
|
public function getWaitingSinceField()
|
||
|
{
|
||
|
if ($this->type == \App\Folder::TYPE_CLOSED) {
|
||
|
return 'closed_at';
|
||
|
} elseif ($this->type == \App\Folder::TYPE_DRAFTS) {
|
||
|
return 'updated_at';
|
||
|
} elseif ($this->type == \App\Folder::TYPE_DELETED) {
|
||
|
return 'user_updated_at';
|
||
|
} else {
|
||
|
return'last_reply_at';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public function url($mailbox_id)
|
||
|
{
|
||
|
return \Eventy::filter('folder.url', route('mailboxes.view.folder', ['id'=>$mailbox_id, 'folder_id'=>$this->id]), $mailbox_id, $this);
|
||
|
}
|
||
|
|
||
|
public static function create($data, $unique_per_user = true, $save = true)
|
||
|
{
|
||
|
if (!isset($data['mailbox_id']) || !isset($data['type'])) {
|
||
|
return null;
|
||
|
}
|
||
|
$folder = new Folder ();
|
||
|
$folder->mailbox_id = $data['mailbox_id'];
|
||
|
$folder->type = $data['type'];
|
||
|
|
||
|
if (!empty($data['user_id'])) {
|
||
|
if ($unique_per_user) {
|
||
|
$user_folder = Folder::where('mailbox_id', $data['mailbox_id'])
|
||
|
->where('user_id', $data['user_id'])
|
||
|
->where('type', $data['type'])
|
||
|
->first();
|
||
|
// User folder already exists.
|
||
|
if ($user_folder) {
|
||
|
return $user_folder;
|
||
|
}
|
||
|
}
|
||
|
$folder->user_id = $data['user_id'];
|
||
|
}
|
||
|
|
||
|
if ($save) {
|
||
|
$folder->save();
|
||
|
}
|
||
|
|
||
|
return $folder;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get meta value.
|
||
|
*/
|
||
|
public function getMeta($key, $default = null)
|
||
|
{
|
||
|
$metas = $this->meta;
|
||
|
if (isset($metas[$key])) {
|
||
|
return $metas[$key];
|
||
|
} else {
|
||
|
return $default;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set meta value.
|
||
|
*/
|
||
|
public function setMeta($key, $value)
|
||
|
{
|
||
|
$metas = $this->meta;
|
||
|
$metas[$key] = $value;
|
||
|
$this->meta = $metas;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Unset thread meta value.
|
||
|
*/
|
||
|
public function unsetMeta($key)
|
||
|
{
|
||
|
$metas = $this->meta;
|
||
|
if (isset($metas[$key])) {
|
||
|
unset($metas[$key]);
|
||
|
$this->meta = $metas;
|
||
|
}
|
||
|
}
|
||
|
}
|