Page MenuHomePhorge

D3397.1775469610.diff
No OneTemporary

Authored By
Unknown
Size
287 KB
Referenced Files
None
Subscribers
None

D3397.1775469610.diff

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/bin/quickstart.sh b/bin/quickstart.sh
--- a/bin/quickstart.sh
+++ b/bin/quickstart.sh
@@ -97,9 +97,8 @@
./artisan db:ping --wait
php -dmemory_limit=512M ./artisan migrate:refresh --seed
./artisan data:import || :
-./artisan swoole:http stop >/dev/null 2>&1 || :
-SWOOLE_HTTP_DAEMONIZE=true ./artisan swoole:http start
+./artisan octane:stop >/dev/null 2>&1 || :
+./artisan octane:start >/dev/null 2>&1 &
./artisan horizon:terminate >/dev/null 2>&1 || :
nohup ./artisan horizon >/dev/null 2>&1 &
popd
-
diff --git a/docker/swoole/rootfs/usr/local/bin/run-container b/docker/swoole/rootfs/usr/local/bin/run-container
--- a/docker/swoole/rootfs/usr/local/bin/run-container
+++ b/docker/swoole/rootfs/usr/local/bin/run-container
@@ -36,7 +36,7 @@
env
- exec ./artisan swoole:http start
+ exec ./artisan octane:start
else
exec $@
fi
diff --git a/src/.env.example b/src/.env.example
--- a/src/.env.example
+++ b/src/.env.example
@@ -30,6 +30,8 @@
LOG_CHANNEL=stack
LOG_SLOW_REQUESTS=5
+LOG_DEPRECATIONS_CHANNEL=null
+LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_DATABASE=kolabdev
@@ -127,7 +129,7 @@
STRIPE_PUBLIC_KEY=
STRIPE_WEBHOOK_SECRET=
-MAIL_DRIVER=smtp
+MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
@@ -147,6 +149,7 @@
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
+AWS_USE_PATH_STYLE_ENDPOINT=false
PUSHER_APP_ID=
PUSHER_APP_KEY=
diff --git a/src/.gitattributes b/src/.gitattributes
--- a/src/.gitattributes
+++ b/src/.gitattributes
@@ -1,5 +1,9 @@
* text=auto
-*.css linguist-vendored
-*.scss linguist-vendored
-*.js linguist-vendored
+
+*.blade.php diff=html
+*.css diff=css
+*.html diff=html
+*.md diff=markdown
+*.php diff=php
+
CHANGELOG.md export-ignore
diff --git a/src/.styleci.yml b/src/.styleci.yml
--- a/src/.styleci.yml
+++ b/src/.styleci.yml
@@ -1,11 +1,10 @@
php:
preset: laravel
disabled:
- - unused_use
+ - no_unused_imports
finder:
not-name:
- index.php
- - server.php
js:
finder:
not-name:
diff --git a/src/app/Auth/LDAPUserProvider.php b/src/app/Auth/LDAPUserProvider.php
deleted file mode 100644
--- a/src/app/Auth/LDAPUserProvider.php
+++ /dev/null
@@ -1,54 +0,0 @@
-<?php
-
-namespace App\Auth;
-
-use App\User;
-use Illuminate\Auth\EloquentUserProvider;
-use Illuminate\Support\Facades\Hash;
-use Illuminate\Contracts\Auth\Authenticatable;
-use Illuminate\Contracts\Auth\UserProvider;
-
-/**
- * A user provider that integrates an LDAP deployment.
- */
-class LDAPUserProvider extends EloquentUserProvider implements UserProvider
-{
- /**
- * Retrieve the user by its credentials (email).
- *
- * @param array $credentials An array containing the email and password.
- *
- * @return User|null
- */
- public function retrieveByCredentials(array $credentials)
- {
- $entries = User::where('email', \strtolower($credentials['email']))->get();
-
- $count = $entries->count();
-
- if ($count == 1) {
- return $entries->first();
- }
-
- if ($count > 1) {
- \Log::warning("Multiple entries for {$credentials['email']}");
- } else {
- \Log::warning("No entries for {$credentials['email']}");
- }
-
- return null;
- }
-
- /**
- * Validate the credentials for a user.
- *
- * @param Authenticatable $user The user.
- * @param array $credentials The credentials.
- *
- * @return bool
- */
- public function validateCredentials(Authenticatable $user, array $credentials): bool
- {
- return $user->validateCredentials($credentials['email'], $credentials['password']);
- }
-}
diff --git a/src/app/AuthAttempt.php b/src/app/AuthAttempt.php
--- a/src/app/AuthAttempt.php
+++ b/src/app/AuthAttempt.php
@@ -2,10 +2,10 @@
namespace App;
-use Illuminate\Database\Eloquent\Model;
-use Iatstuti\Database\Support\NullableFields;
use App\Traits\UuidStrKeyTrait;
use Carbon\Carbon;
+use Dyrynda\Database\Support\NullableFields;
+use Illuminate\Database\Eloquent\Model;
/**
* The eloquent definition of an AuthAttempt.
@@ -27,10 +27,10 @@
private const STATUS_ACCEPTED = 'ACCEPTED';
private const STATUS_DENIED = 'DENIED';
- protected $nullable = [
- 'reason',
- ];
+ /** @var array<int, string> The attributes that can be not set */
+ protected $nullable = ['reason'];
+ /** @var array<int, string> The attributes that are mass assignable */
protected $fillable = [
'ip',
'user_id',
@@ -40,6 +40,7 @@
'last_seen',
];
+ /** @var array<string, string> The attributes that should be cast */
protected $casts = [
'expires_at' => 'datetime',
'last_seen' => 'datetime'
@@ -106,7 +107,7 @@
*/
public function notify(): bool
{
- return \App\CompanionApp::notifyUser($this->user_id, ['token' => $this->id]);
+ return CompanionApp::notifyUser($this->user_id, ['token' => $this->id]);
}
/**
@@ -154,12 +155,12 @@
*
* @return \App\AuthAttempt
*/
- public static function recordAuthAttempt(\App\User $user, $clientIP)
+ public static function recordAuthAttempt(User $user, $clientIP)
{
- $authAttempt = \App\AuthAttempt::where('ip', $clientIP)->where('user_id', $user->id)->first();
+ $authAttempt = AuthAttempt::where('ip', $clientIP)->where('user_id', $user->id)->first();
if (!$authAttempt) {
- $authAttempt = new \App\AuthAttempt();
+ $authAttempt = new AuthAttempt();
$authAttempt->ip = $clientIP;
$authAttempt->user_id = $user->id;
}
diff --git a/src/app/Backends/LDAP.php b/src/app/Backends/LDAP.php
--- a/src/app/Backends/LDAP.php
+++ b/src/app/Backends/LDAP.php
@@ -1031,6 +1031,7 @@
$entry['kolabfoldertype'] = $folder->type;
$entry['kolabtargetfolder'] = $settings['folder'] ?? '';
$entry['acl'] = !empty($settings['acl']) ? json_decode($settings['acl'], true) : '';
+ $entry['alias'] = $folder->aliases()->pluck('alias')->all();
}
/**
@@ -1073,7 +1074,7 @@
$entry['inetuserstatus'] = $user->status;
$entry['o'] = $settings['organization'];
$entry['mailquota'] = 0;
- $entry['alias'] = $user->aliases->pluck('alias')->toArray();
+ $entry['alias'] = $user->aliases()->pluck('alias')->all();
$roles = [];
@@ -1193,7 +1194,7 @@
$domainName = explode('@', $email, 2)[1];
$base_dn = self::baseDN($ldap, $domainName, 'Shared Folders');
- $attrs = ['dn', 'cn', 'mail', 'objectclass', 'kolabtargetfolder', 'kolabfoldertype', 'acl'];
+ $attrs = ['dn', 'cn', 'mail', 'objectclass', 'kolabtargetfolder', 'kolabfoldertype', 'acl', 'alias'];
// For shared folders we're using search() instead of get_entry() because
// a folder name is not constant, so e.g. on update we might have
@@ -1355,7 +1356,7 @@
*/
private static function throwException($ldap, string $message): void
{
- if (empty(self::$ldap) && !empty($ldap)) {
+ if (empty(self::$ldap)) {
$ldap->close();
}
diff --git a/src/app/CompanionApp.php b/src/app/CompanionApp.php
--- a/src/app/CompanionApp.php
+++ b/src/app/CompanionApp.php
@@ -11,6 +11,7 @@
*/
class CompanionApp extends Model
{
+ /** @var array<int, string> The attributes that are mass assignable */
protected $fillable = [
'name',
'user_id',
@@ -67,7 +68,7 @@
*/
public static function notifyUser($userId, $data): bool
{
- $notificationTokens = \App\CompanionApp::where('user_id', $userId)
+ $notificationTokens = CompanionApp::where('user_id', $userId)
->where('mfa_enabled', true)
->pluck('notification_token')
->all();
diff --git a/src/app/Console/Command.php b/src/app/Console/Command.php
--- a/src/app/Console/Command.php
+++ b/src/app/Console/Command.php
@@ -9,9 +9,9 @@
/**
* This needs to be here to be used.
*
- * @var null
+ * @var string
*/
- protected $commandPrefix = null;
+ protected $commandPrefix = '';
/**
* Annotate this command as being dangerous for any potential unintended consequences.
diff --git a/src/app/Console/Commands/Data/Import/LdifCommand.php b/src/app/Console/Commands/Data/Import/LdifCommand.php
--- a/src/app/Console/Commands/Data/Import/LdifCommand.php
+++ b/src/app/Console/Commands/Data/Import/LdifCommand.php
@@ -121,6 +121,7 @@
$lastAttr = null;
$insertFunc = function ($limit = 0) use (&$entry, &$inserts) {
+ // @phpstan-ignore-next-line
if (!empty($entry)) {
if ($entry = $this->parseLDAPEntry($entry)) {
$inserts[] = $entry;
@@ -342,7 +343,7 @@
$resource = new \App\Resource();
$resource->name = $data->name;
- $resource->domain = $data->domain;
+ $resource->domainName = $data->domain;
$resource->save();
$resource->assignToWallet($this->wallet);
@@ -396,7 +397,7 @@
$folder = new \App\SharedFolder();
$folder->name = $data->name;
$folder->type = $data->type ?? 'mail';
- $folder->domain = $data->domain;
+ $folder->domainName = $data->domain;
$folder->save();
$folder->assignToWallet($this->wallet);
@@ -410,6 +411,11 @@
if (!empty($data->folder)) {
$folder->setSetting('folder', $data->folder);
}
+
+ // Import aliases
+ if (!empty($data->aliases)) {
+ $this->setObjectAliases($folder, $data->aliases);
+ }
}
$bar->finish();
@@ -431,7 +437,7 @@
// Import aliases of the owner, we got from importOwner() call
if (!empty($this->aliases) && $this->wallet) {
- $this->setUserAliases($this->wallet->owner, $this->aliases);
+ $this->setObjectAliases($this->wallet->owner, $this->aliases);
}
$bar = $this->createProgressBar($users->count(), "Importing users");
@@ -537,7 +543,7 @@
// domain records yet, save the aliases to be inserted later (in importUsers())
$this->aliases = $data->aliases;
} else {
- $this->setUserAliases($user, $data->aliases);
+ $this->setObjectAliases($user, $data->aliases);
}
}
@@ -706,6 +712,10 @@
if (!empty($entry['acl'])) {
$result['acl'] = $this->parseACL($this->attrArrayValue($entry, 'acl'));
}
+
+ if (!empty($entry['alias'])) {
+ $result['aliases'] = $this->attrArrayValue($entry, 'alias');
+ }
}
return [$result, $error];
@@ -957,14 +967,14 @@
}
/**
- * Set aliases for the user
+ * Set aliases for for an object
*/
- protected function setUserAliases(\App\User $user, array $aliases = [])
+ protected function setObjectAliases($object, array $aliases = [])
{
if (!empty($aliases)) {
// Some users might have alias entry with their main address, remove it
$aliases = array_map('strtolower', $aliases);
- $aliases = array_diff(array_unique($aliases), [$user->email]);
+ $aliases = array_diff(array_unique($aliases), [$object->email]);
// Remove aliases for domains that do not exist
if (!empty($aliases)) {
@@ -977,7 +987,7 @@
}
if (!empty($aliases)) {
- $user->setAliases($aliases);
+ $object->setAliases($aliases);
}
}
}
diff --git a/src/app/Console/Commands/SharedFolder/AddAliasCommand.php b/src/app/Console/Commands/SharedFolder/AddAliasCommand.php
new file mode 100644
--- /dev/null
+++ b/src/app/Console/Commands/SharedFolder/AddAliasCommand.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace App\Console\Commands\SharedFolder;
+
+use App\Console\Command;
+use App\Http\Controllers\API\V4\UsersController;
+
+class AddAliasCommand extends Command
+{
+ /**
+ * The name and signature of the console command.
+ *
+ * @var string
+ */
+ protected $signature = 'sharedfolder:add-alias {--force} {folder} {alias}';
+
+ /**
+ * The console command description.
+ *
+ * @var string
+ */
+ protected $description = "Add an email alias to a shared folder (forcefully)";
+
+ /**
+ * Execute the console command.
+ *
+ * @return mixed
+ */
+ public function handle()
+ {
+ $folder = $this->getSharedFolder($this->argument('folder'));
+
+ if (!$folder) {
+ $this->error("Folder not found.");
+ return 1;
+ }
+
+ $alias = \strtolower($this->argument('alias'));
+
+ // Check if the alias already exists
+ if ($folder->aliases()->where('alias', $alias)->first()) {
+ $this->error("Address is already assigned to the folder.");
+ return 1;
+ }
+
+ // Validate the alias
+ $error = UsersController::validateAlias($alias, $folder->walletOwner());
+
+ if ($error) {
+ if (!$this->option('force')) {
+ $this->error($error);
+ return 1;
+ }
+ }
+
+ $folder->aliases()->create(['alias' => $alias]);
+ }
+}
diff --git a/src/app/Console/Commands/User/AliasesCommand.php b/src/app/Console/Commands/SharedFolder/AliasesCommand.php
copy from src/app/Console/Commands/User/AliasesCommand.php
copy to src/app/Console/Commands/SharedFolder/AliasesCommand.php
--- a/src/app/Console/Commands/User/AliasesCommand.php
+++ b/src/app/Console/Commands/SharedFolder/AliasesCommand.php
@@ -1,6 +1,6 @@
<?php
-namespace App\Console\Commands\User;
+namespace App\Console\Commands\SharedFolder;
use App\Console\Command;
@@ -11,14 +11,14 @@
*
* @var string
*/
- protected $signature = 'user:aliases {user}';
+ protected $signature = 'sharedfolder:aliases {folder}';
/**
* The console command description.
*
* @var string
*/
- protected $description = "List a user's aliases";
+ protected $description = "List shared folder's aliases";
/**
* Execute the console command.
@@ -27,15 +27,15 @@
*/
public function handle()
{
- $user = $this->getUser($this->argument('user'));
+ $folder = $this->getSharedFolder($this->argument('folder'));
- if (!$user) {
- $this->error("User not found.");
+ if (!$folder) {
+ $this->error("Folder not found.");
return 1;
}
- foreach ($user->aliases as $alias) {
- $this->info("{$alias->alias}");
+ foreach ($folder->aliases()->pluck('alias')->all() as $alias) {
+ $this->info($alias);
}
}
}
diff --git a/src/app/Console/Commands/SharedFolder/CreateCommand.php b/src/app/Console/Commands/SharedFolder/CreateCommand.php
--- a/src/app/Console/Commands/SharedFolder/CreateCommand.php
+++ b/src/app/Console/Commands/SharedFolder/CreateCommand.php
@@ -79,7 +79,7 @@
$folder = new SharedFolder();
$folder->name = $name;
$folder->type = $type;
- $folder->domain = $domainName;
+ $folder->domainName = $domainName;
$folder->save();
$folder->assignToWallet($owner->wallets->first());
diff --git a/src/app/Console/Commands/SharedFolderAliasesCommand.php b/src/app/Console/Commands/SharedFolderAliasesCommand.php
new file mode 100644
--- /dev/null
+++ b/src/app/Console/Commands/SharedFolderAliasesCommand.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Console\ObjectListCommand;
+
+class SharedFolderAliasesCommand extends ObjectListCommand
+{
+ protected $objectClass = \App\SharedFolderAlias::class;
+ protected $objectName = 'shared-folder-alias';
+ protected $objectNamePlural = 'shared-folder-aliases';
+ protected $objectTitle = 'alias';
+}
diff --git a/src/app/Console/Commands/User/AliasesCommand.php b/src/app/Console/Commands/User/AliasesCommand.php
--- a/src/app/Console/Commands/User/AliasesCommand.php
+++ b/src/app/Console/Commands/User/AliasesCommand.php
@@ -34,8 +34,8 @@
return 1;
}
- foreach ($user->aliases as $alias) {
- $this->info("{$alias->alias}");
+ foreach ($user->aliases()->pluck('alias')->all() as $alias) {
+ $this->info($alias);
}
}
}
diff --git a/src/app/Console/Kernel.php b/src/app/Console/Kernel.php
--- a/src/app/Console/Kernel.php
+++ b/src/app/Console/Kernel.php
@@ -7,15 +7,6 @@
class Kernel extends ConsoleKernel
{
- /**
- * The Artisan commands provided by your application.
- *
- * @var array
- */
- protected $commands = [
- //
- ];
-
/**
* Define the application's command schedule.
*
diff --git a/src/app/Console/ObjectCommand.php b/src/app/Console/ObjectCommand.php
--- a/src/app/Console/ObjectCommand.php
+++ b/src/app/Console/ObjectCommand.php
@@ -13,7 +13,7 @@
*
* @var string
*/
- protected $commandPrefix = null;
+ protected $commandPrefix = '';
/**
* The object class that we are operating on, for example \App\User::class
diff --git a/src/app/Console/ObjectCreateCommand.php b/src/app/Console/ObjectCreateCommand.php
--- a/src/app/Console/ObjectCreateCommand.php
+++ b/src/app/Console/ObjectCreateCommand.php
@@ -61,7 +61,5 @@
} else {
$this->error("Object could not be created.");
}
-
- return $object;
}
}
diff --git a/src/app/Discount.php b/src/app/Discount.php
--- a/src/app/Discount.php
+++ b/src/app/Discount.php
@@ -26,17 +26,12 @@
'discount' => 'integer',
];
- protected $fillable = [
- 'active',
- 'code',
- 'description',
- 'discount',
- ];
+ /** @var array<int, string> The attributes that are mass assignable */
+ protected $fillable = ['active', 'code', 'description', 'discount'];
+
+ /** @var array<int, string> Translatable properties */
+ public $translatable = ['description'];
- /** @var array Translatable properties */
- public $translatable = [
- 'description',
- ];
/**
* Discount value mutator
@@ -67,6 +62,6 @@
*/
public function wallets()
{
- return $this->hasMany('App\Wallet');
+ return $this->hasMany(Wallet::class);
}
}
diff --git a/src/app/Domain.php b/src/app/Domain.php
--- a/src/app/Domain.php
+++ b/src/app/Domain.php
@@ -2,7 +2,6 @@
namespace App;
-use App\Wallet;
use App\Traits\BelongsToTenantTrait;
use App\Traits\DomainConfigTrait;
use App\Traits\EntitleableTrait;
@@ -56,12 +55,16 @@
public const HASH_TEXT = 2;
public const HASH_CNAME = 3;
- protected $fillable = [
- 'namespace',
- 'status',
- 'type'
+ /** @var array<string, string> The attributes that should be cast */
+ protected $casts = [
+ 'created_at' => 'datetime:Y-m-d H:i:s',
+ 'deleted_at' => 'datetime:Y-m-d H:i:s',
+ 'updated_at' => 'datetime:Y-m-d H:i:s',
];
+ /** @var array<int, string> The attributes that are mass assignable */
+ protected $fillable = ['namespace', 'status', 'type'];
+
/**
* Assign a package to a domain. The domain should not belong to any existing entitlements.
*
@@ -97,8 +100,8 @@
public static function getPublicDomains(): array
{
return self::withEnvTenantContext()
- ->whereRaw(sprintf('(type & %s)', Domain::TYPE_PUBLIC))
- ->get(['namespace'])->pluck('namespace')->toArray();
+ ->where('type', '&', Domain::TYPE_PUBLIC)
+ ->pluck('namespace')->all();
}
/**
@@ -315,11 +318,11 @@
$suffixLen = strlen($suffix);
return !(
- \App\User::whereRaw('substr(email, ?) = ?', [-$suffixLen, $suffix])->exists()
- || \App\UserAlias::whereRaw('substr(alias, ?) = ?', [-$suffixLen, $suffix])->exists()
- || \App\Group::whereRaw('substr(email, ?) = ?', [-$suffixLen, $suffix])->exists()
- || \App\Resource::whereRaw('substr(email, ?) = ?', [-$suffixLen, $suffix])->exists()
- || \App\SharedFolder::whereRaw('substr(email, ?) = ?', [-$suffixLen, $suffix])->exists()
+ User::whereRaw('substr(email, ?) = ?', [-$suffixLen, $suffix])->exists()
+ || UserAlias::whereRaw('substr(alias, ?) = ?', [-$suffixLen, $suffix])->exists()
+ || Group::whereRaw('substr(email, ?) = ?', [-$suffixLen, $suffix])->exists()
+ || Resource::whereRaw('substr(email, ?) = ?', [-$suffixLen, $suffix])->exists()
+ || SharedFolder::whereRaw('substr(email, ?) = ?', [-$suffixLen, $suffix])->exists()
);
}
@@ -370,7 +373,7 @@
return [];
}
- $mailboxSKU = \App\Sku::withObjectTenantContext($this)->where('title', 'mailbox')->first();
+ $mailboxSKU = Sku::withObjectTenantContext($this)->where('title', 'mailbox')->first();
if (!$mailboxSKU) {
\Log::error("No mailbox SKU available.");
@@ -378,7 +381,7 @@
}
return $wallet->entitlements()
- ->where('entitleable_type', \App\User::class)
+ ->where('entitleable_type', User::class)
->where('sku_id', $mailboxSKU->id)
->get()
->pluck('entitleable')
diff --git a/src/app/DomainSetting.php b/src/app/DomainSetting.php
--- a/src/app/DomainSetting.php
+++ b/src/app/DomainSetting.php
@@ -14,9 +14,8 @@
*/
class DomainSetting extends Model
{
- protected $fillable = [
- 'domain_id', 'key', 'value'
- ];
+ /** @var array<int, string> The attributes that are mass assignable */
+ protected $fillable = ['domain_id', 'key', 'value'];
/**
* The domain to which this setting belongs.
@@ -26,7 +25,7 @@
public function domain()
{
return $this->belongsTo(
- '\App\Domain',
+ Domain::class,
'domain_id', /* local */
'id' /* remote */
);
diff --git a/src/app/Entitlement.php b/src/app/Entitlement.php
--- a/src/app/Entitlement.php
+++ b/src/app/Entitlement.php
@@ -2,9 +2,9 @@
namespace App;
+use App\Traits\UuidStrKeyTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
-use App\Traits\UuidStrKeyTrait;
/**
* The eloquent definition of an Entitlement.
@@ -29,11 +29,7 @@
use SoftDeletes;
use UuidStrKeyTrait;
- /**
- * The fillable columns for this Entitlement
- *
- * @var array
- */
+ /** @var array<int, string> The attributes that are mass assignable */
protected $fillable = [
'sku_id',
'wallet_id',
@@ -44,6 +40,7 @@
'fee',
];
+ /** @var array<string, string> The attributes that should be cast */
protected $casts = [
'cost' => 'integer',
'fee' => 'integer'
@@ -80,10 +77,10 @@
*/
public function createTransaction($type, $amount = null)
{
- $transaction = \App\Transaction::create(
+ $transaction = Transaction::create(
[
'object_id' => $this->id,
- 'object_type' => \App\Entitlement::class,
+ 'object_type' => Entitlement::class,
'type' => $type,
'amount' => $amount
]
@@ -110,7 +107,7 @@
*/
public function entitleableTitle(): ?string
{
- if ($this->entitleable instanceof \App\Domain) {
+ if ($this->entitleable instanceof Domain) {
return $this->entitleable->namespace;
}
@@ -151,7 +148,7 @@
*/
public function sku()
{
- return $this->belongsTo('App\Sku');
+ return $this->belongsTo(Sku::class);
}
/**
@@ -161,7 +158,7 @@
*/
public function wallet()
{
- return $this->belongsTo('App\Wallet');
+ return $this->belongsTo(Wallet::class);
}
/**
diff --git a/src/app/Exceptions/Handler.php b/src/app/Exceptions/Handler.php
--- a/src/app/Exceptions/Handler.php
+++ b/src/app/Exceptions/Handler.php
@@ -2,61 +2,37 @@
namespace App\Exceptions;
-use Exception;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Support\Facades\DB;
class Handler extends ExceptionHandler
{
- /**
- * A list of the exception types that are not reported.
- *
- * @var array
- */
+ /** @var string[] A list of the exception types that are not reported */
protected $dontReport = [
\Laravel\Passport\Exceptions\OAuthServerException::class,
\League\OAuth2\Server\Exception\OAuthServerException::class
];
- /**
- * A list of the inputs that are never flashed for validation exceptions.
- *
- * @var array
- */
+ /** @var string[] A list of the inputs that are never flashed for validation exceptions */
protected $dontFlash = [
+ 'current_password',
'password',
'password_confirmation',
];
- /**
- * Report or log an exception.
- *
- * @param \Exception $exception
- *
- * @return void
- */
- public function report(Exception $exception)
- {
- parent::report($exception);
- }
/**
- * Render an exception into an HTTP response.
- *
- * @param \Illuminate\Http\Request $request
- * @param \Exception $exception
- *
- * @return \Symfony\Component\HttpFoundation\Response
+ * Register the exception handling callbacks for the application.
*/
- public function render($request, Exception $exception)
+ public function register()
{
- // Rollback uncommitted transactions
- while (DB::transactionLevel() > 0) {
- DB::rollBack();
- }
-
- return parent::render($request, $exception);
+ $this->reportable(function (\Throwable $e) {
+ // Rollback uncommitted transactions
+ while (DB::transactionLevel() > 0) {
+ DB::rollBack();
+ }
+ });
}
/**
diff --git a/src/app/Group.php b/src/app/Group.php
--- a/src/app/Group.php
+++ b/src/app/Group.php
@@ -4,11 +4,11 @@
use App\Traits\BelongsToTenantTrait;
use App\Traits\EntitleableTrait;
+use App\Traits\EmailPropertyTrait;
use App\Traits\GroupConfigTrait;
use App\Traits\SettingsTrait;
use App\Traits\StatusPropertyTrait;
use App\Traits\UuidIntKeyTrait;
-use App\Wallet;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
@@ -26,6 +26,7 @@
{
use BelongsToTenantTrait;
use EntitleableTrait;
+ use EmailPropertyTrait;
use GroupConfigTrait;
use SettingsTrait;
use SoftDeletes;
@@ -43,6 +44,14 @@
// group has been created in LDAP
public const STATUS_LDAP_READY = 1 << 4;
+ /** @var array<string, string> The attributes that should be cast */
+ protected $casts = [
+ 'created_at' => 'datetime:Y-m-d H:i:s',
+ 'deleted_at' => 'datetime:Y-m-d H:i:s',
+ 'updated_at' => 'datetime:Y-m-d H:i:s',
+ ];
+
+ /** @var array<int, string> The attributes that are mass assignable */
protected $fillable = [
'email',
'members',
@@ -51,43 +60,6 @@
];
- /**
- * Returns group domain.
- *
- * @return ?\App\Domain The domain group belongs to, NULL if it does not exist
- */
- public function domain(): ?Domain
- {
- list($local, $domainName) = explode('@', $this->email);
-
- return Domain::where('namespace', $domainName)->first();
- }
-
- /**
- * Find whether an email address exists as a group (including deleted groups).
- *
- * @param string $email Email address
- * @param bool $return_group Return Group instance instead of boolean
- *
- * @return \App\Group|bool True or Group model object if found, False otherwise
- */
- public static function emailExists(string $email, bool $return_group = false)
- {
- if (strpos($email, '@') === false) {
- return false;
- }
-
- $email = \strtolower($email);
-
- $group = self::withTrashed()->where('email', $email)->first();
-
- if ($group) {
- return $return_group ? $group : true;
- }
-
- return false;
- }
-
/**
* Group members propert accessor. Converts internal comma-separated list into an array
*
@@ -100,16 +72,6 @@
return $members ? explode(',', $members) : [];
}
- /**
- * Ensure the email is appropriately cased.
- *
- * @param string $email Group email address
- */
- public function setEmailAttribute(string $email)
- {
- $this->attributes['email'] = strtolower($email);
- }
-
/**
* Ensure the members are appropriately formatted.
*
diff --git a/src/app/GroupSetting.php b/src/app/GroupSetting.php
--- a/src/app/GroupSetting.php
+++ b/src/app/GroupSetting.php
@@ -14,9 +14,8 @@
*/
class GroupSetting extends Model
{
- protected $fillable = [
- 'group_id', 'key', 'value'
- ];
+ /** @var array<int, string> The attributes that are mass assignable */
+ protected $fillable = ['group_id', 'key', 'value'];
/**
* The group to which this setting belongs.
@@ -25,6 +24,6 @@
*/
public function group()
{
- return $this->belongsTo(\App\Group::class, 'group_id', 'id');
+ return $this->belongsTo(Group::class, 'group_id', 'id');
}
}
diff --git a/src/app/Http/Controllers/API/AuthController.php b/src/app/Http/Controllers/API/AuthController.php
--- a/src/app/Http/Controllers/API/AuthController.php
+++ b/src/app/Http/Controllers/API/AuthController.php
@@ -20,12 +20,13 @@
public function info()
{
$user = Auth::guard()->user();
- $response = V4\UsersController::userResponse($user);
if (!empty(request()->input('refresh'))) {
- return $this->refreshAndRespond(request(), $response);
+ return $this->refreshAndRespond(request(), $user);
}
+ $response = V4\UsersController::userResponse($user);
+
return response()->json($response);
}
@@ -51,10 +52,7 @@
$tokenResponse = app()->handle($proxyRequest);
- $response = V4\UsersController::userResponse($user);
- $response['status'] = 'success';
-
- return self::respondWithToken($tokenResponse, $response);
+ return self::respondWithToken($tokenResponse, $user);
}
/**
@@ -104,6 +102,7 @@
// Revoke all of the token's refresh tokens...
$refreshTokenRepository->revokeRefreshTokensByAccessTokenId($tokenId);
+
return response()->json([
'status' => 'success',
'message' => \trans('auth.logoutsuccess')
@@ -124,11 +123,11 @@
* Refresh the token and respond with it.
*
* @param \Illuminate\Http\Request $request The API request.
- * @param array $response Additional response data
+ * @param ?\App\User $user The user being authenticated
*
* @return \Illuminate\Http\JsonResponse
*/
- protected static function refreshAndRespond(Request $request, array $response = [])
+ protected static function refreshAndRespond(Request $request, $user = null)
{
$proxyRequest = Request::create('/oauth/token', 'POST', [
'grant_type' => 'refresh_token',
@@ -139,18 +138,18 @@
$tokenResponse = app()->handle($proxyRequest);
- return self::respondWithToken($tokenResponse, $response);
+ return self::respondWithToken($tokenResponse, $user);
}
/**
* Get the token array structure.
*
* @param \Illuminate\Http\JsonResponse $tokenResponse The response containing the token.
- * @param array $response Additional response data
+ * @param ?\App\User $user The user being authenticated
*
* @return \Illuminate\Http\JsonResponse
*/
- protected static function respondWithToken($tokenResponse, array $response = [])
+ protected static function respondWithToken($tokenResponse, $user = null)
{
$data = json_decode($tokenResponse->getContent());
@@ -163,6 +162,13 @@
return response()->json(['status' => 'error', 'message' => \trans('auth.failed')], 401);
}
+ if ($user) {
+ $response = V4\UsersController::userResponse($user);
+ } else {
+ $response = [];
+ }
+
+ $response['status'] = 'success';
$response['access_token'] = $data->access_token;
$response['refresh_token'] = $data->refresh_token;
$response['token_type'] = 'bearer';
diff --git a/src/app/Http/Controllers/API/PasswordResetController.php b/src/app/Http/Controllers/API/PasswordResetController.php
--- a/src/app/Http/Controllers/API/PasswordResetController.php
+++ b/src/app/Http/Controllers/API/PasswordResetController.php
@@ -16,10 +16,6 @@
*/
class PasswordResetController extends Controller
{
- /** @var \App\VerificationCode A verification code object */
- protected $code;
-
-
/**
* Sends password reset code to the user's external email
*
@@ -98,7 +94,7 @@
// For last-step remember the code object, so we can delete it
// with single SQL query (->delete()) instead of two (::destroy())
- $this->code = $code;
+ $request->code = $code;
return response()->json([
'status' => 'success',
@@ -121,7 +117,7 @@
return $v;
}
- $user = $this->code->user;
+ $user = $request->code->user;
// Validate the password
$v = Validator::make(
@@ -138,7 +134,7 @@
$user->save();
// Remove the verification code
- $this->code->delete();
+ $request->code->delete();
return AuthController::logonResponse($user, $request->password);
}
diff --git a/src/app/Http/Controllers/API/SignupController.php b/src/app/Http/Controllers/API/SignupController.php
--- a/src/app/Http/Controllers/API/SignupController.php
+++ b/src/app/Http/Controllers/API/SignupController.php
@@ -25,13 +25,6 @@
*/
class SignupController extends Controller
{
- /** @var ?\App\SignupCode A verification code object */
- protected $code;
-
- /** @var ?\App\Plan Signup plan object */
- protected $plan;
-
-
/**
* Returns plans definitions for signup.
*
@@ -177,7 +170,7 @@
// For signup last-step mode remember the code object, so we can delete it
// with single SQL query (->delete()) instead of two (::destroy())
- $this->code = $code;
+ $request->code = $code;
$has_domain = $this->getPlan()->hasDomain();
@@ -328,8 +321,8 @@
}
// Remove the verification code
- if ($this->code) {
- $this->code->delete();
+ if ($request->code) {
+ $request->code->delete();
}
DB::commit();
@@ -344,10 +337,12 @@
*/
protected function getPlan()
{
- if (!$this->plan) {
+ $request = request();
+
+ if (!$request->plan || !$request->plan instanceof Plan) {
// Get the plan if specified and exists...
- if ($this->code && $this->code->plan) {
- $plan = Plan::withEnvTenantContext()->where('title', $this->code->plan)->first();
+ if ($request->code && $request->code->plan) {
+ $plan = Plan::withEnvTenantContext()->where('title', $request->code->plan)->first();
}
// ...otherwise use the default plan
@@ -356,10 +351,10 @@
$plan = Plan::withEnvTenantContext()->where('title', 'individual')->first();
}
- $this->plan = $plan;
+ $request->plan = $plan;
}
- return $this->plan;
+ return $request->plan;
}
/**
diff --git a/src/app/Http/Controllers/API/V4/Admin/UsersController.php b/src/app/Http/Controllers/API/V4/Admin/UsersController.php
--- a/src/app/Http/Controllers/API/V4/Admin/UsersController.php
+++ b/src/app/Http/Controllers/API/V4/Admin/UsersController.php
@@ -58,13 +58,15 @@
$user_ids = $user_ids->merge($ext_user_ids)->unique();
- // Search by a distribution list or resource email
+ // Search by an email of a group, resource, shared folder, etc.
if ($group = \App\Group::withTrashed()->where('email', $search)->first()) {
$user_ids = $user_ids->merge([$group->wallet()->user_id])->unique();
} elseif ($resource = \App\Resource::withTrashed()->where('email', $search)->first()) {
$user_ids = $user_ids->merge([$resource->wallet()->user_id])->unique();
} elseif ($folder = \App\SharedFolder::withTrashed()->where('email', $search)->first()) {
$user_ids = $user_ids->merge([$folder->wallet()->user_id])->unique();
+ } elseif ($alias = \App\SharedFolderAlias::where('alias', $search)->first()) {
+ $user_ids = $user_ids->merge([$alias->sharedFolder->wallet()->user_id])->unique();
}
if (!$user_ids->isEmpty()) {
@@ -213,6 +215,8 @@
}
$user->assignSku($sku);
+
+ /** @var \App\Entitlement $entitlement */
$entitlement = $user->entitlements()->where('sku_id', $sku->id)->first();
return response()->json([
diff --git a/src/app/Http/Controllers/API/V4/AuthAttemptsController.php b/src/app/Http/Controllers/API/V4/AuthAttemptsController.php
--- a/src/app/Http/Controllers/API/V4/AuthAttemptsController.php
+++ b/src/app/Http/Controllers/API/V4/AuthAttemptsController.php
@@ -9,7 +9,6 @@
class AuthAttemptsController extends Controller
{
-
/**
* Confirm the authentication attempt.
*
diff --git a/src/app/Http/Controllers/API/V4/Reseller/UsersController.php b/src/app/Http/Controllers/API/V4/Reseller/UsersController.php
--- a/src/app/Http/Controllers/API/V4/Reseller/UsersController.php
+++ b/src/app/Http/Controllers/API/V4/Reseller/UsersController.php
@@ -50,13 +50,15 @@
$user_ids = $user_ids->merge($ext_user_ids)->unique();
- // Search by a distribution list email
+ // Search by an email of a group, resource, shared folder, etc.
if ($group = Group::withTrashed()->where('email', $search)->first()) {
$user_ids = $user_ids->merge([$group->wallet()->user_id])->unique();
} elseif ($resource = \App\Resource::withTrashed()->where('email', $search)->first()) {
$user_ids = $user_ids->merge([$resource->wallet()->user_id])->unique();
} elseif ($folder = \App\SharedFolder::withTrashed()->where('email', $search)->first()) {
$user_ids = $user_ids->merge([$folder->wallet()->user_id])->unique();
+ } elseif ($alias = \App\SharedFolderAlias::where('alias', $search)->first()) {
+ $user_ids = $user_ids->merge([$alias->sharedFolder->wallet()->user_id])->unique();
}
if (!$user_ids->isEmpty()) {
diff --git a/src/app/Http/Controllers/API/V4/ResourcesController.php b/src/app/Http/Controllers/API/V4/ResourcesController.php
--- a/src/app/Http/Controllers/API/V4/ResourcesController.php
+++ b/src/app/Http/Controllers/API/V4/ResourcesController.php
@@ -74,7 +74,7 @@
// Create the resource
$resource = new Resource();
$resource->name = request()->input('name');
- $resource->domain = $domain;
+ $resource->domainName = $domain;
$resource->save();
$resource->assignToWallet($owner->wallets->first());
diff --git a/src/app/Http/Controllers/API/V4/SharedFoldersController.php b/src/app/Http/Controllers/API/V4/SharedFoldersController.php
--- a/src/app/Http/Controllers/API/V4/SharedFoldersController.php
+++ b/src/app/Http/Controllers/API/V4/SharedFoldersController.php
@@ -9,6 +9,7 @@
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
+use Illuminate\Support\Str;
class SharedFoldersController extends RelationController
{
@@ -54,34 +55,29 @@
public function store(Request $request)
{
$current_user = $this->guard()->user();
- $owner = $current_user->wallet()->owner;
+ $owner = $current_user->walletOwner();
- if ($owner->id != $current_user->id) {
+ if (empty($owner) || $owner->id != $current_user->id) {
return $this->errorResponse(403);
}
- $domain = request()->input('domain');
-
- $rules = [
- 'name' => ['required', 'string', new SharedFolderName($owner, $domain)],
- 'type' => ['required', 'string', new SharedFolderType()]
- ];
-
- $v = Validator::make($request->all(), $rules);
-
- if ($v->fails()) {
- return response()->json(['status' => 'error', 'errors' => $v->errors()], 422);
+ if ($error_response = $this->validateFolderRequest($request, null, $owner)) {
+ return $error_response;
}
DB::beginTransaction();
// Create the shared folder
$folder = new SharedFolder();
- $folder->name = request()->input('name');
- $folder->type = request()->input('type');
- $folder->domain = $domain;
+ $folder->name = $request->input('name');
+ $folder->type = $request->input('type');
+ $folder->domainName = $request->input('domain');
$folder->save();
+ if (!empty($request->aliases) && $folder->type === 'mail') {
+ $folder->setAliases($request->aliases);
+ }
+
$folder->assignToWallet($owner->wallets->first());
DB::commit();
@@ -114,30 +110,25 @@
return $this->errorResponse(403);
}
- $owner = $folder->wallet()->owner;
+ if ($error_response = $this->validateFolderRequest($request, $folder, $folder->walletOwner())) {
+ return $error_response;
+ }
$name = $request->input('name');
- $errors = [];
- // Validate the folder name
- if ($name !== null && $name != $folder->name) {
- $domainName = explode('@', $folder->email, 2)[1];
- $rules = ['name' => ['required', 'string', new SharedFolderName($owner, $domainName)]];
-
- $v = Validator::make($request->all(), $rules);
+ DB::beginTransaction();
- if ($v->fails()) {
- $errors = $v->errors()->toArray();
- } else {
- $folder->name = $name;
- }
+ if ($name && $name != $folder->name) {
+ $folder->name = $name;
}
- if (!empty($errors)) {
- return response()->json(['status' => 'error', 'errors' => $errors], 422);
+ $folder->save();
+
+ if (isset($request->aliases) && $folder->type === 'mail') {
+ $folder->setAliases($request->aliases);
}
- $folder->save();
+ DB::commit();
return response()->json([
'status' => 'success',
@@ -194,4 +185,78 @@
return false;
}
+
+ /**
+ * Validate shared folder input
+ *
+ * @param \Illuminate\Http\Request $request The API request.
+ * @param \App\SharedFolder|null $folder Shared folder
+ * @param \App\User|null $owner Account owner
+ *
+ * @return \Illuminate\Http\JsonResponse|null The error response on error
+ */
+ protected function validateFolderRequest(Request $request, $folder, $owner)
+ {
+ $errors = [];
+
+ if (empty($folder)) {
+ $domain = $request->input('domain');
+ $rules = [
+ 'name' => ['required', 'string', new SharedFolderName($owner, $domain)],
+ 'type' => ['required', 'string', new SharedFolderType()],
+ ];
+ } else {
+ // On update validate the folder name (if changed)
+ $name = $request->input('name');
+ if ($name !== null && $name != $folder->name) {
+ $domain = explode('@', $folder->email, 2)[1];
+ $rules = ['name' => ['required', 'string', new SharedFolderName($owner, $domain)]];
+ }
+ }
+
+ if (!empty($rules)) {
+ $v = Validator::make($request->all(), $rules);
+
+ if ($v->fails()) {
+ $errors = $v->errors()->toArray();
+ }
+ }
+
+ // Validate aliases input
+ if (isset($request->aliases)) {
+ $aliases = [];
+ $existing_aliases = $owner->aliases()->get()->pluck('alias')->toArray();
+
+ foreach ($request->aliases as $idx => $alias) {
+ if (is_string($alias) && !empty($alias)) {
+ // Alias cannot be the same as the email address
+ if (!empty($folder) && Str::lower($alias) == Str::lower($folder->email)) {
+ continue;
+ }
+
+ // validate new aliases
+ if (
+ !in_array($alias, $existing_aliases)
+ && ($error = UsersController::validateAlias($alias, $owner))
+ ) {
+ if (!isset($errors['aliases'])) {
+ $errors['aliases'] = [];
+ }
+ $errors['aliases'][$idx] = $error;
+ continue;
+ }
+
+ $aliases[] = $alias;
+ }
+ }
+
+ $request->aliases = $aliases;
+ }
+
+ if (!empty($errors)) {
+ return response()->json(['status' => 'error', 'errors' => $errors], 422);
+ }
+
+ return null;
+ }
}
diff --git a/src/app/Http/Controllers/API/V4/UsersController.php b/src/app/Http/Controllers/API/V4/UsersController.php
--- a/src/app/Http/Controllers/API/V4/UsersController.php
+++ b/src/app/Http/Controllers/API/V4/UsersController.php
@@ -4,7 +4,6 @@
use App\Http\Controllers\RelationController;
use App\Domain;
-use App\Group;
use App\Rules\Password;
use App\Rules\UserEmailDomain;
use App\Rules\UserEmailLocal;
@@ -133,6 +132,7 @@
$response['skus'] = \App\Entitlement::objectEntitlementsSummary($user);
$response['config'] = $user->getConfig();
+ $response['aliases'] = $user->aliases()->pluck('alias')->all();
$code = $user->verificationcodes()->where('active', true)
->where('expires_at', '>', \Carbon\Carbon::now())
@@ -207,7 +207,7 @@
public function store(Request $request)
{
$current_user = $this->guard()->user();
- $owner = $current_user->wallet()->owner;
+ $owner = $current_user->walletOwner();
if ($owner->id != $current_user->id) {
return $this->errorResponse(403);
@@ -394,12 +394,6 @@
$response['settings'][$item->key] = $item->value;
}
- // Aliases
- $response['aliases'] = [];
- foreach ($user->aliases as $item) {
- $response['aliases'][] = $item->alias;
- }
-
// Status info
$response['statusInfo'] = self::statusInfo($user);
@@ -652,33 +646,19 @@
}
// Check if it is one of domains available to the user
- if (!$user->domains()->where('namespace', $domain->namespace)->exists()) {
+ if (!$domain->isPublic() && $user->id != $domain->walletOwner()->id) {
return \trans('validation.entryexists', ['attribute' => 'domain']);
}
- // Check if a user with specified address already exists
- if ($existing_user = User::emailExists($email, true)) {
- // If this is a deleted user in the same custom domain
- // we'll force delete him before
- if (!$domain->isPublic() && $existing_user->trashed()) {
- $deleted = $existing_user;
- } else {
- return \trans('validation.entryexists', ['attribute' => 'email']);
- }
- }
-
- // Check if an alias with specified address already exists.
- if (User::aliasExists($email)) {
- return \trans('validation.entryexists', ['attribute' => 'email']);
- }
-
- // Check if a group or resource with specified address already exists
+ // Check if a user/group/resource/shared folder with specified address already exists
if (
- ($existing = Group::emailExists($email, true))
+ ($existing = User::emailExists($email, true))
+ || ($existing = \App\Group::emailExists($email, true))
|| ($existing = \App\Resource::emailExists($email, true))
+ || ($existing = \App\SharedFolder::emailExists($email, true))
) {
- // If this is a deleted group/resource in the same custom domain
- // we'll force delete it before
+ // If this is a deleted user/group/resource/folder in the same custom domain
+ // we'll force delete it before creating the target user
if (!$domain->isPublic() && $existing->trashed()) {
$deleted = $existing;
} else {
@@ -686,6 +666,11 @@
}
}
+ // Check if an alias with specified address already exists.
+ if (User::aliasExists($email) || \App\SharedFolder::aliasExists($email)) {
+ return \trans('validation.entryexists', ['attribute' => 'email']);
+ }
+
return null;
}
@@ -727,7 +712,7 @@
}
// Check if it is one of domains available to the user
- if (!$user->domains()->where('namespace', $domain->namespace)->exists()) {
+ if (!$domain->isPublic() && $user->id != $domain->walletOwner()->id) {
return \trans('validation.entryexists', ['attribute' => 'domain']);
}
@@ -739,8 +724,17 @@
}
}
+ // Check if a group/resource/shared folder with specified address already exists
+ if (
+ \App\Group::emailExists($email)
+ || \App\Resource::emailExists($email)
+ || \App\SharedFolder::emailExists($email)
+ ) {
+ return \trans('validation.entryexists', ['attribute' => 'alias']);
+ }
+
// Check if an alias with specified address already exists
- if (User::aliasExists($email)) {
+ if (User::aliasExists($email) || \App\SharedFolder::aliasExists($email)) {
// Allow assigning the same alias to a user in the same group account,
// but only for non-public domains
if ($domain->isPublic()) {
@@ -748,11 +742,6 @@
}
}
- // Check if a group with specified address already exists
- if (Group::emailExists($email)) {
- return \trans('validation.entryexists', ['attribute' => 'alias']);
- }
-
return null;
}
diff --git a/src/app/Http/Controllers/API/V4/WalletsController.php b/src/app/Http/Controllers/API/V4/WalletsController.php
--- a/src/app/Http/Controllers/API/V4/WalletsController.php
+++ b/src/app/Http/Controllers/API/V4/WalletsController.php
@@ -161,6 +161,7 @@
// Get sub-transactions for the specified transaction ID, first
// check access rights to the transaction's wallet
+ /** @var ?\App\Transaction $transaction */
$transaction = $wallet->transactions()->where('id', $transaction)->first();
if (!$transaction) {
diff --git a/src/app/Http/Controllers/RelationController.php b/src/app/Http/Controllers/RelationController.php
--- a/src/app/Http/Controllers/RelationController.php
+++ b/src/app/Http/Controllers/RelationController.php
@@ -308,6 +308,10 @@
$response['config'] = $resource->getConfig();
}
+ if (method_exists($resource, 'aliases')) {
+ $response['aliases'] = $resource->aliases()->pluck('alias')->all();
+ }
+
return response()->json($response);
}
diff --git a/src/app/Http/Kernel.php b/src/app/Http/Kernel.php
--- a/src/app/Http/Kernel.php
+++ b/src/app/Http/Kernel.php
@@ -11,12 +11,13 @@
*
* These middleware are run during every request to your application.
*
- * @var array
+ * @var array<int, class-string|string>
*/
protected $middleware = [
+ // \App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\RequestLogger::class,
\App\Http\Middleware\TrustProxies::class,
- \App\Http\Middleware\CheckForMaintenanceMode::class,
+ \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
@@ -31,7 +32,7 @@
/**
* The application's route middleware groups.
*
- * @var array
+ * @var array<string, array<int, class-string|string>>
*/
protected $middlewareGroups = [
'web' => [
@@ -45,8 +46,8 @@
],
'api' => [
- //'throttle:120,1',
- 'bindings',
+ // 'throttle:api',
+ \Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
@@ -55,13 +56,12 @@
*
* These middleware may be assigned to groups or used individually.
*
- * @var array
+ * @var array<string, class-string|string>
*/
protected $routeMiddleware = [
'admin' => \App\Http\Middleware\AuthenticateAdmin::class,
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
- 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
@@ -71,25 +71,6 @@
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
- /**
- * The priority-sorted list of middleware.
- *
- * This forces non-global middleware to always be in the given order.
- *
- * @var array
- */
- protected $middlewarePriority = [
- \Illuminate\Session\Middleware\StartSession::class,
- \Illuminate\View\Middleware\ShareErrorsFromSession::class,
- \App\Http\Middleware\AuthenticateAdmin::class,
- \App\Http\Middleware\AuthenticateReseller::class,
- \App\Http\Middleware\Authenticate::class,
- \Illuminate\Session\Middleware\AuthenticateSession::class,
- \Illuminate\Routing\Middleware\SubstituteBindings::class,
- \Illuminate\Auth\Middleware\Authorize::class,
- \App\Http\Middleware\ContentSecurityPolicy::class,
- ];
-
/**
* Handle an incoming HTTP request.
*
diff --git a/src/app/Http/Middleware/EncryptCookies.php b/src/app/Http/Middleware/EncryptCookies.php
--- a/src/app/Http/Middleware/EncryptCookies.php
+++ b/src/app/Http/Middleware/EncryptCookies.php
@@ -9,7 +9,7 @@
/**
* The names of the cookies that should not be encrypted.
*
- * @var array
+ * @var array<int, string>
*/
protected $except = [
//
diff --git a/src/app/Http/Middleware/CheckForMaintenanceMode.php b/src/app/Http/Middleware/PreventRequestsDuringMaintenance.php
rename from src/app/Http/Middleware/CheckForMaintenanceMode.php
rename to src/app/Http/Middleware/PreventRequestsDuringMaintenance.php
--- a/src/app/Http/Middleware/CheckForMaintenanceMode.php
+++ b/src/app/Http/Middleware/PreventRequestsDuringMaintenance.php
@@ -2,14 +2,14 @@
namespace App\Http\Middleware;
-use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode as Middleware;
+use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance as Middleware;
-class CheckForMaintenanceMode extends Middleware
+class PreventRequestsDuringMaintenance extends Middleware
{
/**
* The URIs that should be reachable while maintenance mode is enabled.
*
- * @var array
+ * @var array<int, string>
*/
protected $except = [
//
diff --git a/src/app/Http/Middleware/RedirectIfAuthenticated.php b/src/app/Http/Middleware/RedirectIfAuthenticated.php
--- a/src/app/Http/Middleware/RedirectIfAuthenticated.php
+++ b/src/app/Http/Middleware/RedirectIfAuthenticated.php
@@ -3,6 +3,7 @@
namespace App\Http\Middleware;
use Closure;
+use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class RedirectIfAuthenticated
@@ -12,14 +13,18 @@
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
- * @param string|null $guard
+ * @param string|null ...$guards
*
* @return mixed
*/
- public function handle($request, Closure $next, $guard = null)
+ public function handle(Request $request, Closure $next, ...$guards)
{
- if (Auth::guard($guard)->check()) {
- return redirect('/dashboard');
+ $guards = empty($guards) ? [null] : $guards;
+
+ foreach ($guards as $guard) {
+ if (Auth::guard($guard)->check()) {
+ return redirect('/dashboard');
+ }
}
return $next($request);
diff --git a/src/app/Http/Middleware/TrustHosts.php b/src/app/Http/Middleware/TrustHosts.php
new file mode 100644
--- /dev/null
+++ b/src/app/Http/Middleware/TrustHosts.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Illuminate\Http\Middleware\TrustHosts as Middleware;
+
+class TrustHosts extends Middleware
+{
+ /**
+ * Get the host patterns that should be trusted.
+ *
+ * @return array<int, string|null>
+ */
+ public function hosts()
+ {
+ return [
+ $this->allSubdomainsOfApplicationUrl(),
+ ];
+ }
+}
diff --git a/src/app/Http/Middleware/TrustProxies.php b/src/app/Http/Middleware/TrustProxies.php
--- a/src/app/Http/Middleware/TrustProxies.php
+++ b/src/app/Http/Middleware/TrustProxies.php
@@ -2,15 +2,15 @@
namespace App\Http\Middleware;
+use Illuminate\Http\Middleware\TrustProxies as Middleware;
use Illuminate\Http\Request;
-use Fideloper\Proxy\TrustProxies as Middleware;
class TrustProxies extends Middleware
{
/**
* The trusted proxies for this application.
*
- * @var array|string
+ * @var array<int, string>|string|null
*/
protected $proxies = [
'10.0.0.0/8',
@@ -24,5 +24,9 @@
*
* @var int
*/
- protected $headers = Request::HEADER_X_FORWARDED_ALL;
+ protected $headers = Request::HEADER_X_FORWARDED_FOR |
+ Request::HEADER_X_FORWARDED_HOST |
+ Request::HEADER_X_FORWARDED_PORT |
+ Request::HEADER_X_FORWARDED_PROTO |
+ Request::HEADER_X_FORWARDED_AWS_ELB;
}
diff --git a/src/app/Http/Middleware/VerifyCsrfToken.php b/src/app/Http/Middleware/VerifyCsrfToken.php
--- a/src/app/Http/Middleware/VerifyCsrfToken.php
+++ b/src/app/Http/Middleware/VerifyCsrfToken.php
@@ -6,17 +6,10 @@
class VerifyCsrfToken extends Middleware
{
- /**
- * Indicates whether the XSRF-TOKEN cookie should be set on the response.
- *
- * @var bool
- */
- protected $addHttpCookie = true;
-
/**
* The URIs that should be excluded from CSRF verification.
*
- * @var array
+ * @var array<int, string>
*/
protected $except = [
//
diff --git a/src/app/IP4Net.php b/src/app/IP4Net.php
--- a/src/app/IP4Net.php
+++ b/src/app/IP4Net.php
@@ -9,6 +9,7 @@
{
protected $table = "ip4nets";
+ /** @var array<int, string> The attributes that are mass assignable */
protected $fillable = [
'rir_name',
'net_number',
diff --git a/src/app/IP6Net.php b/src/app/IP6Net.php
--- a/src/app/IP6Net.php
+++ b/src/app/IP6Net.php
@@ -9,6 +9,7 @@
{
protected $table = "ip6nets";
+ /** @var array<int, string> The attributes that are mass assignable */
protected $fillable = [
'rir_name',
'net_number',
diff --git a/src/app/Jobs/PaymentEmail.php b/src/app/Jobs/PaymentEmail.php
--- a/src/app/Jobs/PaymentEmail.php
+++ b/src/app/Jobs/PaymentEmail.php
@@ -22,7 +22,7 @@
public $tries = 2;
/** @var int The number of seconds to wait before retrying the job. */
- public $retryAfter = 10;
+ public $backoff = 10;
/** @var bool Delete the job if the wallet no longer exist. */
public $deleteWhenMissingModels = true;
@@ -30,7 +30,7 @@
/** @var \App\Payment A payment object */
protected $payment;
- /** @var \App\User A wallet controller */
+ /** @var ?\App\User A wallet controller */
protected $controller;
diff --git a/src/app/Jobs/PaymentMandateDisabledEmail.php b/src/app/Jobs/PaymentMandateDisabledEmail.php
--- a/src/app/Jobs/PaymentMandateDisabledEmail.php
+++ b/src/app/Jobs/PaymentMandateDisabledEmail.php
@@ -22,7 +22,7 @@
public $tries = 2;
/** @var int The number of seconds to wait before retrying the job. */
- public $retryAfter = 10;
+ public $backoff = 10;
/** @var bool Delete the job if the wallet no longer exist. */
public $deleteWhenMissingModels = true;
@@ -30,7 +30,7 @@
/** @var \App\Wallet A wallet object */
protected $wallet;
- /** @var \App\User A wallet controller */
+ /** @var ?\App\User A wallet controller */
protected $controller;
diff --git a/src/app/Jobs/SignupInvitationEmail.php b/src/app/Jobs/SignupInvitationEmail.php
--- a/src/app/Jobs/SignupInvitationEmail.php
+++ b/src/app/Jobs/SignupInvitationEmail.php
@@ -24,7 +24,7 @@
public $deleteWhenMissingModels = true;
/** @var int The number of seconds to wait before retrying the job. */
- public $retryAfter = 10;
+ public $backoff = 10;
/** @var SignupInvitation Signup invitation object */
protected $invitation;
diff --git a/src/app/Jobs/WalletCharge.php b/src/app/Jobs/WalletCharge.php
--- a/src/app/Jobs/WalletCharge.php
+++ b/src/app/Jobs/WalletCharge.php
@@ -21,7 +21,7 @@
protected $wallet;
/** @var int The number of seconds to wait before retrying the job. */
- public $retryAfter = 10;
+ public $backoff = 10;
/** @var int How many times retry the job if it fails. */
public $tries = 5;
diff --git a/src/app/Jobs/WalletCheck.php b/src/app/Jobs/WalletCheck.php
--- a/src/app/Jobs/WalletCheck.php
+++ b/src/app/Jobs/WalletCheck.php
@@ -30,7 +30,7 @@
public const THRESHOLD_INITIAL = 'initial';
/** @var int The number of seconds to wait before retrying the job. */
- public $retryAfter = 10;
+ public $backoff = 10;
/** @var int How many times retry the job if it fails. */
public $tries = 5;
diff --git a/src/app/Observers/OpenVidu/ConnectionObserver.php b/src/app/Observers/OpenVidu/ConnectionObserver.php
--- a/src/app/Observers/OpenVidu/ConnectionObserver.php
+++ b/src/app/Observers/OpenVidu/ConnectionObserver.php
@@ -34,7 +34,7 @@
foreach ($keys as $key => $type) {
$newState = $connection->metadata[$key] ?? null;
- $oldState = $this->getOriginal($connection, 'metadata')[$key] ?? null;
+ $oldState = $connection->getOriginal('metadata')[$key] ?? null;
if ($newState !== $oldState) {
$params[$key] = $type == 'bool' ? !empty($newState) : $newState;
@@ -47,25 +47,4 @@
$connection->room->signal('connectionUpdate', $params);
}
}
-
- /**
- * A wrapper to getOriginal() on an object
- *
- * @param \App\OpenVidu\Connection $connection The connection.
- * @param string $property The property name
- *
- * @return mixed
- */
- private function getOriginal($connection, $property)
- {
- $original = $connection->getOriginal($property);
-
- // The original value for a property is in a format stored in database
- // I.e. for 'metadata' it is a JSON string instead of an array
- if ($property == 'metadata') {
- $original = json_decode($original, true);
- }
-
- return $original;
- }
}
diff --git a/src/app/Observers/ResourceObserver.php b/src/app/Observers/ResourceObserver.php
--- a/src/app/Observers/ResourceObserver.php
+++ b/src/app/Observers/ResourceObserver.php
@@ -15,18 +15,6 @@
*/
public function creating(Resource $resource): void
{
- if (empty($resource->email)) {
- if (!isset($resource->domain)) {
- throw new \Exception("Missing 'domain' property for a new resource");
- }
-
- $domainName = \strtolower($resource->domain);
-
- $resource->email = "resource-{$resource->id}@{$domainName}";
- } else {
- $resource->email = \strtolower($resource->email);
- }
-
$resource->status |= Resource::STATUS_NEW | Resource::STATUS_ACTIVE;
}
diff --git a/src/app/Observers/SharedFolderAliasObserver.php b/src/app/Observers/SharedFolderAliasObserver.php
new file mode 100644
--- /dev/null
+++ b/src/app/Observers/SharedFolderAliasObserver.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace App\Observers;
+
+use App\Domain;
+use App\SharedFolder;
+use App\SharedFolderAlias;
+
+class SharedFolderAliasObserver
+{
+ /**
+ * Handle the "creating" event on an alias
+ *
+ * @param \App\SharedFolderAlias $alias The shared folder email alias
+ *
+ * @return bool
+ */
+ public function creating(SharedFolderAlias $alias): bool
+ {
+ $alias->alias = \strtolower($alias->alias);
+
+ $domainName = explode('@', $alias->alias)[1];
+
+ $domain = Domain::where('namespace', $domainName)->first();
+
+ if (!$domain) {
+ \Log::error("Failed creating alias {$alias->alias}. Domain does not exist.");
+ return false;
+ }
+
+ if ($alias->sharedFolder) {
+ if ($alias->sharedFolder->tenant_id != $domain->tenant_id) {
+ \Log::error("Reseller for folder '{$alias->sharedFolder->email}' and domain '{$domainName}' differ.");
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Handle the shared folder alias "created" event.
+ *
+ * @param \App\SharedFolderAlias $alias Shared folder email alias
+ *
+ * @return void
+ */
+ public function created(SharedFolderAlias $alias)
+ {
+ if ($alias->sharedFolder) {
+ \App\Jobs\SharedFolder\UpdateJob::dispatch($alias->shared_folder_id);
+ }
+ }
+
+ /**
+ * Handle the shared folder alias "updated" event.
+ *
+ * @param \App\SharedFolderAlias $alias Shared folder email alias
+ *
+ * @return void
+ */
+ public function updated(SharedFolderAlias $alias)
+ {
+ if ($alias->sharedFolder) {
+ \App\Jobs\SharedFolder\UpdateJob::dispatch($alias->shared_folder_id);
+ }
+ }
+
+ /**
+ * Handle the shared folder alias "deleted" event.
+ *
+ * @param \App\SharedFolderAlias $alias Shared folder email alias
+ *
+ * @return void
+ */
+ public function deleted(SharedFolderAlias $alias)
+ {
+ if ($alias->sharedFolder) {
+ \App\Jobs\SharedFolder\UpdateJob::dispatch($alias->shared_folder_id);
+ }
+ }
+}
diff --git a/src/app/Observers/SharedFolderObserver.php b/src/app/Observers/SharedFolderObserver.php
--- a/src/app/Observers/SharedFolderObserver.php
+++ b/src/app/Observers/SharedFolderObserver.php
@@ -19,18 +19,6 @@
$folder->type = 'mail';
}
- if (empty($folder->email)) {
- if (!isset($folder->domain)) {
- throw new \Exception("Missing 'domain' property for a new shared folder");
- }
-
- $domainName = \strtolower($folder->domain);
-
- $folder->email = "{$folder->type}-{$folder->id}@{$domainName}";
- } else {
- $folder->email = \strtolower($folder->email);
- }
-
$folder->status |= SharedFolder::STATUS_NEW | SharedFolder::STATUS_ACTIVE;
}
diff --git a/src/app/Observers/UserAliasObserver.php b/src/app/Observers/UserAliasObserver.php
--- a/src/app/Observers/UserAliasObserver.php
+++ b/src/app/Observers/UserAliasObserver.php
@@ -12,8 +12,6 @@
/**
* Handle the "creating" event on an alias
*
- * Ensures that there's no user with specified email.
- *
* @param \App\UserAlias $alias The user email alias
*
* @return bool
@@ -60,7 +58,7 @@
}
/**
- * Handle the user setting "updated" event.
+ * Handle the user alias "updated" event.
*
* @param \App\UserAlias $alias User email alias
*
@@ -74,7 +72,7 @@
}
/**
- * Handle the user setting "deleted" event.
+ * Handle the user alias "deleted" event.
*
* @param \App\UserAlias $alias User email alias
*
diff --git a/src/app/OpenVidu/Room.php b/src/app/OpenVidu/Room.php
--- a/src/app/OpenVidu/Room.php
+++ b/src/app/OpenVidu/Room.php
@@ -27,9 +27,9 @@
public const REQUEST_ACCEPTED = 'accepted';
public const REQUEST_DENIED = 'denied';
- private const OV_ROLE_MODERATOR = 'MODERATOR';
+ private const OV_ROLE_MODERATOR = 'MODERATOR'; // @phpstan-ignore-line
private const OV_ROLE_PUBLISHER = 'PUBLISHER';
- private const OV_ROLE_SUBSCRIBER = 'SUBSCRIBER';
+ private const OV_ROLE_SUBSCRIBER = 'SUBSCRIBER'; // @phpstan-ignore-line
protected $fillable = [
'user_id',
diff --git a/src/app/Package.php b/src/app/Package.php
--- a/src/app/Package.php
+++ b/src/app/Package.php
@@ -39,6 +39,7 @@
public $timestamps = false;
+ /** @var array<int, string> The attributes that are mass assignable */
protected $fillable = [
'description',
'discount_rate',
@@ -46,7 +47,7 @@
'title',
];
- /** @var array Translatable properties */
+ /** @var array<int, string> Translatable properties */
public $translatable = [
'name',
'description',
@@ -83,7 +84,7 @@
public function isDomain(): bool
{
foreach ($this->skus as $sku) {
- if ($sku->handler_class::entitleableClass() == \App\Domain::class) {
+ if ($sku->handler_class::entitleableClass() == Domain::class) {
return true;
}
}
@@ -98,11 +99,8 @@
*/
public function skus()
{
- return $this->belongsToMany(
- 'App\Sku',
- 'package_skus'
- )->using('App\PackageSku')->withPivot(
- ['qty']
- );
+ return $this->belongsToMany(Sku::class, 'package_skus')
+ ->using(PackageSku::class)
+ ->withPivot(['qty']);
}
}
diff --git a/src/app/PackageSku.php b/src/app/PackageSku.php
--- a/src/app/PackageSku.php
+++ b/src/app/PackageSku.php
@@ -16,6 +16,7 @@
*/
class PackageSku extends Pivot
{
+ /** @var array<int, string> The attributes that are mass assignable */
protected $fillable = [
'package_id',
'sku_id',
@@ -23,6 +24,7 @@
'qty'
];
+ /** @var array<string, string> The attributes that should be cast */
protected $casts = [
'cost' => 'integer',
'qty' => 'integer'
@@ -71,7 +73,7 @@
*/
public function package()
{
- return $this->belongsTo('App\Package');
+ return $this->belongsTo(Package::class);
}
/**
@@ -81,6 +83,6 @@
*/
public function sku()
{
- return $this->belongsTo('App\Sku');
+ return $this->belongsTo(Sku::class);
}
}
diff --git a/src/app/Payment.php b/src/app/Payment.php
--- a/src/app/Payment.php
+++ b/src/app/Payment.php
@@ -20,10 +20,12 @@
public $incrementing = false;
protected $keyType = 'string';
+ /** @var array<string, string> The attributes that should be cast */
protected $casts = [
'amount' => 'integer'
];
+ /** @var array<int,string> The attributes that are mass assignable */
protected $fillable = [
'id',
'wallet_id',
@@ -52,6 +54,6 @@
*/
public function wallet()
{
- return $this->belongsTo('\App\Wallet', 'wallet_id', 'id');
+ return $this->belongsTo(Wallet::class, 'wallet_id', 'id');
}
}
diff --git a/src/app/Plan.php b/src/app/Plan.php
--- a/src/app/Plan.php
+++ b/src/app/Plan.php
@@ -34,6 +34,7 @@
public $timestamps = false;
+ /** @var array<int, string> The attributes that are mass assignable */
protected $fillable = [
'title',
'name',
@@ -47,14 +48,15 @@
'discount_rate',
];
+ /** @var array<string, string> The attributes that should be cast */
protected $casts = [
- 'promo_from' => 'datetime',
- 'promo_to' => 'datetime',
+ 'promo_from' => 'datetime:Y-m-d H:i:s',
+ 'promo_to' => 'datetime:Y-m-d H:i:s',
'discount_qty' => 'integer',
'discount_rate' => 'integer'
];
- /** @var array Translatable properties */
+ /** @var array<int, string> Translatable properties */
public $translatable = [
'name',
'description',
@@ -87,18 +89,15 @@
*/
public function packages()
{
- return $this->belongsToMany(
- 'App\Package',
- 'plan_packages'
- )->using('App\PlanPackage')->withPivot(
- [
- 'qty',
- 'qty_min',
- 'qty_max',
- 'discount_qty',
- 'discount_rate'
- ]
- );
+ return $this->belongsToMany(Package::class, 'plan_packages')
+ ->using(PlanPackage::class)
+ ->withPivot([
+ 'qty',
+ 'qty_min',
+ 'qty_max',
+ 'discount_qty',
+ 'discount_rate'
+ ]);
}
/**
diff --git a/src/app/PlanPackage.php b/src/app/PlanPackage.php
--- a/src/app/PlanPackage.php
+++ b/src/app/PlanPackage.php
@@ -19,6 +19,7 @@
*/
class PlanPackage extends Pivot
{
+ /** @var array<int, string> The attributes that are mass assignable */
protected $fillable = [
'plan_id',
'package_id',
@@ -29,6 +30,7 @@
'discount_rate'
];
+ /** @var array<string, string> The attributes that should be cast */
protected $casts = [
'qty' => 'integer',
'qty_max' => 'integer',
@@ -62,7 +64,7 @@
*/
public function package()
{
- return $this->belongsTo('App\Package');
+ return $this->belongsTo(Package::class);
}
/**
@@ -72,6 +74,6 @@
*/
public function plan()
{
- return $this->belongsTo('App\Plan');
+ return $this->belongsTo(Plan::class);
}
}
diff --git a/src/app/Providers/AppServiceProvider.php b/src/app/Providers/AppServiceProvider.php
--- a/src/app/Providers/AppServiceProvider.php
+++ b/src/app/Providers/AppServiceProvider.php
@@ -54,6 +54,7 @@
\App\Resource::observe(\App\Observers\ResourceObserver::class);
\App\ResourceSetting::observe(\App\Observers\ResourceSettingObserver::class);
\App\SharedFolder::observe(\App\Observers\SharedFolderObserver::class);
+ \App\SharedFolderAlias::observe(\App\Observers\SharedFolderAliasObserver::class);
\App\SharedFolderSetting::observe(\App\Observers\SharedFolderSettingObserver::class);
\App\SignupCode::observe(\App\Observers\SignupCodeObserver::class);
\App\SignupInvitation::observe(\App\Observers\SignupInvitationObserver::class);
diff --git a/src/app/Providers/AuthServiceProvider.php b/src/app/Providers/AuthServiceProvider.php
--- a/src/app/Providers/AuthServiceProvider.php
+++ b/src/app/Providers/AuthServiceProvider.php
@@ -2,9 +2,8 @@
namespace App\Providers;
-use App\Auth\LDAPUserProvider;
use Illuminate\Support\Facades\Auth;
-use Illuminate\Support\Facades\Gate;
+use Illuminate\Support\Facades\Route;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Laravel\Passport\Passport;
@@ -13,7 +12,7 @@
/**
* The policy mappings for the application.
*
- * @var array
+ * @var array<class-string, class-string>
*/
protected $policies = [
// 'App\Model' => 'App\Policies\ModelPolicy',
@@ -28,13 +27,6 @@
{
$this->registerPolicies();
- Auth::provider(
- 'ldap',
- function ($app, array $config) {
- return new LDAPUserProvider($app['hash'], $config['model']);
- }
- );
-
// Hashes all secrets and thus makes them non-recoverable
/* Passport::hashClientSecrets(); */
// Only enable routes for access tokens
@@ -43,7 +35,7 @@
$router->forAccessTokens();
// Override the default route to avoid rate-limiting.
- \Route::post('/token', [
+ Route::post('/token', [
'uses' => 'AccessTokenController@issueToken',
'as' => 'passport.token',
]);
diff --git a/src/app/Providers/EventServiceProvider.php b/src/app/Providers/EventServiceProvider.php
--- a/src/app/Providers/EventServiceProvider.php
+++ b/src/app/Providers/EventServiceProvider.php
@@ -12,7 +12,7 @@
/**
* The event listener mappings for the application.
*
- * @var array
+ * @var array<class-string, array<int, class-string>>
*/
protected $listen = [
Registered::class => [
@@ -27,8 +27,16 @@
*/
public function boot()
{
- parent::boot();
-
//
}
+
+ /**
+ * Determine if events and listeners should be automatically discovered.
+ *
+ * @return bool
+ */
+ public function shouldDiscoverEvents()
+ {
+ return false;
+ }
}
diff --git a/src/app/Providers/PassportServiceProvider.php b/src/app/Providers/PassportServiceProvider.php
--- a/src/app/Providers/PassportServiceProvider.php
+++ b/src/app/Providers/PassportServiceProvider.php
@@ -10,7 +10,6 @@
class PassportServiceProvider extends \Laravel\Passport\PassportServiceProvider
{
-
/**
* Make the authorization service instance.
*
diff --git a/src/app/Providers/RouteServiceProvider.php b/src/app/Providers/RouteServiceProvider.php
--- a/src/app/Providers/RouteServiceProvider.php
+++ b/src/app/Providers/RouteServiceProvider.php
@@ -2,20 +2,14 @@
namespace App\Providers;
-use Illuminate\Support\Facades\Route;
+use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\RateLimiter;
+use Illuminate\Support\Facades\Route;
class RouteServiceProvider extends ServiceProvider
{
- /**
- * This namespace is applied to your controller routes.
- *
- * In addition, it is set as the URL generator's root namespace.
- *
- * @var string
- */
- protected $namespace = 'App\Http\Controllers';
-
/**
* Define your route model bindings, pattern filters, etc.
*
@@ -23,52 +17,28 @@
*/
public function boot()
{
- //
+ $this->configureRateLimiting();
- parent::boot();
- }
+ $this->routes(function () {
+ $prefix = \trim(\parse_url(\config('app.url'), PHP_URL_PATH), '/') . '/';
- /**
- * Define the routes for the application.
- *
- * @return void
- */
- public function map()
- {
- $this->mapApiRoutes();
-
- $this->mapWebRoutes();
+ Route::prefix($prefix . 'api')
+ ->group(base_path('routes/api.php'));
- //
+ Route::middleware('web')
+ ->group(base_path('routes/web.php'));
+ });
}
/**
- * Define the "web" routes for the application.
- *
- * These routes all receive session state, CSRF protection, etc.
- *
- * @return void
- */
- protected function mapWebRoutes()
- {
- Route::middleware('web')
- ->namespace($this->namespace)
- ->group(base_path('routes/web.php'));
- }
-
- /**
- * Define the "api" routes for the application.
- *
- * These routes are typically stateless.
+ * Configure the rate limiters for the application.
*
* @return void
*/
- protected function mapApiRoutes()
+ protected function configureRateLimiting()
{
- // Note: We removed the prefix from here, to have more control
- // over it in routes/api.php
- Route::middleware('api')
- ->namespace($this->namespace)
- ->group(base_path('routes/api.php'));
+ RateLimiter::for('api', function (Request $request) {
+ return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
+ });
}
}
diff --git a/src/app/Resource.php b/src/app/Resource.php
--- a/src/app/Resource.php
+++ b/src/app/Resource.php
@@ -4,11 +4,11 @@
use App\Traits\BelongsToTenantTrait;
use App\Traits\EntitleableTrait;
+use App\Traits\EmailPropertyTrait;
use App\Traits\ResourceConfigTrait;
use App\Traits\SettingsTrait;
use App\Traits\StatusPropertyTrait;
use App\Traits\UuidIntKeyTrait;
-use App\Wallet;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
@@ -30,6 +30,7 @@
use SoftDeletes;
use StatusPropertyTrait;
use UuidIntKeyTrait;
+ use EmailPropertyTrait; // must be after UuidIntKeyTrait
// we've simply never heard of this resource
public const STATUS_NEW = 1 << 0;
@@ -44,54 +45,16 @@
// resource has been created in IMAP
public const STATUS_IMAP_READY = 1 << 8;
- protected $fillable = [
- 'email',
- 'name',
- 'status',
- ];
-
- /** @var ?string Domain name for a resource to be created */
- public $domain;
-
-
- /**
- * Returns the resource domain.
- *
- * @return ?\App\Domain The domain to which the resource belongs to, NULL if it does not exist
- */
- public function domain(): ?Domain
- {
- if (isset($this->domain)) {
- $domainName = $this->domain;
- } else {
- list($local, $domainName) = explode('@', $this->email);
- }
-
- return Domain::where('namespace', $domainName)->first();
- }
+ // A template for the email attribute on a resource creation
+ public const EMAIL_TEMPLATE = 'resource-{id}@{domainName}';
- /**
- * Find whether an email address exists as a resource (including deleted resources).
- *
- * @param string $email Email address
- * @param bool $return_resource Return Resource instance instead of boolean
- *
- * @return \App\Resource|bool True or Resource model object if found, False otherwise
- */
- public static function emailExists(string $email, bool $return_resource = false)
- {
- if (strpos($email, '@') === false) {
- return false;
- }
-
- $email = \strtolower($email);
-
- $resource = self::withTrashed()->where('email', $email)->first();
-
- if ($resource) {
- return $return_resource ? $resource : true;
- }
+ /** @var array<string, string> The attributes that should be cast */
+ protected $casts = [
+ 'created_at' => 'datetime:Y-m-d H:i:s',
+ 'deleted_at' => 'datetime:Y-m-d H:i:s',
+ 'updated_at' => 'datetime:Y-m-d H:i:s',
+ ];
- return false;
- }
+ /** @var array<int, string> The attributes that are mass assignable */
+ protected $fillable = ['email', 'name', 'status'];
}
diff --git a/src/app/ResourceSetting.php b/src/app/ResourceSetting.php
--- a/src/app/ResourceSetting.php
+++ b/src/app/ResourceSetting.php
@@ -14,9 +14,8 @@
*/
class ResourceSetting extends Model
{
- protected $fillable = [
- 'resource_id', 'key', 'value'
- ];
+ /** @var array<int, string> The attributes that are mass assignable */
+ protected $fillable = ['resource_id', 'key', 'value'];
/**
* The resource to which this setting belongs.
@@ -25,6 +24,6 @@
*/
public function resource()
{
- return $this->belongsTo(\App\Resource::class, 'resource_id', 'id');
+ return $this->belongsTo(Resource::class, 'resource_id', 'id');
}
}
diff --git a/src/app/SharedFolder.php b/src/app/SharedFolder.php
--- a/src/app/SharedFolder.php
+++ b/src/app/SharedFolder.php
@@ -2,13 +2,14 @@
namespace App;
+use App\Traits\AliasesTrait;
use App\Traits\BelongsToTenantTrait;
use App\Traits\EntitleableTrait;
+use App\Traits\EmailPropertyTrait;
use App\Traits\SharedFolderConfigTrait;
use App\Traits\SettingsTrait;
use App\Traits\StatusPropertyTrait;
use App\Traits\UuidIntKeyTrait;
-use App\Wallet;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
@@ -24,6 +25,7 @@
*/
class SharedFolder extends Model
{
+ use AliasesTrait;
use BelongsToTenantTrait;
use EntitleableTrait;
use SharedFolderConfigTrait;
@@ -31,6 +33,7 @@
use SoftDeletes;
use StatusPropertyTrait;
use UuidIntKeyTrait;
+ use EmailPropertyTrait; // must be after UuidIntKeyTrait
// we've simply never heard of this folder
public const STATUS_NEW = 1 << 0;
@@ -48,7 +51,17 @@
/** @const array Supported folder type labels */
public const SUPPORTED_TYPES = ['mail', 'event', 'contact', 'task', 'note', 'file'];
- /** @var array Mass-assignable properties */
+ /** @const string A template for the email attribute on a folder creation */
+ public const EMAIL_TEMPLATE = '{type}-{id}@{domainName}';
+
+ /** @var array<string, string> The attributes that should be cast */
+ protected $casts = [
+ 'created_at' => 'datetime:Y-m-d H:i:s',
+ 'deleted_at' => 'datetime:Y-m-d H:i:s',
+ 'updated_at' => 'datetime:Y-m-d H:i:s',
+ ];
+
+ /** @var array<int, string> The attributes that are mass assignable */
protected $fillable = [
'email',
'name',
@@ -56,51 +69,6 @@
'type',
];
- /** @var ?string Domain name for a shared folder to be created */
- public $domain;
-
-
- /**
- * Returns the shared folder domain.
- *
- * @return ?\App\Domain The domain to which the folder belongs to, NULL if it does not exist
- */
- public function domain(): ?Domain
- {
- if (isset($this->domain)) {
- $domainName = $this->domain;
- } else {
- list($local, $domainName) = explode('@', $this->email);
- }
-
- return Domain::where('namespace', $domainName)->first();
- }
-
- /**
- * Find whether an email address exists as a shared folder (including deleted folders).
- *
- * @param string $email Email address
- * @param bool $return_folder Return SharedFolder instance instead of boolean
- *
- * @return \App\SharedFolder|bool True or Resource model object if found, False otherwise
- */
- public static function emailExists(string $email, bool $return_folder = false)
- {
- if (strpos($email, '@') === false) {
- return false;
- }
-
- $email = \strtolower($email);
-
- $folder = self::withTrashed()->where('email', $email)->first();
-
- if ($folder) {
- return $return_folder ? $folder : true;
- }
-
- return false;
- }
-
/**
* Folder type mutator
*
diff --git a/src/app/SharedFolderAlias.php b/src/app/SharedFolderAlias.php
new file mode 100644
--- /dev/null
+++ b/src/app/SharedFolderAlias.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace App;
+
+use Illuminate\Database\Eloquent\Model;
+
+/**
+ * An email address alias for a SharedFolder.
+ *
+ * @property string $alias
+ * @property int $id
+ * @property int $shared_folder_id
+ */
+class SharedFolderAlias extends Model
+{
+ /** @var array<int, string> The attributes that are mass assignable */
+ protected $fillable = ['shared_folder_id', 'alias'];
+
+ /**
+ * Ensure the email address is appropriately cased.
+ *
+ * @param string $alias Email address
+ */
+ public function setAliasAttribute(string $alias)
+ {
+ $this->attributes['alias'] = \strtolower($alias);
+ }
+
+ /**
+ * The shared folder to which this alias belongs.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function sharedFolder()
+ {
+ return $this->belongsTo(SharedFolder::class, 'shared_folder_id', 'id');
+ }
+}
diff --git a/src/app/SharedFolderSetting.php b/src/app/SharedFolderSetting.php
--- a/src/app/SharedFolderSetting.php
+++ b/src/app/SharedFolderSetting.php
@@ -14,9 +14,8 @@
*/
class SharedFolderSetting extends Model
{
- protected $fillable = [
- 'shared_folder_id', 'key', 'value'
- ];
+ /** @var array<int, string> The attributes that are mass assignable */
+ protected $fillable = ['shared_folder_id', 'key', 'value'];
/**
* The folder to which this setting belongs.
@@ -25,6 +24,6 @@
*/
public function folder()
{
- return $this->belongsTo(\App\SharedFolder::class, 'shared_folder_id', 'id');
+ return $this->belongsTo(SharedFolder::class, 'shared_folder_id', 'id');
}
}
diff --git a/src/app/SignupCode.php b/src/app/SignupCode.php
--- a/src/app/SignupCode.php
+++ b/src/app/SignupCode.php
@@ -35,32 +35,16 @@
public const CODE_EXP_HOURS = 24;
- /**
- * The primary key associated with the table.
- *
- * @var string
- */
+ /** @var string The primary key associated with the table */
protected $primaryKey = 'code';
- /**
- * Indicates if the IDs are auto-incrementing.
- *
- * @var bool
- */
+ /** @var bool Indicates if the IDs are auto-incrementing */
public $incrementing = false;
- /**
- * The "type" of the auto-incrementing ID.
- *
- * @var string
- */
+ /** @var string The "type" of the auto-incrementing ID */
protected $keyType = 'string';
- /**
- * The attributes that are mass assignable.
- *
- * @var array
- */
+ /** @var array<int, string> The attributes that are mass assignable */
protected $fillable = [
'code',
'email',
@@ -72,14 +56,12 @@
'voucher'
];
- protected $casts = ['headers' => 'array'];
+ /** @var array<string, string> The attributes that should be cast */
+ protected $casts = [
+ 'expires_at' => 'datetime:Y-m-d H:i:s',
+ 'headers' => 'array'
+ ];
- /**
- * The attributes that should be mutated to dates.
- *
- * @var array
- */
- protected $dates = ['expires_at'];
/**
* Check if code is expired.
diff --git a/src/app/SignupInvitation.php b/src/app/SignupInvitation.php
--- a/src/app/SignupInvitation.php
+++ b/src/app/SignupInvitation.php
@@ -31,13 +31,10 @@
public const STATUS_COMPLETED = 1 << 3;
- /**
- * The attributes that are mass assignable.
- *
- * @var array
- */
+ /** @var array<int, string> The attributes that are mass assignable */
protected $fillable = ['email'];
+
/**
* Returns whether this invitation process completed (user signed up)
*
@@ -85,6 +82,6 @@
*/
public function user()
{
- return $this->belongsTo('App\User', 'user_id', 'id');
+ return $this->belongsTo(User::class, 'user_id', 'id');
}
}
diff --git a/src/app/Sku.php b/src/app/Sku.php
--- a/src/app/Sku.php
+++ b/src/app/Sku.php
@@ -2,10 +2,10 @@
namespace App;
-use Illuminate\Database\Eloquent\Model;
-use Spatie\Translatable\HasTranslations;
use App\Traits\BelongsToTenantTrait;
use App\Traits\UuidStrKeyTrait;
+use Illuminate\Database\Eloquent\Model;
+use Spatie\Translatable\HasTranslations;
/**
* The eloquent definition of a Stock Keeping Unit (SKU).
@@ -28,10 +28,12 @@
use HasTranslations;
use UuidStrKeyTrait;
+ /** @var array<string, string> The attributes that should be cast */
protected $casts = [
'units_free' => 'integer'
];
+ /** @var array<int, string> The attributes that are mass assignable */
protected $fillable = [
'active',
'cost',
@@ -45,7 +47,7 @@
'units_free',
];
- /** @var array Translatable properties */
+ /** @var array<int, string> Translatable properties */
public $translatable = [
'name',
'description',
@@ -58,7 +60,7 @@
*/
public function entitlements()
{
- return $this->hasMany('App\Entitlement');
+ return $this->hasMany(Entitlement::class);
}
/**
@@ -68,9 +70,8 @@
*/
public function packages()
{
- return $this->belongsToMany(
- 'App\Package',
- 'package_skus'
- )->using('App\PackageSku')->withPivot(['cost', 'qty']);
+ return $this->belongsToMany(Package::class, 'package_skus')
+ ->using(PackageSku::class)
+ ->withPivot(['cost', 'qty']);
}
}
diff --git a/src/app/Tenant.php b/src/app/Tenant.php
--- a/src/app/Tenant.php
+++ b/src/app/Tenant.php
@@ -15,10 +15,8 @@
{
use SettingsTrait;
- protected $fillable = [
- 'id',
- 'title',
- ];
+ /** @var array<int, string> The attributes that are mass assignable */
+ protected $fillable = ['id', 'title'];
/**
@@ -67,7 +65,7 @@
*/
public function discounts()
{
- return $this->hasMany('App\Discount');
+ return $this->hasMany(Discount::class);
}
/**
@@ -77,7 +75,7 @@
*/
public function signupInvitations()
{
- return $this->hasMany('App\SignupInvitation');
+ return $this->hasMany(SignupInvitation::class);
}
/*
@@ -87,7 +85,7 @@
*/
public function wallet(): ?Wallet
{
- $user = \App\User::where('role', 'reseller')->where('tenant_id', $this->id)->first();
+ $user = User::where('role', 'reseller')->where('tenant_id', $this->id)->first();
return $user ? $user->wallets->first() : null;
}
diff --git a/src/app/TenantSetting.php b/src/app/TenantSetting.php
--- a/src/app/TenantSetting.php
+++ b/src/app/TenantSetting.php
@@ -14,9 +14,8 @@
*/
class TenantSetting extends Model
{
- protected $fillable = [
- 'tenant_id', 'key', 'value'
- ];
+ /** @var array<int, string> The attributes that are mass assignable */
+ protected $fillable = ['tenant_id', 'key', 'value'];
/**
* The tenant to which this setting belongs.
@@ -25,6 +24,6 @@
*/
public function tenant()
{
- return $this->belongsTo('\App\Tenant', 'tenant_id', 'id');
+ return $this->belongsTo(Tenant::class, 'tenant_id', 'id');
}
}
diff --git a/src/app/Traits/UserAliasesTrait.php b/src/app/Traits/AliasesTrait.php
rename from src/app/Traits/UserAliasesTrait.php
rename to src/app/Traits/AliasesTrait.php
--- a/src/app/Traits/UserAliasesTrait.php
+++ b/src/app/Traits/AliasesTrait.php
@@ -2,11 +2,22 @@
namespace App\Traits;
-trait UserAliasesTrait
+use Illuminate\Support\Str;
+
+trait AliasesTrait
{
+ /**
+ * Email aliases of this object.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ public function aliases()
+ {
+ return $this->hasMany(static::class . 'Alias');
+ }
+
/**
* Find whether an email address exists as an alias
- * (including aliases of deleted users).
*
* @param string $email Email address
*
@@ -19,14 +30,13 @@
}
$email = \strtolower($email);
+ $class = static::class . 'Alias';
- $count = \App\UserAlias::where('alias', $email)->count();
-
- return $count > 0;
+ return $class::where('alias', $email)->count() > 0;
}
/**
- * A helper to update user aliases list.
+ * A helper to update object's aliases list.
*
* Example Usage:
*
@@ -47,6 +57,7 @@
$existing_aliases = [];
foreach ($this->aliases()->get() as $alias) {
+ /** @var \App\UserAlias|\App\SharedFolderAlias $alias */
if (!in_array($alias->alias, $aliases)) {
$alias->delete();
} else {
diff --git a/src/app/Traits/EmailPropertyTrait.php b/src/app/Traits/EmailPropertyTrait.php
new file mode 100644
--- /dev/null
+++ b/src/app/Traits/EmailPropertyTrait.php
@@ -0,0 +1,92 @@
+<?php
+
+namespace App\Traits;
+
+trait EmailPropertyTrait
+{
+ /** @var ?string Domain name for the to-be-created object */
+ public $domainName;
+
+
+ /**
+ * Boot function from Laravel.
+ */
+ protected static function bootEmailPropertyTrait()
+ {
+ static::creating(function ($model) {
+ if (empty($model->email) && defined('static::EMAIL_TEMPLATE')) {
+ $template = static::EMAIL_TEMPLATE; // @phpstan-ignore-line
+ $defaults = [
+ 'type' => 'mail',
+ ];
+
+ foreach (['id', 'domainName', 'type'] as $prop) {
+ if (strpos($template, "{{$prop}}") === false) {
+ continue;
+ }
+
+ $value = $model->{$prop} ?? ($defaults[$prop] ?? '');
+
+ if ($value === '' || $value === null) {
+ throw new \Exception("Missing '{$prop}' property for " . static::class);
+ }
+
+ $template = str_replace("{{$prop}}", $value, $template);
+ }
+
+ $model->email = strtolower($template);
+ }
+ });
+ }
+
+ /**
+ * Returns the object's domain (including soft-deleted).
+ *
+ * @return ?\App\Domain The domain to which the object belongs to, NULL if it does not exist
+ */
+ public function domain(): ?\App\Domain
+ {
+ if (empty($this->email) && isset($this->domainName)) {
+ $domainName = $this->domainName;
+ } else {
+ list($local, $domainName) = explode('@', $this->email);
+ }
+
+ return \App\Domain::withTrashed()->where('namespace', $domainName)->first();
+ }
+
+ /**
+ * Find whether an email address exists as a model object (including soft-deleted).
+ *
+ * @param string $email Email address
+ * @param bool $return_object Return model instance instead of a boolean
+ *
+ * @return static|bool True or Model object if found, False otherwise
+ */
+ public static function emailExists(string $email, bool $return_object = false)
+ {
+ if (strpos($email, '@') === false) {
+ return false;
+ }
+
+ $email = \strtolower($email);
+
+ $object = static::withTrashed()->where('email', $email)->first();
+
+ if ($object) {
+ return $return_object ? $object : true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Ensure the email is appropriately cased.
+ *
+ * @param string $email Email address
+ */
+ public function setEmailAttribute(string $email): void
+ {
+ $this->attributes['email'] = strtolower($email);
+ }
+}
diff --git a/src/app/Transaction.php b/src/app/Transaction.php
--- a/src/app/Transaction.php
+++ b/src/app/Transaction.php
@@ -2,8 +2,6 @@
namespace App;
-use App\Entitlement;
-use App\Wallet;
use App\Traits\UuidStrKeyTrait;
use Illuminate\Database\Eloquent\Model;
@@ -34,27 +32,23 @@
public const WALLET_REFUND = 'refund';
public const WALLET_CHARGEBACK = 'chback';
+ /** @var array<int, string> The attributes that are mass assignable */
protected $fillable = [
// actor, if any
'user_email',
-
// entitlement, wallet
'object_id',
'object_type',
-
// entitlement: created, deleted, billed
// wallet: debit, credit, award, penalty
'type',
-
'amount',
-
'description',
-
// parent, for example wallet debit is parent for entitlements charged.
'transaction_id'
];
- /** @var array Casts properties as type */
+ /** @var array<string, string> Casts properties as type */
protected $casts = [
'amount' => 'integer',
];
diff --git a/src/app/User.php b/src/app/User.php
--- a/src/app/User.php
+++ b/src/app/User.php
@@ -2,20 +2,19 @@
namespace App;
-use App\UserAlias;
+use App\Traits\AliasesTrait;
use App\Traits\BelongsToTenantTrait;
use App\Traits\EntitleableTrait;
-use App\Traits\UserAliasesTrait;
+use App\Traits\EmailPropertyTrait;
use App\Traits\UserConfigTrait;
use App\Traits\UuidIntKeyTrait;
use App\Traits\SettingsTrait;
use App\Traits\StatusPropertyTrait;
-use App\Wallet;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Foundation\Auth\User as Authenticatable;
-use Iatstuti\Database\Support\NullableFields;
+use Dyrynda\Database\Support\NullableFields;
use Laravel\Passport\HasApiTokens;
use League\OAuth2\Server\Exception\OAuthServerException;
@@ -31,12 +30,13 @@
*/
class User extends Authenticatable
{
+ use AliasesTrait;
use BelongsToTenantTrait;
use EntitleableTrait;
+ use EmailPropertyTrait;
use HasApiTokens;
use NullableFields;
use UserConfigTrait;
- use UserAliasesTrait;
use UuidIntKeyTrait;
use SettingsTrait;
use SoftDeletes;
@@ -57,11 +57,7 @@
// user in "limited feature-set" state
public const STATUS_DEGRADED = 1 << 6;
- /**
- * The attributes that are mass assignable.
- *
- * @var array
- */
+ /** @var array<int, string> The attributes that are mass assignable */
protected $fillable = [
'id',
'email',
@@ -70,22 +66,26 @@
'status',
];
- /**
- * The attributes that should be hidden for arrays.
- *
- * @var array
- */
+ /** @var array<int, string> The attributes that should be hidden for arrays */
protected $hidden = [
'password',
'password_ldap',
'role'
];
+ /** @var array<int, string> The attributes that can be null */
protected $nullable = [
'password',
'password_ldap'
];
+ /** @var array<string, string> The attributes that should be cast */
+ protected $casts = [
+ 'created_at' => 'datetime:Y-m-d H:i:s',
+ 'deleted_at' => 'datetime:Y-m-d H:i:s',
+ 'updated_at' => 'datetime:Y-m-d H:i:s',
+ ];
+
/**
* Any wallets on which this user is a controller.
*
@@ -96,23 +96,13 @@
public function accounts()
{
return $this->belongsToMany(
- 'App\Wallet', // The foreign object definition
+ Wallet::class, // The foreign object definition
'user_accounts', // The table name
'user_id', // The local foreign key
'wallet_id' // The remote foreign key
);
}
- /**
- * Email aliases of this user.
- *
- * @return \Illuminate\Database\Eloquent\Relations\HasMany
- */
- public function aliases()
- {
- return $this->hasMany('App\UserAlias', 'user_id');
- }
-
/**
* Assign a package to a user. The user should not have any existing entitlements.
*
@@ -263,20 +253,6 @@
$this->save();
}
- /**
- * Return the \App\Domain for this user.
- *
- * @return \App\Domain|null
- */
- public function domain()
- {
- list($local, $domainName) = explode('@', $this->email);
-
- $domain = \App\Domain::withTrashed()->where('namespace', $domainName)->first();
-
- return $domain;
- }
-
/**
* List the domains to which this user is entitled.
*
@@ -298,39 +274,14 @@
$query->withEnvTenantContext();
}
- $query->whereRaw(sprintf('(domains.type & %s)', Domain::TYPE_PUBLIC))
- ->whereRaw(sprintf('(domains.status & %s)', Domain::STATUS_ACTIVE));
+ $query->where('domains.type', '&', Domain::TYPE_PUBLIC)
+ ->where('domains.status', '&', Domain::STATUS_ACTIVE);
});
}
return $domains;
}
- /**
- * Find whether an email address exists as a user (including deleted users).
- *
- * @param string $email Email address
- * @param bool $return_user Return User instance instead of boolean
- *
- * @return \App\User|bool True or User model object if found, False otherwise
- */
- public static function emailExists(string $email, bool $return_user = false)
- {
- if (strpos($email, '@') === false) {
- return false;
- }
-
- $email = \strtolower($email);
-
- $user = self::withTrashed()->where('email', $email)->first();
-
- if ($user) {
- return $return_user ? $user : true;
- }
-
- return false;
- }
-
/**
* Return entitleable objects of a specified type controlled by the current user.
*
@@ -444,7 +395,7 @@
$name = trim($settings['first_name'] . ' ' . $settings['last_name']);
if (empty($name) && $fallback) {
- return trim(\trans('app.siteuser', ['site' => \App\Tenant::getConfig($this->tenant_id, 'app.name')]));
+ return trim(\trans('app.siteuser', ['site' => Tenant::getConfig($this->tenant_id, 'app.name')]));
}
return $name;
@@ -457,7 +408,7 @@
*/
public function passwords()
{
- return $this->hasMany('App\UserPassword');
+ return $this->hasMany(UserPassword::class);
}
/**
@@ -470,7 +421,7 @@
*/
public function resources($with_accounts = true)
{
- return $this->entitleables(\App\Resource::class, $with_accounts);
+ return $this->entitleables(Resource::class, $with_accounts);
}
/**
@@ -483,7 +434,7 @@
*/
public function sharedFolders($with_accounts = true)
{
- return $this->entitleables(\App\SharedFolder::class, $with_accounts);
+ return $this->entitleables(SharedFolder::class, $with_accounts);
}
public function senderPolicyFrameworkWhitelist($clientName)
@@ -561,7 +512,7 @@
*/
public function verificationcodes()
{
- return $this->hasMany('App\VerificationCode', 'user_id', 'id');
+ return $this->hasMany(VerificationCode::class, 'user_id', 'id');
}
/**
@@ -571,7 +522,7 @@
*/
public function wallets()
{
- return $this->hasMany('App\Wallet');
+ return $this->hasMany(Wallet::class);
}
/**
diff --git a/src/app/UserAlias.php b/src/app/UserAlias.php
--- a/src/app/UserAlias.php
+++ b/src/app/UserAlias.php
@@ -13,9 +13,18 @@
*/
class UserAlias extends Model
{
- protected $fillable = [
- 'user_id', 'alias'
- ];
+ /** @var array<int, string> The attributes that are mass assignable */
+ protected $fillable = ['user_id', 'alias'];
+
+ /**
+ * Ensure the email address is appropriately cased.
+ *
+ * @param string $alias Email address
+ */
+ public function setAliasAttribute(string $alias)
+ {
+ $this->attributes['alias'] = \strtolower($alias);
+ }
/**
* The user to which this alias belongs.
@@ -24,6 +33,6 @@
*/
public function user()
{
- return $this->belongsTo('\App\User', 'user_id', 'id');
+ return $this->belongsTo(User::class, 'user_id', 'id');
}
}
diff --git a/src/app/UserPassword.php b/src/app/UserPassword.php
--- a/src/app/UserPassword.php
+++ b/src/app/UserPassword.php
@@ -16,13 +16,15 @@
/** @var bool Indicates if the model should be timestamped. */
public $timestamps = false;
- /** @var array The attributes that should be mutated to dates. */
- protected $dates = ['created_at'];
+ /** @var array<string, string> The attributes that should be cast. */
+ protected $casts = [
+ 'created_at' => 'datetime:Y-m-d H:i:s',
+ ];
- /** @var array The attributes that are mass assignable. */
+ /** @var array<int, string> The attributes that are mass assignable. */
protected $fillable = ['user_id', 'password'];
- /** @var array The attributes that should be hidden for arrays. */
+ /** @var array<int, string> The attributes that should be hidden for arrays. */
protected $hidden = ['password'];
/**
@@ -32,6 +34,6 @@
*/
public function user()
{
- return $this->belongsTo('\App\User', 'user_id', 'id');
+ return $this->belongsTo(User::class, 'user_id', 'id');
}
}
diff --git a/src/app/UserSetting.php b/src/app/UserSetting.php
--- a/src/app/UserSetting.php
+++ b/src/app/UserSetting.php
@@ -14,9 +14,8 @@
*/
class UserSetting extends Model
{
- protected $fillable = [
- 'user_id', 'key', 'value'
- ];
+ /** @var array<int, string> The attributes that are mass assignable */
+ protected $fillable = ['user_id', 'key', 'value'];
/**
* The user to which this setting belongs.
@@ -25,6 +24,6 @@
*/
public function user()
{
- return $this->belongsTo('\App\User', 'user_id', 'id');
+ return $this->belongsTo(User::class, 'user_id', 'id');
}
}
diff --git a/src/app/VerificationCode.php b/src/app/VerificationCode.php
--- a/src/app/VerificationCode.php
+++ b/src/app/VerificationCode.php
@@ -26,49 +26,25 @@
// Code expires after so many hours
public const CODE_EXP_HOURS = 8;
- /**
- * The primary key associated with the table.
- *
- * @var string
- */
+ /** @var string The primary key associated with the table */
protected $primaryKey = 'code';
- /**
- * Indicates if the IDs are auto-incrementing.
- *
- * @var bool
- */
+ /** @var bool Indicates if the IDs are auto-incrementing */
public $incrementing = false;
- /**
- * The "type" of the auto-incrementing ID.
- *
- * @var string
- */
+ /** @var string The "type" of the auto-incrementing ID */
protected $keyType = 'string';
- /**
- * Indicates if the model should be timestamped.
- *
- * @var bool
- */
+ /** @var bool Indicates if the model should be timestamped */
public $timestamps = false;
- /**
- * Casts properties as type
- *
- * @var array
- */
+ /** @var array<string, string> Casts properties as type */
protected $casts = [
'active' => 'boolean',
'expires_at' => 'datetime',
];
- /**
- * The attributes that are mass assignable.
- *
- * @var array
- */
+ /** @var array<int, string> The attributes that are mass assignable */
protected $fillable = ['user_id', 'code', 'short_code', 'mode', 'expires_at', 'active'];
@@ -102,6 +78,6 @@
*/
public function user()
{
- return $this->belongsTo('\App\User', 'user_id', 'id');
+ return $this->belongsTo(User::class, 'user_id', 'id');
}
}
diff --git a/src/app/Wallet.php b/src/app/Wallet.php
--- a/src/app/Wallet.php
+++ b/src/app/Wallet.php
@@ -2,11 +2,10 @@
namespace App;
-use App\User;
use App\Traits\SettingsTrait;
use App\Traits\UuidStrKeyTrait;
use Carbon\Carbon;
-use Iatstuti\Database\Support\NullableFields;
+use Dyrynda\Database\Support\NullableFields;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
@@ -30,39 +29,23 @@
public $timestamps = false;
- /**
- * The attributes' default values.
- *
- * @var array
- */
+ /** @var array The attributes' default values */
protected $attributes = [
'balance' => 0,
];
- /**
- * The attributes that are mass assignable.
- *
- * @var array
- */
+ /** @var array<int, string> The attributes that are mass assignable */
protected $fillable = [
'currency',
'description'
];
- /**
- * The attributes that can be not set.
- *
- * @var array
- */
+ /** @var array<int, string> The attributes that can be not set */
protected $nullable = [
'description',
];
- /**
- * The types of attributes to which its values will be cast
- *
- * @var array
- */
+ /** @var array<string, string> The types of attributes to which its values will be cast */
protected $casts = [
'balance' => 'integer',
];
@@ -160,7 +143,7 @@
}
$entitlementTransactions[] = $entitlement->createTransaction(
- \App\Transaction::ENTITLEMENT_BILLED,
+ Transaction::ENTITLEMENT_BILLED,
$cost
);
}
@@ -232,8 +215,8 @@
public function controllers()
{
return $this->belongsToMany(
- 'App\User', // The foreign object definition
- 'user_accounts', // The table name
+ User::class, // The foreign object definition
+ 'user_accounts', // The table name
'wallet_id', // The local foreign key
'user_id' // The remote foreign key
);
@@ -269,11 +252,11 @@
$this->save();
- \App\Transaction::create(
+ Transaction::create(
[
'object_id' => $this->id,
- 'object_type' => \App\Wallet::class,
- 'type' => \App\Transaction::WALLET_CREDIT,
+ 'object_type' => Wallet::class,
+ 'type' => Transaction::WALLET_CREDIT,
'amount' => $amount,
'description' => $description
]
@@ -301,18 +284,18 @@
$this->save();
- $transaction = \App\Transaction::create(
+ $transaction = Transaction::create(
[
'object_id' => $this->id,
- 'object_type' => \App\Wallet::class,
- 'type' => \App\Transaction::WALLET_DEBIT,
+ 'object_type' => Wallet::class,
+ 'type' => Transaction::WALLET_DEBIT,
'amount' => $amount * -1,
'description' => $description
]
);
if (!empty($eTIDs)) {
- \App\Transaction::whereIn('id', $eTIDs)->update(['transaction_id' => $transaction->id]);
+ Transaction::whereIn('id', $eTIDs)->update(['transaction_id' => $transaction->id]);
}
return $this;
@@ -325,7 +308,7 @@
*/
public function discount()
{
- return $this->belongsTo('App\Discount', 'discount_id', 'id');
+ return $this->belongsTo(Discount::class, 'discount_id', 'id');
}
/**
@@ -335,7 +318,7 @@
*/
public function entitlements()
{
- return $this->hasMany('App\Entitlement');
+ return $this->hasMany(Entitlement::class);
}
/**
@@ -396,7 +379,7 @@
*/
public function owner()
{
- return $this->belongsTo('App\User', 'user_id', 'id');
+ return $this->belongsTo(User::class, 'user_id', 'id');
}
/**
@@ -406,7 +389,7 @@
*/
public function payments()
{
- return $this->hasMany('App\Payment');
+ return $this->hasMany(Payment::class);
}
/**
@@ -430,10 +413,10 @@
*/
public function transactions()
{
- return \App\Transaction::where(
+ return Transaction::where(
[
'object_id' => $this->id,
- 'object_type' => \App\Wallet::class
+ 'object_type' => Wallet::class
]
);
}
@@ -492,7 +475,7 @@
// FIXME: Shouldn't we store also cost=0 transactions (to have the full history)?
$entitlementTransactions[] = $entitlement->createTransaction(
- \App\Transaction::ENTITLEMENT_BILLED,
+ Transaction::ENTITLEMENT_BILLED,
$cost
);
}
diff --git a/src/app/WalletSetting.php b/src/app/WalletSetting.php
--- a/src/app/WalletSetting.php
+++ b/src/app/WalletSetting.php
@@ -14,9 +14,8 @@
*/
class WalletSetting extends Model
{
- protected $fillable = [
- 'wallet_id', 'key', 'value'
- ];
+ /** @var array<int, string> The attributes that are mass assignable */
+ protected $fillable = ['wallet_id', 'key', 'value'];
/**
* The wallet to which this setting belongs.
@@ -25,6 +24,6 @@
*/
public function wallet()
{
- return $this->belongsTo('\App\Wallet', 'wallet_id', 'id');
+ return $this->belongsTo(Wallet::class, 'wallet_id', 'id');
}
}
diff --git a/src/composer.json b/src/composer.json
--- a/src/composer.json
+++ b/src/composer.json
@@ -1,7 +1,7 @@
{
- "name": "laravel/laravel",
+ "name": "kolab/kolab4",
"type": "project",
- "description": "The Laravel Framework.",
+ "description": "Kolab 4",
"keywords": [
"framework",
"laravel"
@@ -14,37 +14,33 @@
}
],
"require": {
- "php": "^7.3",
- "barryvdh/laravel-dompdf": "^0.8.6",
- "doctrine/dbal": "^2.13",
- "dyrynda/laravel-nullable-fields": "*",
- "fideloper/proxy": "^4.0",
- "guzzlehttp/guzzle": "^7.3",
+ "php": "^8.0",
+ "barryvdh/laravel-dompdf": "^1.0.0",
+ "doctrine/dbal": "^3.3.2",
+ "dyrynda/laravel-nullable-fields": "^4.2.0",
+ "guzzlehttp/guzzle": "^7.4.1",
"kolab/net_ldap3": "dev-master",
- "laravel/framework": "6.*",
- "laravel/horizon": "^3",
- "laravel/passport": "^9",
- "laravel/tinker": "^2.4",
- "mlocati/spf-lib": "^3.0",
- "mollie/laravel-mollie": "^2.9",
+ "laravel/framework": "^9.2",
+ "laravel/horizon": "^5.9",
+ "laravel/octane": "^1.2",
+ "laravel/passport": "^10.3",
+ "laravel/tinker": "^2.7",
+ "mlocati/spf-lib": "^3.1",
+ "mollie/laravel-mollie": "^2.19",
"moontoast/math": "^1.2",
- "morrislaptop/laravel-queue-clear": "^1.2",
"pear/crypt_gpg": "^1.6.6",
- "silviolleite/laravelpwa": "^2.0",
- "spatie/laravel-translatable": "^4.2",
+ "predis/predis": "^1.1.10",
+ "spatie/laravel-translatable": "^5.2",
"spomky-labs/otphp": "~4.0.0",
- "stripe/stripe-php": "^7.29",
- "swooletw/laravel-swoole": "^2.6"
+ "stripe/stripe-php": "^7.29"
},
"require-dev": {
- "beyondcode/laravel-er-diagram-generator": "^1.3",
- "code-lts/doctum": "^5.1",
- "kirschbaum-development/mail-intercept": "^0.2.4",
- "laravel/dusk": "~6.15.0",
- "nunomaduro/larastan": "^0.7",
- "phpstan/phpstan": "^0.12",
+ "code-lts/doctum": "^5.5.1",
+ "laravel/dusk": "~6.22.0",
+ "nunomaduro/larastan": "^2.0",
+ "phpstan/phpstan": "^1.4",
"phpunit/phpunit": "^9",
- "squizlabs/php_codesniffer": "3.*"
+ "squizlabs/php_codesniffer": "^3.6"
},
"config": {
"optimize-autoloader": true,
@@ -77,6 +73,9 @@
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
],
+ "post-update-cmd": [
+ "@php artisan vendor:publish --tag=laravel-assets --ansi --force"
+ ],
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
diff --git a/src/config/app.php b/src/config/app.php
--- a/src/config/app.php
+++ b/src/config/app.php
@@ -57,7 +57,7 @@
'public_url' => env('APP_PUBLIC_URL', env('APP_URL', 'http://localhost')),
- 'asset_url' => env('ASSET_URL', null),
+ 'asset_url' => env('ASSET_URL'),
'support_url' => env('SUPPORT_URL', null),
@@ -215,44 +215,9 @@
|
*/
- 'aliases' => [
- 'App' => Illuminate\Support\Facades\App::class,
- 'Arr' => Illuminate\Support\Arr::class,
- 'Artisan' => Illuminate\Support\Facades\Artisan::class,
- 'Auth' => Illuminate\Support\Facades\Auth::class,
- 'Blade' => Illuminate\Support\Facades\Blade::class,
- 'Broadcast' => Illuminate\Support\Facades\Broadcast::class,
- 'Bus' => Illuminate\Support\Facades\Bus::class,
- 'Cache' => Illuminate\Support\Facades\Cache::class,
- 'Config' => Illuminate\Support\Facades\Config::class,
- 'Cookie' => Illuminate\Support\Facades\Cookie::class,
- 'Crypt' => Illuminate\Support\Facades\Crypt::class,
- 'DB' => Illuminate\Support\Facades\DB::class,
- 'Eloquent' => Illuminate\Database\Eloquent\Model::class,
- 'Event' => Illuminate\Support\Facades\Event::class,
- 'File' => Illuminate\Support\Facades\File::class,
- 'Gate' => Illuminate\Support\Facades\Gate::class,
- 'Hash' => Illuminate\Support\Facades\Hash::class,
- 'Lang' => Illuminate\Support\Facades\Lang::class,
- 'Log' => Illuminate\Support\Facades\Log::class,
- 'Mail' => Illuminate\Support\Facades\Mail::class,
- 'Notification' => Illuminate\Support\Facades\Notification::class,
- 'Password' => Illuminate\Support\Facades\Password::class,
+ 'aliases' => \Illuminate\Support\Facades\Facade::defaultAliases()->merge([
'PDF' => Barryvdh\DomPDF\Facade::class,
- 'Queue' => Illuminate\Support\Facades\Queue::class,
- 'Redirect' => Illuminate\Support\Facades\Redirect::class,
- 'Redis' => Illuminate\Support\Facades\Redis::class,
- 'Request' => Illuminate\Support\Facades\Request::class,
- 'Response' => Illuminate\Support\Facades\Response::class,
- 'Route' => Illuminate\Support\Facades\Route::class,
- 'Schema' => Illuminate\Support\Facades\Schema::class,
- 'Session' => Illuminate\Support\Facades\Session::class,
- 'Storage' => Illuminate\Support\Facades\Storage::class,
- 'Str' => Illuminate\Support\Str::class,
- 'URL' => Illuminate\Support\Facades\URL::class,
- 'Validator' => Illuminate\Support\Facades\Validator::class,
- 'View' => Illuminate\Support\Facades\View::class,
- ],
+ ])->toArray(),
'headers' => [
'csp' => env('APP_HEADER_CSP', ""),
diff --git a/src/config/auth.php b/src/config/auth.php
--- a/src/config/auth.php
+++ b/src/config/auth.php
@@ -66,7 +66,7 @@
'providers' => [
'users' => [
- 'driver' => 'ldap',
+ 'driver' => 'eloquent',
'model' => App\User::class,
],
diff --git a/src/config/broadcasting.php b/src/config/broadcasting.php
--- a/src/config/broadcasting.php
+++ b/src/config/broadcasting.php
@@ -11,7 +11,7 @@
| framework when an event needs to be broadcast. You may set this to
| any of the connections defined in the "connections" array below.
|
- | Supported: "pusher", "redis", "log", "null"
+ | Supported: "pusher", "ably", "redis", "log", "null"
|
*/
@@ -39,6 +39,14 @@
'cluster' => env('PUSHER_APP_CLUSTER'),
'useTLS' => true,
],
+ 'client_options' => [
+ // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
+ ],
+ ],
+
+ 'ably' => [
+ 'driver' => 'ably',
+ 'key' => env('ABLY_KEY'),
],
'redis' => [
diff --git a/src/config/cache.php b/src/config/cache.php
--- a/src/config/cache.php
+++ b/src/config/cache.php
@@ -39,12 +39,14 @@
'array' => [
'driver' => 'array',
+ 'serialize' => false,
],
'database' => [
'driver' => 'database',
'table' => 'cache',
'connection' => null,
+ 'lock_connection' => null,
],
'file' => [
@@ -74,6 +76,7 @@
'redis' => [
'driver' => 'redis',
'connection' => 'cache',
+ 'lock_connection' => 'default',
],
'dynamodb' => [
@@ -85,6 +88,10 @@
'endpoint' => env('DYNAMODB_ENDPOINT'),
],
+ 'octane' => [
+ 'driver' => 'octane',
+ ],
+
],
/*
@@ -98,6 +105,6 @@
|
*/
- 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache'),
+ 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_cache_'),
];
diff --git a/src/config/database.php b/src/config/database.php
--- a/src/config/database.php
+++ b/src/config/database.php
@@ -75,7 +75,7 @@
'charset' => 'utf8',
'prefix' => '',
'prefix_indexes' => true,
- 'schema' => 'public',
+ 'search_path' => 'public',
'sslmode' => 'prefer',
],
@@ -135,7 +135,7 @@
'default' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
- 'password' => env('REDIS_PASSWORD', null),
+ 'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', 6379),
'database' => env('REDIS_DB', 0),
],
@@ -143,7 +143,7 @@
'cache' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
- 'password' => env('REDIS_PASSWORD', null),
+ 'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', 6379),
'database' => env('REDIS_CACHE_DB', 1),
],
diff --git a/src/config/filesystems.php b/src/config/filesystems.php
--- a/src/config/filesystems.php
+++ b/src/config/filesystems.php
@@ -13,20 +13,7 @@
|
*/
- 'default' => env('FILESYSTEM_DRIVER', 'local'),
-
- /*
- |--------------------------------------------------------------------------
- | Default Cloud Filesystem Disk
- |--------------------------------------------------------------------------
- |
- | Many applications store files both locally and in the cloud. For this
- | reason, you may specify a default "cloud" driver here. This driver
- | will be bound as the Cloud disk implementation in the container.
- |
- */
-
- 'cloud' => env('FILESYSTEM_CLOUD', 's3'),
+ 'default' => env('FILESYSTEM_DISK', 'local'),
/*
|--------------------------------------------------------------------------
@@ -37,7 +24,7 @@
| may even configure multiple disks of the same driver. Defaults have
| been setup for each driver as an example of the required options.
|
- | Supported Drivers: "local", "ftp", "sftp", "s3", "rackspace"
+ | Supported Drivers: "local", "ftp", "sftp", "s3"
|
*/
@@ -56,7 +43,7 @@
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
- 'url' => env('APP_URL').'/storage',
+ 'url' => env('APP_URL') . '/storage',
'visibility' => 'public',
],
@@ -67,8 +54,25 @@
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
+ 'endpoint' => env('AWS_ENDPOINT'),
+ 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
],
],
+ /*
+ |--------------------------------------------------------------------------
+ | Symbolic Links
+ |--------------------------------------------------------------------------
+ |
+ | Here you may configure the symbolic links that will be created when the
+ | `storage:link` Artisan command is executed. The array keys should be
+ | the locations of the links and the values should be their targets.
+ |
+ */
+
+ 'links' => [
+ public_path('storage') => storage_path('app/public'),
+ ],
+
];
diff --git a/src/config/hashing.php b/src/config/hashing.php
--- a/src/config/hashing.php
+++ b/src/config/hashing.php
@@ -44,9 +44,9 @@
*/
'argon' => [
- 'memory' => 1024,
- 'threads' => 2,
- 'time' => 2,
+ 'memory' => 65536,
+ 'threads' => 1,
+ 'time' => 4,
],
];
diff --git a/src/config/logging.php b/src/config/logging.php
--- a/src/config/logging.php
+++ b/src/config/logging.php
@@ -1,5 +1,6 @@
<?php
+use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler;
@@ -18,6 +19,19 @@
'default' => env('LOG_CHANNEL', 'stack'),
+ /*
+ |--------------------------------------------------------------------------
+ | Deprecations Log Channel
+ |--------------------------------------------------------------------------
+ |
+ | This option controls the log channel that should be used to log warnings
+ | regarding deprecated PHP and library features. This allows you to get
+ | your application ready for upcoming major versions of dependencies.
+ |
+ */
+
+ 'deprecations' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
+
/*
|--------------------------------------------------------------------------
| Log Channels
@@ -43,13 +57,13 @@
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
- 'level' => 'debug',
+ 'level' => env('LOG_LEVEL', 'debug'),
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
- 'level' => 'debug',
+ 'level' => env('LOG_LEVEL', 'debug'),
'days' => 14,
],
@@ -58,21 +72,23 @@
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'Laravel Log',
'emoji' => ':boom:',
- 'level' => 'critical',
+ 'level' => env('LOG_LEVEL', 'critical'),
],
'papertrail' => [
'driver' => 'monolog',
- 'level' => 'debug',
- 'handler' => SyslogUdpHandler::class,
+ 'level' => env('LOG_LEVEL', 'debug'),
+ 'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
'handler_with' => [
'host' => env('PAPERTRAIL_URL'),
'port' => env('PAPERTRAIL_PORT'),
+ 'connectionString' => 'tls://' . env('PAPERTRAIL_URL') . ':' . env('PAPERTRAIL_PORT'),
],
],
'stderr' => [
'driver' => 'monolog',
+ 'level' => env('LOG_LEVEL', 'debug'),
'handler' => StreamHandler::class,
'formatter' => env('LOG_STDERR_FORMATTER'),
'with' => [
@@ -82,15 +98,22 @@
'syslog' => [
'driver' => 'syslog',
- 'level' => 'debug',
+ 'level' => env('LOG_LEVEL', 'debug'),
],
'errorlog' => [
'driver' => 'errorlog',
- 'level' => 'debug',
+ 'level' => env('LOG_LEVEL', 'debug'),
+ ],
+
+ 'null' => [
+ 'driver' => 'monolog',
+ 'handler' => NullHandler::class,
],
- ],
- 'slow_log' => (float) env('LOG_SLOW_REQUESTS', 5),
+ 'emergency' => [
+ 'path' => storage_path('logs/laravel.log'),
+ ],
+ ],
];
diff --git a/src/config/mail.php b/src/config/mail.php
--- a/src/config/mail.php
+++ b/src/config/mail.php
@@ -4,45 +4,80 @@
/*
|--------------------------------------------------------------------------
- | Mail Driver
+ | Default Mailer
|--------------------------------------------------------------------------
|
- | Laravel supports both SMTP and PHP's "mail" function as drivers for the
- | sending of e-mail. You may specify which one you're using throughout
- | your application here. By default, Laravel is setup for SMTP mail.
- |
- | Supported: "smtp", "sendmail", "mailgun", "mandrill", "ses",
- | "sparkpost", "postmark", "log", "array"
+ | This option controls the default mailer that is used to send any email
+ | messages sent by your application. Alternative mailers may be setup
+ | and used as needed; however, this mailer will be used by default.
|
*/
- 'driver' => env('MAIL_DRIVER', 'smtp'),
+ 'default' => env('MAIL_MAILER', 'smtp'),
/*
|--------------------------------------------------------------------------
- | SMTP Host Address
+ | Mailer Configurations
|--------------------------------------------------------------------------
|
- | Here you may provide the host address of the SMTP server used by your
- | applications. A default option is provided that is compatible with
- | the Mailgun mail service which will provide reliable deliveries.
+ | Here you may configure all of the mailers used by your application plus
+ | their respective settings. Several examples have been configured for
+ | you and you are free to add your own as your application requires.
|
- */
-
- 'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
-
- /*
- |--------------------------------------------------------------------------
- | SMTP Host Port
- |--------------------------------------------------------------------------
+ | Laravel supports a variety of mail "transport" drivers to be used while
+ | sending an e-mail. You will specify which one you are using for your
+ | mailers below. You are free to add additional mailers as required.
|
- | This is the SMTP port used by your application to deliver e-mails to
- | users of the application. Like the host we have set this value to
- | stay compatible with the Mailgun e-mail application by default.
+ | Supported: "smtp", "sendmail", "mailgun", "ses",
+ | "postmark", "log", "array", "failover"
|
*/
- 'port' => env('MAIL_PORT', 587),
+ 'mailers' => [
+ 'smtp' => [
+ 'transport' => 'smtp',
+ 'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
+ 'port' => env('MAIL_PORT', 587),
+ 'encryption' => env('MAIL_ENCRYPTION', 'tls'),
+ 'username' => env('MAIL_USERNAME'),
+ 'password' => env('MAIL_PASSWORD'),
+ 'timeout' => null,
+ ],
+
+ 'ses' => [
+ 'transport' => 'ses',
+ ],
+
+ 'mailgun' => [
+ 'transport' => 'mailgun',
+ ],
+
+ 'postmark' => [
+ 'transport' => 'postmark',
+ ],
+
+ 'sendmail' => [
+ 'transport' => 'sendmail',
+ 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -t -i'),
+ ],
+
+ 'log' => [
+ 'transport' => 'log',
+ 'channel' => env('MAIL_LOG_CHANNEL'),
+ ],
+
+ 'array' => [
+ 'transport' => 'array',
+ ],
+
+ 'failover' => [
+ 'transport' => 'failover',
+ 'mailers' => [
+ 'smtp',
+ 'log',
+ ],
+ ],
+ ],
/*
|--------------------------------------------------------------------------
@@ -76,47 +111,6 @@
'name' => env('MAIL_REPLYTO_NAME', ''),
],
- /*
- |--------------------------------------------------------------------------
- | E-Mail Encryption Protocol
- |--------------------------------------------------------------------------
- |
- | Here you may specify the encryption protocol that should be used when
- | the application send e-mail messages. A sensible default using the
- | transport layer security protocol should provide great security.
- |
- */
-
- 'encryption' => env('MAIL_ENCRYPTION', 'tls'),
-
- /*
- |--------------------------------------------------------------------------
- | SMTP Server Username
- |--------------------------------------------------------------------------
- |
- | If your SMTP server requires a username for authentication, you should
- | set it here. This will get used to authenticate with your server on
- | connection. You may also set the "password" value below this one.
- |
- */
-
- 'username' => env('MAIL_USERNAME'),
-
- 'password' => env('MAIL_PASSWORD'),
-
- /*
- |--------------------------------------------------------------------------
- | Sendmail System Path
- |--------------------------------------------------------------------------
- |
- | When using the "sendmail" driver to send e-mails, we will need to know
- | the path to where Sendmail lives on this server. A default path has
- | been provided here, which will work well on most of your systems.
- |
- */
-
- 'sendmail' => '/usr/sbin/sendmail -bs',
-
/*
|--------------------------------------------------------------------------
| Markdown Mail Settings
@@ -132,21 +126,8 @@
'theme' => 'default',
'paths' => [
- resource_path('views/emails'),
+ resource_path('views/vendor/mail'),
],
],
- /*
- |--------------------------------------------------------------------------
- | Log Channel
- |--------------------------------------------------------------------------
- |
- | If you are using the "log" driver, you may specify the logging channel
- | if you prefer to keep mail messages separate from other log entries
- | for simpler reading. Otherwise, the default channel will be used.
- |
- */
-
- 'log_channel' => env('MAIL_LOG_CHANNEL'),
-
];
diff --git a/src/config/octane.php b/src/config/octane.php
new file mode 100644
--- /dev/null
+++ b/src/config/octane.php
@@ -0,0 +1,222 @@
+<?php
+
+use Laravel\Octane\Contracts\OperationTerminated;
+use Laravel\Octane\Events\RequestHandled;
+use Laravel\Octane\Events\RequestReceived;
+use Laravel\Octane\Events\RequestTerminated;
+use Laravel\Octane\Events\TaskReceived;
+use Laravel\Octane\Events\TaskTerminated;
+use Laravel\Octane\Events\TickReceived;
+use Laravel\Octane\Events\TickTerminated;
+use Laravel\Octane\Events\WorkerErrorOccurred;
+use Laravel\Octane\Events\WorkerStarting;
+use Laravel\Octane\Events\WorkerStopping;
+use Laravel\Octane\Listeners\CollectGarbage;
+use Laravel\Octane\Listeners\DisconnectFromDatabases;
+use Laravel\Octane\Listeners\EnsureUploadedFilesAreValid;
+use Laravel\Octane\Listeners\EnsureUploadedFilesCanBeMoved;
+use Laravel\Octane\Listeners\FlushTemporaryContainerInstances;
+use Laravel\Octane\Listeners\FlushUploadedFiles;
+use Laravel\Octane\Listeners\ReportException;
+use Laravel\Octane\Listeners\StopWorkerIfNecessary;
+use Laravel\Octane\Octane;
+
+return [
+
+ /*
+ |--------------------------------------------------------------------------
+ | Octane Server
+ |--------------------------------------------------------------------------
+ |
+ | This value determines the default "server" that will be used by Octane
+ | when starting, restarting, or stopping your server via the CLI. You
+ | are free to change this to the supported server of your choosing.
+ |
+ | Supported: "roadrunner", "swoole"
+ |
+ */
+
+ 'server' => env('OCTANE_SERVER', 'swoole'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Force HTTPS
+ |--------------------------------------------------------------------------
+ |
+ | When this configuration value is set to "true", Octane will inform the
+ | framework that all absolute links must be generated using the HTTPS
+ | protocol. Otherwise your links may be generated using plain HTTP.
+ |
+ */
+
+ 'https' => env('OCTANE_HTTPS', true),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Octane Listeners
+ |--------------------------------------------------------------------------
+ |
+ | All of the event listeners for Octane's events are defined below. These
+ | listeners are responsible for resetting your application's state for
+ | the next request. You may even add your own listeners to the list.
+ |
+ */
+
+ 'listeners' => [
+ WorkerStarting::class => [
+ EnsureUploadedFilesAreValid::class,
+ EnsureUploadedFilesCanBeMoved::class,
+ ],
+
+ RequestReceived::class => [
+ ...Octane::prepareApplicationForNextOperation(),
+ ...Octane::prepareApplicationForNextRequest(),
+ //
+ ],
+
+ RequestHandled::class => [
+ //
+ ],
+
+ RequestTerminated::class => [
+ // FlushUploadedFiles::class,
+ ],
+
+ TaskReceived::class => [
+ ...Octane::prepareApplicationForNextOperation(),
+ //
+ ],
+
+ TaskTerminated::class => [
+ //
+ ],
+
+ TickReceived::class => [
+ ...Octane::prepareApplicationForNextOperation(),
+ //
+ ],
+
+ TickTerminated::class => [
+ //
+ ],
+
+ OperationTerminated::class => [
+ FlushTemporaryContainerInstances::class,
+ // DisconnectFromDatabases::class,
+ CollectGarbage::class,
+ ],
+
+ WorkerErrorOccurred::class => [
+ ReportException::class,
+ StopWorkerIfNecessary::class,
+ ],
+
+ WorkerStopping::class => [
+ //
+ ],
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Warm / Flush Bindings
+ |--------------------------------------------------------------------------
+ |
+ | The bindings listed below will either be pre-warmed when a worker boots
+ | or they will be flushed before every new request. Flushing a binding
+ | will force the container to resolve that binding again when asked.
+ |
+ */
+
+ 'warm' => [
+ ...Octane::defaultServicesToWarm(),
+ ],
+
+ 'flush' => [
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Octane Cache Table
+ |--------------------------------------------------------------------------
+ |
+ | While using Swoole, you may leverage the Octane cache, which is powered
+ | by a Swoole table. You may set the maximum number of rows as well as
+ | the number of bytes per row using the configuration options below.
+ |
+ */
+
+ 'cache' => [
+ 'rows' => 1000,
+ 'bytes' => 10000,
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Octane Swoole Tables
+ |--------------------------------------------------------------------------
+ |
+ | While using Swoole, you may define additional tables as required by the
+ | application. These tables can be used to store data that needs to be
+ | quickly accessed by other workers on the particular Swoole server.
+ |
+ */
+
+ 'tables' => [
+/*
+ 'example:1000' => [
+ 'name' => 'string:1000',
+ 'votes' => 'int',
+ ],
+*/
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | File Watching
+ |--------------------------------------------------------------------------
+ |
+ | The following list of files and directories will be watched when using
+ | the --watch option offered by Octane. If any of the directories and
+ | files are changed, Octane will automatically reload your workers.
+ |
+ */
+
+ 'watch' => [
+ 'app',
+ 'bootstrap',
+ 'config',
+ 'database',
+ 'public/**/*.php',
+ 'resources/**/*.php',
+ 'routes',
+ 'composer.lock',
+ '.env',
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Garbage Collection Threshold
+ |--------------------------------------------------------------------------
+ |
+ | When executing long-lived PHP scripts such as Octane, memory can build
+ | up before being cleared by PHP. You can force Octane to run garbage
+ | collection if your application consumes this amount of megabytes.
+ |
+ */
+
+ 'garbage' => 64,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Maximum Execution Time
+ |--------------------------------------------------------------------------
+ |
+ | The following setting configures the maximum execution time for requests
+ | being handled by Octane. You may set this value to 0 to indicate that
+ | there isn't a specific time limit on Octane request execution time.
+ |
+ */
+
+ 'max_execution_time' => 30,
+
+];
diff --git a/src/config/queue.php b/src/config/queue.php
--- a/src/config/queue.php
+++ b/src/config/queue.php
@@ -39,6 +39,7 @@
'table' => 'jobs',
'queue' => 'default',
'retry_after' => 90,
+ 'after_commit' => false,
],
'beanstalkd' => [
@@ -47,6 +48,7 @@
'queue' => 'default',
'retry_after' => 90,
'block_for' => 0,
+ 'after_commit' => false,
],
'sqs' => [
@@ -54,8 +56,10 @@
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
- 'queue' => env('SQS_QUEUE', 'your-queue-name'),
+ 'queue' => env('SQS_QUEUE', 'default'),
+ 'suffix' => env('SQS_SUFFIX'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
+ 'after_commit' => false,
],
'redis' => [
@@ -64,6 +68,7 @@
'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => 90,
'block_for' => null,
+ 'after_commit' => false,
],
],
diff --git a/src/config/session.php b/src/config/session.php
--- a/src/config/session.php
+++ b/src/config/session.php
@@ -72,7 +72,7 @@
|
*/
- 'connection' => env('SESSION_CONNECTION', null),
+ 'connection' => env('SESSION_CONNECTION'),
/*
|--------------------------------------------------------------------------
@@ -92,13 +92,15 @@
| Session Cache Store
|--------------------------------------------------------------------------
|
- | When using the "apc", "memcached", or "dynamodb" session drivers you may
+ | While using one of the framework's cache driven session backends you may
| list a cache store that should be used for these sessions. This value
| must match with one of the application's configured cache "stores".
|
+ | Affects: "apc", "dynamodb", "memcached", "redis"
+ |
*/
- 'store' => env('SESSION_STORE', null),
+ 'store' => env('SESSION_STORE'),
/*
|--------------------------------------------------------------------------
@@ -124,10 +126,7 @@
|
*/
- 'cookie' => env(
- 'SESSION_COOKIE',
- Str::slug(env('APP_NAME', 'laravel'), '_') . '_session'
- ),
+ 'cookie' => env('SESSION_COOKIE', Str::slug(env('APP_NAME', 'laravel'), '_') . '_session'),
/*
|--------------------------------------------------------------------------
@@ -153,7 +152,7 @@
|
*/
- 'domain' => env('SESSION_DOMAIN', null),
+ 'domain' => env('SESSION_DOMAIN'),
/*
|--------------------------------------------------------------------------
@@ -162,11 +161,11 @@
|
| By setting this option to true, session cookies will only be sent back
| to the server if the browser has a HTTPS connection. This will keep
- | the cookie from being sent to you if it can not be done securely.
+ | the cookie from being sent to you when it can't be done securely.
|
*/
- 'secure' => env('SESSION_SECURE_COOKIE', false),
+ 'secure' => env('SESSION_SECURE_COOKIE'),
/*
|--------------------------------------------------------------------------
@@ -188,12 +187,12 @@
|
| This option determines how your cookies behave when cross-site requests
| take place, and can be used to mitigate CSRF attacks. By default, we
- | do not enable this as other CSRF protection services are in place.
+ | will set this value to "lax" since this is a secure default value.
|
- | Supported: "lax", "strict"
+ | Supported: "lax", "strict", "none", null
|
*/
- 'same_site' => null,
+ 'same_site' => 'lax',
];
diff --git a/src/config/swoole_http.php b/src/config/swoole_http.php
deleted file mode 100644
--- a/src/config/swoole_http.php
+++ /dev/null
@@ -1,141 +0,0 @@
-<?php
-
-return [
- /*
- |--------------------------------------------------------------------------
- | HTTP server configurations.
- |--------------------------------------------------------------------------
- |
- | @see https://www.swoole.co.uk/docs/modules/swoole-server/configuration
- |
- */
- 'server' => [
- 'host' => env('SWOOLE_HTTP_HOST', '127.0.0.1'),
- 'port' => env('SWOOLE_HTTP_PORT', '1215'),
- 'public_path' => base_path('public'),
- // Determine if to use swoole to respond request for static files
- 'handle_static_files' => env('SWOOLE_HANDLE_STATIC', true),
- 'access_log' => env('SWOOLE_HTTP_ACCESS_LOG', false),
- // You must add --enable-openssl while compiling Swoole
- // Put `SWOOLE_SOCK_TCP | SWOOLE_SSL` if you want to enable SSL
- 'socket_type' => SWOOLE_SOCK_TCP,
- 'process_type' => SWOOLE_PROCESS,
- 'options' => [
- 'pid_file' => env('SWOOLE_HTTP_PID_FILE', base_path('storage/logs/swoole_http.pid')),
- 'log_file' => env('SWOOLE_HTTP_LOG_FILE', base_path('storage/logs/swoole_http.log')),
- 'daemonize' => env('SWOOLE_HTTP_DAEMONIZE', false),
- // Normally this value should be 1~4 times larger according to your cpu cores.
- 'reactor_num' => env('SWOOLE_HTTP_REACTOR_NUM', swoole_cpu_num()),
- 'worker_num' => env('SWOOLE_HTTP_WORKER_NUM', swoole_cpu_num()),
- 'task_worker_num' => env('SWOOLE_HTTP_TASK_WORKER_NUM', swoole_cpu_num()),
- // The data to receive can't be larger than buffer_output_size.
- 'package_max_length' => 20 * 1024 * 1024,
- // The data to send can't be larger than buffer_output_size.
- 'buffer_output_size' => 10 * 1024 * 1024,
- // Max buffer size for socket connections
- 'socket_buffer_size' => 128 * 1024 * 1024,
- // Worker will restart after processing this number of requests
- 'max_request' => 3000,
- // Enable coroutine send
- 'send_yield' => true,
- // You must add --enable-openssl while compiling Swoole
- 'ssl_cert_file' => null,
- 'ssl_key_file' => null,
- ],
- ],
-
- /*
- |--------------------------------------------------------------------------
- | Enable to turn on websocket server.
- |--------------------------------------------------------------------------
- */
- 'websocket' => [
- 'enabled' => env('SWOOLE_HTTP_WEBSOCKET', false),
- ],
-
- /*
- |--------------------------------------------------------------------------
- | Hot reload configuration
- |--------------------------------------------------------------------------
- */
- 'hot_reload' => [
- 'enabled' => env('SWOOLE_HOT_RELOAD_ENABLE', false),
- 'recursively' => env('SWOOLE_HOT_RELOAD_RECURSIVELY', true),
- 'directory' => env('SWOOLE_HOT_RELOAD_DIRECTORY', base_path()),
- 'log' => env('SWOOLE_HOT_RELOAD_LOG', true),
- 'filter' => env('SWOOLE_HOT_RELOAD_FILTER', '.php'),
- ],
-
- /*
- |--------------------------------------------------------------------------
- | Console output will be transferred to response content if enabled.
- |--------------------------------------------------------------------------
- */
- 'ob_output' => env('SWOOLE_OB_OUTPUT', true),
-
- /*
- |--------------------------------------------------------------------------
- | Pre-resolved instances here will be resolved when sandbox created.
- |--------------------------------------------------------------------------
- */
- 'pre_resolved' => [
- 'view', 'files', 'session', 'session.store', 'routes',
- 'db', 'db.factory', 'cache', 'cache.store', 'config', 'cookie',
- 'encrypter', 'hash', 'router', 'translator', 'url', 'log',
- ],
-
- /*
- |--------------------------------------------------------------------------
- | Instances here will be cleared on every request.
- |--------------------------------------------------------------------------
- */
- 'instances' => [
- 'auth', 'translator'
- ],
-
- /*
- |--------------------------------------------------------------------------
- | Providers here will be registered on every request.
- |--------------------------------------------------------------------------
- */
- 'providers' => [
- Illuminate\Pagination\PaginationServiceProvider::class,
- App\Providers\AuthServiceProvider::class,
- //Without this passport will sort of work,
- //but PassportServiceProvider will not contain a valid app instance.
- App\Providers\PassportServiceProvider::class,
- ],
-
- /*
- |--------------------------------------------------------------------------
- | Resetters for sandbox app.
- |--------------------------------------------------------------------------
- */
- 'resetters' => [
- SwooleTW\Http\Server\Resetters\ResetConfig::class,
- SwooleTW\Http\Server\Resetters\ResetSession::class,
- SwooleTW\Http\Server\Resetters\ResetCookie::class,
- SwooleTW\Http\Server\Resetters\ClearInstances::class,
- SwooleTW\Http\Server\Resetters\BindRequest::class,
- SwooleTW\Http\Server\Resetters\RebindKernelContainer::class,
- SwooleTW\Http\Server\Resetters\RebindRouterContainer::class,
- SwooleTW\Http\Server\Resetters\RebindViewContainer::class,
- SwooleTW\Http\Server\Resetters\ResetProviders::class,
- ],
-
- /*
- |--------------------------------------------------------------------------
- | Define your swoole tables here.
- |
- | @see https://www.swoole.co.uk/docs/modules/swoole-table
- |--------------------------------------------------------------------------
- */
- 'tables' => [
- // 'table_name' => [
- // 'size' => 1024,
- // 'columns' => [
- // ['name' => 'column_name', 'type' => Table::TYPE_STRING, 'size' => 1024],
- // ]
- // ],
- ],
-];
diff --git a/src/config/swoole_websocket.php b/src/config/swoole_websocket.php
deleted file mode 100644
--- a/src/config/swoole_websocket.php
+++ /dev/null
@@ -1,107 +0,0 @@
-<?php
-
-return [
- /*
- |--------------------------------------------------------------------------
- | Websocket handler for onOpen and onClose callback
- | Replace this handler if you want to customize your websocket handler
- |--------------------------------------------------------------------------
- */
- 'handler' => SwooleTW\Http\Websocket\SocketIO\WebsocketHandler::class,
-
- /*
- |--------------------------------------------------------------------------
- | Default frame parser
- | Replace it if you want to customize your websocket payload
- |--------------------------------------------------------------------------
- */
- 'parser' => SwooleTW\Http\Websocket\SocketIO\SocketIOParser::class,
-
- /*
- |--------------------------------------------------------------------------
- | Websocket route file path
- |--------------------------------------------------------------------------
- */
- 'route_file' => base_path('routes/websocket.php'),
-
- /*
- |--------------------------------------------------------------------------
- | Default middleware for on connect request
- |--------------------------------------------------------------------------
- */
- 'middleware' => [
- SwooleTW\Http\Websocket\Middleware\DecryptCookies::class,
- SwooleTW\Http\Websocket\Middleware\StartSession::class,
- SwooleTW\Http\Websocket\Middleware\Authenticate::class,
- ],
-
- /*
- |--------------------------------------------------------------------------
- | Websocket handler for customized onHandShake callback
- |--------------------------------------------------------------------------
- */
- 'handshake' => [
- 'enabled' => false,
- 'handler' => SwooleTW\Http\Websocket\HandShakeHandler::class,
- ],
-
- /*
- |--------------------------------------------------------------------------
- | Default websocket driver
- |--------------------------------------------------------------------------
- */
- 'default' => 'table',
-
- /*
- |--------------------------------------------------------------------------
- | Websocket client's heartbeat interval (ms)
- |--------------------------------------------------------------------------
- */
- 'ping_interval' => 25000,
-
- /*
- |--------------------------------------------------------------------------
- | Websocket client's heartbeat interval timeout (ms)
- |--------------------------------------------------------------------------
- */
- 'ping_timeout' => 60000,
-
- /*
- |--------------------------------------------------------------------------
- | Room drivers mapping
- |--------------------------------------------------------------------------
- */
- 'drivers' => [
- 'table' => SwooleTW\Http\Websocket\Rooms\TableRoom::class,
- 'redis' => SwooleTW\Http\Websocket\Rooms\RedisRoom::class,
- ],
-
- /*
- |--------------------------------------------------------------------------
- | Room drivers settings
- |--------------------------------------------------------------------------
- */
- 'settings' => [
-
- 'table' => [
- 'room_rows' => 4096,
- 'room_size' => 2048,
- 'client_rows' => 8192,
- 'client_size' => 2048,
- ],
-
- 'redis' => [
- 'server' => [
- 'host' => env('REDIS_HOST', '127.0.0.1'),
- 'password' => env('REDIS_PASSWORD', null),
- 'port' => env('REDIS_PORT', 6379),
- 'database' => 0,
- 'persistent' => true,
- ],
- 'options' => [
- //
- ],
- 'prefix' => 'swoole:',
- ],
- ],
-];
diff --git a/src/database/migrations/2022_01_25_100000_create_shared_folder_aliases_table.php b/src/database/migrations/2022_01_25_100000_create_shared_folder_aliases_table.php
new file mode 100644
--- /dev/null
+++ b/src/database/migrations/2022_01_25_100000_create_shared_folder_aliases_table.php
@@ -0,0 +1,43 @@
+<?php
+
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Migrations\Migration;
+
+// phpcs:ignore
+class CreateSharedFolderAliasesTable extends Migration
+{
+ /**
+ * Run the migrations.
+ *
+ * @return void
+ */
+ public function up()
+ {
+ Schema::create(
+ 'shared_folder_aliases',
+ function (Blueprint $table) {
+ $table->bigIncrements('id');
+ $table->unsignedBigInteger('shared_folder_id');
+ $table->string('alias');
+ $table->timestamp('created_at')->useCurrent();
+ $table->timestamp('updated_at')->useCurrent();
+
+ $table->unique(['alias', 'shared_folder_id']);
+
+ $table->foreign('shared_folder_id')->references('id')->on('shared_folders')
+ ->onDelete('cascade')->onUpdate('cascade');
+ }
+ );
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('shared_folder_aliases');
+ }
+}
diff --git a/src/phpstan.neon b/src/phpstan.neon
--- a/src/phpstan.neon
+++ b/src/phpstan.neon
@@ -8,7 +8,6 @@
- '#Call to an undefined [a-zA-Z0-9<>\\ ]+::withObjectTenantContext\(\)#'
- '#Call to an undefined [a-zA-Z0-9<>\\ ]+::withSubjectTenantContext\(\)#'
- '#Call to an undefined method Tests\\Browser::#'
- - '#Call to an undefined method Illuminate\\Support\\Fluent::references\(\)#'
level: 4
parallel:
processTimeout: 300.0
@@ -17,4 +16,5 @@
- config/
- database/
- resources/
+ - routes/
- tests/
diff --git a/src/phpunit.xml b/src/phpunit.xml
--- a/src/phpunit.xml
+++ b/src/phpunit.xml
@@ -37,7 +37,7 @@
<server name="APP_ENV" value="testing"/>
<server name="APP_DEBUG" value="true"/>
<server name="BCRYPT_ROUNDS" value="4"/>
- <server name="MAIL_DRIVER" value="array"/>
+ <server name="MAIL_MAILER" value="array"/>
<server name="QUEUE_CONNECTION" value="sync"/>
<server name="SESSION_DRIVER" value="array"/>
<server name="SWOOLE_HTTP_ACCESS_LOG" value="false"/>
diff --git a/src/public/index.php b/src/public/index.php
--- a/src/public/index.php
+++ b/src/public/index.php
@@ -1,14 +1,14 @@
<?php
-/**
- * Laravel - A PHP Framework For Web Artisans
- *
- * @package Laravel
- * @author Taylor Otwell <taylor@laravel.com>
- */
+use Illuminate\Contracts\Http\Kernel;
+use Illuminate\Http\Request;
define('LARAVEL_START', microtime(true));
+if (file_exists($maintenance = __DIR__ . '/../storage/framework/maintenance.php')) {
+ require $maintenance;
+}
+
/*
|--------------------------------------------------------------------------
| Register The Auto Loader
@@ -21,7 +21,7 @@
|
*/
-require __DIR__.'/../vendor/autoload.php';
+require __DIR__ . '/../vendor/autoload.php';
/*
|--------------------------------------------------------------------------
@@ -35,7 +35,7 @@
|
*/
-$app = require_once __DIR__.'/../bootstrap/app.php';
+$app = require_once __DIR__ . '/../bootstrap/app.php';
/*
|--------------------------------------------------------------------------
@@ -49,10 +49,10 @@
|
*/
-$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
+$kernel = $app->make(Kernel::class);
$response = $kernel->handle(
- $request = Illuminate\Http\Request::capture()
+ $request = Request::capture()
);
$response->send();
diff --git a/src/resources/js/app.js b/src/resources/js/app.js
--- a/src/resources/js/app.js
+++ b/src/resources/js/app.js
@@ -494,7 +494,7 @@
}
})
- form.find('.is-invalid:not(.listinput-widget)').first().focus()
+ form.find('.is-invalid:not(.list-input)').first().focus()
})
}
else if (data.status == 'error') {
diff --git a/src/resources/lang/en/auth.php b/src/resources/lang/en/auth.php
--- a/src/resources/lang/en/auth.php
+++ b/src/resources/lang/en/auth.php
@@ -14,6 +14,7 @@
*/
'failed' => 'Invalid username or password.',
+ 'password' => 'The provided password is incorrect.',
'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
'logoutsuccess' => 'Successfully logged out.',
diff --git a/src/resources/lang/en/ui.php b/src/resources/lang/en/ui.php
--- a/src/resources/lang/en/ui.php
+++ b/src/resources/lang/en/ui.php
@@ -124,6 +124,7 @@
'disabled' => "disabled",
'domain' => "Domain",
'email' => "Email Address",
+ 'emails' => "Email Addresses",
'enabled' => "enabled",
'firstname' => "First Name",
'general' => "General",
@@ -294,6 +295,7 @@
],
'password' => [
+ 'link-invalid' => "The password reset code is expired or invalid.",
'reset' => "Password Reset",
'reset-step1' => "Enter your email address to reset your password.",
'reset-step1-hint' => "You may need to check your spam folder or unblock {email}.",
@@ -317,6 +319,7 @@
],
'shf' => [
+ 'aliases-none' => "This shared folder has no email aliases.",
'create' => "Create folder",
'delete' => "Delete folder",
'acl-text' => "Defines user permissions to access the shared folder.",
@@ -424,6 +427,7 @@
'pass-input' => "Enter password",
'pass-link' => "Set via link",
'pass-link-label' => "Link:",
+ 'pass-link-hint' => "Press Submit to activate the link",
'passwordpolicy' => "Password Policy",
'price' => "Price",
'profile-title' => "Your profile",
diff --git a/src/resources/lang/en/validation.php b/src/resources/lang/en/validation.php
--- a/src/resources/lang/en/validation.php
+++ b/src/resources/lang/en/validation.php
@@ -14,12 +14,13 @@
*/
'accepted' => 'The :attribute must be accepted.',
+ 'accepted_if' => 'The :attribute must be accepted when :other is :value.',
'active_url' => 'The :attribute is not a valid URL.',
'after' => 'The :attribute must be a date after :date.',
'after_or_equal' => 'The :attribute must be a date after or equal to :date.',
- 'alpha' => 'The :attribute may only contain letters.',
- 'alpha_dash' => 'The :attribute may only contain letters, numbers, dashes and underscores.',
- 'alpha_num' => 'The :attribute may only contain letters and numbers.',
+ 'alpha' => 'The :attribute must only contain letters.',
+ 'alpha_dash' => 'The :attribute must only contain letters, numbers, dashes and underscores.',
+ 'alpha_num' => 'The :attribute must only contain letters and numbers.',
'array' => 'The :attribute must be an array.',
'before' => 'The :attribute must be a date before :date.',
'before_or_equal' => 'The :attribute must be a date before or equal to :date.',
@@ -31,9 +32,12 @@
],
'boolean' => 'The :attribute field must be true or false.',
'confirmed' => 'The :attribute confirmation does not match.',
+ 'current_password' => 'The password is incorrect.',
'date' => 'The :attribute is not a valid date.',
'date_equals' => 'The :attribute must be a date equal to :date.',
'date_format' => 'The :attribute does not match the format :format.',
+ 'declined' => 'The :attribute must be declined.',
+ 'declined_if' => 'The :attribute must be declined when :other is :value.',
'different' => 'The :attribute and :other must be different.',
'digits' => 'The :attribute must be :digits digits.',
'digits_between' => 'The :attribute must be between :min and :max digits.',
@@ -41,6 +45,7 @@
'distinct' => 'The :attribute field has a duplicate value.',
'email' => 'The :attribute must be a valid email address.',
'ends_with' => 'The :attribute must end with one of the following: :values',
+ 'enum' => 'The selected :attribute is invalid.',
'exists' => 'The selected :attribute is invalid.',
'file' => 'The :attribute must be a file.',
'filled' => 'The :attribute field must have a value.',
@@ -82,6 +87,7 @@
'string' => 'The :attribute may not be greater than :max characters.',
'array' => 'The :attribute may not have more than :max items.',
],
+ 'mac_address' => 'The :attribute must be a valid MAC address.',
'mimes' => 'The :attribute must be a file of type: :values.',
'mimetypes' => 'The :attribute must be a file of type: :values.',
'min' => [
@@ -90,12 +96,18 @@
'string' => 'The :attribute must be at least :min characters.',
'array' => 'The :attribute must have at least :min items.',
],
+ 'multiple_of' => 'The :attribute must be a multiple of :value.',
'not_in' => 'The selected :attribute is invalid.',
'not_regex' => 'The :attribute format is invalid.',
'numeric' => 'The :attribute must be a number.',
'present' => 'The :attribute field must be present.',
+ 'prohibited' => 'The :attribute field is prohibited.',
+ 'prohibited_if' => 'The :attribute field is prohibited when :other is :value.',
+ 'prohibited_unless' => 'The :attribute field is prohibited unless :other is in :values.',
+ 'prohibits' => 'The :attribute field prohibits :other from being present.',
'regex' => 'The :attribute format is invalid.',
'required' => 'The :attribute field is required.',
+ 'required_array_keys' => 'The :attribute field must contain entries for: :values.',
'required_if' => 'The :attribute field is required when :other is :value.',
'required_unless' => 'The :attribute field is required unless :other is in :values.',
'required_with' => 'The :attribute field is required when :values is present.',
@@ -111,10 +123,10 @@
],
'starts_with' => 'The :attribute must start with one of the following: :values',
'string' => 'The :attribute must be a string.',
- 'timezone' => 'The :attribute must be a valid zone.',
+ 'timezone' => 'The :attribute must be a valid timezone.',
'unique' => 'The :attribute has already been taken.',
'uploaded' => 'The :attribute failed to upload.',
- 'url' => 'The :attribute format is invalid.',
+ 'url' => 'The :attribute must be a valid URL.',
'uuid' => 'The :attribute must be a valid UUID.',
'2fareq' => 'Second factor code is required.',
diff --git a/src/resources/vue/Admin/SharedFolder.vue b/src/resources/vue/Admin/SharedFolder.vue
--- a/src/resources/vue/Admin/SharedFolder.vue
+++ b/src/resources/vue/Admin/SharedFolder.vue
@@ -43,6 +43,11 @@
{{ $t('form.settings') }}
</a>
</li>
+ <li class="nav-item">
+ <a class="nav-link" id="tab-aliases" href="#folder-aliases" role="tab" aria-controls="folder-aliases" aria-selected="false" @click="$root.tab">
+ {{ $t('user.aliases-email') }} ({{ folder.aliases.length }})
+ </a>
+ </li>
</ul>
<div class="tab-content">
<div class="tab-pane show active" id="folder-settings" role="tabpanel" aria-labelledby="tab-settings">
@@ -66,6 +71,29 @@
</div>
</div>
</div>
+ <div class="tab-pane" id="folder-aliases" role="tabpanel" aria-labelledby="tab-aliases">
+ <div class="card-body">
+ <div class="card-text">
+ <table class="table table-sm table-hover mb-0">
+ <thead>
+ <tr>
+ <th scope="col">{{ $t('form.email') }}</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr v-for="(alias, index) in folder.aliases" :id="'alias' + index" :key="index">
+ <td>{{ alias }}</td>
+ </tr>
+ </tbody>
+ <tfoot class="table-fake-body">
+ <tr>
+ <td>{{ $t('shf.aliases-none') }}</td>
+ </tr>
+ </tfoot>
+ </table>
+ </div>
+ </div>
+ </div>
</div>
</div>
</template>
@@ -74,7 +102,7 @@
export default {
data() {
return {
- folder: { config: {} }
+ folder: { config: {}, aliases: [] }
}
},
created() {
diff --git a/src/resources/vue/PasswordReset.vue b/src/resources/vue/PasswordReset.vue
--- a/src/resources/vue/PasswordReset.vue
+++ b/src/resources/vue/PasswordReset.vue
@@ -85,8 +85,7 @@
this.short_code = RegExp.$1
this.code = RegExp.$2
this.submitStep2(true)
- }
- else {
+ } else {
this.$root.errorPage(404)
}
}
@@ -109,23 +108,28 @@
},
// Submits the code to the API for verification
submitStep2(bylink) {
+ let post = {
+ code: this.code,
+ short_code: this.short_code
+ }
+
+ let params = {}
+
if (bylink === true) {
- this.displayForm(2, false)
+ this.$root.startLoading()
+ params.ignoreErrors = true
}
this.$root.clearFormValidation($('#step2 form'))
- axios.post('/api/auth/password-reset/verify', {
- code: this.code,
- short_code: this.short_code
- }).then(response => {
+ axios.post('/api/auth/password-reset/verify', post, params).then(response => {
+ this.$root.stopLoading()
this.userId = response.data.userId
this.displayForm(3, true)
}).catch(error => {
if (bylink === true) {
- // FIXME: display step 1, user can do nothing about it anyway
- // Maybe we should display 404 error page?
- this.displayForm(1, true)
+ this.$root.stopLoading()
+ this.$root.errorPage(404, '', this.$t('password.link-invalid'))
}
})
},
diff --git a/src/resources/vue/SharedFolder/Info.vue b/src/resources/vue/SharedFolder/Info.vue
--- a/src/resources/vue/SharedFolder/Info.vue
+++ b/src/resources/vue/SharedFolder/Info.vue
@@ -53,10 +53,10 @@
</select>
</div>
</div>
- <div v-if="folder.email" class="row mb-3">
- <label for="email" class="col-sm-4 col-form-label">{{ $t('form.email') }}</label>
+ <div class="row mb-3" v-if="folder.type == 'mail'">
+ <label for="aliases-input" class="col-sm-4 col-form-label">{{ $t('form.emails') }}</label>
<div class="col-sm-8">
- <input type="text" class="form-control" id="email" disabled v-model="folder.email">
+ <list-input id="aliases" :list="folder.aliases"></list-input>
</div>
</div>
<btn class="btn-primary" type="submit" icon="check">{{ $t('btn.submit') }}</btn>
@@ -85,18 +85,20 @@
<script>
import AclInput from '../Widgets/AclInput'
+ import ListInput from '../Widgets/ListInput'
import StatusComponent from '../Widgets/Status'
export default {
components: {
AclInput,
+ ListInput,
StatusComponent
},
data() {
return {
domains: [],
folder_id: null,
- folder: { type: 'mail', config: {} },
+ folder: { type: 'mail', config: {}, aliases: [] },
status: {},
types: [ 'mail', 'event', 'task', 'contact', 'note', 'file' ]
}
@@ -153,7 +155,11 @@
location += '/' + this.folder_id
}
- const post = this.$root.pick(this.folder, ['id', 'name', 'domain', 'type'])
+ const post = this.$root.pick(this.folder, ['id', 'name', 'domain', 'type', 'aliases'])
+
+ if (post.type != 'mail') {
+ delete post.aliases
+ }
axios[method](location, post)
.then(response => {
diff --git a/src/resources/vue/SharedFolder/List.vue b/src/resources/vue/SharedFolder/List.vue
--- a/src/resources/vue/SharedFolder/List.vue
+++ b/src/resources/vue/SharedFolder/List.vue
@@ -15,7 +15,6 @@
<tr>
<th scope="col">{{ $t('form.name') }}</th>
<th scope="col">{{ $t('form.type') }}</th>
- <th scope="col">{{ $t('form.email') }}</th>
</tr>
</thead>
<tbody>
@@ -25,12 +24,11 @@
<router-link :to="{ path: 'shared-folder/' + folder.id }">{{ folder.name }}</router-link>
</td>
<td>{{ $t('shf.type-' + folder.type) }}</td>
- <td><router-link :to="{ path: 'shared-folder/' + folder.id }">{{ folder.email }}</router-link></td>
</tr>
</tbody>
<tfoot class="table-fake-body">
<tr>
- <td colspan="3">{{ $t('shf.list-empty') }}</td>
+ <td colspan="2">{{ $t('shf.list-empty') }}</td>
</tr>
</tfoot>
</table>
diff --git a/src/resources/vue/User/Info.vue b/src/resources/vue/User/Info.vue
--- a/src/resources/vue/User/Info.vue
+++ b/src/resources/vue/User/Info.vue
@@ -78,6 +78,7 @@
<btn class="btn-link p-1" :icon="['far', 'clipboard']" :title="$t('btn.copy')" @click="passwordLinkCopy"></btn>
<btn v-if="user.passwordLinkCode" class="btn-link text-danger p-1" icon="trash-alt" :title="$t('btn.delete')" @click="passwordLinkDelete"></btn>
</span>
+ <div v-if="!user.passwordLinkCode" class="form-text m-0">{{ $t('user.pass-link-hint') }}</div>
</div>
</div>
</div>
diff --git a/src/routes/api.php b/src/routes/api.php
--- a/src/routes/api.php
+++ b/src/routes/api.php
@@ -1,6 +1,8 @@
<?php
+use App\Http\Controllers\API;
use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
@@ -13,23 +15,21 @@
|
*/
-$prefix = \trim(\parse_url(\config('app.url'), PHP_URL_PATH), '/') . '/';
-
Route::group(
[
'middleware' => 'api',
- 'prefix' => $prefix . 'api/auth'
+ 'prefix' => 'auth'
],
- function ($router) {
- Route::post('login', 'API\AuthController@login');
+ function () {
+ Route::post('login', [API\AuthController::class, 'login']);
Route::group(
['middleware' => 'auth:api'],
- function ($router) {
- Route::get('info', 'API\AuthController@info');
- Route::post('info', 'API\AuthController@info');
- Route::post('logout', 'API\AuthController@logout');
- Route::post('refresh', 'API\AuthController@refresh');
+ function () {
+ Route::get('info', [API\AuthController::class, 'info']);
+ Route::post('info', [API\AuthController::class, 'info']);
+ Route::post('logout', [API\AuthController::class, 'logout']);
+ Route::post('refresh', [API\AuthController::class, 'refresh']);
}
);
}
@@ -39,131 +39,115 @@
[
'domain' => \config('app.website_domain'),
'middleware' => 'api',
- 'prefix' => $prefix . 'api/auth'
- ],
- function ($router) {
- Route::post('password-policy/check', 'API\PasswordPolicyController@check');
-
- Route::post('password-reset/init', 'API\PasswordResetController@init');
- Route::post('password-reset/verify', 'API\PasswordResetController@verify');
- Route::post('password-reset', 'API\PasswordResetController@reset');
-
- Route::post('signup/init', 'API\SignupController@init');
- Route::get('signup/invitations/{id}', 'API\SignupController@invitation');
- Route::get('signup/plans', 'API\SignupController@plans');
- Route::post('signup/verify', 'API\SignupController@verify');
- Route::post('signup', 'API\SignupController@signup');
- }
-);
-
-Route::group(
- [
- 'domain' => \config('app.website_domain'),
- 'middleware' => 'auth:api',
- 'prefix' => $prefix . 'api/v4'
+ 'prefix' => 'auth'
],
function () {
- Route::post('companion/register', 'API\V4\CompanionAppsController@register');
-
- Route::post('auth-attempts/{id}/confirm', 'API\V4\AuthAttemptsController@confirm');
- Route::post('auth-attempts/{id}/deny', 'API\V4\AuthAttemptsController@deny');
- Route::get('auth-attempts/{id}/details', 'API\V4\AuthAttemptsController@details');
- Route::get('auth-attempts', 'API\V4\AuthAttemptsController@index');
-
- Route::apiResource('domains', 'API\V4\DomainsController');
- Route::get('domains/{id}/confirm', 'API\V4\DomainsController@confirm');
- Route::get('domains/{id}/skus', 'API\V4\SkusController@domainSkus');
- Route::get('domains/{id}/status', 'API\V4\DomainsController@status');
- Route::post('domains/{id}/config', 'API\V4\DomainsController@setConfig');
-
- Route::apiResource('groups', 'API\V4\GroupsController');
- Route::get('groups/{id}/status', 'API\V4\GroupsController@status');
- Route::post('groups/{id}/config', 'API\V4\GroupsController@setConfig');
-
- Route::apiResource('packages', 'API\V4\PackagesController');
-
- Route::apiResource('resources', 'API\V4\ResourcesController');
- Route::get('resources/{id}/status', 'API\V4\ResourcesController@status');
- Route::post('resources/{id}/config', 'API\V4\ResourcesController@setConfig');
-
- Route::apiResource('shared-folders', 'API\V4\SharedFoldersController');
- Route::get('shared-folders/{id}/status', 'API\V4\SharedFoldersController@status');
- Route::post('shared-folders/{id}/config', 'API\V4\SharedFoldersController@setConfig');
-
- Route::apiResource('skus', 'API\V4\SkusController');
-
- Route::apiResource('users', 'API\V4\UsersController');
- Route::post('users/{id}/config', 'API\V4\UsersController@setConfig');
- Route::get('users/{id}/skus', 'API\V4\SkusController@userSkus');
- Route::get('users/{id}/status', 'API\V4\UsersController@status');
-
- Route::apiResource('wallets', 'API\V4\WalletsController');
- Route::get('wallets/{id}/transactions', 'API\V4\WalletsController@transactions');
- Route::get('wallets/{id}/receipts', 'API\V4\WalletsController@receipts');
- Route::get('wallets/{id}/receipts/{receipt}', 'API\V4\WalletsController@receiptDownload');
-
- Route::get('password-policy', 'API\PasswordPolicyController@index');
- Route::post('password-reset/code', 'API\PasswordResetController@codeCreate');
- Route::delete('password-reset/code/{id}', 'API\PasswordResetController@codeDelete');
-
- Route::post('payments', 'API\V4\PaymentsController@store');
- //Route::delete('payments', 'API\V4\PaymentsController@cancel');
- Route::get('payments/mandate', 'API\V4\PaymentsController@mandate');
- Route::post('payments/mandate', 'API\V4\PaymentsController@mandateCreate');
- Route::put('payments/mandate', 'API\V4\PaymentsController@mandateUpdate');
- Route::delete('payments/mandate', 'API\V4\PaymentsController@mandateDelete');
- Route::get('payments/methods', 'API\V4\PaymentsController@paymentMethods');
- Route::get('payments/pending', 'API\V4\PaymentsController@payments');
- Route::get('payments/has-pending', 'API\V4\PaymentsController@hasPayments');
-
- Route::get('openvidu/rooms', 'API\V4\OpenViduController@index');
- Route::post('openvidu/rooms/{id}/close', 'API\V4\OpenViduController@closeRoom');
- Route::post('openvidu/rooms/{id}/config', 'API\V4\OpenViduController@setRoomConfig');
+ Route::post('password-policy/check', [API\PasswordPolicyController::class, 'check']);
- // FIXME: I'm not sure about this one, should we use DELETE request maybe?
- Route::post('openvidu/rooms/{id}/connections/{conn}/dismiss', 'API\V4\OpenViduController@dismissConnection');
- Route::put('openvidu/rooms/{id}/connections/{conn}', 'API\V4\OpenViduController@updateConnection');
- Route::post('openvidu/rooms/{id}/request/{reqid}/accept', 'API\V4\OpenViduController@acceptJoinRequest');
- Route::post('openvidu/rooms/{id}/request/{reqid}/deny', 'API\V4\OpenViduController@denyJoinRequest');
+ Route::post('password-reset/init', [API\PasswordResetController::class, 'init']);
+ Route::post('password-reset/verify', [API\PasswordResetController::class, 'verify']);
+ Route::post('password-reset', [API\PasswordResetController::class, 'reset']);
+
+ Route::post('signup/init', [API\SignupController::class, 'init']);
+ Route::get('signup/invitations/{id}', [API\SignupController::class, 'invitation']);
+ Route::get('signup/plans', [API\SignupController::class, 'plans']);
+ Route::post('signup/verify', [API\SignupController::class, 'verify']);
+ Route::post('signup', [API\SignupController::class, 'signup']);
}
);
-// Note: In Laravel 7.x we could just use withoutMiddleware() instead of a separate group
Route::group(
[
'domain' => \config('app.website_domain'),
- 'prefix' => $prefix . 'api/v4'
+ 'middleware' => 'auth:api',
+ 'prefix' => 'v4'
],
function () {
- Route::post('openvidu/rooms/{id}', 'API\V4\OpenViduController@joinRoom');
- Route::post('openvidu/rooms/{id}/connections', 'API\V4\OpenViduController@createConnection');
+ Route::post('companion/register', [API\V4\CompanionAppsController::class, 'register']);
+
+ Route::post('auth-attempts/{id}/confirm', [API\V4\AuthAttemptsController::class, 'confirm']);
+ Route::post('auth-attempts/{id}/deny', [API\V4\AuthAttemptsController::class, 'deny']);
+ Route::get('auth-attempts/{id}/details', [API\V4\AuthAttemptsController::class, 'details']);
+ Route::get('auth-attempts', [API\V4\AuthAttemptsController::class, 'index']);
+
+ Route::apiResource('domains', API\V4\DomainsController::class);
+ Route::get('domains/{id}/confirm', [API\V4\DomainsController::class, 'confirm']);
+ Route::get('domains/{id}/skus', [API\V4\SkusController::class, 'domainSkus']);
+ Route::get('domains/{id}/status', [API\V4\DomainsController::class, 'status']);
+ Route::post('domains/{id}/config', [API\V4\DomainsController::class, 'setConfig']);
+
+ Route::apiResource('groups', API\V4\GroupsController::class);
+ Route::get('groups/{id}/status', [API\V4\GroupsController::class, 'status']);
+ Route::post('groups/{id}/config', [API\V4\GroupsController::class, 'setConfig']);
+
+ Route::apiResource('packages', API\V4\PackagesController::class);
+
+ Route::apiResource('resources', API\V4\ResourcesController::class);
+ Route::get('resources/{id}/status', [API\V4\ResourcesController::class, 'status']);
+ Route::post('resources/{id}/config', [API\V4\ResourcesController::class, 'setConfig']);
+
+ Route::apiResource('shared-folders', API\V4\SharedFoldersController::class);
+ Route::get('shared-folders/{id}/status', [API\V4\SharedFoldersController::class, 'status']);
+ Route::post('shared-folders/{id}/config', [API\V4\SharedFoldersController::class, 'setConfig']);
+
+ Route::apiResource('skus', API\V4\SkusController::class);
+
+ Route::apiResource('users', API\V4\UsersController::class);
+ Route::post('users/{id}/config', [API\V4\UsersController::class, 'setConfig']);
+ Route::get('users/{id}/skus', [API\V4\SkusController::class, 'userSkus']);
+ Route::get('users/{id}/status', [API\V4\UsersController::class, 'status']);
+
+ Route::apiResource('wallets', API\V4\WalletsController::class);
+ Route::get('wallets/{id}/transactions', [API\V4\WalletsController::class, 'transactions']);
+ Route::get('wallets/{id}/receipts', [API\V4\WalletsController::class, 'receipts']);
+ Route::get('wallets/{id}/receipts/{receipt}', [API\V4\WalletsController::class, 'receiptDownload']);
+
+ Route::get('password-policy', [API\PasswordPolicyController::class, 'index']);
+ Route::post('password-reset/code', [API\PasswordResetController::class, 'codeCreate']);
+ Route::delete('password-reset/code/{id}', [API\PasswordResetController::class, 'codeDelete']);
+
+ Route::post('payments', [API\V4\PaymentsController::class, 'store']);
+ //Route::delete('payments', [API\V4\PaymentsController::class, 'cancel']);
+ Route::get('payments/mandate', [API\V4\PaymentsController::class, 'mandate']);
+ Route::post('payments/mandate', [API\V4\PaymentsController::class, 'mandateCreate']);
+ Route::put('payments/mandate', [API\V4\PaymentsController::class, 'mandateUpdate']);
+ Route::delete('payments/mandate', [API\V4\PaymentsController::class, 'mandateDelete']);
+ Route::get('payments/methods', [API\V4\PaymentsController::class, 'paymentMethods']);
+ Route::get('payments/pending', [API\V4\PaymentsController::class, 'payments']);
+ Route::get('payments/has-pending', [API\V4\PaymentsController::class, 'hasPayments']);
+
+ Route::get('openvidu/rooms', [API\V4\OpenViduController::class, 'index']);
+ Route::post('openvidu/rooms/{id}/close', [API\V4\OpenViduController::class, 'closeRoom']);
+ Route::post('openvidu/rooms/{id}/config', [API\V4\OpenViduController::class, 'setRoomConfig']);
+
+ Route::post('openvidu/rooms/{id}', [API\V4\OpenViduController::class, 'joinRoom'])
+ ->withoutMiddleware(['auth:api']);
+ Route::post('openvidu/rooms/{id}/connections', [API\V4\OpenViduController::class, 'createConnection'])
+ ->withoutMiddleware(['auth:api']);
// FIXME: I'm not sure about this one, should we use DELETE request maybe?
- Route::post('openvidu/rooms/{id}/connections/{conn}/dismiss', 'API\V4\OpenViduController@dismissConnection');
- Route::put('openvidu/rooms/{id}/connections/{conn}', 'API\V4\OpenViduController@updateConnection');
- Route::post('openvidu/rooms/{id}/request/{reqid}/accept', 'API\V4\OpenViduController@acceptJoinRequest');
- Route::post('openvidu/rooms/{id}/request/{reqid}/deny', 'API\V4\OpenViduController@denyJoinRequest');
- }
-);
-
-Route::group(
- [
- 'domain' => \config('app.website_domain'),
- 'middleware' => 'api',
- 'prefix' => $prefix . 'api/v4'
- ],
- function ($router) {
- Route::post('support/request', 'API\V4\SupportController@request');
+ Route::post('openvidu/rooms/{id}/connections/{conn}/dismiss', [API\V4\OpenViduController::class, 'dismissConnection'])
+ ->withoutMiddleware(['auth:api']);
+ Route::put('openvidu/rooms/{id}/connections/{conn}', [API\V4\OpenViduController::class, 'updateConnection'])
+ ->withoutMiddleware(['auth:api']);
+ Route::post('openvidu/rooms/{id}/request/{reqid}/accept', [API\V4\OpenViduController::class, 'acceptJoinRequest'])
+ ->withoutMiddleware(['auth:api']);
+ Route::post('openvidu/rooms/{id}/request/{reqid}/deny', [API\V4\OpenViduController::class, 'denyJoinRequest'])
+ ->withoutMiddleware(['auth:api']);
+
+ Route::post('support/request', [API\V4\SupportController::class, 'request'])
+ ->withoutMiddleware(['auth:api'])
+ ->middleware(['api']);
}
);
Route::group(
[
'domain' => \config('app.website_domain'),
- 'prefix' => $prefix . 'api/webhooks'
+ 'prefix' => 'webhooks'
],
function () {
- Route::post('payment/{provider}', 'API\V4\PaymentsController@webhook');
- Route::post('meet/openvidu', 'API\V4\OpenViduController@webhook');
+ Route::post('payment/{provider}', [API\V4\PaymentsController::class, 'webhook']);
+ Route::post('meet/openvidu', [API\V4\OpenViduController::class, 'webhook']);
}
);
@@ -171,14 +155,14 @@
Route::group(
[
'domain' => 'services.' . \config('app.website_domain'),
- 'prefix' => $prefix . 'api/webhooks'
+ 'prefix' => 'webhooks'
],
function () {
- Route::get('nginx', 'API\V4\NGINXController@authenticate');
- Route::get('nginx-httpauth', 'API\V4\NGINXController@httpauth');
- Route::post('policy/greylist', 'API\V4\PolicyController@greylist');
- Route::post('policy/ratelimit', 'API\V4\PolicyController@ratelimit');
- Route::post('policy/spf', 'API\V4\PolicyController@senderPolicyFramework');
+ Route::get('nginx', [API\V4\NGINXController::class, 'authenticate']);
+ Route::get('nginx-httpauth', [API\V4\NGINXController::class, 'httpauth']);
+ Route::post('policy/greylist', [API\V4\PolicyController::class, 'greylist']);
+ Route::post('policy/ratelimit', [API\V4\PolicyController::class, 'ratelimit']);
+ Route::post('policy/spf', [API\V4\PolicyController::class, 'senderPolicyFramework']);
}
);
}
@@ -188,33 +172,35 @@
[
'domain' => 'admin.' . \config('app.website_domain'),
'middleware' => ['auth:api', 'admin'],
- 'prefix' => $prefix . 'api/v4',
+ 'prefix' => 'v4',
],
function () {
- Route::apiResource('domains', 'API\V4\Admin\DomainsController');
- Route::get('domains/{id}/skus', 'API\V4\Admin\SkusController@domainSkus');
- Route::post('domains/{id}/suspend', 'API\V4\Admin\DomainsController@suspend');
- Route::post('domains/{id}/unsuspend', 'API\V4\Admin\DomainsController@unsuspend');
-
- Route::apiResource('groups', 'API\V4\Admin\GroupsController');
- Route::post('groups/{id}/suspend', 'API\V4\Admin\GroupsController@suspend');
- Route::post('groups/{id}/unsuspend', 'API\V4\Admin\GroupsController@unsuspend');
-
- Route::apiResource('resources', 'API\V4\Admin\ResourcesController');
- Route::apiResource('shared-folders', 'API\V4\Admin\SharedFoldersController');
- Route::apiResource('skus', 'API\V4\Admin\SkusController');
- Route::apiResource('users', 'API\V4\Admin\UsersController');
- Route::get('users/{id}/discounts', 'API\V4\Reseller\DiscountsController@userDiscounts');
- Route::post('users/{id}/reset2FA', 'API\V4\Admin\UsersController@reset2FA');
- Route::get('users/{id}/skus', 'API\V4\Admin\SkusController@userSkus');
- Route::post('users/{id}/skus/{sku}', 'API\V4\Admin\UsersController@setSku');
- Route::post('users/{id}/suspend', 'API\V4\Admin\UsersController@suspend');
- Route::post('users/{id}/unsuspend', 'API\V4\Admin\UsersController@unsuspend');
- Route::apiResource('wallets', 'API\V4\Admin\WalletsController');
- Route::post('wallets/{id}/one-off', 'API\V4\Admin\WalletsController@oneOff');
- Route::get('wallets/{id}/transactions', 'API\V4\Admin\WalletsController@transactions');
-
- Route::get('stats/chart/{chart}', 'API\V4\Admin\StatsController@chart');
+ Route::apiResource('domains', API\V4\Admin\DomainsController::class);
+ Route::get('domains/{id}/skus', [API\V4\Admin\SkusController::class, 'domainSkus']);
+ Route::post('domains/{id}/suspend', [API\V4\Admin\DomainsController::class, 'suspend']);
+ Route::post('domains/{id}/unsuspend', [API\V4\Admin\DomainsController::class, 'unsuspend']);
+
+ Route::apiResource('groups', API\V4\Admin\GroupsController::class);
+ Route::post('groups/{id}/suspend', [API\V4\Admin\GroupsController::class, 'suspend']);
+ Route::post('groups/{id}/unsuspend', [API\V4\Admin\GroupsController::class, 'unsuspend']);
+
+ Route::apiResource('resources', API\V4\Admin\ResourcesController::class);
+ Route::apiResource('shared-folders', API\V4\Admin\SharedFoldersController::class);
+ Route::apiResource('skus', API\V4\Admin\SkusController::class);
+
+ Route::apiResource('users', API\V4\Admin\UsersController::class);
+ Route::get('users/{id}/discounts', [API\V4\Reseller\DiscountsController::class, 'userDiscounts']);
+ Route::post('users/{id}/reset2FA', [API\V4\Admin\UsersController::class, 'reset2FA']);
+ Route::get('users/{id}/skus', [API\V4\Admin\SkusController::class, 'userSkus']);
+ Route::post('users/{id}/skus/{sku}', [API\V4\Admin\UsersController::class, 'setSku']);
+ Route::post('users/{id}/suspend', [API\V4\Admin\UsersController::class, 'suspend']);
+ Route::post('users/{id}/unsuspend', [API\V4\Admin\UsersController::class, 'unsuspend']);
+
+ Route::apiResource('wallets', API\V4\Admin\WalletsController::class);
+ Route::post('wallets/{id}/one-off', [API\V4\Admin\WalletsController::class, 'oneOff']);
+ Route::get('wallets/{id}/transactions', [API\V4\Admin\WalletsController::class, 'transactions']);
+
+ Route::get('stats/chart/{chart}', [API\V4\Admin\StatsController::class, 'chart']);
}
);
}
@@ -224,47 +210,49 @@
[
'domain' => 'reseller.' . \config('app.website_domain'),
'middleware' => ['auth:api', 'reseller'],
- 'prefix' => $prefix . 'api/v4',
+ 'prefix' => 'v4',
],
function () {
- Route::apiResource('domains', 'API\V4\Reseller\DomainsController');
- Route::get('domains/{id}/skus', 'API\V4\Reseller\SkusController@domainSkus');
- Route::post('domains/{id}/suspend', 'API\V4\Reseller\DomainsController@suspend');
- Route::post('domains/{id}/unsuspend', 'API\V4\Reseller\DomainsController@unsuspend');
-
- Route::apiResource('groups', 'API\V4\Reseller\GroupsController');
- Route::post('groups/{id}/suspend', 'API\V4\Reseller\GroupsController@suspend');
- Route::post('groups/{id}/unsuspend', 'API\V4\Reseller\GroupsController@unsuspend');
-
- Route::apiResource('invitations', 'API\V4\Reseller\InvitationsController');
- Route::post('invitations/{id}/resend', 'API\V4\Reseller\InvitationsController@resend');
-
- Route::post('payments', 'API\V4\Reseller\PaymentsController@store');
- Route::get('payments/mandate', 'API\V4\Reseller\PaymentsController@mandate');
- Route::post('payments/mandate', 'API\V4\Reseller\PaymentsController@mandateCreate');
- Route::put('payments/mandate', 'API\V4\Reseller\PaymentsController@mandateUpdate');
- Route::delete('payments/mandate', 'API\V4\Reseller\PaymentsController@mandateDelete');
- Route::get('payments/methods', 'API\V4\Reseller\PaymentsController@paymentMethods');
- Route::get('payments/pending', 'API\V4\Reseller\PaymentsController@payments');
- Route::get('payments/has-pending', 'API\V4\Reseller\PaymentsController@hasPayments');
-
- Route::apiResource('resources', 'API\V4\Reseller\ResourcesController');
- Route::apiResource('shared-folders', 'API\V4\Reseller\SharedFoldersController');
- Route::apiResource('skus', 'API\V4\Reseller\SkusController');
- Route::apiResource('users', 'API\V4\Reseller\UsersController');
- Route::get('users/{id}/discounts', 'API\V4\Reseller\DiscountsController@userDiscounts');
- Route::post('users/{id}/reset2FA', 'API\V4\Reseller\UsersController@reset2FA');
- Route::get('users/{id}/skus', 'API\V4\Reseller\SkusController@userSkus');
- Route::post('users/{id}/skus/{sku}', 'API\V4\Admin\UsersController@setSku');
- Route::post('users/{id}/suspend', 'API\V4\Reseller\UsersController@suspend');
- Route::post('users/{id}/unsuspend', 'API\V4\Reseller\UsersController@unsuspend');
- Route::apiResource('wallets', 'API\V4\Reseller\WalletsController');
- Route::post('wallets/{id}/one-off', 'API\V4\Reseller\WalletsController@oneOff');
- Route::get('wallets/{id}/receipts', 'API\V4\Reseller\WalletsController@receipts');
- Route::get('wallets/{id}/receipts/{receipt}', 'API\V4\Reseller\WalletsController@receiptDownload');
- Route::get('wallets/{id}/transactions', 'API\V4\Reseller\WalletsController@transactions');
-
- Route::get('stats/chart/{chart}', 'API\V4\Reseller\StatsController@chart');
+ Route::apiResource('domains', API\V4\Reseller\DomainsController::class);
+ Route::get('domains/{id}/skus', [API\V4\Reseller\SkusController::class, 'domainSkus']);
+ Route::post('domains/{id}/suspend', [API\V4\Reseller\DomainsController::class, 'suspend']);
+ Route::post('domains/{id}/unsuspend', [API\V4\Reseller\DomainsController::class, 'unsuspend']);
+
+ Route::apiResource('groups', API\V4\Reseller\GroupsController::class);
+ Route::post('groups/{id}/suspend', [API\V4\Reseller\GroupsController::class, 'suspend']);
+ Route::post('groups/{id}/unsuspend', [API\V4\Reseller\GroupsController::class, 'unsuspend']);
+
+ Route::apiResource('invitations', API\V4\Reseller\InvitationsController::class);
+ Route::post('invitations/{id}/resend', [API\V4\Reseller\InvitationsController::class, 'resend']);
+
+ Route::post('payments', [API\V4\Reseller\PaymentsController::class, 'store']);
+ Route::get('payments/mandate', [API\V4\Reseller\PaymentsController::class, 'mandate']);
+ Route::post('payments/mandate', [API\V4\Reseller\PaymentsController::class, 'mandateCreate']);
+ Route::put('payments/mandate', [API\V4\Reseller\PaymentsController::class, 'mandateUpdate']);
+ Route::delete('payments/mandate', [API\V4\Reseller\PaymentsController::class, 'mandateDelete']);
+ Route::get('payments/methods', [API\V4\Reseller\PaymentsController::class, 'paymentMethods']);
+ Route::get('payments/pending', [API\V4\Reseller\PaymentsController::class, 'payments']);
+ Route::get('payments/has-pending', [API\V4\Reseller\PaymentsController::class, 'hasPayments']);
+
+ Route::apiResource('resources', API\V4\Reseller\ResourcesController::class);
+ Route::apiResource('shared-folders', API\V4\Reseller\SharedFoldersController::class);
+ Route::apiResource('skus', API\V4\Reseller\SkusController::class);
+
+ Route::apiResource('users', API\V4\Reseller\UsersController::class);
+ Route::get('users/{id}/discounts', [API\V4\Reseller\DiscountsController::class, 'userDiscounts']);
+ Route::post('users/{id}/reset2FA', [API\V4\Reseller\UsersController::class, 'reset2FA']);
+ Route::get('users/{id}/skus', [API\V4\Reseller\SkusController::class, 'userSkus']);
+ Route::post('users/{id}/skus/{sku}', [API\V4\Admin\UsersController::class, 'setSku']);
+ Route::post('users/{id}/suspend', [API\V4\Reseller\UsersController::class, 'suspend']);
+ Route::post('users/{id}/unsuspend', [API\V4\Reseller\UsersController::class, 'unsuspend']);
+
+ Route::apiResource('wallets', API\V4\Reseller\WalletsController::class);
+ Route::post('wallets/{id}/one-off', [API\V4\Reseller\WalletsController::class, 'oneOff']);
+ Route::get('wallets/{id}/receipts', [API\V4\Reseller\WalletsController::class, 'receipts']);
+ Route::get('wallets/{id}/receipts/{receipt}', [API\V4\Reseller\WalletsController::class, 'receiptDownload']);
+ Route::get('wallets/{id}/transactions', [API\V4\Reseller\WalletsController::class, 'transactions']);
+
+ Route::get('stats/chart/{chart}', [API\V4\Reseller\StatsController::class, 'chart']);
}
);
}
diff --git a/src/routes/channels.php b/src/routes/channels.php
--- a/src/routes/channels.php
+++ b/src/routes/channels.php
@@ -1,5 +1,7 @@
<?php
+use Illuminate\Support\Facades\Broadcast;
+
/*
|--------------------------------------------------------------------------
| Broadcast Channels
diff --git a/src/routes/console.php b/src/routes/console.php
--- a/src/routes/console.php
+++ b/src/routes/console.php
@@ -1,6 +1,7 @@
<?php
use Illuminate\Foundation\Inspiring;
+use Illuminate\Support\Facades\Artisan;
/*
|--------------------------------------------------------------------------
@@ -14,5 +15,5 @@
*/
Artisan::command('inspire', function () {
- $this->comment(Inspiring::quote());
+ $this->comment(Inspiring::quote()); // @phpstan-ignore-line
})->describe('Display an inspiring quote');
diff --git a/src/routes/web.php b/src/routes/web.php
--- a/src/routes/web.php
+++ b/src/routes/web.php
@@ -1,5 +1,8 @@
<?php
+use App\Http\Controllers;
+use Illuminate\Support\Facades\Route;
+
// We can handle every URL with the default action because
// we have client-side router (including 404 error handler).
// This way we don't have to define any "deep link" routes here.
@@ -8,16 +11,16 @@
//'domain' => \config('app.website_domain'),
],
function () {
- Route::get('content/page/{page}', 'ContentController@pageContent')
+ Route::get('content/page/{page}', Controllers\ContentController::class . '@pageContent')
->where('page', '(.*)');
- Route::get('content/faq/{page}', 'ContentController@faqContent')
+ Route::get('content/faq/{page}', Controllers\ContentController::class . '@faqContent')
->where('page', '(.*)');
Route::fallback(
function () {
// Return 404 for requests to the API end-points that do not exist
if (strpos(request()->path(), 'api/') === 0) {
- return \App\Http\Controllers\Controller::errorResponse(404);
+ return Controllers\Controller::errorResponse(404);
}
$env = \App\Utils::uiEnv();
diff --git a/src/routes/websocket.php b/src/routes/websocket.php
deleted file mode 100644
--- a/src/routes/websocket.php
+++ /dev/null
@@ -1,37 +0,0 @@
-<?php
-
-use Illuminate\Http\Request;
-use SwooleTW\Http\Websocket\Facades\Websocket;
-
-/*
-|--------------------------------------------------------------------------
-| Websocket Routes
-|--------------------------------------------------------------------------
-|
-| Here is where you can register websocket events for your application.
-|
-*/
-
-Websocket::on(
- 'connect',
- function ($websocket, Request $request) {
- return;
- }
-);
-
-Websocket::on(
- 'open',
- function ($websocket, Request $request) {
- return;
- }
-);
-
-Websocket::on(
- 'disconnect',
- function ($websocket) {
- return;
- }
-);
-
-//Websocket::on('message', 'App\Http\Controllers\WebsocketController@message');
-//Websocket::on('ping', 'App\Http\Controllers\WebsocketController@ping');
diff --git a/src/storage/framework/.gitignore b/src/storage/framework/.gitignore
--- a/src/storage/framework/.gitignore
+++ b/src/storage/framework/.gitignore
@@ -1,8 +1,9 @@
+compiled.php
config.php
+down
+events.scanned.php
+maintenance.php
routes.php
+routes.scanned.php
schedule-*
-compiled.php
services.json
-events.scanned.php
-routes.scanned.php
-down
diff --git a/src/tests/Browser/Admin/SharedFolderTest.php b/src/tests/Browser/Admin/SharedFolderTest.php
--- a/src/tests/Browser/Admin/SharedFolderTest.php
+++ b/src/tests/Browser/Admin/SharedFolderTest.php
@@ -56,6 +56,7 @@
$user = $this->getTestUser('john@kolab.org');
$folder = $this->getTestSharedFolder('folder-event@kolab.org');
$folder->setConfig(['acl' => ['anyone, read-only', 'jack@kolab.org, read-write']]);
+ $folder->setAliases(['folder-alias1@kolab.org', 'folder-alias2@kolab.org']);
$folder->status = SharedFolder::STATUS_NEW | SharedFolder::STATUS_ACTIVE
| SharedFolder::STATUS_LDAP_READY | SharedFolder::STATUS_IMAP_READY;
$folder->save();
@@ -85,13 +86,20 @@
->assertSeeIn('.row:nth-child(4) label', 'Type')
->assertSeeIn('.row:nth-child(4) #type', 'Calendar');
})
- ->assertElementsCount('ul.nav-tabs', 1)
- ->assertSeeIn('ul.nav-tabs .nav-link', 'Settings')
+ ->assertElementsCount('ul.nav-tabs .nav-item', 2)
+ ->assertSeeIn('ul.nav-tabs .nav-item:nth-child(1) .nav-link', 'Settings')
->with('@folder-settings form', function (Browser $browser) {
$browser->assertElementsCount('.row', 1)
->assertSeeIn('.row:nth-child(1) label', 'Access rights')
->assertSeeIn('.row:nth-child(1) #acl', 'anyone: read-only')
->assertSeeIn('.row:nth-child(1) #acl', 'jack@kolab.org: read-write');
+ })
+ ->assertSeeIn('ul.nav-tabs .nav-item:nth-child(2) .nav-link', 'Email Aliases (2)')
+ ->click('ul.nav-tabs .nav-item:nth-child(2) .nav-link')
+ ->with('@folder-aliases table', function (Browser $browser) {
+ $browser->assertElementsCount('tbody tr', 2)
+ ->assertSeeIn('tbody tr:nth-child(1) td', 'folder-alias1@kolab.org')
+ ->assertSeeIn('tbody tr:nth-child(2) td', 'folder-alias2@kolab.org');
});
// Test invalid shared folder identifier
diff --git a/src/tests/Browser/DomainTest.php b/src/tests/Browser/DomainTest.php
--- a/src/tests/Browser/DomainTest.php
+++ b/src/tests/Browser/DomainTest.php
@@ -46,6 +46,17 @@
});
}
+ /**
+ * Test domains list page (unauthenticated)
+ */
+ public function testDomainListUnauth(): void
+ {
+ // Test that the page requires authentication
+ $this->browse(function ($browser) {
+ $browser->visit('/domains')->on(new Home());
+ });
+ }
+
/**
* Test domain info page (non-existing domain id)
*/
@@ -163,19 +174,6 @@
});
}
- /**
- * Test domains list page (unauthenticated)
- */
- public function testDomainListUnauth(): void
- {
- // Test that the page requires authentication
- $this->browse(function ($browser) {
- $browser->visit('/logout')
- ->visit('/domains')
- ->on(new Home());
- });
- }
-
/**
* Test domains list page
*
@@ -245,7 +243,7 @@
$this->browse(function ($browser) {
$browser->visit('/login')
->on(new Home())
- ->submitLogon('john@kolab.org', 'simple123')
+ ->submitLogon('john@kolab.org', 'simple123', true)
->visit('/domains')
->on(new DomainList())
->assertSeeIn('.card-title button.btn-success', 'Create domain')
diff --git a/src/tests/Browser/ErrorTest.php b/src/tests/Browser/ErrorTest.php
--- a/src/tests/Browser/ErrorTest.php
+++ b/src/tests/Browser/ErrorTest.php
@@ -7,7 +7,6 @@
class ErrorTest extends TestCaseDusk
{
-
/**
* Test error 404 page on unknown route
*
diff --git a/src/tests/Browser/Pages/Admin/SharedFolder.php b/src/tests/Browser/Pages/Admin/SharedFolder.php
--- a/src/tests/Browser/Pages/Admin/SharedFolder.php
+++ b/src/tests/Browser/Pages/Admin/SharedFolder.php
@@ -52,6 +52,7 @@
'@app' => '#app',
'@folder-info' => '#folder-info',
'@folder-settings' => '#folder-settings',
+ '@folder-aliases' => '#folder-aliases',
];
}
}
diff --git a/src/tests/Browser/Pages/PasswordReset.php b/src/tests/Browser/Pages/PasswordReset.php
--- a/src/tests/Browser/Pages/PasswordReset.php
+++ b/src/tests/Browser/Pages/PasswordReset.php
@@ -25,8 +25,7 @@
*/
public function assert($browser)
{
- $browser->assertPathIs('/password-reset');
- $browser->assertPresent('@step1');
+ $browser->assertPathBeginsWith('/password-reset');
}
/**
diff --git a/src/tests/Browser/PasswordResetTest.php b/src/tests/Browser/PasswordResetTest.php
--- a/src/tests/Browser/PasswordResetTest.php
+++ b/src/tests/Browser/PasswordResetTest.php
@@ -5,6 +5,7 @@
use App\User;
use App\VerificationCode;
use Tests\Browser;
+use Tests\Browser\Components\Menu;
use Tests\Browser\Pages\Dashboard;
use Tests\Browser\Pages\Home;
use Tests\Browser\Pages\PasswordReset;
@@ -39,13 +40,11 @@
public function testLinkOnLogon(): void
{
$this->browse(function (Browser $browser) {
- $browser->visit(new Home());
-
- $browser->assertSeeLink('Forgot password?');
- $browser->clickLink('Forgot password?');
-
- $browser->on(new PasswordReset());
- $browser->assertVisible('@step1');
+ $browser->visit(new Home())
+ ->assertSeeLink('Forgot password?')
+ ->clickLink('Forgot password?')
+ ->on(new PasswordReset())
+ ->assertVisible('@step1');
});
}
@@ -284,6 +283,44 @@
});
}
+ /**
+ * Test password-reset via a link
+ */
+ public function testResetViaLink(): void
+ {
+ $user = $this->getTestUser('passwordresettestdusk@' . \config('app.domain'));
+ $user->setSetting('external_email', 'external@domain.tld');
+
+ $code = new VerificationCode(['mode' => 'password-reset']);
+ $user->verificationcodes()->save($code);
+
+ $this->browse(function (Browser $browser) use ($code) {
+ // Test a valid link
+ $browser->visit("/password-reset/{$code->short_code}-{$code->code}")
+ ->on(new PasswordReset())
+ ->waitFor('@step3')
+ ->assertMissing('@step1')
+ ->assertMissing('@step2')
+ ->with('@step3', function ($step) {
+ $step->type('#reset_password', 'A2345678')
+ ->type('#reset_password_confirmation', 'A2345678')
+ ->click('[type=submit]');
+ })
+ ->waitUntilMissing('@step3')
+ // At this point we should be auto-logged-in to dashboard
+ ->on(new Dashboard())
+ ->within(new Menu(), function ($browser) {
+ $browser->clickMenuItem('logout');
+ });
+
+ $this->assertNull(VerificationCode::find($code->code));
+
+ // Test an invalid link
+ $browser->visit("/password-reset/{$code->short_code}-{$code->code}")
+ ->assertErrorPage(404, 'The password reset code is expired or invalid.');
+ });
+ }
+
/**
* Test password reset process for a user with 2FA enabled.
*/
diff --git a/src/tests/Browser/Reseller/SharedFolderTest.php b/src/tests/Browser/Reseller/SharedFolderTest.php
--- a/src/tests/Browser/Reseller/SharedFolderTest.php
+++ b/src/tests/Browser/Reseller/SharedFolderTest.php
@@ -56,6 +56,7 @@
$user = $this->getTestUser('john@kolab.org');
$folder = $this->getTestSharedFolder('folder-event@kolab.org');
$folder->setConfig(['acl' => ['anyone, read-only', 'jack@kolab.org, read-write']]);
+ $folder->setAliases(['folder-alias1@kolab.org', 'folder-alias2@kolab.org']);
$folder->status = SharedFolder::STATUS_NEW | SharedFolder::STATUS_ACTIVE
| SharedFolder::STATUS_LDAP_READY | SharedFolder::STATUS_IMAP_READY;
$folder->save();
@@ -85,13 +86,20 @@
->assertSeeIn('.row:nth-child(4) label', 'Type')
->assertSeeIn('.row:nth-child(4) #type', 'Calendar');
})
- ->assertElementsCount('ul.nav-tabs', 1)
- ->assertSeeIn('ul.nav-tabs .nav-link', 'Settings')
+ ->assertElementsCount('ul.nav-tabs .nav-item', 2)
+ ->assertSeeIn('ul.nav-tabs .nav-item:nth-child(1) .nav-link', 'Settings')
->with('@folder-settings form', function (Browser $browser) {
$browser->assertElementsCount('.row', 1)
->assertSeeIn('.row:nth-child(1) label', 'Access rights')
->assertSeeIn('.row:nth-child(1) #acl', 'anyone: read-only')
->assertSeeIn('.row:nth-child(1) #acl', 'jack@kolab.org: read-write');
+ })
+ ->assertSeeIn('ul.nav-tabs .nav-item:nth-child(2) .nav-link', 'Email Aliases (2)')
+ ->click('ul.nav-tabs .nav-item:nth-child(2) .nav-link')
+ ->with('@folder-aliases table', function (Browser $browser) {
+ $browser->assertElementsCount('tbody tr', 2)
+ ->assertSeeIn('tbody tr:nth-child(1) td', 'folder-alias1@kolab.org')
+ ->assertSeeIn('tbody tr:nth-child(2) td', 'folder-alias2@kolab.org');
});
// Test invalid shared folder identifier
diff --git a/src/tests/Browser/SharedFolderTest.php b/src/tests/Browser/SharedFolderTest.php
--- a/src/tests/Browser/SharedFolderTest.php
+++ b/src/tests/Browser/SharedFolderTest.php
@@ -5,6 +5,7 @@
use App\SharedFolder;
use Tests\Browser;
use Tests\Browser\Components\AclInput;
+use Tests\Browser\Components\ListInput;
use Tests\Browser\Components\Status;
use Tests\Browser\Components\Toast;
use Tests\Browser\Pages\Dashboard;
@@ -95,17 +96,15 @@
->on(new SharedFolderList())
->whenAvailable('@table', function (Browser $browser) {
$browser->waitFor('tbody tr')
+ ->assertElementsCount('thead th', 2)
->assertSeeIn('thead tr th:nth-child(1)', 'Name')
->assertSeeIn('thead tr th:nth-child(2)', 'Type')
- ->assertSeeIn('thead tr th:nth-child(3)', 'Email Address')
->assertElementsCount('tbody tr', 2)
->assertSeeIn('tbody tr:nth-child(1) td:nth-child(1) a', 'Calendar')
->assertSeeIn('tbody tr:nth-child(1) td:nth-child(2)', 'Calendar')
- ->assertSeeIn('tbody tr:nth-child(1) td:nth-child(3) a', 'folder-event@kolab.org')
->assertText('tbody tr:nth-child(1) td:nth-child(1) svg.text-success title', 'Active')
->assertSeeIn('tbody tr:nth-child(2) td:nth-child(1) a', 'Contacts')
->assertSeeIn('tbody tr:nth-child(2) td:nth-child(2)', 'Address Book')
- ->assertSeeIn('tbody tr:nth-child(2) td:nth-child(3) a', 'folder-contact@kolab.org')
->assertMissing('tfoot');
});
});
@@ -152,23 +151,45 @@
->assertSeeIn('div.row:nth-child(3) label', 'Domain')
->assertSelectHasOptions('div.row:nth-child(3) select', ['kolab.org'])
->assertValue('div.row:nth-child(3) select', 'kolab.org')
+ ->assertSeeIn('div.row:nth-child(4) label', 'Email Addresses')
+ ->with(new ListInput('#aliases'), function (Browser $browser) {
+ $browser->assertListInputValue([])
+ ->assertValue('@input', '');
+ })
->assertSeeIn('button[type=submit]', 'Submit');
})
// Test error conditions
->type('#name', str_repeat('A', 192))
+ ->select('#type', 'event')
+ ->assertMissing('#aliases')
->click('@general button[type=submit]')
->waitFor('#name + .invalid-feedback')
->assertSeeIn('#name + .invalid-feedback', 'The name may not be greater than 191 characters.')
->assertFocused('#name')
->assertToast(Toast::TYPE_ERROR, 'Form validation error')
- // Test successful folder creation
+ // Test error handling on aliases input
->type('#name', 'Test Folder')
+ ->select('#type', 'mail')
+ ->with(new ListInput('#aliases'), function (Browser $browser) {
+ $browser->addListEntry('folder-alias@unknown');
+ })
+ ->click('@general button[type=submit]')
+ ->assertMissing('#name + .invalid-feedback')
+ ->waitFor('#aliases + .invalid-feedback')
+ ->with(new ListInput('#aliases'), function (Browser $browser) {
+ $browser->assertFormError(1, "The specified domain is invalid.", true);
+ })
+ ->assertToast(Toast::TYPE_ERROR, 'Form validation error')
+ // Test successful folder creation
->select('#type', 'event')
->click('@general button[type=submit]')
->assertToast(Toast::TYPE_SUCCESS, 'Shared folder created successfully.')
->on(new SharedFolderList())
->assertElementsCount('@table tbody tr', 3);
+ $this->assertSame(1, SharedFolder::where('name', 'Test Folder')->count());
+ $this->assertSame(0, SharedFolder::where('name', 'Test Folder')->first()->aliases()->count());
+
// Test folder update
$browser->click('@table tr:nth-child(3) td:first-child a')
->on(new SharedFolderInfo())
@@ -182,12 +203,7 @@
->assertValue('div.row:nth-child(2) input[type=text]', 'Test Folder')
->assertSeeIn('div.row:nth-child(3) label', 'Type')
->assertSelected('div.row:nth-child(3) select:disabled', 'event')
- ->assertSeeIn('div.row:nth-child(4) label', 'Email Address')
- ->assertAttributeRegExp(
- 'div.row:nth-child(4) input[type=text]:disabled',
- 'value',
- '/^event-[0-9]+@kolab\.org$/'
- )
+ ->assertMissing('#aliases')
->assertSeeIn('button[type=submit]', 'Submit');
})
// Test error handling
@@ -219,6 +235,54 @@
$this->assertNull(SharedFolder::where('name', 'Test Folder Update')->first());
});
+
+ // Test creation/updating a mail folder with mail aliases
+ $this->browse(function (Browser $browser) {
+ $browser->on(new SharedFolderList())
+ ->click('button.shared-folder-new')
+ ->on(new SharedFolderInfo())
+ ->type('#name', 'Test Folder2')
+ ->with(new ListInput('#aliases'), function (Browser $browser) {
+ $browser->addListEntry('folder-alias1@kolab.org')
+ ->addListEntry('folder-alias2@kolab.org');
+ })
+ ->click('@general button[type=submit]')
+ ->assertToast(Toast::TYPE_SUCCESS, 'Shared folder created successfully.')
+ ->on(new SharedFolderList())
+ ->assertElementsCount('@table tbody tr', 3);
+
+ $folder = SharedFolder::where('name', 'Test Folder2')->first();
+
+ $this->assertSame(
+ ['folder-alias1@kolab.org', 'folder-alias2@kolab.org'],
+ $folder->aliases()->pluck('alias')->all()
+ );
+
+ // Test folder update
+ $browser->click('@table tr:nth-child(3) td:first-child a')
+ ->on(new SharedFolderInfo())
+ ->with('@general', function (Browser $browser) {
+ // Assert form content
+ $browser->assertFocused('#name')
+ ->assertValue('div.row:nth-child(2) input[type=text]', 'Test Folder2')
+ ->assertSelected('div.row:nth-child(3) select:disabled', 'mail')
+ ->with(new ListInput('#aliases'), function (Browser $browser) {
+ $browser->assertListInputValue(['folder-alias1@kolab.org', 'folder-alias2@kolab.org'])
+ ->assertValue('@input', '');
+ });
+ })
+ // change folder name, and remove one alias
+ ->type('#name', 'Test Folder Update2')
+ ->with(new ListInput('#aliases'), function (Browser $browser) {
+ $browser->removeListEntry(2);
+ })
+ ->click('@general button[type=submit]')
+ ->assertToast(Toast::TYPE_SUCCESS, 'Shared folder updated successfully.');
+
+ $folder->refresh();
+ $this->assertSame('Test Folder Update2', $folder->name);
+ $this->assertSame(['folder-alias1@kolab.org'], $folder->aliases()->pluck('alias')->all());
+ });
}
/**
diff --git a/src/tests/Browser/UsersTest.php b/src/tests/Browser/UsersTest.php
--- a/src/tests/Browser/UsersTest.php
+++ b/src/tests/Browser/UsersTest.php
@@ -323,7 +323,8 @@
->assertVisible('#password-link button.text-danger')
->assertVisible('#password-link button:not(.text-danger)')
->assertAttribute('#password-link button:not(.text-danger)', 'title', 'Copy')
- ->assertAttribute('#password-link button.text-danger', 'title', 'Delete');
+ ->assertAttribute('#password-link button.text-danger', 'title', 'Delete')
+ ->assertMissing('#password-link div.form-text');
// Test deleting an existing password reset link
$browser->click('#password-link button.text-danger')
@@ -341,7 +342,8 @@
->assertMissing('#password')
->assertMissing('#password_confirmation')
->waitFor('#password-link code')
- ->assertSeeIn('#password-link code', $link);
+ ->assertSeeIn('#password-link code', $link)
+ ->assertSeeIn('#password-link div.form-text', "Press Submit to activate the link");
// Test copy to clipboard
/* TODO: Figure out how to give permission to do this operation
diff --git a/src/tests/Feature/Auth/SecondFactorTest.php b/src/tests/Feature/Auth/SecondFactorTest.php
--- a/src/tests/Feature/Auth/SecondFactorTest.php
+++ b/src/tests/Feature/Auth/SecondFactorTest.php
@@ -10,7 +10,9 @@
class SecondFactorTest extends TestCase
{
-
+ /**
+ * {@inheritDoc}
+ */
public function setUp(): void
{
parent::setUp();
@@ -18,6 +20,9 @@
$this->deleteTestUser('entitlement-test@kolabnow.com');
}
+ /**
+ * {@inheritDoc}
+ */
public function tearDown(): void
{
$this->deleteTestUser('entitlement-test@kolabnow.com');
diff --git a/src/tests/Feature/AuthAttemptTest.php b/src/tests/Feature/AuthAttemptTest.php
--- a/src/tests/Feature/AuthAttemptTest.php
+++ b/src/tests/Feature/AuthAttemptTest.php
@@ -7,7 +7,9 @@
class AuthAttemptTest extends TestCase
{
-
+ /**
+ * {@inheritDoc}
+ */
public function setUp(): void
{
parent::setUp();
@@ -15,6 +17,9 @@
$this->deleteTestUser('jane@kolabnow.com');
}
+ /**
+ * {@inheritDoc}
+ */
public function tearDown(): void
{
$this->deleteTestUser('jane@kolabnow.com');
diff --git a/src/tests/Feature/Backends/LDAPTest.php b/src/tests/Feature/Backends/LDAPTest.php
--- a/src/tests/Feature/Backends/LDAPTest.php
+++ b/src/tests/Feature/Backends/LDAPTest.php
@@ -331,6 +331,7 @@
'kolabfoldertype' => 'event',
'kolabtargetfolder' => 'shared/test-folder@kolab.org',
'acl' => null,
+ 'alias' => null,
];
foreach ($expected as $attr => $value) {
@@ -342,6 +343,8 @@
$folder->name = 'Te(=ść)1';
$folder->save();
$folder->setSetting('acl', '["john@kolab.org, read-write","anyone, read-only"]');
+ $aliases = ['t1-' . $folder->email, 't2-' . $folder->email];
+ $folder->setAliases($aliases);
LDAP::updateSharedFolder($folder);
@@ -349,6 +352,7 @@
$expected['acl'] = ['john@kolab.org, read-write', 'anyone, read-only'];
$expected['dn'] = 'cn=Te(\\3dść)1,ou=Shared Folders,ou=kolab.org,' . $root_dn;
$expected['cn'] = 'Te(=ść)1';
+ $expected['alias'] = $aliases;
$ldap_folder = LDAP::getSharedFolder($folder->email);
diff --git a/src/tests/Feature/Console/Data/Import/LdifTest.php b/src/tests/Feature/Console/Data/Import/LdifTest.php
--- a/src/tests/Feature/Console/Data/Import/LdifTest.php
+++ b/src/tests/Feature/Console/Data/Import/LdifTest.php
@@ -92,6 +92,7 @@
// Users
$this->assertSame(2, $owner->users(false)->count());
+ /** @var \App\User $user */
$user = $owner->users(false)->where('email', 'user@kolab3.com')->first();
// User settings
@@ -112,6 +113,7 @@
]);
// Domains
+ /** @var \App\Domain[] $domains */
$domains = $owner->domains(false, false)->orderBy('namespace')->get();
$this->assertCount(2, $domains);
@@ -124,6 +126,7 @@
$this->assertEntitlements($domains[1], ['domain-hosting']);
// Shared folders
+ /** @var \App\SharedFolder[] $folders */
$folders = $owner->sharedFolders(false)->orderBy('email')->get();
$this->assertCount(2, $folders);
@@ -137,8 +140,14 @@
$this->assertSame('shared/Folder2@kolab3.com', $folders[0]->getSetting('folder'));
$this->assertSame('["anyone, read-write","owner@kolab3.com, full"]', $folders[1]->getSetting('acl'));
$this->assertSame('shared/Folder1@kolab3.com', $folders[1]->getSetting('folder'));
+ $this->assertSame([], $folders[0]->aliases()->orderBy('alias')->pluck('alias')->all());
+ $this->assertSame(
+ ['folder-alias1@kolab3.com', 'folder-alias2@kolab3.com'],
+ $folders[1]->aliases()->orderBy('alias')->pluck('alias')->all()
+ );
// Groups
+ /** @var \App\Group[] $groups */
$groups = $owner->groups(false)->orderBy('email')->get();
$this->assertCount(1, $groups);
@@ -148,6 +157,7 @@
$this->assertSame('["sender@gmail.com","-"]', $groups[0]->getSetting('sender_policy'));
// Resources
+ /** @var \App\Resource[] $resources */
$resources = $owner->resources(false)->orderBy('email')->get();
$this->assertCount(1, $resources);
@@ -363,6 +373,7 @@
'kolabtargetfolder' => 'Folder',
'kolabfoldertype' => 'event',
'acl' => 'anyone, read-write',
+ 'alias' => ['test1@domain.tld', 'test2@domain.tld'],
];
$expected = [
@@ -371,6 +382,7 @@
'type' => 'event',
'folder' => 'Folder',
'acl' => ['anyone, read-write'],
+ 'aliases' => ['test1@domain.tld', 'test2@domain.tld'],
];
$result = $this->invokeMethod($command, 'parseLDAPSharedFolder', [$entry]);
diff --git a/src/tests/Feature/Console/Scalpel/TenantSetting/CreateCommandTest.php b/src/tests/Feature/Console/Scalpel/TenantSetting/CreateCommandTest.php
--- a/src/tests/Feature/Console/Scalpel/TenantSetting/CreateCommandTest.php
+++ b/src/tests/Feature/Console/Scalpel/TenantSetting/CreateCommandTest.php
@@ -38,7 +38,7 @@
$setting = $tenant->settings()->where('key', 'test')->first();
- $this->assertSame('init', $setting->fresh()->value);
+ $this->assertSame('init', $setting->value);
$this->assertSame('init', $tenant->fresh()->getSetting('test'));
}
}
diff --git a/src/tests/Feature/Console/Scalpel/WalletSetting/CreateCommandTest.php b/src/tests/Feature/Console/Scalpel/WalletSetting/CreateCommandTest.php
--- a/src/tests/Feature/Console/Scalpel/WalletSetting/CreateCommandTest.php
+++ b/src/tests/Feature/Console/Scalpel/WalletSetting/CreateCommandTest.php
@@ -17,7 +17,7 @@
$setting = $wallet->settings()->where('key', 'test')->first();
- $this->assertSame('init', $setting->fresh()->value);
+ $this->assertSame('init', $setting->value);
$this->assertSame('init', $wallet->fresh()->getSetting('test'));
}
}
diff --git a/src/tests/Feature/Console/SharedFolder/AddAliasTest.php b/src/tests/Feature/Console/SharedFolder/AddAliasTest.php
new file mode 100644
--- /dev/null
+++ b/src/tests/Feature/Console/SharedFolder/AddAliasTest.php
@@ -0,0 +1,90 @@
+<?php
+
+namespace Tests\Feature\Console\SharedFolder;
+
+use Tests\TestCase;
+
+class AddAliasTest extends TestCase
+{
+ /**
+ * {@inheritDoc}
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+
+ $this->deleteTestSharedFolder('folder-test@force-delete.com');
+ $this->deleteTestUser('user@force-delete.com');
+ $this->deleteTestDomain('force-delete.com');
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function tearDown(): void
+ {
+ $this->deleteTestSharedFolder('folder-test@force-delete.com');
+ $this->deleteTestUser('user@force-delete.com');
+ $this->deleteTestDomain('force-delete.com');
+
+ parent::tearDown();
+ }
+
+ /**
+ * Test the command
+ */
+ public function testHandle(): void
+ {
+ // Non-existing folder
+ $code = \Artisan::call("sharedfolder:add-alias unknown unknown");
+ $output = trim(\Artisan::output());
+
+ $this->assertSame(1, $code);
+ $this->assertSame("Folder not found.", $output);
+
+ $user = $this->getTestUser('user@force-delete.com');
+ $domain = $this->getTestDomain('force-delete.com', [
+ 'status' => \App\Domain::STATUS_NEW,
+ 'type' => \App\Domain::TYPE_EXTERNAL,
+ ]);
+ $package_kolab = \App\Package::withEnvTenantContext()->where('title', 'kolab')->first();
+ $package_domain = \App\Package::withEnvTenantContext()->where('title', 'domain-hosting')->first();
+ $user->assignPackage($package_kolab);
+ $domain->assignPackage($package_domain, $user);
+ $folder = $this->getTestSharedFolder('folder-test@force-delete.com');
+ $folder->assignToWallet($user->wallets->first());
+
+ // Invalid alias
+ $code = \Artisan::call("sharedfolder:add-alias {$folder->email} invalid");
+ $output = trim(\Artisan::output());
+
+ $this->assertSame(1, $code);
+ $this->assertSame("The specified alias is invalid.", $output);
+ $this->assertSame([], $folder->aliases()->pluck('alias')->all());
+
+ // A domain of another user
+ $code = \Artisan::call("sharedfolder:add-alias {$folder->email} test@kolab.org");
+ $output = trim(\Artisan::output());
+
+ $this->assertSame(1, $code);
+ $this->assertSame("The specified domain is not available.", $output);
+ $this->assertSame([], $folder->aliases()->pluck('alias')->all());
+
+ // Test success
+ $code = \Artisan::call("sharedfolder:add-alias {$folder->email} test@force-delete.com");
+ $output = trim(\Artisan::output());
+
+ $this->assertSame(0, $code);
+ $this->assertSame("", $output);
+ $this->assertSame(['test@force-delete.com'], $folder->aliases()->pluck('alias')->all());
+
+ // Alias already exists
+ $code = \Artisan::call("sharedfolder:add-alias {$folder->email} test@force-delete.com");
+ $output = trim(\Artisan::output());
+
+ $this->assertSame(1, $code);
+ $this->assertSame("Address is already assigned to the folder.", $output);
+
+ // TODO: test --force option
+ }
+}
diff --git a/src/tests/Feature/Controller/Admin/UsersTest.php b/src/tests/Feature/Controller/Admin/UsersTest.php
--- a/src/tests/Feature/Controller/Admin/UsersTest.php
+++ b/src/tests/Feature/Controller/Admin/UsersTest.php
@@ -38,6 +38,8 @@
$jack = $this->getTestUser('jack@kolab.org');
$jack->setSetting('external_email', null);
+ \App\SharedFolderAlias::truncate();
+
parent::tearDown();
}
@@ -205,6 +207,19 @@
$this->assertSame($user->id, $json['list'][0]['id']);
$this->assertSame($user->email, $json['list'][0]['email']);
+ // Search by shared folder alias
+ $folder = $this->getTestSharedFolder('folder-event@kolab.org');
+ $folder->setAliases(['folder-alias@kolab.org']);
+ $response = $this->actingAs($admin)->get("api/v4/users?search=folder-alias@kolab.org");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame(1, $json['count']);
+ $this->assertCount(1, $json['list']);
+ $this->assertSame($user->id, $json['list'][0]['id']);
+ $this->assertSame($user->email, $json['list'][0]['email']);
+
// Deleted users/domains
$domain = $this->getTestDomain('testsearch.com', ['type' => \App\Domain::TYPE_EXTERNAL]);
$user = $this->getTestUser('test@testsearch.com');
diff --git a/src/tests/Feature/Controller/AuthTest.php b/src/tests/Feature/Controller/AuthTest.php
--- a/src/tests/Feature/Controller/AuthTest.php
+++ b/src/tests/Feature/Controller/AuthTest.php
@@ -75,7 +75,6 @@
$this->assertEquals(User::STATUS_NEW | User::STATUS_ACTIVE, $json['status']);
$this->assertTrue(is_array($json['statusInfo']));
$this->assertTrue(is_array($json['settings']));
- $this->assertTrue(is_array($json['aliases']));
$this->assertTrue(!isset($json['access_token']));
// Note: Details of the content are tested in testUserResponse()
@@ -95,7 +94,6 @@
$this->assertEquals('john@kolab.org', $json['email']);
$this->assertTrue(is_array($json['statusInfo']));
$this->assertTrue(is_array($json['settings']));
- $this->assertTrue(is_array($json['aliases']));
$this->assertTrue(!empty($json['access_token']));
$this->assertTrue(!empty($json['expires_in']));
}
@@ -142,7 +140,6 @@
$this->assertEquals($user->email, $json['email']);
$this->assertTrue(is_array($json['statusInfo']));
$this->assertTrue(is_array($json['settings']));
- $this->assertTrue(is_array($json['aliases']));
// Valid user+password (upper-case)
$post = ['email' => 'John@Kolab.org', 'password' => 'simple123'];
diff --git a/src/tests/Feature/Controller/Reseller/UsersTest.php b/src/tests/Feature/Controller/Reseller/UsersTest.php
--- a/src/tests/Feature/Controller/Reseller/UsersTest.php
+++ b/src/tests/Feature/Controller/Reseller/UsersTest.php
@@ -32,6 +32,8 @@
$this->deleteTestUser('test@testsearch.com');
$this->deleteTestDomain('testsearch.com');
+ \App\SharedFolderAlias::truncate();
+
parent::tearDown();
}
@@ -130,6 +132,30 @@
$this->assertSame(0, $json['count']);
$this->assertSame([], $json['list']);
+ // Search by shared folder email
+ $response = $this->actingAs($reseller1)->get("api/v4/users?search=folder-event@kolab.org");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame(1, $json['count']);
+ $this->assertCount(1, $json['list']);
+ $this->assertSame($user->id, $json['list'][0]['id']);
+ $this->assertSame($user->email, $json['list'][0]['email']);
+
+ // Search by shared folder alias
+ $folder = $this->getTestSharedFolder('folder-event@kolab.org');
+ $folder->setAliases(['folder-alias@kolab.org']);
+ $response = $this->actingAs($reseller1)->get("api/v4/users?search=folder-alias@kolab.org");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame(1, $json['count']);
+ $this->assertCount(1, $json['list']);
+ $this->assertSame($user->id, $json['list'][0]['id']);
+ $this->assertSame($user->email, $json['list'][0]['email']);
+
// Create a domain with some users in the Sample Tenant so we have anything to search for
$domain = $this->getTestDomain('testsearch.com', ['type' => \App\Domain::TYPE_EXTERNAL]);
$domain->tenant_id = $reseller2->tenant_id;
diff --git a/src/tests/Feature/Controller/SharedFoldersTest.php b/src/tests/Feature/Controller/SharedFoldersTest.php
--- a/src/tests/Feature/Controller/SharedFoldersTest.php
+++ b/src/tests/Feature/Controller/SharedFoldersTest.php
@@ -208,6 +208,7 @@
$folder = $this->getTestSharedFolder('folder-test@kolab.org');
$folder->assignToWallet($john->wallets->first());
$folder->setSetting('acl', '["anyone, full"]');
+ $folder->setAliases(['folder-alias@kolab.org']);
// Test unauthenticated access
$response = $this->get("/api/v4/shared-folders/{$folder->id}");
@@ -231,6 +232,7 @@
$this->assertSame($folder->email, $json['email']);
$this->assertSame($folder->name, $json['name']);
$this->assertSame($folder->type, $json['type']);
+ $this->assertSame(['folder-alias@kolab.org'], $json['aliases']);
$this->assertTrue(!empty($json['statusInfo']));
$this->assertArrayHasKey('isDeleted', $json);
$this->assertArrayHasKey('isActive', $json);
@@ -404,8 +406,14 @@
$this->assertCount(2, $json);
$this->assertCount(2, $json['errors']);
- // Test too long name
- $post = ['domain' => 'kolab.org', 'name' => str_repeat('A', 192), 'type' => 'unknown'];
+ // Test too long name, invalid alias domain
+ $post = [
+ 'domain' => 'kolab.org',
+ 'name' => str_repeat('A', 192),
+ 'type' => 'unknown',
+ 'aliases' => ['folder-alias@unknown.org'],
+ ];
+
$response = $this->actingAs($john)->post("/api/v4/shared-folders", $post);
$response->assertStatus(422);
@@ -415,11 +423,14 @@
$this->assertCount(2, $json);
$this->assertSame(["The name may not be greater than 191 characters."], $json['errors']['name']);
$this->assertSame(["The specified type is invalid."], $json['errors']['type']);
- $this->assertCount(2, $json['errors']);
+ $this->assertSame(["The specified domain is invalid."], $json['errors']['aliases']);
+ $this->assertCount(3, $json['errors']);
// Test successful folder creation
$post['name'] = 'Test Folder';
$post['type'] = 'event';
+ $post['aliases'] = ['folder-alias@kolab.org']; // expected to be ignored
+
$response = $this->actingAs($john)->post("/api/v4/shared-folders", $post);
$json = $response->json();
@@ -433,6 +444,7 @@
$this->assertInstanceOf(SharedFolder::class, $folder);
$this->assertSame($post['type'], $folder->type);
$this->assertTrue($john->sharedFolders()->get()->contains($folder));
+ $this->assertSame([], $folder->aliases()->pluck('alias')->all());
// Shared folder name must be unique within a domain
$post['type'] = 'mail';
@@ -444,6 +456,20 @@
$this->assertCount(2, $json);
$this->assertCount(1, $json['errors']);
$this->assertSame("The specified name is not available.", $json['errors']['name'][0]);
+
+ $folder->forceDelete();
+
+ // Test successful folder creation with aliases
+ $post['name'] = 'Test Folder';
+ $post['type'] = 'mail';
+ $post['aliases'] = ['folder-alias@kolab.org'];
+ $response = $this->actingAs($john)->post("/api/v4/shared-folders", $post);
+ $json = $response->json();
+
+ $response->assertStatus(200);
+
+ $folder = SharedFolder::where('name', $post['name'])->first();
+ $this->assertSame(['folder-alias@kolab.org'], $folder->aliases()->pluck('alias')->all());
}
/**
@@ -484,5 +510,41 @@
$folder->refresh();
$this->assertSame($post['name'], $folder->name);
+
+ // Aliases with error
+ $post['aliases'] = ['folder-alias1@kolab.org', 'folder-alias2@unknown.com'];
+
+ $response = $this->actingAs($john)->put("/api/v4/shared-folders/{$folder->id}", $post);
+ $json = $response->json();
+
+ $response->assertStatus(422);
+
+ $this->assertSame('error', $json['status']);
+ $this->assertCount(2, $json);
+ $this->assertCount(1, $json['errors']);
+ $this->assertCount(1, $json['errors']['aliases']);
+ $this->assertSame("The specified domain is invalid.", $json['errors']['aliases'][1]);
+ $this->assertSame([], $folder->aliases()->pluck('alias')->all());
+
+ // Aliases with success expected
+ $post['aliases'] = ['folder-alias1@kolab.org', 'folder-alias2@kolab.org'];
+
+ $response = $this->actingAs($john)->put("/api/v4/shared-folders/{$folder->id}", $post);
+ $json = $response->json();
+
+ $response->assertStatus(200);
+
+ $this->assertSame('success', $json['status']);
+ $this->assertSame("Shared folder updated successfully.", $json['message']);
+ $this->assertCount(2, $json);
+ $this->assertSame($post['aliases'], $folder->aliases()->pluck('alias')->all());
+
+ // All aliases removal
+ $post['aliases'] = [];
+
+ $response = $this->actingAs($john)->put("/api/v4/shared-folders/{$folder->id}", $post);
+ $response->assertStatus(200);
+
+ $this->assertSame($post['aliases'], $folder->aliases()->pluck('alias')->all());
}
}
diff --git a/src/tests/Feature/Controller/SupportTest.php b/src/tests/Feature/Controller/SupportTest.php
--- a/src/tests/Feature/Controller/SupportTest.php
+++ b/src/tests/Feature/Controller/SupportTest.php
@@ -47,7 +47,7 @@
$this->assertCount(1, $json['errors']);
$this->assertSame(['The email must be a valid email address.'], $json['errors']['email']);
- $this->assertCount(0, $this->app->make('swift.transport')->driver()->messages());
+ $this->assertCount(0, $this->getSentMessages());
// Valid input
$post = [
@@ -66,17 +66,44 @@
$this->assertSame('success', $json['status']);
$this->assertSame('Support request submitted successfully.', $json['message']);
- $emails = $this->app->make('swift.transport')->driver()->messages();
+ $emails = $this->getSentMessages();
- $expected_body = "ID: 1234567\nName: Username\nWorking email address: test@test.com\n"
+ $this->assertCount(1, $emails);
+
+ $to = $emails[0]->getTo();
+ $from = $emails[0]->getFrom();
+ $replyTo = $emails[0]->getReplyTo();
+ $expectedBody = "ID: 1234567\nName: Username\nWorking email address: test@test.com\n"
. "Subject: Test summary\n\nTest body";
- $this->assertCount(1, $emails);
+ $this->assertCount(1, $to);
+ $this->assertCount(1, $from);
+ $this->assertCount(1, $replyTo);
$this->assertSame('Test summary', $emails[0]->getSubject());
- $this->assertSame(['test@test.com' => 'Username'], $emails[0]->getFrom());
- $this->assertSame(['test@test.com' => 'Username'], $emails[0]->getReplyTo());
- $this->assertNull($emails[0]->getCc());
- $this->assertSame([$support_email => null], $emails[0]->getTo());
- $this->assertSame($expected_body, trim($emails[0]->getBody()));
+ $this->assertSame('test@test.com', $from[0]->getAddress());
+ $this->assertSame('Username', $from[0]->getName());
+ $this->assertSame('test@test.com', $replyTo[0]->getAddress());
+ $this->assertSame('Username', $replyTo[0]->getName());
+ $this->assertSame([], $emails[0]->getCc());
+ $this->assertSame($support_email, $to[0]->getAddress());
+ $this->assertSame('', $to[0]->getName());
+ $this->assertSame($expectedBody, trim($emails[0]->getTextBody()));
+ $this->assertSame('', trim($emails[0]->getHtmlBody()));
+ }
+
+ /**
+ * Get all messages that have been sent
+ *
+ * @return \Symfony\Component\Mime\Email[]
+ */
+ protected function getSentMessages(): array
+ {
+ $transport = $this->app->make('mail.manager')->mailer()->getSymfonyTransport();
+
+ return $this->getObjectProperty($transport, 'messages')
+ ->map(function (\Symfony\Component\Mailer\SentMessage $item) {
+ return $item->getOriginalMessage();
+ })
+ ->all();
}
}
diff --git a/src/tests/Feature/Controller/UsersTest.php b/src/tests/Feature/Controller/UsersTest.php
--- a/src/tests/Feature/Controller/UsersTest.php
+++ b/src/tests/Feature/Controller/UsersTest.php
@@ -34,6 +34,8 @@
$this->deleteTestDomain('userscontroller.com');
$this->deleteTestGroup('group-test@kolabnow.com');
$this->deleteTestGroup('group-test@kolab.org');
+ $this->deleteTestSharedFolder('folder-test@kolabnow.com');
+ $this->deleteTestResource('resource-test@kolabnow.com');
$user = $this->getTestUser('john@kolab.org');
$wallet = $user->wallets()->first();
@@ -60,6 +62,8 @@
$this->deleteTestDomain('userscontroller.com');
$this->deleteTestGroup('group-test@kolabnow.com');
$this->deleteTestGroup('group-test@kolab.org');
+ $this->deleteTestSharedFolder('folder-test@kolabnow.com');
+ $this->deleteTestResource('resource-test@kolabnow.com');
$user = $this->getTestUser('john@kolab.org');
$wallet = $user->wallets()->first();
@@ -287,9 +291,9 @@
$this->assertEquals($userA->email, $json['email']);
$this->assertTrue(is_array($json['statusInfo']));
$this->assertTrue(is_array($json['settings']));
- $this->assertTrue(is_array($json['aliases']));
$this->assertTrue($json['config']['greylist_enabled']);
$this->assertSame([], $json['skus']);
+ $this->assertSame([], $json['aliases']);
// Values below are tested by Unit tests
$this->assertArrayHasKey('isDeleted', $json);
$this->assertArrayHasKey('isDegraded', $json);
@@ -311,6 +315,10 @@
// Ned: Additional account controller
$response = $this->actingAs($ned)->get("/api/v4/users/{$john->id}");
$response->assertStatus(200);
+
+ $json = $response->json();
+ $this->assertSame(['john.doe@kolab.org'], $json['aliases']);
+
$response = $this->actingAs($ned)->get("/api/v4/users/{$jack->id}");
$response->assertStatus(200);
@@ -337,6 +345,8 @@
$this->assertSame([500], $json['skus'][$mailbox_sku->id]['costs']);
$this->assertSame(1, $json['skus'][$secondfactor_sku->id]['count']);
$this->assertSame([0], $json['skus'][$secondfactor_sku->id]['costs']);
+
+ $this->assertSame([], $json['aliases']);
}
/**
@@ -726,6 +736,7 @@
$this->assertSame('John2', $user->getSetting('first_name'));
$this->assertSame('Doe2', $user->getSetting('last_name'));
$this->assertSame('TestOrg', $user->getSetting('organization'));
+ /** @var array<int, \App\UserAlias> $aliases */
$aliases = $user->aliases()->orderBy('alias')->get();
$this->assertCount(2, $aliases);
$this->assertSame('deleted@kolab.org', $aliases[0]->alias);
@@ -1190,10 +1201,6 @@
$this->assertEquals($user->status, $result['status']);
$this->assertTrue(is_array($result['statusInfo']));
- $this->assertTrue(is_array($result['aliases']));
- $this->assertCount(1, $result['aliases']);
- $this->assertSame('john.doe@kolab.org', $result['aliases'][0]);
-
$this->assertTrue(is_array($result['settings']));
$this->assertSame('US', $result['settings']['country']);
$this->assertSame('USD', $result['settings']['currency']);
@@ -1263,21 +1270,39 @@
}
/**
- * List of email address validation cases for testValidateEmail()
+ * User email address validation.
*
- * @return array Arguments for testValidateEmail()
+ * Note: Technically these include unit tests, but let's keep it here for now.
+ * FIXME: Shall we do a http request for each case?
*/
- public function dataValidateEmail(): array
+ public function testValidateEmail(): void
{
- $this->refreshApplication();
+ Queue::fake();
+
$public_domains = Domain::getPublicDomains();
$domain = reset($public_domains);
$john = $this->getTestUser('john@kolab.org');
$jack = $this->getTestUser('jack@kolab.org');
$user = $this->getTestUser('UsersControllerTest1@userscontroller.com');
+ $folder = $this->getTestSharedFolder('folder-event@kolab.org');
+ $folder->setAliases(['folder-alias1@kolab.org']);
+ $folder_del = $this->getTestSharedFolder('folder-test@kolabnow.com');
+ $folder_del->setAliases(['folder-alias2@kolabnow.com']);
+ $folder_del->delete();
+ $pub_group = $this->getTestGroup('group-test@kolabnow.com');
+ $pub_group->delete();
+ $priv_group = $this->getTestGroup('group-test@kolab.org');
+ $resource = $this->getTestResource('resource-test@kolabnow.com');
+ $resource->delete();
+
+ $cases = [
+ // valid (user domain)
+ ["admin@kolab.org", $john, null],
+
+ // valid (public domain)
+ ["test.test@$domain", $john, null],
- return [
// Invalid format
["$domain", $john, 'The specified email is invalid.'],
[".@$domain", $john, 'The specified email is invalid.'],
@@ -1294,29 +1319,39 @@
// forbidden (other user's domain)
["testtest@kolab.org", $user, 'The specified domain is not available.'],
- // existing alias of other user, to be a user email
+ // existing alias of other user
["jack.daniels@kolab.org", $john, 'The specified email is not available.'],
- // valid (user domain)
- ["admin@kolab.org", $john, null],
+ // An existing shared folder or folder alias
+ ["folder-event@kolab.org", $john, 'The specified email is not available.'],
+ ["folder-alias1@kolab.org", $john, 'The specified email is not available.'],
- // valid (public domain)
- ["test.test@$domain", $john, null],
+ // A soft-deleted shared folder or folder alias
+ ["folder-test@kolabnow.com", $john, 'The specified email is not available.'],
+ ["folder-alias2@kolabnow.com", $john, 'The specified email is not available.'],
+
+ // A group
+ ["group-test@kolab.org", $john, 'The specified email is not available.'],
+
+ // A soft-deleted group
+ ["group-test@kolabnow.com", $john, 'The specified email is not available.'],
+
+ // A resource
+ ["resource-test1@kolab.org", $john, 'The specified email is not available.'],
+
+ // A soft-deleted resource
+ ["resource-test@kolabnow.com", $john, 'The specified email is not available.'],
];
- }
- /**
- * User email address validation.
- *
- * Note: Technically these include unit tests, but let's keep it here for now.
- * FIXME: Shall we do a http request for each case?
- *
- * @dataProvider dataValidateEmail
- */
- public function testValidateEmail($email, $user, $expected_result): void
- {
- $result = UsersController::validateEmail($email, $user);
- $this->assertSame($expected_result, $result);
+ foreach ($cases as $idx => $case) {
+ list($email, $user, $expected) = $case;
+
+ $deleted = null;
+ $result = UsersController::validateEmail($email, $user, $deleted);
+
+ $this->assertSame($expected, $result, "Case {$email}");
+ $this->assertNull($deleted, "Case {$email}");
+ }
}
/**
@@ -1346,19 +1381,7 @@
$result = UsersController::validateEmail('jack@kolab.org', $john, $deleted);
$this->assertSame('The specified email is not available.', $result);
$this->assertSame(null, $deleted);
- }
-
- /**
- * User email validation - tests for an address being a group email address
- *
- * Note: Technically these include unit tests, but let's keep it here for now.
- * FIXME: Shall we do a http request for each case?
- */
- public function testValidateEmailGroup(): void
- {
- Queue::fake();
- $john = $this->getTestUser('john@kolab.org');
$pub_group = $this->getTestGroup('group-test@kolabnow.com');
$priv_group = $this->getTestGroup('group-test@kolab.org');
@@ -1385,23 +1408,43 @@
$result = UsersController::validateEmail($priv_group->email, $john, $deleted);
$this->assertSame(null, $result);
$this->assertSame($priv_group->id, $deleted->id);
+
+ // TODO: Test the same with a resource and shared folder
}
/**
- * List of alias validation cases for testValidateAlias()
+ * User email alias validation.
*
- * @return array Arguments for testValidateAlias()
+ * Note: Technically these include unit tests, but let's keep it here for now.
+ * FIXME: Shall we do a http request for each case?
*/
- public function dataValidateAlias(): array
+ public function testValidateAlias(): void
{
- $this->refreshApplication();
+ Queue::fake();
+
$public_domains = Domain::getPublicDomains();
$domain = reset($public_domains);
$john = $this->getTestUser('john@kolab.org');
$user = $this->getTestUser('UsersControllerTest1@userscontroller.com');
+ $deleted_priv = $this->getTestUser('deleted@kolab.org');
+ $deleted_priv->setAliases(['deleted-alias@kolab.org']);
+ $deleted_priv->delete();
+ $deleted_pub = $this->getTestUser('deleted@kolabnow.com');
+ $deleted_pub->setAliases(['deleted-alias@kolabnow.com']);
+ $deleted_pub->delete();
+ $folder = $this->getTestSharedFolder('folder-event@kolab.org');
+ $folder->setAliases(['folder-alias1@kolab.org']);
+ $folder_del = $this->getTestSharedFolder('folder-test@kolabnow.com');
+ $folder_del->setAliases(['folder-alias2@kolabnow.com']);
+ $folder_del->delete();
+ $group_priv = $this->getTestGroup('group-test@kolab.org');
+ $group = $this->getTestGroup('group-test@kolabnow.com');
+ $group->delete();
+ $resource = $this->getTestResource('resource-test@kolabnow.com');
+ $resource->delete();
- return [
+ $cases = [
// Invalid format
["$domain", $john, 'The specified alias is invalid.'],
[".@$domain", $john, 'The specified alias is invalid.'],
@@ -1429,59 +1472,38 @@
// valid (public domain)
["test.test@$domain", $john, null],
- ];
- }
- /**
- * User email alias validation.
- *
- * Note: Technically these include unit tests, but let's keep it here for now.
- * FIXME: Shall we do a http request for each case?
- *
- * @dataProvider dataValidateAlias
- */
- public function testValidateAlias($alias, $user, $expected_result): void
- {
- $result = UsersController::validateAlias($alias, $user);
- $this->assertSame($expected_result, $result);
- }
+ // An alias that was a user email before is allowed, but only for custom domains
+ ["deleted@kolab.org", $john, null],
+ ["deleted-alias@kolab.org", $john, null],
+ ["deleted@kolabnow.com", $john, 'The specified alias is not available.'],
+ ["deleted-alias@kolabnow.com", $john, 'The specified alias is not available.'],
- /**
- * User alias validation - more cases.
- *
- * Note: Technically these include unit tests, but let's keep it here for now.
- * FIXME: Shall we do a http request for each case?
- */
- public function testValidateAlias2(): void
- {
- Queue::fake();
+ // An existing shared folder or folder alias
+ ["folder-event@kolab.org", $john, 'The specified alias is not available.'],
+ ["folder-alias1@kolab.org", $john, null],
- $john = $this->getTestUser('john@kolab.org');
- $jack = $this->getTestUser('jack@kolab.org');
- $user = $this->getTestUser('UsersControllerTest1@userscontroller.com');
- $deleted_priv = $this->getTestUser('deleted@kolab.org');
- $deleted_priv->setAliases(['deleted-alias@kolab.org']);
- $deleted_priv->delete();
- $deleted_pub = $this->getTestUser('deleted@kolabnow.com');
- $deleted_pub->setAliases(['deleted-alias@kolabnow.com']);
- $deleted_pub->delete();
- $group = $this->getTestGroup('group-test@kolabnow.com');
+ // A soft-deleted shared folder or folder alias
+ ["folder-test@kolabnow.com", $john, 'The specified alias is not available.'],
+ ["folder-alias2@kolabnow.com", $john, 'The specified alias is not available.'],
- // An alias that was a user email before is allowed, but only for custom domains
- $result = UsersController::validateAlias('deleted@kolab.org', $john);
- $this->assertSame(null, $result);
+ // A group with the same email address exists
+ ["group-test@kolab.org", $john, 'The specified alias is not available.'],
- $result = UsersController::validateAlias('deleted-alias@kolab.org', $john);
- $this->assertSame(null, $result);
+ // A soft-deleted group
+ ["group-test@kolabnow.com", $john, 'The specified alias is not available.'],
- $result = UsersController::validateAlias('deleted@kolabnow.com', $john);
- $this->assertSame('The specified alias is not available.', $result);
+ // A resource
+ ["resource-test1@kolab.org", $john, 'The specified alias is not available.'],
- $result = UsersController::validateAlias('deleted-alias@kolabnow.com', $john);
- $this->assertSame('The specified alias is not available.', $result);
+ // A soft-deleted resource
+ ["resource-test@kolabnow.com", $john, 'The specified alias is not available.'],
+ ];
- // A grpoup with the same email address exists
- $result = UsersController::validateAlias($group->email, $john);
- $this->assertSame('The specified alias is not available.', $result);
+ foreach ($cases as $idx => $case) {
+ list($alias, $user, $expected) = $case;
+ $result = UsersController::validateAlias($alias, $user);
+ $this->assertSame($expected, $result, "Case {$alias}");
+ }
}
}
diff --git a/src/tests/Feature/Jobs/PasswordResetEmailTest.php b/src/tests/Feature/Jobs/PasswordResetEmailTest.php
--- a/src/tests/Feature/Jobs/PasswordResetEmailTest.php
+++ b/src/tests/Feature/Jobs/PasswordResetEmailTest.php
@@ -11,8 +11,6 @@
class PasswordResetEmailTest extends TestCase
{
- private $code;
-
/**
* {@inheritDoc}
*
diff --git a/src/tests/Feature/ResourceTest.php b/src/tests/Feature/ResourceTest.php
--- a/src/tests/Feature/ResourceTest.php
+++ b/src/tests/Feature/ResourceTest.php
@@ -106,7 +106,7 @@
$resource = new Resource();
$resource->name = 'Reśo';
- $resource->domain = 'kolabnow.com';
+ $resource->domainName = 'kolabnow.com';
$resource->save();
$this->assertMatchesRegularExpression('/^[0-9]{1,20}$/', $resource->id);
diff --git a/src/tests/Feature/SharedFolderTest.php b/src/tests/Feature/SharedFolderTest.php
--- a/src/tests/Feature/SharedFolderTest.php
+++ b/src/tests/Feature/SharedFolderTest.php
@@ -28,6 +28,60 @@
parent::tearDown();
}
+ /**
+ * Tests for AliasesTrait methods
+ */
+ public function testAliases(): void
+ {
+ Queue::fake();
+ Queue::assertNothingPushed();
+
+ $folder = $this->getTestSharedFolder('folder-test@kolabnow.com');
+
+ $this->assertCount(0, $folder->aliases->all());
+
+ // Add an alias
+ $folder->setAliases(['FolderAlias1@kolabnow.com']);
+
+ Queue::assertPushed(\App\Jobs\SharedFolder\UpdateJob::class, 1);
+
+ $aliases = $folder->aliases()->get();
+
+ $this->assertCount(1, $aliases);
+ $this->assertSame('folderalias1@kolabnow.com', $aliases[0]->alias);
+ $this->assertTrue(SharedFolder::aliasExists('folderalias1@kolabnow.com'));
+
+ // Add another alias
+ $folder->setAliases(['FolderAlias1@kolabnow.com', 'FolderAlias2@kolabnow.com']);
+
+ Queue::assertPushed(\App\Jobs\SharedFolder\UpdateJob::class, 2);
+
+ $aliases = $folder->aliases()->orderBy('alias')->get();
+ $this->assertCount(2, $aliases);
+ $this->assertSame('folderalias1@kolabnow.com', $aliases[0]->alias);
+ $this->assertSame('folderalias2@kolabnow.com', $aliases[1]->alias);
+
+ // Remove an alias
+ $folder->setAliases(['FolderAlias1@kolabnow.com']);
+
+ Queue::assertPushed(\App\Jobs\SharedFolder\UpdateJob::class, 3);
+
+ $aliases = $folder->aliases()->get();
+
+ $this->assertCount(1, $aliases);
+ $this->assertSame('folderalias1@kolabnow.com', $aliases[0]->alias);
+ $this->assertFalse(SharedFolder::aliasExists('folderalias2@kolabnow.com'));
+
+ // Remove all aliases
+ $folder->setAliases([]);
+
+ Queue::assertPushed(\App\Jobs\SharedFolder\UpdateJob::class, 4);
+
+ $this->assertCount(0, $folder->aliases()->get());
+ $this->assertFalse(SharedFolder::aliasExists('folderalias1@kolabnow.com'));
+ $this->assertFalse(SharedFolder::aliasExists('folderalias2@kolabnow.com'));
+ }
+
/**
* Tests for SharedFolder::assignToWallet()
*/
@@ -116,7 +170,7 @@
$folder = new SharedFolder();
$folder->name = 'Reśo';
- $folder->domain = 'kolabnow.com';
+ $folder->domainName = 'kolabnow.com';
$folder->save();
$this->assertMatchesRegularExpression('/^[0-9]{1,20}$/', $folder->id);
diff --git a/src/tests/Feature/SkuTest.php b/src/tests/Feature/SkuTest.php
--- a/src/tests/Feature/SkuTest.php
+++ b/src/tests/Feature/SkuTest.php
@@ -11,7 +11,9 @@
class SkuTest extends TestCase
{
-
+ /**
+ * {@inheritDoc}
+ */
public function setUp(): void
{
parent::setUp();
@@ -19,6 +21,9 @@
$this->deleteTestUser('jane@kolabnow.com');
}
+ /**
+ * {@inheritDoc}
+ */
public function tearDown(): void
{
$this->deleteTestUser('jane@kolabnow.com');
diff --git a/src/tests/Feature/Stories/GreylistTest.php b/src/tests/Feature/Stories/GreylistTest.php
--- a/src/tests/Feature/Stories/GreylistTest.php
+++ b/src/tests/Feature/Stories/GreylistTest.php
@@ -14,7 +14,6 @@
class GreylistTest extends TestCase
{
private $clientAddress;
- private $requests = [];
private $net;
public function setUp(): void
@@ -254,11 +253,11 @@
$this->assertFalse($request->shouldDefer());
// Ensure we also find the setting by alias
- $aliases = $this->domainOwner->aliases()->orderBy('alias')->get();
+ $aliases = $this->domainOwner->aliases()->orderBy('alias')->pluck('alias')->all();
$request = new Greylist\Request(
[
'sender' => 'someone@sender.domain',
- 'recipient' => $aliases[0]->alias,
+ 'recipient' => $aliases[0],
'client_address' => $this->clientAddress
]
);
diff --git a/src/tests/Feature/TenantTest.php b/src/tests/Feature/TenantTest.php
--- a/src/tests/Feature/TenantTest.php
+++ b/src/tests/Feature/TenantTest.php
@@ -8,7 +8,6 @@
class TenantTest extends TestCase
{
-
/**
* {@inheritDoc}
*/
diff --git a/src/tests/Feature/UserTest.php b/src/tests/Feature/UserTest.php
--- a/src/tests/Feature/UserTest.php
+++ b/src/tests/Feature/UserTest.php
@@ -1096,7 +1096,7 @@
}
/**
- * Tests for UserAliasesTrait::setAliases()
+ * Tests for AliasesTrait::setAliases()
*/
public function testSetAliases(): void
{
diff --git a/src/tests/MailInterceptTrait.php b/src/tests/MailInterceptTrait.php
deleted file mode 100644
--- a/src/tests/MailInterceptTrait.php
+++ /dev/null
@@ -1,79 +0,0 @@
-<?php
-
-namespace Tests;
-
-use Illuminate\Mail\Mailable;
-use Illuminate\Support\Facades\Mail;
-use KirschbaumDevelopment\MailIntercept\WithMailInterceptor;
-
-trait MailInterceptTrait
-{
- use WithMailInterceptor;
-
- /**
- * Extract content of a email message.
- *
- * @param \Illuminate\Mail\Mailable $mail Mailable object
- *
- * @return array Parsed message data:
- * - 'plain': Plain text body
- * - 'html: HTML body
- * - 'message': Swift_Message object
- */
- protected function fakeMail(Mailable $mail): array
- {
- $this->interceptMail();
-
- Mail::send($mail);
-
- $message = $this->interceptedMail()->last();
-
- // SwiftMailer does not have methods to get the bodies, we'll parse the message
- list($plain, $html) = $this->extractMailBody($message->toString());
-
- return [
- 'plain' => $plain,
- 'html' => $html,
- 'message' => $message,
- ];
- }
-
- /**
- * Simple message parser to extract plain and html body
- *
- * @param string $message Email message as string
- *
- * @return array Plain text and HTML body
- */
- protected function extractMailBody(string $message): array
- {
- // Note that we're not supporting every message format, we only
- // support what Laravel/SwiftMailer produces
- // TODO: It may stop working if we start using attachments
- $plain = '';
- $html = '';
-
- if (preg_match('/[\s\t]boundary="([^"]+)"/', $message, $matches)) {
- // multipart message assume plain and html parts
- $split = preg_split('/--' . preg_quote($matches[1]) . '/', $message);
-
- list($plain_head, $plain) = explode("\r\n\r\n", $split[1], 2);
- list($html_head, $html) = explode("\r\n\r\n", $split[2], 2);
-
- if (strpos($plain_head, 'Content-Transfer-Encoding: quoted-printable') !== false) {
- $plain = quoted_printable_decode($plain);
- }
-
- if (strpos($html_head, 'Content-Transfer-Encoding: quoted-printable') !== false) {
- $html = quoted_printable_decode($html);
- }
- } else {
- list($header, $html) = explode("\r\n\r\n", $message, 2);
- if (strpos($header, 'Content-Transfer-Encoding: quoted-printable') !== false) {
- $html = quoted_printable_decode($html);
- }
- }
-
- return [$plain, $html];
- }
-}
diff --git a/src/tests/TestCaseTrait.php b/src/tests/TestCaseTrait.php
--- a/src/tests/TestCaseTrait.php
+++ b/src/tests/TestCaseTrait.php
@@ -417,7 +417,7 @@
$resource = new Resource();
$resource->email = $email;
- $resource->domain = $domain;
+ $resource->domainName = $domain;
if (!isset($attrib['name'])) {
$resource->name = $local;
@@ -449,7 +449,7 @@
$folder = new SharedFolder();
$folder->email = $email;
- $folder->domain = $domain;
+ $folder->domainName = $domain;
if (!isset($attrib['name'])) {
$folder->name = $local;
@@ -495,7 +495,7 @@
*
* @return mixed Method return.
*/
- protected function invokeMethod($object, $methodName, array $parameters = array())
+ protected function invokeMethod($object, $methodName, array $parameters = [])
{
$reflection = new \ReflectionClass(get_class($object));
$method = $reflection->getMethod($methodName);
@@ -504,6 +504,29 @@
return $method->invokeArgs($object, $parameters);
}
+ /**
+ * Extract content of an email message.
+ *
+ * @param \Illuminate\Mail\Mailable $mail Mailable object
+ *
+ * @return array Parsed message data:
+ * - 'plain': Plain text body
+ * - 'html: HTML body
+ * - 'subject': Mail subject
+ */
+ protected function renderMail(\Illuminate\Mail\Mailable $mail): array
+ {
+ $mail->build(); // @phpstan-ignore-line
+
+ $result = $this->invokeMethod($mail, 'renderForAssertions');
+
+ return [
+ 'plain' => $result[1],
+ 'html' => $result[0],
+ 'subject' => $mail->subject,
+ ];
+ }
+
protected function setUpTest()
{
$this->userPassword = \App\Utils::generatePassphrase();
diff --git a/src/tests/Unit/Mail/DegradedAccountReminderTest.php b/src/tests/Unit/Mail/DegradedAccountReminderTest.php
--- a/src/tests/Unit/Mail/DegradedAccountReminderTest.php
+++ b/src/tests/Unit/Mail/DegradedAccountReminderTest.php
@@ -5,13 +5,10 @@
use App\Mail\DegradedAccountReminder;
use App\User;
use App\Wallet;
-use Tests\MailInterceptTrait;
use Tests\TestCase;
class DegradedAccountReminderTest extends TestCase
{
- use MailInterceptTrait;
-
/**
* Test email content
*/
@@ -20,7 +17,7 @@
$user = $this->getTestUser('ned@kolab.org');
$wallet = $user->wallets->first();
- $mail = $this->fakeMail(new DegradedAccountReminder($wallet, $user));
+ $mail = $this->renderMail(new DegradedAccountReminder($wallet, $user));
$html = $mail['html'];
$plain = $mail['plain'];
@@ -29,7 +26,7 @@
$dashboardLink = sprintf('<a href="%s">%s</a>', $dashboardUrl, $dashboardUrl);
$appName = $user->tenant->title;
- $this->assertMailSubject("$appName Reminder: Your account is free", $mail['message']);
+ $this->assertSame("$appName Reminder: Your account is free", $mail['subject']);
$this->assertStringStartsWith('<!DOCTYPE html>', $html);
$this->assertTrue(strpos($html, $user->name(true)) > 0);
diff --git a/src/tests/Unit/Mail/NegativeBalanceBeforeDeleteTest.php b/src/tests/Unit/Mail/NegativeBalanceBeforeDeleteTest.php
--- a/src/tests/Unit/Mail/NegativeBalanceBeforeDeleteTest.php
+++ b/src/tests/Unit/Mail/NegativeBalanceBeforeDeleteTest.php
@@ -6,13 +6,10 @@
use App\Mail\NegativeBalanceBeforeDelete;
use App\User;
use App\Wallet;
-use Tests\MailInterceptTrait;
use Tests\TestCase;
class NegativeBalanceBeforeDeleteTest extends TestCase
{
- use MailInterceptTrait;
-
/**
* {@inheritDoc}
*/
@@ -49,7 +46,7 @@
'app.support_url' => 'https://kolab.org/support',
]);
- $mail = $this->fakeMail(new NegativeBalanceBeforeDelete($wallet, $user));
+ $mail = $this->renderMail(new NegativeBalanceBeforeDelete($wallet, $user));
$html = $mail['html'];
$plain = $mail['plain'];
@@ -60,7 +57,7 @@
$supportLink = sprintf('<a href="%s">%s</a>', $supportUrl, $supportUrl);
$appName = $user->tenant->title;
- $this->assertMailSubject("$appName Final Warning", $mail['message']);
+ $this->assertSame("$appName Final Warning", $mail['subject']);
$this->assertStringStartsWith('<!DOCTYPE html>', $html);
$this->assertTrue(strpos($html, $user->name(true)) > 0);
@@ -93,7 +90,7 @@
'app.public_url' => 'https://test.org',
]);
- $mail = $this->fakeMail(new NegativeBalanceBeforeDelete($wallet, $user));
+ $mail = $this->renderMail(new NegativeBalanceBeforeDelete($wallet, $user));
$html = $mail['html'];
$plain = $mail['plain'];
@@ -103,7 +100,7 @@
$supportUrl = 'https://test.org/support';
$supportLink = sprintf('<a href="%s">%s</a>', $supportUrl, $supportUrl);
- $this->assertMailSubject("{$tenant->title} Final Warning", $mail['message']);
+ $this->assertSame("{$tenant->title} Final Warning", $mail['subject']);
$this->assertStringStartsWith('<!DOCTYPE html>', $html);
$this->assertTrue(strpos($html, $user->name(true)) > 0);
diff --git a/src/tests/Unit/Mail/NegativeBalanceDegradedTest.php b/src/tests/Unit/Mail/NegativeBalanceDegradedTest.php
--- a/src/tests/Unit/Mail/NegativeBalanceDegradedTest.php
+++ b/src/tests/Unit/Mail/NegativeBalanceDegradedTest.php
@@ -6,13 +6,10 @@
use App\Mail\NegativeBalanceDegraded;
use App\User;
use App\Wallet;
-use Tests\MailInterceptTrait;
use Tests\TestCase;
class NegativeBalanceDegradedTest extends TestCase
{
- use MailInterceptTrait;
-
/**
* Test email content
*/
@@ -27,7 +24,7 @@
'app.support_url' => 'https://kolab.org/support',
]);
- $mail = $this->fakeMail(new NegativeBalanceDegraded($wallet, $user));
+ $mail = $this->renderMail(new NegativeBalanceDegraded($wallet, $user));
$html = $mail['html'];
$plain = $mail['plain'];
@@ -38,7 +35,7 @@
$supportLink = sprintf('<a href="%s">%s</a>', $supportUrl, $supportUrl);
$appName = $user->tenant->title;
- $this->assertMailSubject("$appName Account Degraded", $mail['message']);
+ $this->assertSame("$appName Account Degraded", $mail['subject']);
$this->assertStringStartsWith('<!DOCTYPE html>', $html);
$this->assertTrue(strpos($html, $user->name(true)) > 0);
diff --git a/src/tests/Unit/Mail/NegativeBalanceReminderDegradeTest.php b/src/tests/Unit/Mail/NegativeBalanceReminderDegradeTest.php
--- a/src/tests/Unit/Mail/NegativeBalanceReminderDegradeTest.php
+++ b/src/tests/Unit/Mail/NegativeBalanceReminderDegradeTest.php
@@ -6,13 +6,10 @@
use App\Mail\NegativeBalanceReminderDegrade;
use App\User;
use App\Wallet;
-use Tests\MailInterceptTrait;
use Tests\TestCase;
class NegativeBalanceReminderDegradeTest extends TestCase
{
- use MailInterceptTrait;
-
/**
* Test email content
*/
@@ -29,7 +26,7 @@
'app.support_url' => 'https://kolab.org/support',
]);
- $mail = $this->fakeMail(new NegativeBalanceReminderDegrade($wallet, $user));
+ $mail = $this->renderMail(new NegativeBalanceReminderDegrade($wallet, $user));
$html = $mail['html'];
$plain = $mail['plain'];
@@ -40,7 +37,7 @@
$supportLink = sprintf('<a href="%s">%s</a>', $supportUrl, $supportUrl);
$appName = $user->tenant->title;
- $this->assertMailSubject("$appName Payment Reminder", $mail['message']);
+ $this->assertSame("$appName Payment Reminder", $mail['subject']);
$this->assertStringStartsWith('<!DOCTYPE html>', $html);
$this->assertTrue(strpos($html, $user->name(true)) > 0);
diff --git a/src/tests/Unit/Mail/NegativeBalanceReminderTest.php b/src/tests/Unit/Mail/NegativeBalanceReminderTest.php
--- a/src/tests/Unit/Mail/NegativeBalanceReminderTest.php
+++ b/src/tests/Unit/Mail/NegativeBalanceReminderTest.php
@@ -6,13 +6,10 @@
use App\Mail\NegativeBalanceReminder;
use App\User;
use App\Wallet;
-use Tests\MailInterceptTrait;
use Tests\TestCase;
class NegativeBalanceReminderTest extends TestCase
{
- use MailInterceptTrait;
-
/**
* Test email content
*/
@@ -29,7 +26,7 @@
'app.support_url' => 'https://kolab.org/support',
]);
- $mail = $this->fakeMail(new NegativeBalanceReminder($wallet, $user));
+ $mail = $this->renderMail(new NegativeBalanceReminder($wallet, $user));
$html = $mail['html'];
$plain = $mail['plain'];
@@ -40,7 +37,7 @@
$supportLink = sprintf('<a href="%s">%s</a>', $supportUrl, $supportUrl);
$appName = $user->tenant->title;
- $this->assertMailSubject("$appName Payment Reminder", $mail['message']);
+ $this->assertSame("$appName Payment Reminder", $mail['subject']);
$this->assertStringStartsWith('<!DOCTYPE html>', $html);
$this->assertTrue(strpos($html, $user->name(true)) > 0);
diff --git a/src/tests/Unit/Mail/NegativeBalanceSuspendedTest.php b/src/tests/Unit/Mail/NegativeBalanceSuspendedTest.php
--- a/src/tests/Unit/Mail/NegativeBalanceSuspendedTest.php
+++ b/src/tests/Unit/Mail/NegativeBalanceSuspendedTest.php
@@ -6,13 +6,10 @@
use App\Mail\NegativeBalanceSuspended;
use App\User;
use App\Wallet;
-use Tests\MailInterceptTrait;
use Tests\TestCase;
class NegativeBalanceSuspendedTest extends TestCase
{
- use MailInterceptTrait;
-
/**
* Test email content
*/
@@ -29,7 +26,7 @@
'app.support_url' => 'https://kolab.org/support',
]);
- $mail = $this->fakeMail(new NegativeBalanceSuspended($wallet, $user));
+ $mail = $this->renderMail(new NegativeBalanceSuspended($wallet, $user));
$html = $mail['html'];
$plain = $mail['plain'];
@@ -40,7 +37,7 @@
$supportLink = sprintf('<a href="%s">%s</a>', $supportUrl, $supportUrl);
$appName = $user->tenant->title;
- $this->assertMailSubject("$appName Account Suspended", $mail['message']);
+ $this->assertSame("$appName Account Suspended", $mail['subject']);
$this->assertStringStartsWith('<!DOCTYPE html>', $html);
$this->assertTrue(strpos($html, $user->name(true)) > 0);
diff --git a/src/tests/Unit/Mail/NegativeBalanceTest.php b/src/tests/Unit/Mail/NegativeBalanceTest.php
--- a/src/tests/Unit/Mail/NegativeBalanceTest.php
+++ b/src/tests/Unit/Mail/NegativeBalanceTest.php
@@ -5,13 +5,10 @@
use App\Mail\NegativeBalance;
use App\User;
use App\Wallet;
-use Tests\MailInterceptTrait;
use Tests\TestCase;
class NegativeBalanceTest extends TestCase
{
- use MailInterceptTrait;
-
/**
* Test email content
*/
@@ -24,7 +21,7 @@
'app.support_url' => 'https://kolab.org/support',
]);
- $mail = $this->fakeMail(new NegativeBalance($wallet, $user));
+ $mail = $this->renderMail(new NegativeBalance($wallet, $user));
$html = $mail['html'];
$plain = $mail['plain'];
@@ -35,7 +32,7 @@
$supportLink = sprintf('<a href="%s">%s</a>', $supportUrl, $supportUrl);
$appName = \config('app.name');
- $this->assertMailSubject("$appName Payment Required", $mail['message']);
+ $this->assertSame("$appName Payment Required", $mail['subject']);
$this->assertStringStartsWith('<!DOCTYPE html>', $html);
$this->assertTrue(strpos($html, $user->name(true)) > 0);
diff --git a/src/tests/Unit/Mail/PasswordResetTest.php b/src/tests/Unit/Mail/PasswordResetTest.php
--- a/src/tests/Unit/Mail/PasswordResetTest.php
+++ b/src/tests/Unit/Mail/PasswordResetTest.php
@@ -6,13 +6,10 @@
use App\User;
use App\Utils;
use App\VerificationCode;
-use Tests\MailInterceptTrait;
use Tests\TestCase;
class PasswordResetTest extends TestCase
{
- use MailInterceptTrait;
-
/**
* Test email content
*/
@@ -29,7 +26,7 @@
'name' => 'User Name',
]);
- $mail = $this->fakeMail(new PasswordReset($code));
+ $mail = $this->renderMail(new PasswordReset($code));
$html = $mail['html'];
$plain = $mail['plain'];
@@ -38,7 +35,7 @@
$link = "<a href=\"$url\">$url</a>";
$appName = \config('app.name');
- $this->assertMailSubject("$appName Password Reset", $mail['message']);
+ $this->assertSame("$appName Password Reset", $mail['subject']);
$this->assertStringStartsWith('<!DOCTYPE html>', $html);
$this->assertTrue(strpos($html, $link) > 0);
diff --git a/src/tests/Unit/Mail/PaymentFailureTest.php b/src/tests/Unit/Mail/PaymentFailureTest.php
--- a/src/tests/Unit/Mail/PaymentFailureTest.php
+++ b/src/tests/Unit/Mail/PaymentFailureTest.php
@@ -5,13 +5,10 @@
use App\Mail\PaymentFailure;
use App\Payment;
use App\User;
-use Tests\MailInterceptTrait;
use Tests\TestCase;
class PaymentFailureTest extends TestCase
{
- use MailInterceptTrait;
-
/**
* Test email content
*/
@@ -23,7 +20,7 @@
\config(['app.support_url' => 'https://kolab.org/support']);
- $mail = $this->fakeMail(new PaymentFailure($payment, $user));
+ $mail = $this->renderMail(new PaymentFailure($payment, $user));
$html = $mail['html'];
$plain = $mail['plain'];
@@ -34,7 +31,7 @@
$supportLink = sprintf('<a href="%s">%s</a>', $supportUrl, $supportUrl);
$appName = \config('app.name');
- $this->assertMailSubject("$appName Payment Failed", $mail['message']);
+ $this->assertSame("$appName Payment Failed", $mail['subject']);
$this->assertStringStartsWith('<!DOCTYPE html>', $html);
$this->assertTrue(strpos($html, $user->name(true)) > 0);
diff --git a/src/tests/Unit/Mail/PaymentMandateDisabledTest.php b/src/tests/Unit/Mail/PaymentMandateDisabledTest.php
--- a/src/tests/Unit/Mail/PaymentMandateDisabledTest.php
+++ b/src/tests/Unit/Mail/PaymentMandateDisabledTest.php
@@ -5,13 +5,10 @@
use App\Mail\PaymentMandateDisabled;
use App\Wallet;
use App\User;
-use Tests\MailInterceptTrait;
use Tests\TestCase;
class PaymentMandateDisabledTest extends TestCase
{
- use MailInterceptTrait;
-
/**
* Test email content
*/
@@ -22,7 +19,7 @@
\config(['app.support_url' => 'https://kolab.org/support']);
- $mail = $this->fakeMail(new PaymentMandateDisabled($wallet, $user));
+ $mail = $this->renderMail(new PaymentMandateDisabled($wallet, $user));
$html = $mail['html'];
$plain = $mail['plain'];
@@ -33,7 +30,7 @@
$supportLink = sprintf('<a href="%s">%s</a>', $supportUrl, $supportUrl);
$appName = \config('app.name');
- $this->assertMailSubject("$appName Auto-payment Problem", $mail['message']);
+ $this->assertSame("$appName Auto-payment Problem", $mail['subject']);
$this->assertStringStartsWith('<!DOCTYPE html>', $html);
$this->assertTrue(strpos($html, $user->name(true)) > 0);
diff --git a/src/tests/Unit/Mail/PaymentSuccessTest.php b/src/tests/Unit/Mail/PaymentSuccessTest.php
--- a/src/tests/Unit/Mail/PaymentSuccessTest.php
+++ b/src/tests/Unit/Mail/PaymentSuccessTest.php
@@ -5,13 +5,10 @@
use App\Mail\PaymentSuccess;
use App\Payment;
use App\User;
-use Tests\MailInterceptTrait;
use Tests\TestCase;
class PaymentSuccessTest extends TestCase
{
- use MailInterceptTrait;
-
/**
* Test email content
*/
@@ -23,7 +20,7 @@
\config(['app.support_url' => 'https://kolab.org/support']);
- $mail = $this->fakeMail(new PaymentSuccess($payment, $user));
+ $mail = $this->renderMail(new PaymentSuccess($payment, $user));
$html = $mail['html'];
$plain = $mail['plain'];
@@ -34,7 +31,7 @@
$supportLink = sprintf('<a href="%s">%s</a>', $supportUrl, $supportUrl);
$appName = \config('app.name');
- $this->assertMailSubject("$appName Payment Succeeded", $mail['message']);
+ $this->assertSame("$appName Payment Succeeded", $mail['subject']);
$this->assertStringStartsWith('<!DOCTYPE html>', $html);
$this->assertTrue(strpos($html, $user->name(true)) > 0);
diff --git a/src/tests/Unit/Mail/SignupInvitationTest.php b/src/tests/Unit/Mail/SignupInvitationTest.php
--- a/src/tests/Unit/Mail/SignupInvitationTest.php
+++ b/src/tests/Unit/Mail/SignupInvitationTest.php
@@ -5,13 +5,10 @@
use App\Mail\SignupInvitation;
use App\SignupInvitation as SI;
use App\Utils;
-use Tests\MailInterceptTrait;
use Tests\TestCase;
class SignupInvitationTest extends TestCase
{
- use MailInterceptTrait;
-
/**
* Test email content
*/
@@ -22,7 +19,7 @@
'email' => 'test@email',
]);
- $mail = $this->fakeMail(new SignupInvitation($invitation));
+ $mail = $this->renderMail(new SignupInvitation($invitation));
$html = $mail['html'];
$plain = $mail['plain'];
@@ -31,7 +28,7 @@
$link = "<a href=\"$url\">$url</a>";
$appName = \config('app.name');
- $this->assertMailSubject("$appName Invitation", $mail['message']);
+ $this->assertSame("$appName Invitation", $mail['subject']);
$this->assertStringStartsWith('<!DOCTYPE html>', $html);
$this->assertTrue(strpos($html, $link) > 0);
diff --git a/src/tests/Unit/Mail/SignupVerificationTest.php b/src/tests/Unit/Mail/SignupVerificationTest.php
--- a/src/tests/Unit/Mail/SignupVerificationTest.php
+++ b/src/tests/Unit/Mail/SignupVerificationTest.php
@@ -5,13 +5,10 @@
use App\Mail\SignupVerification;
use App\SignupCode;
use App\Utils;
-use Tests\MailInterceptTrait;
use Tests\TestCase;
class SignupVerificationTest extends TestCase
{
- use MailInterceptTrait;
-
/**
* Test email content
*/
@@ -25,7 +22,7 @@
'last_name' => 'Last',
]);
- $mail = $this->fakeMail(new SignupVerification($code));
+ $mail = $this->renderMail(new SignupVerification($code));
$html = $mail['html'];
$plain = $mail['plain'];
@@ -34,7 +31,7 @@
$link = "<a href=\"$url\">$url</a>";
$appName = \config('app.name');
- $this->assertMailSubject("$appName Registration", $mail['message']);
+ $this->assertSame("$appName Registration", $mail['subject']);
$this->assertStringStartsWith('<!DOCTYPE html>', $html);
$this->assertTrue(strpos($html, $link) > 0);
diff --git a/src/tests/Unit/Mail/SuspendedDebtorTest.php b/src/tests/Unit/Mail/SuspendedDebtorTest.php
--- a/src/tests/Unit/Mail/SuspendedDebtorTest.php
+++ b/src/tests/Unit/Mail/SuspendedDebtorTest.php
@@ -4,13 +4,10 @@
use App\Mail\SuspendedDebtor;
use App\User;
-use Tests\MailInterceptTrait;
use Tests\TestCase;
class SuspendedDebtorTest extends TestCase
{
- use MailInterceptTrait;
-
/**
* Test email content
*/
@@ -24,7 +21,7 @@
'app.kb.account_delete' => 'https://kb.kolab.org/account-delete',
]);
- $mail = $this->fakeMail(new SuspendedDebtor($user));
+ $mail = $this->renderMail(new SuspendedDebtor($user));
$html = $mail['html'];
$plain = $mail['plain'];
@@ -39,7 +36,7 @@
$moreLink = sprintf('<a href="%s">here</a>', $moreUrl);
$appName = \config('app.name');
- $this->assertMailSubject("$appName Account Suspended", $mail['message']);
+ $this->assertSame("$appName Account Suspended", $mail['subject']);
$this->assertStringStartsWith('<!DOCTYPE html>', $html);
$this->assertTrue(strpos($html, $user->name(true)) > 0);
diff --git a/src/tests/data/kolab3.ldif b/src/tests/data/kolab3.ldif
--- a/src/tests/data/kolab3.ldif
+++ b/src/tests/data/kolab3.ldif
@@ -105,6 +105,8 @@
kolabFolderType: mail
kolabTargetFolder: shared/Folder1@kolab3.com
mail: folder1@kolab3.com
+alias: folder-alias1@kolab3.com
+alias: folder-alias2@kolab3.com
acl: anyone, read-write
acl: owner@kolab3.com, full

File Metadata

Mime Type
text/plain
Expires
Mon, Apr 6, 10:00 AM (12 h, 18 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18836558
Default Alt Text
D3397.1775469610.diff (287 KB)

Event Timeline