1
0
mirror of https://github.com/freescout-helpdesk/freescout.git synced 2024-11-24 19:33:07 +01:00
freescout/app/Thread.php
2018-09-14 01:24:04 -07:00

577 lines
16 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App;
use App\Attachment;
use Illuminate\Database\Eloquent\Model;
class Thread extends Model
{
/**
* By whom action performed (source_via).
*/
const PERSON_CUSTOMER = 1;
const PERSON_USER = 2;
public static $persons = [
self::PERSON_CUSTOMER => 'customer',
self::PERSON_USER => 'user',
];
/**
* Thread types.
*/
// Email from customer
const TYPE_CUSTOMER = 1;
// Thead created by user
const TYPE_MESSAGE = 2;
const TYPE_NOTE = 3;
// Thread status change
const TYPE_LINEITEM = 4;
const TYPE_PHONE = 5;
// Forwarded threads
const TYPE_FORWARDPARENT = 6;
const TYPE_FORWARDCHILD = 7;
const TYPE_CHAT = 8;
public static $types = [
// Thread by customer
self::TYPE_CUSTOMER => 'customer',
// Thread by user
self::TYPE_MESSAGE => 'message',
self::TYPE_NOTE => 'note',
// lineitem represents a change of state on the conversation. This could include, but not limited to, the conversation was assigned, the status changed, the conversation was moved from one mailbox to another, etc. A line item wont have a body, to/cc/bcc lists, or attachments.
self::TYPE_LINEITEM => 'lineitem',
self::TYPE_PHONE => 'phone',
// When a conversation is forwarded, a new conversation is created to represent the forwarded conversation.
// forwardparent is the type set on the thread of the original conversation that initiated the forward event.
self::TYPE_FORWARDPARENT => 'forwardparent',
// forwardchild is the type set on the first thread of the new forwarded conversation.
self::TYPE_FORWARDCHILD => 'forwardchild',
self::TYPE_CHAT => 'chat',
];
/**
* Statuses (code must be equal to conversations statuses).
*/
const STATUS_ACTIVE = 1;
const STATUS_PENDING = 2;
const STATUS_CLOSED = 3;
const STATUS_SPAM = 4;
const STATUS_NOCHANGE = 6;
public static $statuses = [
self::STATUS_ACTIVE => 'active',
self::STATUS_CLOSED => 'closed',
self::STATUS_NOCHANGE => 'nochange',
self::STATUS_PENDING => 'pending',
self::STATUS_SPAM => 'spam',
];
/**
* States.
*/
const STATE_DRAFT = 1;
const STATE_PUBLISHED = 2;
const STATE_HIDDEN = 3;
// A state of review means the thread has been stopped by Traffic Cop and is waiting
// to be confirmed (or discarded) by the person that created the thread.
const STATE_REVIEW = 4;
public static $states = [
self::STATE_DRAFT => 'draft',
self::STATE_PUBLISHED => 'published',
self::STATE_HIDDEN => 'hidden',
self::STATE_REVIEW => 'review',
];
/**
* Action associated with the line item.
*/
// Conversation's status changed
const ACTION_TYPE_STATUS_CHANGED = 1;
// Conversation's assignee changed
const ACTION_TYPE_USER_CHANGED = 2;
// The conversation was moved from another mailbox
const ACTION_TYPE_MOVED_FROM_MAILBOX = 3;
// Another conversation was merged with this conversation
const ACTION_TYPE_MERGED = 4;
// The conversation was imported (no email notifications were sent)
const ACTION_TYPE_IMPORTED = 5;
// A workflow was run on this conversation (either automatic or manual)
const ACTION_TYPE_WORKFLOW_MANUAL = 6;
const ACTION_TYPE_WORKFLOW_AUTO = 7;
// The ticket was imported from an external Service
const ACTION_TYPE_IMPORTED_EXTERNAL = 8;
// Conversation customer changed
const ACTION_TYPE_CUSTOMER_CHANGED = 9;
// The ticket was deleted
const ACTION_TYPE_DELETED_TICKET = 10;
// The ticket was restored
const ACTION_TYPE_RESTORE_TICKET = 11;
// Describes an optional action associated with the line item
// todo: values need to be checked via HelpScout API
public static $action_types = [
self::ACTION_TYPE_STATUS_CHANGED => 'changed-ticket-status',
self::ACTION_TYPE_USER_CHANGED => 'changed-ticket-assignee',
self::ACTION_TYPE_MOVED_FROM_MAILBOX => 'moved-from-mailbox',
self::ACTION_TYPE_MERGED => 'merged',
self::ACTION_TYPE_IMPORTED => 'imported',
self::ACTION_TYPE_WORKFLOW_MANUAL => 'manual-workflow',
self::ACTION_TYPE_WORKFLOW_AUTO => 'automatic-workflow',
self::ACTION_TYPE_IMPORTED_EXTERNAL => 'imported-external',
self::ACTION_TYPE_CUSTOMER_CHANGED => 'changed-ticket-customer',
self::ACTION_TYPE_DELETED_TICKET => 'deleted-ticket',
self::ACTION_TYPE_RESTORE_TICKET => 'restore-ticket',
];
/**
* Source types (equal to thread source types).
*/
const SOURCE_TYPE_EMAIL = 1;
const SOURCE_TYPE_WEB = 2;
const SOURCE_TYPE_API = 3;
public static $source_types = [
self::SOURCE_TYPE_EMAIL => 'email',
self::SOURCE_TYPE_WEB => 'web',
self::SOURCE_TYPE_API => 'api',
];
/**
* The user assigned to this thread (assignedTo).
*/
public function user()
{
return $this->belongsTo('App\User');
}
/**
* The user assigned to this thread (cached).
*/
public function user_cached()
{
return $this->user()->rememberForever();
}
/**
* Get the thread customer.
*/
public function customer()
{
return $this->belongsTo('App\Customer');
}
/**
* Get the thread customer (cached).
*/
public function customer_cached()
{
return $this->customer()->rememberForever();
}
/**
* Get conversation.
*/
public function conversation()
{
return $this->belongsTo('App\Conversation');
}
/**
* Get thread attachmets.
*/
public function attachments()
{
return $this->hasMany('App\Attachment')->where('embedded', false);
//return $this->hasMany('App\Attachment');
}
/**
* Get thread embedded attachments.
*/
public function embeds()
{
return $this->hasMany('App\Attachment')->where('embedded', true);
}
/**
* All kinds of attachments including embedded.
*/
public function all_attachments()
{
return $this->hasMany('App\Attachment');
}
/**
* Get user who created the thread.
*/
public function created_by_user()
{
return $this->belongsTo('App\User');
}
/**
* Get user who created the thread (cached)
*/
public function created_by_user_cached()
{
return $this->created_by_user()->rememberForever();
}
/**
* Get customer who created the thread.
*/
public function created_by_customer()
{
return $this->belongsTo('App\Customer');
}
/**
* Get user who edited draft.
*/
public function edited_by_user()
{
return $this->belongsTo('App\User');
}
/**
* Get sanitized body HTML.
*
* @return string
*/
public function getCleanBody()
{
$body = \Purifier::clean($this->body);
// Remove all kinds of spaces after tags
// https://stackoverflow.com/questions/3230623/filter-all-types-of-whitespace-in-php
$body = preg_replace("/^(.*)>[\r\n]*\s+/mu", '$1>', $body);
return $body;
}
/**
* Get thread recipients.
*
* @return array
*/
public function getToArray($exclude_array = [])
{
if ($this->to) {
$to_array = json_decode($this->to);
if ($to_array && $exclude_array) {
$to_array = array_diff($to_array, $exclude_array);
}
return $to_array;
} else {
return [];
}
}
public function getToString($exclude_array = [])
{
return implode(', ', $this->getToArray($exclude_array));
}
/**
* Get first address from the To list.
*/
public function getToFirst()
{
$to = $this->getToArray();
return array_shift($to);
}
/**
* Get type name.
*/
public function getTypeName()
{
return self::$types[$this->type];
}
/**
* Get thread CC recipients.
*
* @return array
*/
public function getCcArray($exclude_array = [])
{
if ($this->cc) {
$cc_array = json_decode($this->cc);
if ($cc_array && $exclude_array) {
$cc_array = array_diff($cc_array, $exclude_array);
}
return $cc_array;
} else {
return [];
}
}
public function getCcString($exclude_array = [])
{
return implode(', ', $this->getCcArray($exclude_array));
}
/**
* Get thread BCC recipients.
*
* @return array
*/
public function getBccArray($exclude_array = [])
{
if ($this->bcc) {
$bcc_array = json_decode($this->bcc);
if ($bcc_array && $exclude_array) {
$bcc_array = array_diff($bcc_array, $exclude_array);
}
return $bcc_array;
} else {
return [];
}
}
public function getBccString($exclude_array = [])
{
return implode(', ', $this->getBccArray($exclude_array));
}
/**
* Set to as JSON.
*/
public function setTo($emails)
{
$emails_array = Conversation::sanitizeEmails($emails);
if ($emails_array) {
$emails_array = array_unique($emails_array);
$this->to = json_encode($emails_array);
} else {
$this->to = null;
}
}
public function setCc($emails)
{
$emails_array = Conversation::sanitizeEmails($emails);
if ($emails_array) {
$emails_array = array_unique($emails_array);
$this->cc = json_encode($emails_array);
} else {
$this->cc = null;
}
}
public function setBcc($emails)
{
$emails_array = Conversation::sanitizeEmails($emails);
if ($emails_array) {
$emails_array = array_unique($emails_array);
$this->bcc = json_encode($emails_array);
} else {
$this->bcc = null;
}
}
/**
* Get thread's status name.
*
* @return string
*/
public function getStatusName()
{
return self::statusCodeToName($this->status);
}
/**
* Get status name. Made as a function to allow status names translation.
*
* @param int $status
*
* @return string
*/
public static function statusCodeToName($status)
{
switch ($status) {
case self::STATUS_ACTIVE:
return __('Active');
break;
case self::STATUS_PENDING:
return __('Pending');
break;
case self::STATUS_CLOSED:
return __('Closed');
break;
case self::STATUS_SPAM:
return __('Spam');
break;
case self::STATUS_NOCHANGE:
return __('Not changed');
break;
default:
return '';
break;
}
}
/**
* Get text for the assignee.
*
* @return string
*/
public function getAssigneeName($ucfirst = false, $user = null, $self = true)
{
if (!$this->user_id) {
if ($ucfirst) {
return __('Anyone');
} else {
return __('anyone');
}
} elseif (($user && $this->user_id == $user->id) || (!$user && auth()->user() && $this->user_id == auth()->user()->id)) {
// Not using mb_ucfirst to avoid possible problems with encoding
if ($ucfirst) {
if ($self) {
$name = __('Yourself');
} else {
$name = __('You');
}
} else {
if ($self) {
$name = __('yourself');
} else {
$name = __('you');
}
}
return $name;
} else {
return $this->user->getFullName();
}
}
/**
* Get user or customer who created the thead.
*/
public function getCreatedBy()
{
if (!empty($this->created_by_user_id)) {
return $this->created_by_user;
} else {
return $this->created_by_customer;
}
}
/**
* Get creator of the thread.
*/
public function getPerson($cached = false)
{
if ($this->type == Thread::TYPE_CUSTOMER) {
if ($cached) {
return $this->customer_cached;
} else {
return $this->customer;
}
} else {
if ($cached) {
return $this->created_by_user_cached;
} else {
return $this->created_by_user;
}
}
}
/**
* Description of what happened.
*/
public function getActionDescription($conversation_number, $escape = true)
{
$person = '';
$did_this = '';
// Person
if ($this->type == Thread::TYPE_CUSTOMER) {
$person = $this->customer->getFullName(true);
} elseif ($this->state == Thread::STATE_DRAFT && !empty($this->edited_by_user_id)) {
// Draft
if (auth()->user() && $this->edited_by_user_id == auth()->user()->id) {
$person = __("you");
} else {
$person = $this->edited_by_user->getFullName();
}
} else {
if ($this->created_by_user_id && auth()->user() && $this->created_by_user_cached->id == auth()->user()->id) {
$person = __("you");
} else {
$person = $this->created_by_user_cached->getFullName();
}
}
// Did this
if ($this->type == Thread::TYPE_LINEITEM) {
if ($this->action_type == Thread::ACTION_TYPE_STATUS_CHANGED) {
$did_this = __("marked as :status_name conversation #:conversation_number", ['status_name' => $this->getStatusName(), 'conversation_number' => $conversation_number]);
} elseif ($this->action_type == Thread::ACTION_TYPE_USER_CHANGED) {
$did_this = __("assigned :assignee convsersation #:conversation_number", ['assignee' => $this->getAssigneeName(false, null, false), 'conversation_number' => $conversation_number]);
} elseif ($this->action_type == Thread::ACTION_TYPE_CUSTOMER_CHANGED) {
$did_this = __("changed the customer to :customer in conversation #:conversation_number", ['customer' => $this->customer->getFullName(true), 'conversation_number' => $conversation_number]);
}
} elseif ($this->state == Thread::STATE_DRAFT) {
if (empty($this->edited_by_user_id)) {
$did_this = __("created a draft");
} else {
$did_this = __("edited :creator's draft", ['creator' => $this->created_by_user_cached->getFirstName()]);
}
} else {
if ($this->first) {
$did_this = __("started a new conversation #:conversation_number", ['conversation_number' => $conversation_number]);
} elseif ($this->type == Thread::TYPE_NOTE) {
$did_this = __("added a note to conversation #:conversation_number", ['conversation_number' => $conversation_number]);
} else {
$did_this = __("replied to conversation #:conversation_number", ['conversation_number' => $conversation_number]);
}
}
$description = ':person_tag_start:person:person_tag_end :did_this';
if ($escape) {
$description = htmlspecialchars($description);
}
return __($description, [
'person' => $person,
'person_tag_start' => '<strong>',
'person_tag_end' => '</strong>',
'did_this' => $did_this
]);
}
/**
* Get thread state name.
*/
public function getStateName()
{
return self::$states[$this->state];
}
public function deleteThread()
{
$this->deteleAttachments();
$this->delete();
}
/**
* Delete thread attachments.
*/
public function deteleAttachments()
{
Attachment::deleteByIds($this->all_attachments()->pluck('id')->toArray());
}
}