Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
863ad60308 | ||
|
|
23b371c4f0 | ||
|
|
164303763d | ||
|
|
b1e9249cd2 | ||
|
|
17f060ee9a | ||
|
|
7fe68073f4 | ||
|
|
d772126165 | ||
|
|
4bf6a84223 | ||
|
|
67f23e88a2 | ||
|
|
cd450e819d | ||
|
|
b753c63924 | ||
|
|
773981de78 | ||
|
|
b4332f105f | ||
|
|
ffd758438d | ||
|
|
a6bb63c9fe | ||
|
|
bff838fa0e | ||
|
|
7ec40b691a | ||
|
|
041d7e09b2 | ||
|
|
a8290b421d | ||
|
|
8788b4a8e6 | ||
|
|
f14358c5fe | ||
|
|
f1b35d0d5f | ||
|
|
649d868c62 | ||
|
|
8fe96f83a7 | ||
|
|
cf951ea20b | ||
|
|
4de254f7cd | ||
|
|
a1e876f82d | ||
|
|
48c4fac6b4 | ||
|
|
eec09ff173 | ||
|
|
63546323af | ||
|
|
4a8bd527e3 | ||
|
|
4de16c62b8 |
@@ -39,6 +39,7 @@ If you want to support the project feel free to **star this repository**. It hel
|
||||
* Multilingual (translated into 33 languages).
|
||||
* Seamless email integration (via SMTP, IMAP, POP3, etc).
|
||||
* Modern OAuth authentication for Microsoft Office 365 and Google Workspace.
|
||||
* [Progressive Web App](https://freescout.net/progressive-app/) for phones and desktops (installable from browser and sending Web Push notifications).
|
||||
* Native [S3 storage](https://github.com/freescout-help-desk/S3Storage) support.
|
||||
* Fully supports screen readers (for visually impaired).
|
||||
* Web installer & updater.
|
||||
@@ -67,7 +68,7 @@ Mobile apps support the same functionality and modules as the web version of you
|
||||
|
||||
<a href="https://freescout.net/android-app/" target="_blank" rel="nofollow"><img alt="Android App" src="https://freescout-helpdesk.github.io/img/apps/android.png" width="200px" /></a> <a href="https://freescout.net/ios-app/" target="_blank" rel="nofollow"><img alt="iOS App" src="https://freescout-helpdesk.github.io/img/apps/ios.png?v=1" width="200px" /></a>
|
||||
|
||||
[MacOS Menu Bar App](https://github.com/jonalaniz/scouter)
|
||||
[Progressive Web App for phones and desktops](https://freescout.net/progressive-app/) | [MacOS Menu Bar App](https://github.com/jonalaniz/scouter)
|
||||
|
||||
## Requirements
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ Read about **[FreeScout's Security](https://freescout.net/security/)**.
|
||||
|
||||
* One issue per Advisory — do not put several issues into one Advisory, as GitHub will not assign a CVE to such an Advisory.
|
||||
* CVEs are requested after the fix for the vulnerability is released.
|
||||
* If you found an issue related to the `APP_LIMIT_USER_CUSTOMER_VISIBILITY=true` mode, make sure to read the [FAQ](https://github.com/freescout-help-desk/freescout/wiki/FAQ#is-it-possible-to-have-separate-contactscustomers-per-mailbox).
|
||||
* If you found an issue related to the "Manage » Settings » Customer Visibility" option (`APP_LIMIT_USER_CUSTOMER_VISIBILITY=true`), make sure to read the [FAQ](https://github.com/freescout-help-desk/freescout/wiki/FAQ#is-it-possible-to-have-separate-contactscustomers-per-mailbox).
|
||||
* Support agents are allowed to move conversations to any mailbox, even to ones they don't have access to.
|
||||
* Drafts are viewable/discardable by any member of the mailbox where they are created.
|
||||
* FreeScout loads external images in received emails, and there is a Module allowing to disable loading external images: https://freescout.net/module/block-external-images/.
|
||||
@@ -4,6 +4,8 @@ namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Throwable;
|
||||
|
||||
class Handler extends ExceptionHandler
|
||||
{
|
||||
@@ -26,6 +28,26 @@ class Handler extends ExceptionHandler
|
||||
'password_confirmation',
|
||||
];
|
||||
|
||||
/**
|
||||
* True while an exception is being reported. Protects against
|
||||
* recursive reporting (an exception thrown while reporting another one).
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $is_reporting = false;
|
||||
|
||||
/**
|
||||
* Classes/namespaces whose stack frame arguments may contain DB credentials
|
||||
* (host, username and password are passed as arguments when connecting).
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $sensitive_frame_prefixes = [
|
||||
'Illuminate\Database\Connectors\\',
|
||||
'Doctrine\DBAL\Driver\\',
|
||||
'PDO',
|
||||
];
|
||||
|
||||
/**
|
||||
* Report or log an exception.
|
||||
*
|
||||
@@ -37,7 +59,123 @@ class Handler extends ExceptionHandler
|
||||
*/
|
||||
public function report(Exception $exception)
|
||||
{
|
||||
parent::report($exception);
|
||||
// If an exception occurs while another one is being reported
|
||||
// (for example the database is down and something in the reporting
|
||||
// chain queries it), do not report it again to avoid an infinite
|
||||
// loop and gigantic log files.
|
||||
if ($this->is_reporting) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->is_reporting = true;
|
||||
|
||||
try {
|
||||
// Do not write DB credentials from the stack trace to the logs.
|
||||
$this->maskSensitiveTraceArgs($exception);
|
||||
|
||||
parent::report($exception);
|
||||
} finally {
|
||||
$this->is_reporting = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace string arguments (which may contain DB host, username and
|
||||
* password) of the sensitive stack frames with asterisks (the first
|
||||
* 2 symbols are left untouched), so that they
|
||||
* are not written to the logs. Applies to the whole chain of previous
|
||||
* exceptions.
|
||||
*
|
||||
* @param \Exception $exception
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function maskSensitiveTraceArgs($exception)
|
||||
{
|
||||
try {
|
||||
$property = new \ReflectionProperty(\Exception::class, 'trace');
|
||||
// Has no effect since PHP 8.1 and is deprecated since PHP 8.5.
|
||||
if (PHP_VERSION_ID < 80100) {
|
||||
$property->setAccessible(true);
|
||||
}
|
||||
|
||||
$depth = 0;
|
||||
while ($exception && $depth < 20) {
|
||||
$trace = $property->getValue($exception);
|
||||
|
||||
if (is_array($trace)) {
|
||||
$changed = false;
|
||||
foreach ($trace as $i => $frame) {
|
||||
if (empty($frame['args']) || !is_array($frame['args'])) {
|
||||
continue;
|
||||
}
|
||||
$class = isset($frame['class']) ? $frame['class'] : '';
|
||||
foreach (self::$sensitive_frame_prefixes as $prefix) {
|
||||
if ($class !== '' && strpos($class, $prefix) === 0) {
|
||||
foreach ($frame['args'] as $arg_index => $arg) {
|
||||
if (is_string($arg)) {
|
||||
$trace[$i]['args'][$arg_index] = $this->maskString($arg);
|
||||
}
|
||||
}
|
||||
$changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($changed) {
|
||||
$property->setValue($exception, $trace);
|
||||
}
|
||||
}
|
||||
|
||||
$exception = $exception->getPrevious();
|
||||
$depth++;
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mask a string: keep the first 2 symbols and replace the rest with asterisks.
|
||||
* Strings of 2 symbols or less are masked completely.
|
||||
*
|
||||
* @param string $string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function maskString($string)
|
||||
{
|
||||
if (mb_strlen($string) <= 2) {
|
||||
return '***';
|
||||
}
|
||||
|
||||
return mb_substr($string, 0, 2).'***';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default context variables for logging.
|
||||
*
|
||||
* Same as in Laravel 5.8+: only the user ID is added (no email).
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function context()
|
||||
{
|
||||
// Try to avoid reading user from DB.
|
||||
// https://github.com/freescout-help-desk/freescout/issues/5707
|
||||
$guard = Auth::guard();
|
||||
$user_id = $guard->getSession()->get($guard->getName());
|
||||
/*if (!$user_id) {
|
||||
$user_id = Auth::id();
|
||||
}*/
|
||||
|
||||
try {
|
||||
return array_filter([
|
||||
'userId' => $user_id,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -139,6 +139,8 @@ class Mailbox extends Model
|
||||
const DEFAULT_SIGNATURE = '<br><span style="color:#808080;">--<br>
|
||||
{%mailbox.name%}</span>';
|
||||
|
||||
const META_STATE = 'st';
|
||||
|
||||
/**
|
||||
* Default values.
|
||||
*/
|
||||
@@ -229,7 +231,7 @@ class Mailbox extends Model
|
||||
*/
|
||||
public function getStateAttribute()
|
||||
{
|
||||
return $this->getMeta('st', self::STATE_ACTIVE);
|
||||
return $this->getMeta(self::META_STATE, self::STATE_ACTIVE);
|
||||
}
|
||||
|
||||
public function setStateAttribute($value)
|
||||
@@ -238,7 +240,7 @@ class Mailbox extends Model
|
||||
if (!in_array($value, [self::STATE_ACTIVE, self::STATE_ARCHIVED])) {
|
||||
$value = self::STATE_ACTIVE;
|
||||
}
|
||||
$this->setMeta('st', $value);
|
||||
$this->setMeta(self::META_STATE, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1805,6 +1805,14 @@ class Helper
|
||||
return (int)$request->cookie('in_app');
|
||||
}
|
||||
|
||||
public static function isAndroid($request = null)
|
||||
{
|
||||
if (!$request) {
|
||||
$request = app('request');
|
||||
}
|
||||
return stripos($request->server('HTTP_USER_AGENT') ?? '', 'Android') !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Customers list view mode: cards (default) or table.
|
||||
* The cookie is set from JS (see setCustomersView() in main.js).
|
||||
|
||||
13
app/User.php
13
app/User.php
@@ -335,12 +335,23 @@ class User extends Authenticatable
|
||||
if ($this->isAdmin()) {
|
||||
return Mailbox::pluck('id')->toArray();
|
||||
} else {
|
||||
return $this->mailboxes()->pluck('mailboxes.id')->toArray();
|
||||
$mailboxes = $this->mailboxes()->pluck('mailboxes.meta', 'mailboxes.id');
|
||||
// Exclude archived mailboxes.
|
||||
return $mailboxes->filter(function ($meta) {
|
||||
$meta = json_decode($meta, true);
|
||||
if (!isset($meta[Mailbox::META_STATE])) {
|
||||
return true;
|
||||
}
|
||||
return $meta[Mailbox::META_STATE] == Mailbox::STATE_ACTIVE;
|
||||
})->keys()->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
public function hasAccessToMailbox($mailbox_id)
|
||||
{
|
||||
if ($this->isAdmin()) {
|
||||
return true;
|
||||
}
|
||||
$ids = $this->mailboxesIdsCanView();
|
||||
return in_array($mailbox_id, $ids);
|
||||
}
|
||||
|
||||
@@ -84,6 +84,7 @@ return [
|
||||
'__',
|
||||
'__j',
|
||||
'__h',
|
||||
'__safe_raw_html',
|
||||
'$trans.get',
|
||||
],
|
||||
|
||||
|
||||
@@ -156,6 +156,38 @@ class LaravelLogViewer
|
||||
return basename($this->file ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the log file to display by default: the latest "laravel-*" file
|
||||
* (by date in the file name), otherwise the most recently modified file.
|
||||
*
|
||||
* @param array $files Full paths of log files.
|
||||
* @return string
|
||||
*/
|
||||
protected function getLatestLogFile(array $files)
|
||||
{
|
||||
$laravel_files = [];
|
||||
foreach ($files as $file) {
|
||||
if (preg_match('/^laravel-.*\.log$/', basename($file))) {
|
||||
$laravel_files[] = $file;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($laravel_files)) {
|
||||
// laravel-YYYY-MM-DD.log names sort chronologically.
|
||||
usort($laravel_files, function ($a, $b) {
|
||||
return strcmp(basename($b), basename($a));
|
||||
});
|
||||
|
||||
return $laravel_files[0];
|
||||
}
|
||||
|
||||
usort($files, function ($a, $b) {
|
||||
return @filemtime($b) <=> @filemtime($a);
|
||||
});
|
||||
|
||||
return $files[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
@@ -168,7 +200,7 @@ class LaravelLogViewer
|
||||
if (!count($log_file)) {
|
||||
return [];
|
||||
}
|
||||
$this->file = $log_file[0];
|
||||
$this->file = $this->getLatestLogFile($log_file);
|
||||
}
|
||||
|
||||
$max_file_size = function_exists('config') ? config('logviewer.max_file_size', self::MAX_FILE_SIZE) : self::MAX_FILE_SIZE;
|
||||
|
||||
100
public/css/style.css
vendored
100
public/css/style.css
vendored
@@ -1535,6 +1535,22 @@ table.table-conversations td.conv-attachment {
|
||||
.table-conversations .conv-customer {
|
||||
width: 150px;
|
||||
}
|
||||
/* Only the name is truncated, so the counter always stays visible */
|
||||
.conv-customer-line {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
.conv-customer-name {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.conv-customer-line .conv-counter {
|
||||
flex-shrink: 0;
|
||||
/* As with inline padding, the padding must not make the line taller */
|
||||
margin-top: -1px;
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
.table-conversations .conv-spam .conv-customer a {
|
||||
color: #93a1af;
|
||||
}
|
||||
@@ -1574,7 +1590,7 @@ table.table-conversations td.conv-attachment {
|
||||
.conv-counter {
|
||||
background: #97a3ad;
|
||||
color: #fff;
|
||||
line-height: 17px;
|
||||
line-height: 14px;
|
||||
position: relative;
|
||||
padding: 1px 5px;
|
||||
margin-left: 2px;
|
||||
@@ -1590,6 +1606,14 @@ table.table-conversations td.conv-attachment {
|
||||
margin-top: 2px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
.table-conversations .conv-owner {
|
||||
width: 160px;
|
||||
}
|
||||
.table-conversations td.conv-owner a {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.table-conversations .conv-number {
|
||||
width: 86px;
|
||||
}
|
||||
@@ -1725,8 +1749,10 @@ button.conv-checkbox-clear {
|
||||
height: 80px;
|
||||
border-bottom: 1px solid #e3e8eb;
|
||||
}
|
||||
/* The mailbox name adds a line (20.1px) to the preview. With 97px the subject's link
|
||||
ended below the row, and its fader whitened the row's bottom border */
|
||||
.table.table-conversations.show-mailbox tbody tr {
|
||||
height: 97px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.table-conversations .conv-attachment-mobile
|
||||
@@ -1763,8 +1789,13 @@ button.conv-checkbox-clear {
|
||||
top: 0;
|
||||
margin-left: 10px!important;
|
||||
}
|
||||
/* Stretch to the date, leaving room for it */
|
||||
.table.table-conversations td.conv-customer {
|
||||
width: auto;
|
||||
inset-inline-start: 0;
|
||||
inset-inline-end: 110px;
|
||||
}
|
||||
.conv-customer a {
|
||||
width: 350px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
@@ -1783,6 +1814,27 @@ button.conv-checkbox-clear {
|
||||
background-color: transparent!important;
|
||||
font-weight: normal;
|
||||
}
|
||||
/* The link's ellipsis truncates the assignee, so it gets the assignee's style */
|
||||
.table-conversations td.conv-customer a {
|
||||
color: #93a1af;
|
||||
font-weight: normal;
|
||||
}
|
||||
.conv-customer-line {
|
||||
display: inline;
|
||||
color: #2a3b47;
|
||||
}
|
||||
.conv-active .conv-customer-line {
|
||||
font-weight: bold;
|
||||
}
|
||||
.conv-spam .conv-customer-line {
|
||||
color: #93a1af;
|
||||
}
|
||||
/* Leave room for the counter and the assignee */
|
||||
.conv-customer-name {
|
||||
display: inline-block;
|
||||
max-width: 60%;
|
||||
vertical-align: top;
|
||||
}
|
||||
.conv-counter {
|
||||
top: -1px;
|
||||
font-size: 10px;
|
||||
@@ -1845,11 +1897,6 @@ button.conv-checkbox-clear {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
@media (max-width:700px) {
|
||||
.conv-customer a {
|
||||
width: 250px;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Conversation
|
||||
@@ -2458,33 +2505,31 @@ button.conv-checkbox-clear {
|
||||
.thread-content img {
|
||||
max-width: 100%;
|
||||
}
|
||||
/* Keep aspect ratio when max-width shrinks an image with width/height attributes */
|
||||
/* Keep aspect ratio when max-width shrinks an image with width/height attributes.
|
||||
!important overrides inline height set by Outlook (style="width:13.3in;height:6.6in"). */
|
||||
.thread-content img[width] {
|
||||
height: auto;
|
||||
height: auto !important;
|
||||
}
|
||||
/* Fit wide tables (e.g. from Outlook) into the thread instead of scrolling it horizontally.
|
||||
overflow-wrap: anywhere lets columns shrink below the longest word when there is no room.
|
||||
Words are not broken: a table that is still wider than the thread because of its longest words
|
||||
(like a long link) scrolls the thread. overflow-wrap: anywhere broke words even when there was room,
|
||||
e.g. in a one-column table of IDs a bit wider than the width set by Outlook.
|
||||
Cell widths are reset as Outlook sets arbitrary percentages (like 1.7% for a column with long text),
|
||||
which would squeeze such columns to one letter per line. */
|
||||
which would squeeze such columns to one word per line. */
|
||||
.thread-content table {
|
||||
max-width: 100%;
|
||||
}
|
||||
.thread-content td,
|
||||
.thread-content th {
|
||||
overflow-wrap: anywhere;
|
||||
width: auto !important;
|
||||
}
|
||||
/* On phones there is no room to fit a table without breaking every word,
|
||||
/* On phones a fitted table would have one word per line,
|
||||
so the table is scrolled horizontally on its own instead of the whole thread. */
|
||||
@media (max-width:700px) {
|
||||
.thread-content table {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
width: max-content !important;
|
||||
}
|
||||
.thread-content td,
|
||||
.thread-content th {
|
||||
overflow-wrap: normal;
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
.conv-new #conv-toolbar {
|
||||
@@ -2966,6 +3011,12 @@ ul.sidebar-block-list {
|
||||
.conv-sidebar-block .panel {
|
||||
background-color: #fff;
|
||||
}
|
||||
/* Overrides the border and shadow of .panel-group .panel: the collapsed panel is empty
|
||||
and its border made the bottom border of the customer block 3px thick */
|
||||
.conv-sidebar-block .panel-group .panel {
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
.conv-sidebar-block .panel-group {
|
||||
margin: 0;
|
||||
}
|
||||
@@ -2985,6 +3036,17 @@ ul.sidebar-block-list {
|
||||
display: none!important;
|
||||
height: 0!important;
|
||||
}
|
||||
/* While animating, Bootstrap replaces .collapse with .collapsing, the rules above don't apply
|
||||
and the block was animated the opposite way: on "close" it grew and only then disappeared,
|
||||
on open it appeared after a delay. Show the final state at once instead:
|
||||
aria-expanded is the state Bootstrap is switching to */
|
||||
.conv-sidebar-block .collapsing[aria-expanded="true"] {
|
||||
display: none!important;
|
||||
}
|
||||
.conv-sidebar-block .collapsing[aria-expanded="false"] {
|
||||
display: block!important;
|
||||
height: auto!important;
|
||||
}
|
||||
.customer-social-profiles {
|
||||
white-space: normal;
|
||||
max-width: none;
|
||||
|
||||
36
public/js/main.js
vendored
36
public/js/main.js
vendored
@@ -5189,17 +5189,39 @@ function conversationsTableInit()
|
||||
// if ("ontouchstart" in window)
|
||||
// {
|
||||
$(document).ready(function() {
|
||||
$('.conv-row').on('contextmenu', function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
});
|
||||
// Toggle the row only once per press: on touch devices
|
||||
// both contextmenu and taphold may fire for the same press.
|
||||
var toggleRow = function(row) {
|
||||
if ($(row).data('conv_toggled')) {
|
||||
return;
|
||||
}
|
||||
$(row).data('conv_toggled', true);
|
||||
|
||||
$('.conv-row').on('taphold', {duration: 700}, function(event) {
|
||||
var row = $(event.target).parents('.conv-row');
|
||||
var checkbox = $(row).find('input.conv-checkbox');
|
||||
$(checkbox).prop('checked', !checkbox.prop('checked'));
|
||||
$(checkbox).trigger('change');
|
||||
$(row).toggleClass('selected');
|
||||
};
|
||||
|
||||
$('.conv-row').on('touchstart mousedown', function(event) {
|
||||
$(this).data('conv_touch', event.type == 'touchstart');
|
||||
$(this).data('conv_toggled', false);
|
||||
});
|
||||
|
||||
$('.conv-row').on('contextmenu', function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
// The browser fires contextmenu after its native long press
|
||||
// (with haptic feedback on Android), on press or on release,
|
||||
// which may happen before taphold.
|
||||
if ($(this).data('conv_touch')) {
|
||||
toggleRow(this);
|
||||
}
|
||||
});
|
||||
|
||||
$('.conv-row').on('taphold', {duration: 700}, function(event) {
|
||||
toggleRow(this);
|
||||
});
|
||||
});
|
||||
//}
|
||||
@@ -6222,4 +6244,4 @@ function initMergeCustomers()
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,6 @@
|
||||
":person started the conversation": ":person hat das Gespräch gestartet",
|
||||
":user is replying": ":user antwortet",
|
||||
":user is viewing": ":user betrachtet",
|
||||
"Receiving emails need to be configured for the mailbox in order to fetch emails from your support email address": "Der E-Mail Empfang muss für dieses Postfach konfiguriert werden",
|
||||
"<strong>Administrators<\/strong> can create new users and have access to all mailboxes and settings": "<strong>Administratoren<\/strong> können neue Benutzer erstellen und haben Zugriff auf alle Postfächer und Einstellungen",
|
||||
"<strong>Users<\/strong> have access to the mailbox(es) specified in their permissions": "<strong>Benutzer<\/strong> haben Zugriff auf die Postfächer, die in ihren Berechtigungen festgelegt wurden",
|
||||
"A conversation :person is following is updated": "Ein Gespräch dem :person folgt wurde aktualisiert",
|
||||
@@ -55,6 +54,8 @@
|
||||
"A conversation is assigned to :person": "Ein Gespräch wurde :person zugewiesen",
|
||||
"A conversation is assigned to me": "Ein Gespräch wurde mir zugewiesen",
|
||||
"A conversation is assigned to someone else": "Ein Gespräch wurde jemand anderem zugewiesen",
|
||||
"A new internal conversation is created and the user is connected to it": "Eine neue interne Unterhaltung wird erstellt und der Benutzer damit verbunden.",
|
||||
"A new reply is added to an internal conversation and the user is connected to it": "Eine neue Antwort wird einer internen Unterhaltung hinzugefügt und der Benutzer wird damit verbunden.",
|
||||
"A new version is available": "Eine neue Version ist verfügbar",
|
||||
"Accept Chat": "Chat akzeptieren",
|
||||
"Accepted for delivery": "Akzeptiert zur Zustellung",
|
||||
@@ -88,6 +89,7 @@
|
||||
"Aliases": "Aliasse",
|
||||
"Aliases are other email addresses that also forward to your mailbox address. Separate each email with a comma.": "Aliase sind E-Mail-Adressen die zu diesem Postfach weitergeleitet werden. Trennen Sie jede E-Mail-Adresse mit einem Komma.",
|
||||
"All": "Alle",
|
||||
"All customers": "Alle Kunden",
|
||||
"All from current mailbox": "Alle aus dem aktuellen Postfach",
|
||||
"Allow to reply from aliases": "Antworten von Alias Adressen erlauben",
|
||||
"Alternate Emails": "Alternative E-Mail-Adressen",
|
||||
@@ -115,6 +117,7 @@
|
||||
"Auto Reply status saved": "Automatische Antwort gespeichert",
|
||||
"Auto replies don't include your mailbox signature, so be sure to add your contact information if necessary.": "Automatische Antworten beinhalten nicht die Postfach-Signatur. Fügen Sie Ihre Kontaktinformationen hinzu wenn benötigt.",
|
||||
"Auto reply to customer": "Automatische Antwort an den Kunden",
|
||||
"Autoplay is disabled in your browser. Click anywhere on this page to enable notification sounds, or allow Autoplay in your browser settings.": "Die automatische Wiedergabe ist in Ihrem Browser deaktiviert. Klicken Sie an eine beliebige Stelle auf dieser Seite, um Benachrichtigungstöne zu aktivieren, oder erlauben Sie die automatische Wiedergabe in Ihren Browsereinstellungen.",
|
||||
"Back": "Zurück",
|
||||
"Back to folder": "Zurück zum Ordner",
|
||||
"Background Jobs": "Hintergrundaufgaben",
|
||||
@@ -124,6 +127,7 @@
|
||||
"Browser": "Browser",
|
||||
"Cancel": "Abbrechen",
|
||||
"Cannot merge the same customer": "Die Zusammenführung derselben Kundendaten ist nicht möglich.",
|
||||
"Cards View": "Kartenansicht",
|
||||
"Cc": "CC",
|
||||
"Change": "Ändern",
|
||||
"Change Customer": "Kunde ändern",
|
||||
@@ -202,6 +206,7 @@
|
||||
"Customer": "Kunde",
|
||||
"Customer Name": "Kundenname",
|
||||
"Customer Profile": "Kundenprofil",
|
||||
"Customer Visibility": "Kundensichtbarkeit",
|
||||
"Customer changed": "Kunde geändert",
|
||||
"Customer saved successfully.": "Kunde erfolgreich gespeichert.",
|
||||
"Customer viewed :when": "Kunde betrachtete :when",
|
||||
@@ -426,11 +431,13 @@
|
||||
"Make sure to :%link_start%enable less secure apps:%link_end% in your Google account to send emails from Gmail.": "Sicherstellen, dass :%link_start%enable less secure apps:%link_end% in Ihrem Google-Konto zum Senden der E-Mails aktiviert ist.",
|
||||
"Make sure to save settings before checking connection.": "Zum Ausführen eines Verbindungstests müssen die Einstellungen zuvor gespeichert werden.",
|
||||
"Make sure to save settings before testing.": "Sicherstellen, dass die Einstellungen vor dem Testen gespeichert werden.",
|
||||
"Make this conversation visible to all users with mailbox access": "Diese Unterhaltung für alle Benutzer mit Postfachzugriff sichtbar machen",
|
||||
"Male": "männlich",
|
||||
"Manage": "Verwalten",
|
||||
"Manage Mailboxes": "Postfächer verwalten",
|
||||
"Manage Users": "Benutzer verwalten",
|
||||
"Mark all as read": "Alle als gelesen markieren",
|
||||
"Marketplace": "Marktplatz",
|
||||
"Max. Message Size": "maximale Nachrichtengröße",
|
||||
"Me": "Mich",
|
||||
"Merge": "Zusammenführen",
|
||||
@@ -503,6 +510,7 @@
|
||||
"Off": "Aus",
|
||||
"Older": "Älter",
|
||||
"On": "Ein",
|
||||
"Only customers from accessible mailboxes": "Nur Kunden mit zugänglichen Postfächern",
|
||||
"Only visible in :app_name.": "Nur in :app_name sichtbar.",
|
||||
"Open": "Offen",
|
||||
"Open Conversation": "Gespräch öffnen",
|
||||
@@ -523,6 +531,7 @@
|
||||
"Password changed notification": "Benachrichtigung über die Änderung des Passworts",
|
||||
"Password reset email has been sent": "E-Mail zum zurücksetzen des Passworts wurde gesendet",
|
||||
"Password saved successfully!": "Passwort erfolgreich gespeichert!",
|
||||
"Paste as Plain Text": "Als unformatierten Text einfügen",
|
||||
"Pending": "Ausstehend",
|
||||
"Permissions": "Berechtigungen",
|
||||
"Permissions saved successfully": "Berechtigungen erfolgreich gespeichert",
|
||||
@@ -552,12 +561,14 @@
|
||||
"Profile Photo (URL)": "Profilfoto (URL)",
|
||||
"Profile saved successfully": "Profil erfolgreich gespeichert",
|
||||
"Protocol": "Protokoll",
|
||||
"Public conversation": "Öffentliches Gespräch",
|
||||
"Published": "Veröffentlicht",
|
||||
"Queue": "Warteschlange",
|
||||
"Queued Jobs": "Aufgaben in der Warteschlange",
|
||||
"Queued and failed jobs are cleaned automatically once in a while. No need to worry or delete them manually.": "In die Warteschlange gestellte und fehlgeschlagene Jobs werden von Zeit zu Zeit automatisch bereinigt. Sie müssen sich keine Sorgen machen oder sie manuell löschen.",
|
||||
"Re-send invite email": "Einladungs-E-Mail erneut senden",
|
||||
"Received a new conversation": "Neues Gespräch empfangen",
|
||||
"Receiving emails need to be configured for the mailbox in order to fetch emails from your support email address": "Der E-Mail Empfang muss für dieses Postfach konfiguriert werden",
|
||||
"Recent": "Neueste",
|
||||
"Recipient clicked a link in the message": "Der Empfänger hat auf einen Link in der Nachricht geklickt",
|
||||
"Recipient complained": "Der Empfänger hat sich beschwert",
|
||||
@@ -672,8 +683,10 @@
|
||||
"System Emails": "System E-Mails",
|
||||
"System Status": "System Status",
|
||||
"System error": "System Fehler",
|
||||
"Table View": "Tabellenansicht",
|
||||
"Test email": "Test-E-Mail",
|
||||
"The :module_name module has been deactivated due to an error: :error_message": "Das :module_name Modul wurde auf Grund des folgenden Fehlers deaktiviert : :error_message",
|
||||
"The conversation you replied to could not be found.": "Die Unterhaltung, auf die Sie geantwortet haben, konnte nicht gefunden werden.",
|
||||
"The following modules have to be installed and activated: :modules": "Die folgenden Module müssen installiert und aktiviert sein: :modules",
|
||||
"The new password is the same as the old password.": "Das neue Passwort ist das gleiche wie das alte.",
|
||||
"The original message could not be loaded from mail server, below is the latest truncated copy stored in database.": "Die original Nachricht kann nicht vom Server geladen werden, nachfolgend finden Sie die letzte - in der Datenbank gespeicherte Kopie.",
|
||||
@@ -688,9 +701,9 @@
|
||||
"These folders must be writable by web server user (:user).": "Der Webserver Benutzer (:user) muss für diese Ordner Schreibrechte besitzen.",
|
||||
"These settings are used to send system emails (alerts to admin and invitation emails to users).": "Diese Einstellungen werden verwendet um System-E-Mails zu versenden (Warnungen an Admins und Einladungen an Benutzer).",
|
||||
"Third-Party": "Drittanbieter",
|
||||
"Third-Party Modules": "Drittanbietermodule",
|
||||
"This is a bounce message.": "Dies ist eine Rückantwort.",
|
||||
"This is a forwarded conversation.": "Dies ist ein weitergeleitetes Gespräch.",
|
||||
"This is a public conversation visible to all users with mailbox access": "Dies ist eine öffentliche Unterhaltung, die für alle Benutzer mit Postfachzugriff sichtbar ist.",
|
||||
"This is a quick note to let you know that your :company_name password has been successfully updated. If you didn't request this change, please contact the administrator.": "Dies ist eine kurze Benachrichtigung um Sie zu informieren, dass das Passwort von :company_name erfolgreich aktualisiert wurde.",
|
||||
"This is a test mail sent by :app_name. It means that outgoing email settings of your :mailbox mailbox are fine.": "Dies ist eine Test-E-Mail gesendet von :app_name. Das bedeutet, dass die Einstellungen für ausgehende E-Mails des Postfaches :mailbox korrekt sind.",
|
||||
"This is a test system mail sent by :app_name. It means that mail settings are fine.": "Dies ist eine Test-E-Mail gesendet von :app_name. Das bedeutet, dass die E-Mail-Einstellungen korrekt sind.",
|
||||
@@ -752,6 +765,8 @@
|
||||
"User's Name": "Name des Benutzers",
|
||||
"Username": "Benutzername",
|
||||
"Users": "Benutzer",
|
||||
"Users and teams": "Benutzer und Teams",
|
||||
"Users and teams with access": "Benutzer und Teams mit Zugriff",
|
||||
"Users are allowed to delete conversations": "Benutzer sind berechtigt Gespräche zu löschen",
|
||||
"Users are allowed to edit own notes\/replies": "Benutzer sind dazu berechtigt Notizen\/Antworten zu ändern",
|
||||
"Users are allowed to edit\/delete saved replies": "Benutzer sind dazu berechtigt gespeicherte Antworten zu bearbeiten\/löschen",
|
||||
@@ -780,6 +795,8 @@
|
||||
"Welcome to :company_name, :first_name!": "Willkommen bei :company_name, :first_name!",
|
||||
"Welcome to the team!": "Willkommen im Team!",
|
||||
"When a customer emails this mailbox, application can send an auto reply to the customer immediately.<br\/><br\/>Only one auto reply is sent per new conversation.": "Wenn ein Kunde an dieses Postfach schreibt, kann die Anwendung automatisch antworten.<br\/><br\/>Es wird nur eine automatische Antwort pro neuem Gespräch gesendet.",
|
||||
"When enabled, all users with mailbox access can view this conversation": "Wenn diese Option aktiviert ist, können alle Benutzer mit Postfachzugriff diese Unterhaltung sehen.",
|
||||
"Which customers non-admin users can see.": "Welche Kunden Nicht-Administratoren sehen können.",
|
||||
"Which mailboxes will user use?": "Welches E-Mail-Postfach wird der Benutzer nutzen?",
|
||||
"Who Else Will Use This Mailbox": "Wer wird dieses Postfach noch benutzen?",
|
||||
"Work": "Arbeit",
|
||||
@@ -792,6 +809,7 @@
|
||||
"You can read more about sending emails :%a_begin%here:%a_end%.": "Sie können :%a_begin%here:%a_end% mehr über den Versand von E-Mails lesen.",
|
||||
"You have the latest version installed": "Die aktuellste Version ist installiert",
|
||||
"You have to activate each bundle's module separately": "Jedes Modul im Paket muss separat aktiviert werden",
|
||||
"You no longer have access to the conversation you replied to. Please contact your administrator.": "Sie haben keinen Zugriff mehr auf die Unterhaltung, auf die Sie geantwortet haben. Bitte wenden Sie sich an Ihren Administrator.",
|
||||
"You've already sent this message just recently.": "Die Nachricht wurde grade eben bereits gesendet.",
|
||||
"Your Email": "Ihre Email",
|
||||
"Your Profile": "Ihr Profil",
|
||||
|
||||
@@ -146,7 +146,7 @@
|
||||
@if (empty($no_customer))
|
||||
<td class="conv-customer">
|
||||
<a href="{{ $conversation->url() }}" @if (!empty($params['target_blank'])) target="_blank" @endif title="{{ $conversation->customer_email }}">
|
||||
@if ($conversation->customer_id && $conversation->customer){{ $conversation->customer->getFullName(true)}}@endif @if ($conversation->threads_count > 1)<span class="conv-counter">{{ $conversation->threads_count }}</span>@endif
|
||||
<span class="conv-customer-line"><span class="conv-customer-name">@if ($conversation->customer_id && $conversation->customer){{ $conversation->customer->getFullName(true)}}@endif </span>@if ($conversation->threads_count > 1)<span class="conv-counter">{{ $conversation->threads_count }}</span>@endif</span>
|
||||
@if ($conversation->user_id)
|
||||
<small class="conv-owner-mobile text-help">
|
||||
{{ ($assignee = $conversation->user) ? $assignee->getFullName() : '' }} <small class="glyphicon glyphicon-user"></small>
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
{{-- maximum-scale=1 prevents iOS from zooming in on focusing a field (#4698). Android does not zoom in on focus, but disables pinch zoom with maximum-scale. --}}
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1{{ \Helper::isAndroid() ? '' : ', maximum-scale=1' }}">
|
||||
<meta name="robots" content="noindex,nofollow">
|
||||
|
||||
<!-- CSRF Token -->
|
||||
|
||||
Reference in New Issue
Block a user