Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist
This commit is contained in:
@@ -19,6 +19,8 @@ class Attachment extends Model
|
||||
|
||||
const DIRECTORY = 'attachment';
|
||||
|
||||
const DISK = 'local_app';
|
||||
|
||||
const MIME_TYPE_MAX_LENGTH = 127;
|
||||
|
||||
// This token type was used for backward compatibility for some time
|
||||
@@ -133,7 +135,7 @@ class Attachment extends Model
|
||||
$file_info = self::saveFileToDisk($attachment, $file_name, $content, $uploaded_file);
|
||||
|
||||
$attachment->file_dir = $file_info['file_dir'];
|
||||
$attachment->size = Storage::size($file_info['file_path']);
|
||||
$attachment->size = self::getDisk()->size($file_info['file_path']);
|
||||
$attachment->save();
|
||||
|
||||
return $attachment;
|
||||
@@ -154,15 +156,15 @@ class Attachment extends Model
|
||||
do {
|
||||
$i++;
|
||||
$file_path = self::DIRECTORY.DIRECTORY_SEPARATOR.$file_dir.$i.DIRECTORY_SEPARATOR.$file_name;
|
||||
} while (Storage::exists($file_path));
|
||||
} while (self::getDisk()->exists($file_path));
|
||||
|
||||
$file_dir .= $i.DIRECTORY_SEPARATOR;
|
||||
|
||||
try {
|
||||
if ($uploaded_file) {
|
||||
$uploaded_file->storeAs(self::DIRECTORY.DIRECTORY_SEPARATOR.$file_dir, $file_name);
|
||||
$uploaded_file->storeAs(self::DIRECTORY.DIRECTORY_SEPARATOR.$file_dir, $file_name, self::getDiskName());
|
||||
} else {
|
||||
Storage::put($file_path, $content);
|
||||
self::getDisk()->put($file_path, $content);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
\Helper::logException($e, '[Attachment::saveFileToDisk()]');
|
||||
@@ -268,7 +270,7 @@ class Attachment extends Model
|
||||
// URL must contain only forward slashes.
|
||||
$file_path = str_replace(DIRECTORY_SEPARATOR, '/', $file_path);
|
||||
|
||||
$file_url = Storage::disk('local')->url($file_path);
|
||||
$file_url = self::getDisk()->url($file_path);
|
||||
|
||||
// Fix percents.
|
||||
// https://github.com/freescout-helpdesk/freescout/issues/3530
|
||||
@@ -298,7 +300,7 @@ class Attachment extends Model
|
||||
public function download($view = false, $headers = [])
|
||||
{
|
||||
// #533
|
||||
//return $this->getDisk()->download($this->getStorageFilePath(), \Str::ascii($this->file_name));
|
||||
//return self::getDisk()->download($this->getStorageFilePath(), \Str::ascii($this->file_name));
|
||||
if ($view) {
|
||||
$headers['Content-Disposition'] = '';
|
||||
}
|
||||
@@ -311,7 +313,7 @@ class Attachment extends Model
|
||||
// Cache attachments in browser - 1 month.
|
||||
$headers['Cache-Control'] = 'max-age=2592000';
|
||||
|
||||
return Storage::download($this->getStorageFilePath(), $file_name, $headers);
|
||||
return self::getDisk()->download($this->getStorageFilePath(), $file_name, $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -338,7 +340,7 @@ class Attachment extends Model
|
||||
public function getLocalFilePath($full = true)
|
||||
{
|
||||
if ($full) {
|
||||
return Storage::path(self::DIRECTORY.DIRECTORY_SEPARATOR.$this->file_dir.$this->file_name);
|
||||
return self::getDisk()->path(self::DIRECTORY.DIRECTORY_SEPARATOR.$this->file_dir.$this->file_name);
|
||||
} else {
|
||||
return DIRECTORY_SEPARATOR.'storage'.DIRECTORY_SEPARATOR.'app'.DIRECTORY_SEPARATOR.self::DIRECTORY.DIRECTORY_SEPARATOR.$this->file_dir.$this->file_name;
|
||||
}
|
||||
@@ -349,7 +351,7 @@ class Attachment extends Model
|
||||
*/
|
||||
public function fileExists()
|
||||
{
|
||||
return Storage::exists(self::DIRECTORY.DIRECTORY_SEPARATOR.$this->file_dir.$this->file_name);
|
||||
return self::getDisk()->exists(self::DIRECTORY.DIRECTORY_SEPARATOR.$this->file_dir.$this->file_name);
|
||||
}
|
||||
|
||||
public static function formatBytes($size, $precision = 0)
|
||||
@@ -400,7 +402,7 @@ class Attachment extends Model
|
||||
{
|
||||
// Delete from disk
|
||||
foreach ($attachments as $attachment) {
|
||||
Storage::delete($attachment->getStorageFilePath());
|
||||
self::getDisk()->delete($attachment->getStorageFilePath());
|
||||
}
|
||||
|
||||
// Delete from DB
|
||||
@@ -480,7 +482,13 @@ class Attachment extends Model
|
||||
|
||||
public function getFileContents()
|
||||
{
|
||||
return Storage::get($this->getStorageFilePath());
|
||||
try {
|
||||
return self::getDisk()->get($this->getStorageFilePath());
|
||||
} catch (\Exception $e) {
|
||||
// File not found at path:...
|
||||
\Helper::logException($e, '[Attachment::getFileContents()]');
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -490,6 +498,25 @@ class Attachment extends Model
|
||||
*/
|
||||
public function getFileStream()
|
||||
{
|
||||
return Storage::readStream($this->getStorageFilePath());
|
||||
try {
|
||||
return self::getDisk()->readStream($this->getStorageFilePath());
|
||||
} catch (\Exception $e) {
|
||||
\Helper::logException($e, '[Attachment::getFileStream()]');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static function getDiskName()
|
||||
{
|
||||
if (\Helper::isLocalStorage()) {
|
||||
return self::DISK;
|
||||
} else {
|
||||
return config('filesystems.default');
|
||||
}
|
||||
}
|
||||
|
||||
public static function getDisk()
|
||||
{
|
||||
return Storage::disk(self::getDiskName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1955,52 +1955,74 @@ class Conversation extends Model
|
||||
|
||||
/**
|
||||
* Get information on viewers for conversation table.
|
||||
* Returns users in "users" key with the current user excluded: replying users first, then viewing users.
|
||||
*/
|
||||
public static function getViewersInfo($conversations, $fields = ['id', 'first_name', 'last_name'], $exclude_user_ids = [])
|
||||
{
|
||||
$viewers_cache = \Cache::get('conv_view');
|
||||
$viewers = [];
|
||||
$user_ids = [];
|
||||
// Skip stale records not yet removed by freescout:check-conv-viewers.
|
||||
// Dates have 'Y-m-d H:i:s' format, so they can be compared as strings.
|
||||
// https://github.com/freescout-help-desk/freescout/pull/5670
|
||||
$min_date = \Carbon\Carbon::now()->subSeconds(30)->toDateTimeString();
|
||||
foreach ($conversations as $conversation) {
|
||||
$first_user_id = null;
|
||||
if (!empty($viewers_cache[$conversation->id])) {
|
||||
// Get replying viewers
|
||||
foreach ($viewers_cache[$conversation->id] as $user_id => $viewer) {
|
||||
if (!$first_user_id) {
|
||||
$first_user_id = $user_id;
|
||||
}
|
||||
if (!empty($viewer['r']) && !in_array($user_id, $exclude_user_ids)) {
|
||||
$viewers[$conversation->id] = [
|
||||
'user' => null,
|
||||
'user_id' => $user_id,
|
||||
'replying' => true,
|
||||
];
|
||||
$user_ids[] = $user_id;
|
||||
break;
|
||||
}
|
||||
if (empty($viewers_cache[$conversation->id]) || !is_array($viewers_cache[$conversation->id])) {
|
||||
continue;
|
||||
}
|
||||
$replying = [];
|
||||
$viewing = [];
|
||||
foreach ($viewers_cache[$conversation->id] as $user_id => $viewer) {
|
||||
if (in_array($user_id, $exclude_user_ids)
|
||||
|| empty($viewer['t'])
|
||||
|| $viewer['t'] < $min_date
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
// Get first non-replying viewer
|
||||
if (empty($viewers[$conversation->id]) && !in_array($user_id, $exclude_user_ids)) {
|
||||
$viewers[$conversation->id] = [
|
||||
'user' => null,
|
||||
'user_id' => $first_user_id,
|
||||
'replying' => false,
|
||||
];
|
||||
$user_ids[] = $first_user_id;
|
||||
$item = [
|
||||
'user' => null,
|
||||
'user_id' => $user_id,
|
||||
'replying' => !empty($viewer['r']),
|
||||
];
|
||||
if ($item['replying']) {
|
||||
$replying[] = $item;
|
||||
} else {
|
||||
$viewing[] = $item;
|
||||
}
|
||||
$user_ids[] = $user_id;
|
||||
}
|
||||
// Replying viewers go first.
|
||||
$list = array_merge($replying, $viewing);
|
||||
if ($list) {
|
||||
// First viewer is kept on the top level for backward compatibility.
|
||||
$viewers[$conversation->id] = $list[0];
|
||||
$viewers[$conversation->id]['users'] = $list;
|
||||
}
|
||||
}
|
||||
// Get all viewing users in one query
|
||||
if ($user_ids) {
|
||||
$user_ids = array_unique($user_ids);
|
||||
$users = User::select($fields)->whereIn('id', $user_ids)->get();
|
||||
$users = User::select($fields)->whereIn('id', $user_ids)->get()->keyBy('id');
|
||||
|
||||
foreach ($viewers as $i => $viewer) {
|
||||
foreach ($users as $user) {
|
||||
if ($user->id == $viewer['user_id']) {
|
||||
$viewers[$i]['user'] = $user;
|
||||
foreach ($viewers as $conversation_id => $viewer) {
|
||||
foreach ($viewer['users'] as $i => $item) {
|
||||
if (!empty($users[$item['user_id']])) {
|
||||
$viewers[$conversation_id]['users'][$i]['user'] = $users[$item['user_id']];
|
||||
} else {
|
||||
// User not found in DB.
|
||||
unset($viewers[$conversation_id]['users'][$i]);
|
||||
}
|
||||
}
|
||||
// Remove not found users.
|
||||
$viewers[$conversation_id]['users'] = array_values($viewers[$conversation_id]['users']);
|
||||
if (!$viewers[$conversation_id]['users']) {
|
||||
unset($viewers[$conversation_id]);
|
||||
continue;
|
||||
}
|
||||
$first_user = $viewers[$conversation_id]['users'][0];
|
||||
$viewers[$conversation_id]['user'] = $first_user['user'];
|
||||
$viewers[$conversation_id]['user_id'] = $first_user['user_id'];
|
||||
$viewers[$conversation_id]['replying'] = $first_user['replying'];
|
||||
}
|
||||
}
|
||||
return $viewers;
|
||||
|
||||
@@ -845,7 +845,7 @@ class ConversationsController extends Controller
|
||||
}
|
||||
|
||||
// Check max. message size.
|
||||
if (!$response['msg']) {
|
||||
if (!$response['msg'] && !$is_note) {
|
||||
|
||||
$max_message_size = (int)config('app.max_message_size');
|
||||
if ($max_message_size) {
|
||||
@@ -857,13 +857,17 @@ class ConversationsController extends Controller
|
||||
$attachments_ids = $this->decodeAttachmentsIds($attachments_ids);
|
||||
|
||||
if (count($attachments_ids)) {
|
||||
$attachments_to_check = Attachment::select('size')->whereIn('id', $attachments_ids)->get();
|
||||
foreach ($attachments_to_check as $attachment) {
|
||||
$attachments_query = Attachment::select('size')->whereIn('id', $attachments_ids);
|
||||
// Skip embedded images.
|
||||
if (!\Eventy::filter('attachments.embedded.check_size', false)) {
|
||||
$attachments_query->where('embedded', false);
|
||||
}
|
||||
foreach ($attachments_query->get() as $attachment) {
|
||||
$message_size += (int)$attachment->size;
|
||||
}
|
||||
}
|
||||
|
||||
if ($message_size > $max_message_size*1024*1024) {
|
||||
if ($message_size*1.37 > $max_message_size*1024*1024) {
|
||||
$response['msg'] = __('Message is too large — :info. Please shorten your message or remove some attachments.', ['info' => __('Max. Message Size').': '.$max_message_size.' MB']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,7 +217,7 @@ class OpenController extends Controller
|
||||
}
|
||||
|
||||
// Check attachment name.
|
||||
if (trim($attachment->file_name) != trim($file_name)) {
|
||||
if (mb_strtolower(trim($attachment->file_name)) != mb_strtolower(trim($file_name))) {
|
||||
return \Helper::denyAccess();
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ return [
|
||||
| or any other location as required by the application or its packages.
|
||||
*/
|
||||
|
||||
'version' => '1.8.242',
|
||||
'version' => '1.8.243',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@@ -318,10 +318,11 @@ return [
|
||||
| to avoid sending multiple duplicate emails to other recipients.
|
||||
|
|
||||
| https://github.com/freescout-helpdesk/freescout/issues/870#issuecomment-786477909
|
||||
| https://github.com/freescout-help-desk/freescout/issues/5666
|
||||
|
|
||||
|-------------------------------------------------------------------------
|
||||
*/
|
||||
'no_retry_mail_errors' => env('APP_NO_RETRY_MAIL_ERRORS', '(no valid recipients|does not comply with RFC|message file too big|malformed address)'),
|
||||
'no_retry_mail_errors' => env('APP_NO_RETRY_MAIL_ERRORS', '(no valid recipients|does not comply with RFC|message file too big|malformed address|message size|message too large|size exceeded)'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
||||
@@ -43,6 +43,7 @@ return [
|
||||
|
||||
'disks' => [
|
||||
|
||||
// /storage/app/public
|
||||
'local' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('app/public'),
|
||||
@@ -55,15 +56,16 @@ return [
|
||||
'root' => storage_path('app/public'),
|
||||
'url' => env('APP_URL').'/storage',
|
||||
'visibility' => 'public',
|
||||
],
|
||||
],*/
|
||||
|
||||
// To store file in the storage/app folder
|
||||
'private' => [
|
||||
// To store files in the storage/app folder not available from browser:
|
||||
// /storage/app
|
||||
'local_app' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('app'),
|
||||
'url' => env('APP_URL').'/storage',
|
||||
'visibility' => 'public',
|
||||
],*/
|
||||
],
|
||||
|
||||
's3' => [
|
||||
'driver' => 's3',
|
||||
|
||||
@@ -136,7 +136,7 @@
|
||||
@foreach ($conversations as $conversation)
|
||||
<tr class="conv-row @action('conversations_table.row_class', $conversation) @if ($conversation->isActive()) conv-active @endif @if ($conversation->isSpam()) conv-spam @endif" data-conversation_id="{{ $conversation->id }}">
|
||||
@if (empty($no_checkboxes))<td class="conv-current">@if (!empty($viewers[$conversation->id]))
|
||||
<div class="viewer-badge @if (!empty($viewers[$conversation->id]['replying'])) viewer-replying @endif" data-toggle="tooltip" title="@if (!empty($viewers[$conversation->id]['replying'])){{ __(':user is replying', ['user' => $viewers[$conversation->id]['user']->getFullName()]) }}@else{{ __(':user is viewing', ['user' => $viewers[$conversation->id]['user']->getFullName()]) }}@endif"><div>
|
||||
<div class="viewer-badge @if (!empty($viewers[$conversation->id]['replying'])) viewer-replying @endif" data-toggle="tooltip" data-html="true" title="{{ implode('<br/>', array_map(function ($viewer) { return e(__($viewer['replying'] ? ':user is replying' : ':user is viewing', ['user' => $viewer['user']->getFullName()])); }, $viewers[$conversation->id]['users'])) }}"><div>
|
||||
@endif</td>@else<td class="conv-current"></td>@endif
|
||||
@if (empty($no_checkboxes))
|
||||
<td class="conv-cb">
|
||||
|
||||
Reference in New Issue
Block a user