refactor: replace custom CRM with Monica fork
Some checks failed
Build & Push Monica Image to Gitea Registry / build-and-push (push) Failing after 9s
Some checks failed
Build & Push Monica Image to Gitea Registry / build-and-push (push) Failing after 9s
This commit is contained in:
69
app/Console/Commands/CalculateStatistics.php
Normal file
69
app/Console/Commands/CalculateStatistics.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Account\Account;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\Instance\Statistic;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class CalculateStatistics extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'monica:calculatestatistics';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Calculate general usage statistics';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$statistic = new Statistic;
|
||||
$statistic->number_of_users = DB::table('users')->count();
|
||||
$statistic->number_of_contacts = DB::table('contacts')->count();
|
||||
$statistic->number_of_notes = DB::table('notes')->count();
|
||||
$statistic->number_of_reminders = DB::table('reminders')->count();
|
||||
$statistic->number_of_tasks = DB::table('tasks')->count();
|
||||
$statistic->number_of_invitations_sent = DB::table('accounts')->sum('number_of_invitations_sent');
|
||||
|
||||
// number_of_accounts_with_more_than_one_user
|
||||
$number_of_accounts_with_more_than_one_user = 0;
|
||||
foreach (Account::all() as $account) {
|
||||
if ($account->users()->count() > 1) {
|
||||
$number_of_accounts_with_more_than_one_user = $number_of_accounts_with_more_than_one_user + 1;
|
||||
}
|
||||
}
|
||||
$statistic->number_of_accounts_with_more_than_one_user = $number_of_accounts_with_more_than_one_user;
|
||||
$statistic->number_of_import_jobs = DB::table('import_jobs')->count();
|
||||
$statistic->number_of_tags = DB::table('tags')->count();
|
||||
$statistic->number_of_activities = DB::table('activities')->count();
|
||||
$statistic->number_of_addresses = DB::table('addresses')->count();
|
||||
$statistic->number_of_api_calls = DB::table('api_usage')->count();
|
||||
$statistic->number_of_calls = DB::table('calls')->count();
|
||||
$statistic->number_of_contact_fields = DB::table('contact_fields')->count();
|
||||
$statistic->number_of_contact_field_types = DB::table('contact_field_types')->count();
|
||||
$statistic->number_of_debts = DB::table('debts')->count();
|
||||
$statistic->number_of_entries = DB::table('entries')->count();
|
||||
$statistic->number_of_gifts = DB::table('gifts')->count();
|
||||
$statistic->number_of_oauth_access_tokens = DB::table('oauth_access_tokens')->count();
|
||||
$statistic->number_of_oauth_clients = DB::table('oauth_clients')->count();
|
||||
$statistic->number_of_relationships = DB::table('relationships')->count();
|
||||
$statistic->number_of_subscriptions = DB::table('subscriptions')->count();
|
||||
$statistic->number_of_conversations = DB::table('conversations')->count();
|
||||
$statistic->number_of_messages = DB::table('messages')->count();
|
||||
|
||||
$statistic->save();
|
||||
}
|
||||
}
|
||||
53
app/Console/Commands/Clean.php
Normal file
53
app/Console/Commands/Clean.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\User\SyncToken;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Events\TokenDeleteEvent;
|
||||
use App\Services\Instance\TokenClean;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
|
||||
class Clean extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'monica:clean
|
||||
{--dry-run : Do everything except actually clean monica instance.}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Clean monica instance';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
Event::listen(TokenDeleteEvent::class, function ($event) {
|
||||
$this->handleTokenDelete($event->token);
|
||||
});
|
||||
|
||||
app(TokenClean::class)->execute([
|
||||
'dryrun' => (bool) $this->option('dry-run'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle TokenDeleteEvent event.
|
||||
*
|
||||
* @param SyncToken $token
|
||||
*/
|
||||
private function handleTokenDelete($token)
|
||||
{
|
||||
$this->info('Delete token '.$token->id.' - User '.$token->user_id.' - Type '.$token->name.' - timestamp '.$token->timestamp);
|
||||
}
|
||||
}
|
||||
70
app/Console/Commands/CreateAccount.php
Normal file
70
app/Console/Commands/CreateAccount.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Account\Account;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
|
||||
class CreateAccount extends Command
|
||||
{
|
||||
use ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'account:create
|
||||
{--email= : Login email for the account.}
|
||||
{--password= : Password to set for the account.}
|
||||
{--firstname= : First name for the account.}
|
||||
{--lastname= : Last name for the account.}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a new account';
|
||||
|
||||
/**
|
||||
* Missing argument errors. Exposed for testing.
|
||||
*/
|
||||
const ERROR_MISSING_EMAIL = '! You must specify an email';
|
||||
const ERROR_MISSING_PASSWORD = '! You must specify a password';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$email = $this->option('email');
|
||||
if (empty($email)) {
|
||||
$this->error($this::ERROR_MISSING_EMAIL);
|
||||
}
|
||||
|
||||
$password = $this->option('password');
|
||||
if (empty($password)) {
|
||||
$this->error($this::ERROR_MISSING_PASSWORD);
|
||||
}
|
||||
|
||||
$firstName = $this->option('firstname') ?? 'John';
|
||||
|
||||
$lastName = $this->option('lastname') ?? 'Doe';
|
||||
|
||||
if (empty($email) || empty($password)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->confirmToProceed('This will create a new user for '.$firstName.' '.$lastName.' with email '.$email)) {
|
||||
Account::createDefault($firstName, $lastName, $email, $password);
|
||||
|
||||
$this->info('| You can now sign in to your account:');
|
||||
$this->line('| username: '.$email);
|
||||
$this->line('| password: <hidden>');
|
||||
}
|
||||
}
|
||||
}
|
||||
55
app/Console/Commands/DavClientsUpdate.php
Normal file
55
app/Console/Commands/DavClientsUpdate.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Jobs\SynchronizeAddressBooks;
|
||||
use App\Models\Account\AddressBookSubscription;
|
||||
|
||||
class DavClientsUpdate extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'monica:davclients';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Update all dav subscriptions';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$subscriptions = AddressBookSubscription::active()->get();
|
||||
|
||||
$now = now();
|
||||
$subscriptions->filter(function ($subscription) use ($now) {
|
||||
return $this->isTimeToRunSync($subscription, $now);
|
||||
})->each(function ($subscription) {
|
||||
SynchronizeAddressBooks::dispatch($subscription);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the last synchronized timestamp is older than the subscription's frequency time.
|
||||
*
|
||||
* @param AddressBookSubscription $subscription
|
||||
* @param Carbon $now
|
||||
* @return bool
|
||||
*/
|
||||
private function isTimeToRunSync(AddressBookSubscription $subscription, Carbon $now): bool
|
||||
{
|
||||
return is_null($subscription->last_synchronized_at)
|
||||
|| $subscription->last_synchronized_at->addMinutes($subscription->frequency)->lessThan($now);
|
||||
}
|
||||
}
|
||||
75
app/Console/Commands/Deactivate2FA.php
Normal file
75
app/Console/Commands/Deactivate2FA.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\User\User;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class Deactivate2FA extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = '2fa:deactivate
|
||||
{--force : Force the operation to run when in production.}
|
||||
{--email= : The email of the user to deactivate 2FA.}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Deactivate 2FA for this user';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
// retrieve the email from the option
|
||||
$email = $this->option('email');
|
||||
|
||||
// if no email was passed to the option, prompt the user to enter the email
|
||||
if (! $email) {
|
||||
$email = $this->ask('what is the user\'s email?');
|
||||
}
|
||||
|
||||
// retrieve the user with the specified email
|
||||
$user = User::where('email', $email)->first();
|
||||
|
||||
if (! $user) {
|
||||
// show an error and exist if the user does not exist
|
||||
$this->error('No user with that email.');
|
||||
|
||||
return;
|
||||
}
|
||||
if (is_null($user->google2fa_secret)) {
|
||||
// show an error and exist if the user does not exist
|
||||
$this->error('2FA is currently not activated for this user.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Print a warning
|
||||
$this->info('2FA will be deactivated for '.$user->email);
|
||||
$this->info('This action can\'t be cancelled.');
|
||||
|
||||
// ask for confirmation if not forced
|
||||
if (! $this->option('force') && ! $this->confirm('Do you wish to continue?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// remove google2fa_secret key
|
||||
$user->google2fa_secret = null;
|
||||
|
||||
// save the user
|
||||
$user->save();
|
||||
|
||||
// show the new secret key
|
||||
$this->info('2FA has been deactivated for '.$user->email);
|
||||
}
|
||||
}
|
||||
34
app/Console/Commands/ExportAll.php
Normal file
34
app/Console/Commands/ExportAll.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Jobs\ExportAllAsSQL;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ExportAll extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'export:all';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Exports all data as SQL to storage/app';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$job = new ExportAllAsSQL();
|
||||
$this->info('Exported as '.$job->handle().'');
|
||||
}
|
||||
}
|
||||
32
app/Console/Commands/GetVersion.php
Normal file
32
app/Console/Commands/GetVersion.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class GetVersion extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'monica:getversion';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Get current version of monica';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$this->line(config('monica.app_version'));
|
||||
}
|
||||
}
|
||||
71
app/Console/Commands/Helpers/Command.php
Normal file
71
app/Console/Commands/Helpers/Command.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Helpers;
|
||||
|
||||
use Tests\Helpers\CommandCallerFake;
|
||||
|
||||
/**
|
||||
* @method static void exec(\Illuminate\Console\Command $command, string $message, string $commandline)
|
||||
* @method static void artisan(\Illuminate\Console\Command $command, string $message, string $commandline, array $arguments)
|
||||
*/
|
||||
class Command
|
||||
{
|
||||
/**
|
||||
* Switch to a fake executor for testing purpose.
|
||||
*
|
||||
* @return CommandCallerContract
|
||||
*/
|
||||
public static function fake(): CommandCallerContract
|
||||
{
|
||||
static::setBackend($fake = app(CommandCallerFake::class));
|
||||
|
||||
return $fake;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Command Executor.
|
||||
*
|
||||
* @var CommandCallerContract|null
|
||||
*/
|
||||
private static $commandCaller;
|
||||
|
||||
/**
|
||||
* Get the current backend command.
|
||||
*
|
||||
* @return CommandCallerContract
|
||||
*/
|
||||
private static function getBackend(): CommandCallerContract
|
||||
{
|
||||
if (! static::$commandCaller) {
|
||||
static::$commandCaller = app(CommandCaller::class); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
return static::$commandCaller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current backend command.
|
||||
*
|
||||
* @param CommandCallerContract $executor
|
||||
*/
|
||||
public static function setBackend(CommandCallerContract $executor): void
|
||||
{
|
||||
static::$commandCaller = $executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle dynamic, static calls to the object.
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $args
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public static function __callStatic($method, $args)
|
||||
{
|
||||
$instance = static::getBackend();
|
||||
|
||||
return $instance->$method(...$args);
|
||||
}
|
||||
}
|
||||
54
app/Console/Commands/Helpers/CommandCaller.php
Normal file
54
app/Console/Commands/Helpers/CommandCaller.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Helpers;
|
||||
|
||||
use function Safe\exec;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Console\Application;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class CommandCaller implements CommandCallerContract
|
||||
{
|
||||
/**
|
||||
* Print a message on the console, then execute a command.
|
||||
*
|
||||
* @param Command $command Laravel command context
|
||||
* @param string $message Message to output
|
||||
* @param string $commandline Command to execute
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function exec(Command $command, string $message, string $commandline): void
|
||||
{
|
||||
$command->info($message);
|
||||
$command->line($commandline, null, OutputInterface::VERBOSITY_VERBOSE);
|
||||
exec($commandline.' 2>&1', $output);
|
||||
foreach ($output as $line) {
|
||||
$command->line($line, null, OutputInterface::VERBOSITY_VERY_VERBOSE);
|
||||
}
|
||||
$command->line('', null, OutputInterface::VERBOSITY_VERBOSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a message on the console, then execute an artisan command.
|
||||
*
|
||||
* @param Command $command Laravel command context
|
||||
* @param string $message Message to output
|
||||
* @param string $commandline Artisan command name to execute
|
||||
* @param array $arguments Optional arguments to pass to the artisan command
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function artisan(Command $command, string $message, string $commandline, array $arguments = []): void
|
||||
{
|
||||
$info = '';
|
||||
foreach ($arguments as $key => $value) {
|
||||
if (is_string($key)) {
|
||||
$info .= ' '.$key.'="'.$value.'"';
|
||||
} else {
|
||||
$info .= ' '.$value;
|
||||
}
|
||||
}
|
||||
$this->exec($command, $message, Application::formatCommandString($commandline.$info));
|
||||
}
|
||||
}
|
||||
27
app/Console/Commands/Helpers/CommandCallerContract.php
Normal file
27
app/Console/Commands/Helpers/CommandCallerContract.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Helpers;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
interface CommandCallerContract
|
||||
{
|
||||
/**
|
||||
* Print a message on the console, then execute a command.
|
||||
*
|
||||
* @param Command $command Laravel command context
|
||||
* @param string $message Message to output
|
||||
* @param string $commandline Command to execute
|
||||
*/
|
||||
public function exec(Command $command, string $message, string $commandline): void;
|
||||
|
||||
/**
|
||||
* Print a message on the console, then execute an artisan command.
|
||||
*
|
||||
* @param Command $command Laravel command context
|
||||
* @param string $message Message to output
|
||||
* @param string $commandline Artisan command name to execute
|
||||
* @param array $arguments Optional arguments to pass to the artisan command
|
||||
*/
|
||||
public function artisan(Command $command, string $message, string $commandline, array $arguments = []): void;
|
||||
}
|
||||
134
app/Console/Commands/ImportAccounts.php
Normal file
134
app/Console/Commands/ImportAccounts.php
Normal file
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Account\Account;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ImportAccounts extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'account:import_ldap
|
||||
{--ldap_uri= : LDAP URI.}
|
||||
{--ldap_user= : LDAP Bind DN.}
|
||||
{--ldap_pass= : LDAP Bind Password.}
|
||||
{--ldap_base= : LDAP base DN for searching.}
|
||||
{--ldap_filter= : Filter to search for user accounts.}
|
||||
{--ldap_attr_mail= : LDAP attribute to map to email (default: mail).}
|
||||
{--ldap_attr_firstname= : LDAP attribute to map to firstname (default: gn).}
|
||||
{--ldap_attr_lastname= : LDAP attribute to map to lastname (default: sn).}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Import user accounts from LDAP';
|
||||
|
||||
/**
|
||||
* Missing argument errors. Exposed for testing.
|
||||
*/
|
||||
const ERROR_MISSING_LDAP_FILTER = '! You must specify an LDAP Filter';
|
||||
const ERROR_MISSING_LDAP_BASE = '! You must specify an LDAP Base';
|
||||
const ERROR_MISSING_LDAP_USER = '! You must specify an LDAP User';
|
||||
const ERROR_MISSING_LDAP_PASS = '! You must specify an LDAP Password';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$ldap_uri = $this->option('ldap_uri') ?? '127.0.0.1';
|
||||
$ldap_attr_mail = $this->option('ldap_attr_mail') ?? 'mail';
|
||||
$ldap_attr_firstname = $this->option('ldap_attr_firstname') ?? 'givenName';
|
||||
$ldap_attr_lastname = $this->option('ldap_attr_lastname') ?? 'sn';
|
||||
|
||||
$ldap_user = $this->option('ldap_user');
|
||||
if (empty($ldap_user)) {
|
||||
$this->error($this::ERROR_MISSING_LDAP_USER);
|
||||
}
|
||||
|
||||
$ldap_pass = $this->option('ldap_pass');
|
||||
if (empty($ldap_pass)) {
|
||||
$this->error($this::ERROR_MISSING_LDAP_PASS);
|
||||
}
|
||||
|
||||
$ldap_base = $this->option('ldap_base');
|
||||
if (empty($ldap_base)) {
|
||||
$this->error($this::ERROR_MISSING_LDAP_BASE);
|
||||
}
|
||||
|
||||
$ldap_filter = $this->option('ldap_filter');
|
||||
if (empty($ldap_filter)) {
|
||||
$this->error($this::ERROR_MISSING_LDAP_FILTER);
|
||||
}
|
||||
|
||||
if (empty($ldap_user) || empty($ldap_pass) || empty($ldap_base) || empty($ldap_filter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$ldap_conn = ldap_connect($ldap_uri);
|
||||
if (! $ldap_conn) {
|
||||
$this->error('Could not connect to LDAP URI');
|
||||
|
||||
return;
|
||||
}
|
||||
if (! ldap_set_option($ldap_conn, LDAP_OPT_PROTOCOL_VERSION, 3)) {
|
||||
$this->error('Could not set LDAP protocol v3');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$bind = ldap_bind($ldap_conn, $ldap_user, $ldap_pass);
|
||||
if (! $bind) {
|
||||
$this->error('Could not bind with given LDAP credentials');
|
||||
|
||||
return;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$ldap_res = [];
|
||||
try {
|
||||
$ldap_res = ldap_search($ldap_conn, $ldap_base, $ldap_filter, [$ldap_attr_mail, $ldap_attr_firstname, $ldap_attr_lastname]);
|
||||
} catch (\Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$ldap_data = ldap_get_entries($ldap_conn, $ldap_res);
|
||||
|
||||
for ($i = 0; $i < $ldap_data['count']; $i++) {
|
||||
if (! (isset($ldap_data[$i][$ldap_attr_mail]) && $ldap_data[$i][$ldap_attr_mail]['count'] > 0)) {
|
||||
continue;
|
||||
}
|
||||
$user_mail = $ldap_data[$i][$ldap_attr_mail][0];
|
||||
$user_firstname = 'John';
|
||||
$user_lastname = 'Doe';
|
||||
$user_password = bin2hex(random_bytes(64));
|
||||
if (isset($ldap_data[$i][$ldap_attr_firstname]) && $ldap_data[$i][$ldap_attr_firstname]['count'] > 0) {
|
||||
$user_firstname = $ldap_data[$i][$ldap_attr_firstname][0];
|
||||
}
|
||||
if (isset($ldap_data[$i][$ldap_attr_lastname]) && $ldap_data[$i][$ldap_attr_lastname]['count'] > 0) {
|
||||
$user_lastname = $ldap_data[$i][$ldap_attr_lastname][0];
|
||||
}
|
||||
$this->info('Importing user "'.$user_mail.'"');
|
||||
try {
|
||||
Account::createDefault($user_firstname, $user_lastname, $user_mail, $user_password);
|
||||
} catch (\Exception $import_error) {
|
||||
$this->warn('Could not import user "'.$user_mail.'": '.$import_error->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
249
app/Console/Commands/ImportCSV.php
Normal file
249
app/Console/Commands/ImportCSV.php
Normal file
@@ -0,0 +1,249 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use function Safe\fopen;
|
||||
use App\Models\User\User;
|
||||
use function Safe\fclose;
|
||||
use App\Helpers\DateHelper;
|
||||
use App\Models\Contact\Gender;
|
||||
use App\Models\Contact\Contact;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\Contact\ContactField;
|
||||
use App\Models\Contact\ContactFieldType;
|
||||
use App\Jobs\Avatars\GetAvatarsFromInternet;
|
||||
use App\Services\Contact\Address\CreateAddress;
|
||||
use App\Services\Contact\Reminder\CreateReminder;
|
||||
|
||||
class ImportCSV extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'import:csv {user : email or id of a user} {file : path to the CSV file} {--format=google}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Imports CSV in Google format to user account';
|
||||
|
||||
/**
|
||||
* The contact field email object.
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
public $contactFieldEmailId;
|
||||
|
||||
/**
|
||||
* The contact field phone object.
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
public $contactFieldPhoneId;
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$file = $this->argument('file');
|
||||
|
||||
if (is_numeric($this->argument('user'))) {
|
||||
$user = User::find($this->argument('user'));
|
||||
} else {
|
||||
$user = User::where('email', $this->argument('user'))->first();
|
||||
}
|
||||
|
||||
if (! $user) {
|
||||
$this->error('You need to provide a valid User ID or email address!');
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (! file_exists($file)) {
|
||||
$this->error('You need to provide a valid file path.');
|
||||
|
||||
return -2;
|
||||
}
|
||||
|
||||
if (is_string($file)) {
|
||||
$this->info("Importing CSV file {$file} to user {$user->id}");
|
||||
}
|
||||
|
||||
// create special gender for this import
|
||||
// we don't know which gender all the contacts are, so we need to create a special status for them, as we
|
||||
// can't guess whether they are men, women or else.
|
||||
$gender = Gender::where('name', config('dav.default_gender'))->first();
|
||||
if (! $gender) {
|
||||
$gender = new Gender;
|
||||
$gender->account_id = $user->account_id;
|
||||
$gender->name = config('dav.default_gender');
|
||||
$gender->save();
|
||||
}
|
||||
|
||||
$first = true;
|
||||
$imported = 0;
|
||||
$handle = fopen($file, 'r');
|
||||
try {
|
||||
while (($data = fgetcsv($handle)) !== false) { /** @phpstan-ignore-line */
|
||||
// don't import the columns
|
||||
if ($first) {
|
||||
$first = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// if first & last name do not exist skip row
|
||||
if (empty($data[1]) && empty($data[3])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->csvToContact($data, $user->account_id, $gender->id);
|
||||
|
||||
$imported++;
|
||||
}
|
||||
} finally {
|
||||
fclose($handle);
|
||||
}
|
||||
|
||||
$this->info("Imported {$imported} Contacts");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create contact.
|
||||
*/
|
||||
private function csvToContact($data, $account_id, $gender_id)
|
||||
{
|
||||
$contact = new Contact();
|
||||
$contact->account_id = $account_id;
|
||||
$contact->gender_id = $gender_id;
|
||||
|
||||
if (! empty($data[1])) {
|
||||
$contact->first_name = $data[1]; // Given Name
|
||||
}
|
||||
|
||||
if (! empty($data[2])) {
|
||||
$contact->middle_name = $data[2]; // Additional Name
|
||||
}
|
||||
|
||||
if (! empty($data[3])) {
|
||||
$contact->last_name = $data[3]; // Family Name
|
||||
}
|
||||
|
||||
$street = null;
|
||||
if (! empty($data[49])) {
|
||||
$street = $data[49]; // address 1 street
|
||||
}
|
||||
|
||||
$city = null;
|
||||
if (! empty($data[50])) {
|
||||
$city = $data[50]; // address 1 city
|
||||
}
|
||||
|
||||
$province = null;
|
||||
if (! empty($data[52])) {
|
||||
$province = $data[52]; // address 1 region (state)
|
||||
}
|
||||
|
||||
$postalCode = null;
|
||||
if (! empty($data[53])) {
|
||||
$postalCode = $data[53]; // address 1 postal code (zip) 53
|
||||
}
|
||||
|
||||
if (! empty($data[66])) {
|
||||
$contact->job = $data[66]; // organization 1 name 66
|
||||
}
|
||||
|
||||
$contact->setAvatarColor();
|
||||
$contact->save();
|
||||
|
||||
if (! empty($data[28])) {
|
||||
// Email 1 Value
|
||||
ContactField::firstOrCreate([
|
||||
'account_id' => $contact->account_id,
|
||||
'contact_id' => $contact->id,
|
||||
'data' => $data[28],
|
||||
'contact_field_type_id' => $this->contactFieldEmailId(),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($postalCode || $province || $street || $city) {
|
||||
$request = [
|
||||
'account_id' => $contact->account_id,
|
||||
'contact_id' => $contact->id,
|
||||
'street' => $street,
|
||||
'city' => $city,
|
||||
'province' => $province,
|
||||
'postal_code' => $postalCode,
|
||||
];
|
||||
|
||||
app(CreateAddress::class)->execute($request);
|
||||
}
|
||||
|
||||
if (! empty($data[42])) {
|
||||
// Phone 1 Value
|
||||
ContactField::firstOrCreate([
|
||||
'account_id' => $contact->account_id,
|
||||
'contact_id' => $contact->id,
|
||||
'data' => $data[42],
|
||||
'contact_field_type_id' => $this->contactFieldPhoneId(),
|
||||
]);
|
||||
}
|
||||
|
||||
if (! empty($data[14])) {
|
||||
$birthdate = DateHelper::parseDate($data[14]);
|
||||
|
||||
$specialDate = $contact->setSpecialDate('birthdate', $birthdate->year, $birthdate->month, $birthdate->day);
|
||||
|
||||
app(CreateReminder::class)->execute([
|
||||
'account_id' => $contact->account_id,
|
||||
'contact_id' => $contact->id,
|
||||
'initial_date' => DateHelper::getDate($specialDate),
|
||||
'frequency_type' => 'year',
|
||||
'frequency_number' => 1,
|
||||
'title' => trans(
|
||||
'people.people_add_birthday_reminder',
|
||||
['name' => $contact->first_name]
|
||||
),
|
||||
'delible' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
GetAvatarsFromInternet::dispatch($contact);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default contact field email id for the account.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function contactFieldEmailId()
|
||||
{
|
||||
if (! $this->contactFieldEmailId) {
|
||||
$contactFieldType = ContactFieldType::where('type', 'email')->first();
|
||||
$this->contactFieldEmailId = $contactFieldType->id;
|
||||
}
|
||||
|
||||
return $this->contactFieldEmailId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default contact field phone id for the account.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function contactFieldPhoneId()
|
||||
{
|
||||
if (! $this->contactFieldPhoneId) {
|
||||
$contactFieldType = ContactFieldType::where('type', 'phone')->first();
|
||||
$this->contactFieldPhoneId = $contactFieldType->id;
|
||||
}
|
||||
|
||||
return $this->contactFieldPhoneId;
|
||||
}
|
||||
}
|
||||
116
app/Console/Commands/ImportVCards.php
Normal file
116
app/Console/Commands/ImportVCards.php
Normal file
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\User\User;
|
||||
use Illuminate\Http\File;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Jobs\AddContactFromVCard;
|
||||
use App\Models\Account\ImportJob;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class ImportVCards extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'import:vcard
|
||||
{--user= : user to import the contacts}
|
||||
{--path= : path of the file to import}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Imports contacts from vCard files for a specific user';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @param Filesystem $filesystem
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(Filesystem $filesystem)
|
||||
{
|
||||
$email = $this->option('user');
|
||||
|
||||
// if no email was passed to the option, prompt the user to enter the email
|
||||
if (! $email) {
|
||||
$email = $this->ask('what is the user\'s email?');
|
||||
}
|
||||
|
||||
// retrieve the user with the specified email
|
||||
$user = User::where('email', $email)->first();
|
||||
|
||||
if (! $user) {
|
||||
// show an error and exist if the user does not exist
|
||||
$this->error('No user with that email.');
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
$path = $this->option('path');
|
||||
|
||||
// if no email was passed to the option, prompt the user to enter the email
|
||||
if (! $path) {
|
||||
$path = $this->ask('what file you want to import?');
|
||||
}
|
||||
|
||||
if (! $filesystem->exists($path) || ! $this->acceptedExtensions($filesystem, $path)) {
|
||||
$this->error('The provided vcard file was not found or is not valid!');
|
||||
|
||||
return -2;
|
||||
}
|
||||
|
||||
$importJob = $this->import($path, $user);
|
||||
|
||||
return $this->report($importJob) ? 0 : 1;
|
||||
}
|
||||
|
||||
private function acceptedExtensions(Filesystem $filesystem, string $path): bool
|
||||
{
|
||||
switch ($filesystem->extension($path)) {
|
||||
case 'vcf':
|
||||
case 'vcard':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private function import(string $path, User $user): ImportJob
|
||||
{
|
||||
$pathName = Storage::putFile('public', new File($path));
|
||||
|
||||
$importJob = $user->account->importjobs()->create([
|
||||
'user_id' => $user->id,
|
||||
'type' => 'vcard',
|
||||
'filename' => $pathName,
|
||||
]);
|
||||
|
||||
AddContactFromVCard::dispatchSync($importJob);
|
||||
|
||||
return $importJob;
|
||||
}
|
||||
|
||||
private function report(ImportJob $importJob)
|
||||
{
|
||||
$importJob->refresh();
|
||||
|
||||
if ($importJob->failed) {
|
||||
$this->warn('Error: '.$importJob->failed_reason);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->info('Contacts found: '.$importJob->contacts_found);
|
||||
$this->info('Contacts skipped: '.$importJob->contacts_skipped);
|
||||
$this->info('Contacts imported: '.$importJob->contacts_imported);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
33
app/Console/Commands/Inspire.php
Normal file
33
app/Console/Commands/Inspire.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Foundation\Inspiring;
|
||||
|
||||
class Inspire extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'inspire';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Display an inspiring quote';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$this->comment(PHP_EOL.Inspiring::quote().PHP_EOL);
|
||||
}
|
||||
}
|
||||
50
app/Console/Commands/LangGenerate.php
Normal file
50
app/Console/Commands/LangGenerate.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use DirectoryIterator;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class LangGenerate extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'lang:generate';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Generate i18n json assets';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$dirs = new DirectoryIterator(resource_path('lang').'/');
|
||||
|
||||
foreach ($dirs as $dir) {
|
||||
if (! $dir->isDir()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$lang = $dir->getFilename();
|
||||
if ($lang == '.' || $lang == '..') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->call('lang:js', [
|
||||
'--json' => true,
|
||||
'--source' => $dir->getPathname(),
|
||||
'target' => 'public/js/langs/'.$lang.'.json',
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
121
app/Console/Commands/MigrateDatabaseCollation.php
Normal file
121
app/Console/Commands/MigrateDatabaseCollation.php
Normal file
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Helpers\DBHelper;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
|
||||
class MigrateDatabaseCollation extends Command
|
||||
{
|
||||
use ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'migrate:collation
|
||||
{--force : Force the operation to run when in production.}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Update database for utf8mb4 collation';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if ($this->confirmToProceed()) {
|
||||
try {
|
||||
$connection = DBHelper::connection();
|
||||
|
||||
if ($connection->getDriverName() != 'mysql') {
|
||||
return;
|
||||
}
|
||||
|
||||
$databasename = $connection->getDatabaseName();
|
||||
|
||||
$schemata = $connection->table('information_schema.schemata')
|
||||
->select('DEFAULT_CHARACTER_SET_NAME')
|
||||
->where('schema_name', '=', $databasename)
|
||||
->get();
|
||||
|
||||
$schema = $schemata->first()->DEFAULT_CHARACTER_SET_NAME;
|
||||
|
||||
if (config('database.use_utf8mb4') && $schema == 'utf8') {
|
||||
$this->line('Migrate to utf8mb4 schema collation');
|
||||
$this->toUtf8mb4($connection, $databasename);
|
||||
} elseif (! config('database.use_utf8mb4') && $schema == 'utf8mb4') {
|
||||
$this->line('Migrate to utf8 schema collation');
|
||||
$this->toUtf8($connection, $databasename);
|
||||
} else {
|
||||
$this->info('Nothing to migrate, everything is ok.');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->error(' ');
|
||||
$this->error(' Check if the DB_USE_UTF8MB4 variable in .env file is correctly set ');
|
||||
$this->error(' ');
|
||||
$this->info('');
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch to utf8mb4.
|
||||
*
|
||||
* @param \Illuminate\Database\Connection $connection
|
||||
* @param string $databasename
|
||||
*/
|
||||
private function toUtf8mb4($connection, $databasename)
|
||||
{
|
||||
// Tables
|
||||
$tables = $connection->table('information_schema.tables')
|
||||
->select('table_name')
|
||||
->where('table_schema', '=', $databasename)
|
||||
->get();
|
||||
|
||||
foreach ($tables as $table) {
|
||||
DB::statement('ALTER TABLE `'.$table->table_name.'` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
|
||||
}
|
||||
|
||||
// Database
|
||||
$pdo = $connection->getPdo();
|
||||
$pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, true);
|
||||
DB::statement('ALTER DATABASE `'.$databasename.'` CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;');
|
||||
$pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch to utf8.
|
||||
*
|
||||
* @param \Illuminate\Database\Connection $connection
|
||||
* @param string $databasename
|
||||
*/
|
||||
private function toUtf8($connection, $databasename)
|
||||
{
|
||||
// Tables
|
||||
$tables = $connection->table('information_schema.tables')
|
||||
->select('table_name')
|
||||
->where('table_schema', '=', $databasename)
|
||||
->get();
|
||||
|
||||
foreach ($tables as $table) {
|
||||
DB::statement('ALTER TABLE `'.$table->table_name.'` CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
|
||||
}
|
||||
|
||||
// Database
|
||||
$pdo = $connection->getPdo();
|
||||
$pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, true);
|
||||
DB::statement('ALTER DATABASE `'.$databasename.'` CHARACTER SET = utf8 COLLATE = utf8_unicode_ci;');
|
||||
$pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);
|
||||
}
|
||||
}
|
||||
62
app/Console/Commands/NewAddressBookSubscription.php
Normal file
62
app/Console/Commands/NewAddressBookSubscription.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\User\User;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Jobs\SynchronizeAddressBooks;
|
||||
use App\Services\DavClient\CreateAddressBookSubscription;
|
||||
|
||||
class NewAddressBookSubscription extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'monica:newaddressbooksubscription
|
||||
{--email= : Monica account to add subscription to}
|
||||
{--url= : CardDAV url of the address book}
|
||||
{--login= : Login}
|
||||
{--password= : Password of the account}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Add a new all dav subscriptions';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$user = User::where('email', $this->option('email'))->firstOrFail();
|
||||
|
||||
$url = $this->option('url') ?? $this->ask('url', 'CardDAV url of the address book');
|
||||
$login = $this->option('login') ?? $this->ask('login', 'Login name');
|
||||
$password = $this->option('password') ?? $this->ask('password', 'User password');
|
||||
|
||||
try {
|
||||
$addressBookSubscription = app(CreateAddressBookSubscription::class)->execute([
|
||||
'account_id' => $user->account_id,
|
||||
'user_id' => $user->id,
|
||||
'base_uri' => $url,
|
||||
'username' => $login,
|
||||
'password' => $password,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
|
||||
if (! isset($addressBookSubscription)) {
|
||||
$this->error('Could not add subscription');
|
||||
} else {
|
||||
$this->info('Subscription added');
|
||||
SynchronizeAddressBooks::dispatch($addressBookSubscription, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
137
app/Console/Commands/OneTime/MoveAvatars.php
Normal file
137
app/Console/Commands/OneTime/MoveAvatars.php
Normal file
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\OneTime;
|
||||
|
||||
use App\Models\Contact\Contact;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
||||
|
||||
class MoveAvatars extends Command
|
||||
{
|
||||
use ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'monica:moveavatars
|
||||
{--force : Force the operation to run when in production.}
|
||||
{--dryrun : Simulate the execution but not write anything.}
|
||||
{--storage= : new storage to move avatars to}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Move avatars from their current storage to current default storage';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->confirmToProceed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Contact::where('has_avatar', true)
|
||||
->chunk(200, function ($contacts) {
|
||||
$this->handleContacts($contacts);
|
||||
});
|
||||
}
|
||||
|
||||
private function handleContacts($contacts)
|
||||
{
|
||||
foreach ($contacts as $contact) {
|
||||
if ($contact->avatar_location == $this->newStorage()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->handleOneContact($contact);
|
||||
} catch (FileNotFoundException $e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function handleOneContact($contact)
|
||||
{
|
||||
// move avatars to new location
|
||||
$this->moveContactAvatars($contact);
|
||||
|
||||
if (! $this->option('dryrun')) {
|
||||
$contact->deleteAvatars();
|
||||
$this->line(' Files deleted from old location.', null, OutputInterface::VERBOSITY_VERBOSE);
|
||||
|
||||
// Update location. The filename has not changed.
|
||||
$contact->avatar_location = $this->newStorage();
|
||||
$contact->save();
|
||||
}
|
||||
}
|
||||
|
||||
private function moveContactAvatars($contact)
|
||||
{
|
||||
$this->line('Contact id:'.$contact->id.' | Avatar location:'.$contact->avatar_location.' | File name:'.$contact->avatar_file_name);
|
||||
|
||||
$avatarFileNames = [];
|
||||
array_push($avatarFileNames, $this->getFileName($contact));
|
||||
array_push($avatarFileNames, $this->getFileName($contact, 110));
|
||||
array_push($avatarFileNames, $this->getFileName($contact, 174));
|
||||
|
||||
$storage = Storage::disk($contact->avatar_location);
|
||||
$newStorage = Storage::disk($this->newStorage());
|
||||
|
||||
foreach ($avatarFileNames as $avatarFileName) {
|
||||
if ($newStorage->exists($avatarFileName)) {
|
||||
$this->line(' File already pushed: '.$avatarFileName, null, OutputInterface::VERBOSITY_VERBOSE);
|
||||
continue;
|
||||
}
|
||||
if (! $this->option('dryrun')) {
|
||||
$avatarFile = $storage->get($avatarFileName);
|
||||
$newStorage->put($avatarFileName, $avatarFile, config('filesystems.default_visibility'));
|
||||
}
|
||||
|
||||
$this->line(' File pushed: '.$avatarFileName, null, OutputInterface::VERBOSITY_VERBOSE);
|
||||
}
|
||||
}
|
||||
|
||||
private function getFileName($contact, $size = null)
|
||||
{
|
||||
$filename = pathinfo($contact->avatar_file_name, PATHINFO_FILENAME);
|
||||
$extension = pathinfo($contact->avatar_file_name, PATHINFO_EXTENSION);
|
||||
|
||||
$avatarFileName = 'avatars/'.$filename.'.'.$extension;
|
||||
if (! is_null($size)) {
|
||||
$avatarFileName = 'avatars/'.$filename.'_'.$size.'.'.$extension;
|
||||
}
|
||||
|
||||
if ($this->fileExists($contact->avatar_location, $avatarFileName)) {
|
||||
return $avatarFileName;
|
||||
}
|
||||
}
|
||||
|
||||
private function fileExists($storage, $avatarFileName): bool
|
||||
{
|
||||
$storage = Storage::disk($storage);
|
||||
|
||||
if (! $storage->exists($avatarFileName)) {
|
||||
$this->line(' ! File not found: '.$avatarFileName, null, OutputInterface::VERBOSITY_VERBOSE);
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function newStorage()
|
||||
{
|
||||
return $this->option('storage') ?? config('filesystems.default');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\OneTime;
|
||||
|
||||
use App\Events\MoveAvatarEvent;
|
||||
use App\Models\Contact\Contact;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
use App\Exceptions\FileNotFoundException;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use App\Jobs\Avatars\MoveContactAvatarToPhotosDirectory;
|
||||
|
||||
/**
|
||||
* This command moves current avatars to the new Photos directory and converts
|
||||
* each avatar to a Photo object.
|
||||
*/
|
||||
class MoveAvatarsToPhotosDirectory extends Command
|
||||
{
|
||||
use ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'monica:moveavatarstophotosdirectory
|
||||
{--force : Force the operation to run when in production.}
|
||||
{--dryrun : Simulate the execution but not write anything.}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Move avatars to the Photos directory, and create a photo object for each one of them';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->confirmToProceed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Event::listen(MoveAvatarEvent::class, function ($event) {
|
||||
$this->handleEvent($event->contact);
|
||||
});
|
||||
|
||||
$delay = now();
|
||||
|
||||
Contact::where('has_avatar', true)
|
||||
->chunk(100, function ($contacts) use ($delay) {
|
||||
foreach ($contacts as $contact) {
|
||||
if ($contact->avatar_source === 'default') {
|
||||
$this->handleContact($contact, $delay);
|
||||
}
|
||||
}
|
||||
// add some delay, so we treat 100 contacts each minutes
|
||||
$delay = $delay->addMinutes(1);
|
||||
});
|
||||
}
|
||||
|
||||
private function handleContact($contact, $delay)
|
||||
{
|
||||
try {
|
||||
if ($this->option('dryrun')) {
|
||||
MoveContactAvatarToPhotosDirectory::dispatchNow($contact, true);
|
||||
} else {
|
||||
MoveContactAvatarToPhotosDirectory::dispatch($contact, false)
|
||||
->delay($delay);
|
||||
}
|
||||
} catch (FileNotFoundException $e) {
|
||||
$this->warn(' ! File not found: '.$e->fileName, OutputInterface::VERBOSITY_VERBOSE);
|
||||
}
|
||||
}
|
||||
|
||||
private function handleEvent($contact)
|
||||
{
|
||||
$this->info('Contact id:'.$contact->id.' | Avatar location:'.$contact->avatar_location.' | File name:'.$contact->avatar_file_name);
|
||||
}
|
||||
}
|
||||
73
app/Console/Commands/Passport.php
Normal file
73
app/Console/Commands/Passport.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
use Laravel\Passport\PersonalAccessClient;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class Passport extends Command
|
||||
{
|
||||
use ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'monica:passport {--force : Force the operation to run when in production.}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check if encryption keys and Personal Access Client are present, and create them if not.';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
if ($this->confirmToProceed()) {
|
||||
$this->checkEncryptionKeys();
|
||||
$this->checkPersonalAccessClient();
|
||||
}
|
||||
}
|
||||
|
||||
private function checkEncryptionKeys()
|
||||
{
|
||||
$this->info('Checking encryption keys...', OutputInterface::VERBOSITY_VERBOSE);
|
||||
|
||||
if (! empty(config('passport.private_key')) && ! empty(config('passport.public_key'))) {
|
||||
$this->info('✓ PASSPORT_PRIVATE_KEY and PASSPORT_PUBLIC_KEY detected.', OutputInterface::VERBOSITY_VERBOSE);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (file_exists(base_path('storage/oauth-private.key')) && file_exists(base_path('storage/oauth-public.key'))) {
|
||||
$this->info('✓ Files storage/oauth-private.key and storage/oauth-public.key detected.', OutputInterface::VERBOSITY_VERBOSE);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->artisan('✓ Creating encryption keys', 'passport:keys', ['--no-interaction']);
|
||||
$this->warn('! Please be careful to backup '.base_path('storage/oauth-public.key').' and '.base_path('storage/oauth-private.key').' files !', OutputInterface::VERBOSITY_VERBOSE);
|
||||
}
|
||||
|
||||
private function checkPersonalAccessClient()
|
||||
{
|
||||
$this->info('Checking Personal Access Client...', OutputInterface::VERBOSITY_VERBOSE);
|
||||
|
||||
if (PersonalAccessClient::count() > 0) {
|
||||
$this->info('✓ Personal Access Client already created.', OutputInterface::VERBOSITY_VERBOSE);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->artisan('✓ Creating personal access client', 'passport:client', ['--personal', '--no-interaction']);
|
||||
}
|
||||
}
|
||||
113
app/Console/Commands/PingVersionServer.php
Normal file
113
app/Console/Commands/PingVersionServer.php
Normal file
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use PharIo\Version\Version;
|
||||
use App\Models\Contact\Contact;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\Instance\Instance;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
use Illuminate\Http\Client\RequestException;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class PingVersionServer extends Command
|
||||
{
|
||||
use ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'monica:ping
|
||||
{--force : Force the operation to run when in production.}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Ping version.monicahq.com to know if a new version is available';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! config('monica.check_version')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->confirmToProceed('Checking version deactivated', function () {
|
||||
return $this->getLaravel()->environment() === 'production';
|
||||
})) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$instance = Instance::first();
|
||||
$instance->current_version = config('monica.app_version');
|
||||
|
||||
if ($instance->current_version == '') {
|
||||
Log::warning('Current instance version is not set, skipping version check.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Query version.monicahq.com
|
||||
try {
|
||||
$this->log('Call url: '.config('monica.weekly_ping_server_url'));
|
||||
$response = Http::acceptJson()
|
||||
->post(config('monica.weekly_ping_server_url'), [
|
||||
'uuid' => $instance->uuid,
|
||||
'version' => $instance->current_version,
|
||||
'contacts' => Contact::count(),
|
||||
])
|
||||
->throw();
|
||||
} catch (RequestException $e) {
|
||||
$this->error('Error calling "'.config('monica.weekly_ping_server_url').'": '.$e->getMessage());
|
||||
Log::error(__CLASS__.' Error calling "'.config('monica.weekly_ping_server_url').'": '.$e->getMessage(), [$e]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Receive the JSON
|
||||
$json = $response->json();
|
||||
|
||||
$this->log('instance version: '.$instance->current_version);
|
||||
$currentVersion = $this->getVersion($instance->current_version);
|
||||
|
||||
$this->log('current version: '.$json['latest_version']);
|
||||
$latestVersion = $this->getVersion($json['latest_version']);
|
||||
|
||||
if ($latestVersion > $currentVersion) {
|
||||
$instance->latest_version = $json['latest_version'];
|
||||
$instance->latest_release_notes = $json['notes'];
|
||||
$instance->number_of_versions_since_current_version = $json['number_of_versions_since_user_version'];
|
||||
} else {
|
||||
$instance->latest_release_notes = null;
|
||||
$instance->number_of_versions_since_current_version = null;
|
||||
}
|
||||
|
||||
$instance->save();
|
||||
}
|
||||
|
||||
public function log($string)
|
||||
{
|
||||
$this->info($string, OutputInterface::VERBOSITY_VERBOSE);
|
||||
}
|
||||
|
||||
private function getVersion(string $version): ?Version
|
||||
{
|
||||
try {
|
||||
return new Version($version);
|
||||
} catch (\Exception $e) {
|
||||
$this->error("Error parsing version '$version': ".$e->getMessage());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
56
app/Console/Commands/SendReminders.php
Normal file
56
app/Console/Commands/SendReminders.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\Contact\ReminderOutbox;
|
||||
use App\Jobs\Reminder\NotifyUserAboutReminder;
|
||||
|
||||
class SendReminders extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'send:reminders';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Send reminders that are scheduled for the contacts';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
// Grab all the reminders that are supposed to be sent in the next two days
|
||||
// Why 2? because in terms of timezone, we can have up to more than 24 hours
|
||||
// between two timezones and we need to take into accounts reminders
|
||||
// that are not in the same timezone.
|
||||
ReminderOutbox::where('planned_date', '<', now()->addDays(2))
|
||||
->orderBy('planned_date', 'asc')
|
||||
->chunk(500, function ($reminderOutboxes) {
|
||||
$this->send($reminderOutboxes);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the reminder to the user and schedule the future.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function send($reminderOutboxes)
|
||||
{
|
||||
foreach ($reminderOutboxes as $reminderOutbox) {
|
||||
if ($reminderOutbox->user->isTheRightTimeToBeReminded($reminderOutbox->planned_date)) {
|
||||
NotifyUserAboutReminder::dispatch($reminderOutbox);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
47
app/Console/Commands/SendStayInTouch.php
Normal file
47
app/Console/Commands/SendStayInTouch.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Contact\Contact;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Jobs\StayInTouch\ScheduleStayInTouch;
|
||||
|
||||
class SendStayInTouch extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'send:stay_in_touch';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Send notifications about staying in touch with contacts';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
// we add two days to make sure we cover all timezones
|
||||
Contact::where('stay_in_touch_trigger_date', '<', now()->addDays(2))
|
||||
->whereNotNull('stay_in_touch_frequency')
|
||||
->orderBy('stay_in_touch_trigger_date', 'asc')
|
||||
->chunk(500, function ($contacts) {
|
||||
$this->schedule($contacts);
|
||||
});
|
||||
}
|
||||
|
||||
private function schedule($contacts)
|
||||
{
|
||||
foreach ($contacts as $contact) {
|
||||
ScheduleStayInTouch::dispatch($contact);
|
||||
}
|
||||
}
|
||||
}
|
||||
64
app/Console/Commands/SendTestEmail.php
Normal file
64
app/Console/Commands/SendTestEmail.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class SendTestEmail extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'monica:test-email
|
||||
{--email= : The email address to send to}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Send a test email to check that delivery is working';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
/** retrieve the email from the option.
|
||||
* @var string $email
|
||||
*/
|
||||
$email = $this->option('email');
|
||||
|
||||
// if no email was passed to the option, prompt the user to enter the email
|
||||
if (! $email) {
|
||||
$email = (string) $this->ask('What email address should I send the test email to?');
|
||||
}
|
||||
|
||||
// Validate user provided email address
|
||||
if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
|
||||
$this->error("Invalid email address: \"$email\".");
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
$this->info("Preparing and sending email to \"$email\"");
|
||||
|
||||
// immediately deliver the test email (bypassing the queue)
|
||||
Mail::raw(
|
||||
"Hi $email, you requested a test email from Monica.",
|
||||
function ($message) use ($email) {
|
||||
$message->to($email)
|
||||
->subject('Monica email delivery test');
|
||||
}
|
||||
);
|
||||
|
||||
$this->info('Email sent!');
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
135
app/Console/Commands/SentryRelease.php
Normal file
135
app/Console/Commands/SentryRelease.php
Normal file
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use function Safe\exec;
|
||||
use function Safe\mkdir;
|
||||
use Illuminate\Console\Command;
|
||||
use function Safe\file_put_contents;
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class SentryRelease extends Command
|
||||
{
|
||||
use ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'sentry:release
|
||||
{--force : Force the operation to run when in production.}
|
||||
{--release= : release version for sentry.}
|
||||
{--store-release : store release version in config/.release file.}
|
||||
{--commit= : commit associated with this release.}
|
||||
{--environment= : sentry environment.}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a release for sentry';
|
||||
|
||||
/**
|
||||
* Installation path of sentry cli.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $install_dir;
|
||||
|
||||
/**
|
||||
* sentry cli name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private const SENTRY_CLI = 'sentry-cli';
|
||||
|
||||
/**
|
||||
* Sentry cli download url.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private const SENTRY_URL = 'https://sentry.io/get-cli/';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! config('monica.sentry_support') || ! $this->check()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->confirmToProceed()) {
|
||||
$this->install_dir = env('SENTRY_ROOT', getenv('HOME').'/.local/bin');
|
||||
|
||||
$release = $this->option('release') ?? config('sentry.release');
|
||||
$commit = $this->option('commit') ??
|
||||
(is_dir(__DIR__.'/../../../.git') ? trim(exec('git log --pretty="%H" -n1 HEAD')) : $release);
|
||||
|
||||
// Sentry update
|
||||
$this->exec('Update sentry', $this->getSentryCli().' update');
|
||||
|
||||
// Create a release
|
||||
$this->execSentryCli('Create a release', 'releases new '.$release.' --finalize --project '.config('sentry-release.project'));
|
||||
|
||||
// Associate commits with the release
|
||||
$this->execSentryCli('Associate commits with the release', 'releases set-commits '.$release.' --commit "'.config('sentry-release.repo').'@'.$commit.'"');
|
||||
|
||||
// Create a deploy
|
||||
$this->execSentryCli('Create a deploy', 'releases deploys '.$release.' new --env '.$this->option('environment').' --name '.config('monica.app_version'));
|
||||
|
||||
if ($this->option('store-release')) {
|
||||
// Set sentry release
|
||||
$this->line('Store release in config/.release file', null, OutputInterface::VERBOSITY_VERBOSE);
|
||||
file_put_contents(__DIR__.'/../../../config/.release', $release);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function check(): bool
|
||||
{
|
||||
$check = true;
|
||||
if (empty(config('sentry-release.auth_token'))) {
|
||||
$this->error('You must provide an auth_token (SENTRY_AUTH_TOKEN)');
|
||||
$check = false;
|
||||
}
|
||||
if (empty(config('sentry-release.organisation'))) {
|
||||
$this->error('You must provide an organisation slug (SENTRY_ORG)');
|
||||
$check = false;
|
||||
}
|
||||
if (empty(config('sentry-release.project'))) {
|
||||
$this->error('You must set the project (SENTRY_PROJECT)');
|
||||
$check = false;
|
||||
}
|
||||
if (empty(config('sentry-release.repo'))) {
|
||||
$this->error('You must set the repository (SENTRY_REPO)');
|
||||
$check = false;
|
||||
}
|
||||
if (empty($this->option('environment'))) {
|
||||
$this->error('No environment given');
|
||||
$check = false;
|
||||
}
|
||||
|
||||
return $check;
|
||||
}
|
||||
|
||||
private function getSentryCli()
|
||||
{
|
||||
if (! file_exists($this->install_dir.'/'.self::SENTRY_CLI)) {
|
||||
mkdir($this->install_dir, 0777, true);
|
||||
$this->exec('Downloading sentry-cli', 'curl -sL '.self::SENTRY_URL.' | INSTALL_DIR='.$this->install_dir.' bash');
|
||||
}
|
||||
|
||||
return $this->install_dir.'/'.self::SENTRY_CLI;
|
||||
}
|
||||
|
||||
private function execSentryCli($message, $command)
|
||||
{
|
||||
$this->exec($message, $this->getSentryCli().' '.$command);
|
||||
}
|
||||
}
|
||||
36
app/Console/Commands/SetPremiumAccount.php
Normal file
36
app/Console/Commands/SetPremiumAccount.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Account\Account;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class SetPremiumAccount extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'account:setpremium {accountId}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Give a premium access to an account.';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$account = Account::findOrFail($this->argument('accountId'));
|
||||
$account->update([
|
||||
'has_access_to_paid_version_for_free' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
74
app/Console/Commands/SetUserAdmin.php
Normal file
74
app/Console/Commands/SetUserAdmin.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\User\User;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class SetUserAdmin extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'monica:admin
|
||||
{--force : Force the operation to run when in production.}
|
||||
{--email= : The email of the user whose admin status you want to change}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Toggle administrator privileges for a user';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
// retrieve the email from the option
|
||||
$email = $this->option('email');
|
||||
|
||||
// if no email was passed to the option, prompt the user to enter the email
|
||||
if (! $email) {
|
||||
$email = $this->ask('What is the user’s email?');
|
||||
}
|
||||
|
||||
// retrieve the user with the specified email
|
||||
$user = User::where('email', $email)->first();
|
||||
|
||||
if (! $user) {
|
||||
// show an error and exist if the user does not exist
|
||||
$this->error('No user with that email.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Print a warning
|
||||
if ($user->admin) {
|
||||
$this->warn($user->email.' will be removed from the administrators of this instance');
|
||||
} else {
|
||||
$this->warn($user->email.' will be added to the administrators of this instance');
|
||||
}
|
||||
|
||||
// ask for confirmation if not forced
|
||||
if (! $this->option('force') && ! $this->confirm('Do you wish to continue?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// toglle admin status
|
||||
$user->admin = ! $user->admin;
|
||||
$user->save();
|
||||
|
||||
// Show new status
|
||||
if ($user->admin) {
|
||||
$this->info($user->email.' has been added to the administrators of this instance');
|
||||
} else {
|
||||
$this->info($user->email.' has been removed from the administrators of this instance');
|
||||
}
|
||||
}
|
||||
}
|
||||
82
app/Console/Commands/SetupProduction.php
Normal file
82
app/Console/Commands/SetupProduction.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use function Safe\touch;
|
||||
use App\Helpers\InstanceHelper;
|
||||
use App\Models\Account\Account;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class SetupProduction extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'setup:production
|
||||
{--force : Force the operation to run when in production.}
|
||||
{--email= : Login email for the first account.}
|
||||
{--password= : Password to set for the first account.}
|
||||
{--skipSeed : Skip the populate database process.}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Perform setup of Monica.';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if ((! $this->option('force')) && (! $this->confirm('You are about to setup and configure Monica. Do you wish to continue?'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the .env file does not exist, then key generation
|
||||
* will fail. So we create one if it does not already exist.
|
||||
*/
|
||||
if (! file_exists(__DIR__.'/../../../.env')) {
|
||||
touch(__DIR__.'/../../../.env');
|
||||
}
|
||||
|
||||
$this->call('monica:update', ['--force' => true]);
|
||||
|
||||
if (! $this->option('skipSeed')) {
|
||||
$this->line('✓ Filling database');
|
||||
$this->call('db:seed', ['--force' => true]);
|
||||
}
|
||||
|
||||
$this->line('');
|
||||
$this->line('-----------------------------');
|
||||
$this->line('|');
|
||||
$this->line('| Welcome to Monica v'.config('monica.app_version'));
|
||||
$this->line('|');
|
||||
$this->line('-----------------------------');
|
||||
|
||||
$email = $this->option('email');
|
||||
$password = $this->option('password');
|
||||
if (! empty($email) && ! empty($password)) {
|
||||
Account::createDefault('John', 'Doe', $email, $password);
|
||||
|
||||
$this->info('| You can now sign in to your account:');
|
||||
$this->line('| username: '.$email);
|
||||
$this->line('| password: <hidden>');
|
||||
} elseif (InstanceHelper::hasAtLeastOneAccount()) {
|
||||
$this->info('| You can now log in to your account');
|
||||
} else {
|
||||
$this->info('| You can now register to the first account by opening the application:');
|
||||
}
|
||||
|
||||
$this->line('| URL: '.config('app.url'));
|
||||
$this->line('-----------------------------');
|
||||
|
||||
$this->info('Setup is done. Have fun.');
|
||||
}
|
||||
}
|
||||
677
app/Console/Commands/SetupTest.php
Normal file
677
app/Console/Commands/SetupTest.php
Normal file
@@ -0,0 +1,677 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use function Safe\exec;
|
||||
use App\Models\User\User;
|
||||
use App\Helpers\DateHelper;
|
||||
use Illuminate\Support\Carbon;
|
||||
use App\Models\Account\Account;
|
||||
use App\Models\Contact\Contact;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Helpers\CountriesHelper;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Console\Application;
|
||||
use App\Models\Contact\LifeEventType;
|
||||
use App\Models\Contact\ContactFieldType;
|
||||
use App\Services\Contact\Gift\CreateGift;
|
||||
use App\Services\Contact\Tag\AssociateTag;
|
||||
use Illuminate\Foundation\Testing\WithFaker;
|
||||
use App\Services\Contact\Address\CreateAddress;
|
||||
use App\Services\Contact\Contact\CreateContact;
|
||||
use App\Services\Contact\Reminder\CreateReminder;
|
||||
use Symfony\Component\Console\Helper\ProgressBar;
|
||||
use App\Services\Contact\LifeEvent\CreateLifeEvent;
|
||||
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||
use App\Services\Contact\Conversation\CreateConversation;
|
||||
use App\Services\Contact\Relationship\CreateRelationship;
|
||||
use App\Services\Account\Activity\Activity\CreateActivity;
|
||||
use App\Services\Contact\Contact\UpdateBirthdayInformation;
|
||||
use App\Services\Contact\Contact\UpdateDeceasedInformation;
|
||||
use App\Services\Contact\Conversation\AddMessageToConversation;
|
||||
use App\Services\Account\Activity\Activity\AttachContactToActivity;
|
||||
|
||||
class SetupTest extends Command
|
||||
{
|
||||
use WithFaker;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'setup:test
|
||||
{--skipSeed : Skip the populate database with fake data.}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create the test environment with optional fake data for testing purposes.';
|
||||
|
||||
/**
|
||||
* The number of contacts that need to be generated.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $numberOfContacts;
|
||||
|
||||
/**
|
||||
* The Current contact instance.
|
||||
*
|
||||
* @var Contact
|
||||
*/
|
||||
private $contact;
|
||||
|
||||
/**
|
||||
* The current Contact instance.
|
||||
*
|
||||
* @var Account
|
||||
*/
|
||||
private $account;
|
||||
|
||||
/**
|
||||
* The list of countries.
|
||||
*
|
||||
* @var Collection
|
||||
*/
|
||||
private $countries = null;
|
||||
|
||||
/**
|
||||
* The logged user.
|
||||
*
|
||||
* @var User
|
||||
*/
|
||||
private $user;
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->confirm('Are you sure you want to proceed? This will delete ALL data in your environment.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->artisan('✓ Performing migrations', 'migrate:fresh');
|
||||
|
||||
$this->artisan('✓ Symlink the storage folder', 'storage:link');
|
||||
|
||||
if (! $this->option('skipSeed')) {
|
||||
$this->numberOfContacts = $this->ask('How many contacts would you like to have in this test account?');
|
||||
$this->info('✓ Filling database with fake data');
|
||||
$this->seed();
|
||||
}
|
||||
|
||||
$this->line('');
|
||||
$this->line('-----------------------------');
|
||||
$this->line('|');
|
||||
$this->line('| Welcome to Monica v'.config('monica.app_version'));
|
||||
$this->line('|');
|
||||
$this->line('-----------------------------');
|
||||
$this->info('| You can now sign in to your account:');
|
||||
$this->line('| username: admin@admin.com');
|
||||
$this->line('| password: admin0');
|
||||
$this->line('| URL: '.config('app.url'));
|
||||
$this->line('-----------------------------');
|
||||
|
||||
$this->info('Setup is done. Have fun.');
|
||||
}
|
||||
|
||||
public function exec($message, $command)
|
||||
{
|
||||
$this->info($message);
|
||||
$this->line($command);
|
||||
exec($command, $output);
|
||||
$this->line(implode('\n', $output));
|
||||
$this->line('');
|
||||
}
|
||||
|
||||
public function artisan($message, $command, array $arguments = [])
|
||||
{
|
||||
$this->info($message);
|
||||
$this->line(Application::formatCommandString($command));
|
||||
$this->callSilent($command, $arguments);
|
||||
$this->line('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function seed()
|
||||
{
|
||||
$this->setUpFaker();
|
||||
|
||||
// Get or create the first account
|
||||
if (User::where('email', 'admin@admin.com')->exists()) {
|
||||
$this->user = User::where('email', 'admin@admin.com')->first();
|
||||
$userId = $this->user->value('id');
|
||||
$this->account = Account::where('id', $userId)->first();
|
||||
} else {
|
||||
$this->account = Account::createDefault('John', 'Doe', 'admin@admin.com', 'admin0');
|
||||
|
||||
// set default admin account to confirmed
|
||||
/** @var User */
|
||||
$adminUser = $this->account->users()->first();
|
||||
$this->confirmUser($adminUser);
|
||||
$this->user = $adminUser;
|
||||
}
|
||||
|
||||
// create a random number of contacts
|
||||
//$this->numberOfContacts = rand(60, 100);
|
||||
echo 'Generating '.$this->numberOfContacts.' fake contacts'.PHP_EOL;
|
||||
|
||||
$output = new ConsoleOutput();
|
||||
$progress = new ProgressBar($output, $this->numberOfContacts);
|
||||
$progress->start();
|
||||
|
||||
for ($i = 0; $i < $this->numberOfContacts; $i++) {
|
||||
$gender = (rand(1, 2) == 1) ? 'male' : 'female';
|
||||
|
||||
$this->contact = app(CreateContact::class)->execute([
|
||||
'account_id' => $this->account->id,
|
||||
'author_id' => $this->user->id,
|
||||
'first_name' => $this->faker->firstName($gender),
|
||||
'last_name' => (rand(1, 2) == 1) ? $this->faker->lastName : null,
|
||||
'nickname' => (rand(1, 2) == 1) ? $this->faker->name : null,
|
||||
'gender_id' => $this->getRandomGender()->id,
|
||||
'is_partial' => false,
|
||||
'is_birthdate_known' => false,
|
||||
'is_deceased' => false,
|
||||
'is_deceased_date_known' => false,
|
||||
]);
|
||||
|
||||
$this->populateTags();
|
||||
$this->populateFoodPreferences();
|
||||
$this->populateDeceasedDate();
|
||||
$this->populateBirthday();
|
||||
$this->populateFirstMetInformation();
|
||||
$this->populateRelationships();
|
||||
$this->populateNotes();
|
||||
$this->populateActivities();
|
||||
$this->populateTasks();
|
||||
$this->populateDebts();
|
||||
$this->populateCalls();
|
||||
$this->populateConversations();
|
||||
$this->populateLifeEvents();
|
||||
$this->populateGifts();
|
||||
$this->populateAddresses();
|
||||
$this->populateContactFields();
|
||||
$this->populatePets();
|
||||
$this->changeUpdatedAt();
|
||||
|
||||
$progress->advance();
|
||||
}
|
||||
|
||||
$this->populateDayRatings();
|
||||
$this->populateEntries();
|
||||
|
||||
$progress->finish();
|
||||
|
||||
// create the second test, blank account
|
||||
if (! User::where('email', 'blank@blank.com')->exists()) {
|
||||
$blankAccount = Account::createDefault('Blank', 'State', 'blank@blank.com', 'blank0');
|
||||
$blankUser = $blankAccount->users()->first();
|
||||
$this->confirmUser($blankUser);
|
||||
}
|
||||
}
|
||||
|
||||
public function populateTags()
|
||||
{
|
||||
if (rand(1, 2) == 1) {
|
||||
$i = 0;
|
||||
do {
|
||||
app(AssociateTag::class)->execute([
|
||||
'account_id' => $this->account->id,
|
||||
'contact_id' => $this->contact->id,
|
||||
'name' => $this->faker->word,
|
||||
]);
|
||||
$i++;
|
||||
} while ($i < 10);
|
||||
}
|
||||
}
|
||||
|
||||
public function populateFoodPreferences()
|
||||
{
|
||||
// add food preferences
|
||||
if (rand(1, 2) == 1) {
|
||||
$this->contact->food_preferences = $this->faker->realText();
|
||||
$this->contact->save();
|
||||
}
|
||||
}
|
||||
|
||||
public function populateDeceasedDate()
|
||||
{
|
||||
// deceased?
|
||||
if (rand(1, 7) == 1) {
|
||||
$birthdate = $this->faker->dateTimeThisCentury();
|
||||
|
||||
app(UpdateDeceasedInformation::class)->execute([
|
||||
'account_id' => $this->account->id,
|
||||
'contact_id' => $this->contact->id,
|
||||
'is_deceased' => rand(1, 2) == 1,
|
||||
'is_date_known' => rand(1, 2) == 1,
|
||||
'day' => (int) $birthdate->format('d'),
|
||||
'month' => (int) $birthdate->format('m'),
|
||||
'year' => (int) $birthdate->format('Y'),
|
||||
'add_reminder' => rand(1, 2) == 1,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function populateBirthday()
|
||||
{
|
||||
if (rand(1, 2) == 1) {
|
||||
$birthdate = $this->faker->dateTimeThisCentury();
|
||||
|
||||
app(UpdateBirthdayInformation::class)->execute([
|
||||
'account_id' => $this->account->id,
|
||||
'contact_id' => $this->contact->id,
|
||||
'is_date_known' => rand(1, 2) == 1,
|
||||
'day' => (int) $birthdate->format('d'),
|
||||
'month' => (int) $birthdate->format('m'),
|
||||
'year' => (int) $birthdate->format('Y'),
|
||||
'is_age_based' => rand(1, 2) == 1,
|
||||
'age' => rand(1, 99),
|
||||
'add_reminder' => rand(1, 2) == 1,
|
||||
'is_deceased' => $this->contact->is_dead,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function populateFirstMetInformation()
|
||||
{
|
||||
if (rand(1, 2) == 1) {
|
||||
$this->contact->first_met_where = $this->faker->realText(20);
|
||||
}
|
||||
|
||||
if (rand(1, 2) == 1) {
|
||||
$this->contact->first_met_additional_info = $this->faker->realText(20);
|
||||
$firstMetDate = $this->faker->dateTimeThisCentury();
|
||||
|
||||
if (rand(1, 2) == 1) {
|
||||
// add a date where we don't know the year
|
||||
$specialDate = $this->contact->setSpecialDate('first_met', 0, intval($firstMetDate->format('m')), intval($firstMetDate->format('d')));
|
||||
} else {
|
||||
// add a date where we know the year
|
||||
$specialDate = $this->contact->setSpecialDate('first_met', intval($firstMetDate->format('Y')), intval($firstMetDate->format('m')), intval($firstMetDate->format('d')));
|
||||
}
|
||||
app(CreateReminder::class)->execute([
|
||||
'account_id' => $this->account->id,
|
||||
'contact_id' => $this->contact->id,
|
||||
'initial_date' => $specialDate->date->toDateString(),
|
||||
'frequency_type' => 'year',
|
||||
'frequency_number' => 1,
|
||||
'title' => trans(
|
||||
'people.introductions_reminder_title',
|
||||
['name' => $this->contact->first_name]
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
if (rand(1, 2) == 1) {
|
||||
do {
|
||||
$rand = rand(1, $this->numberOfContacts);
|
||||
} while (in_array($rand, [$this->contact->id]));
|
||||
|
||||
$this->contact->first_met_through_contact_id = $rand;
|
||||
}
|
||||
|
||||
$this->contact->save();
|
||||
}
|
||||
|
||||
public function populateRelationships()
|
||||
{
|
||||
if (rand(1, 2) == 1) {
|
||||
foreach (range(1, rand(2, 6)) as $index) {
|
||||
$gender = (rand(1, 2) == 1) ? 'male' : 'female';
|
||||
|
||||
$relatedContact = app(CreateContact::class)->execute([
|
||||
'account_id' => $this->account->id,
|
||||
'author_id' => $this->user->id,
|
||||
'first_name' => $this->faker->firstName($gender),
|
||||
'last_name' => (rand(1, 2) == 1) ? $this->faker->lastName : null,
|
||||
'nickname' => (rand(1, 2) == 1) ? $this->faker->name : null,
|
||||
'gender_id' => $this->getRandomGender()->id,
|
||||
'is_partial' => rand(1, 2) == 1,
|
||||
'is_birthdate_known' => false,
|
||||
'is_deceased' => false,
|
||||
'is_deceased_date_known' => false,
|
||||
]);
|
||||
|
||||
$relatedContactBirthDate = $this->faker->dateTimeThisCentury();
|
||||
app(UpdateBirthdayInformation::class)->execute([
|
||||
'account_id' => $this->account->id,
|
||||
'contact_id' => $relatedContact->id,
|
||||
'is_date_known' => rand(1, 2) == 1,
|
||||
'day' => (int) $relatedContactBirthDate->format('d'),
|
||||
'month' => (int) $relatedContactBirthDate->format('m'),
|
||||
'year' => (int) $relatedContactBirthDate->format('Y'),
|
||||
'is_age_based' => rand(1, 2) == 1,
|
||||
'age' => rand(1, 99),
|
||||
'add_reminder' => rand(1, 2) == 1,
|
||||
'is_deceased' => $relatedContact->is_dead,
|
||||
]);
|
||||
|
||||
// set relationship
|
||||
$relationshipId = $this->contact->account->relationshipTypes->random()->id;
|
||||
$relationship = app(CreateRelationship::class)->execute([
|
||||
'account_id' => $this->account->id,
|
||||
'contact_is' => $this->contact->id,
|
||||
'of_contact' => $relatedContact->id,
|
||||
'relationship_type_id' => $relationshipId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function populateNotes()
|
||||
{
|
||||
if (rand(1, 2) == 1) {
|
||||
for ($j = 0; $j < rand(1, 13); $j++) {
|
||||
$note = $this->contact->notes()->create([
|
||||
'body' => $this->faker->realText(rand(40, 500)),
|
||||
'account_id' => $this->account->id,
|
||||
'is_favorited' => rand(1, 3) == 1,
|
||||
'favorited_at' => $this->faker->dateTimeThisCentury(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function populateActivities()
|
||||
{
|
||||
if (rand(1, 2) == 1) {
|
||||
for ($j = 0; $j < rand(1, 13); $j++) {
|
||||
$date = DateHelper::getDate(Carbon::instance($this->faker->dateTimeThisYear($max = 'now')));
|
||||
|
||||
$request = [
|
||||
'account_id' => $this->account->id,
|
||||
'activity_type_id' => rand(1, 13),
|
||||
'summary' => $this->faker->realText(rand(40, 100)),
|
||||
'description' => (rand(1, 2) == 1 ? $this->faker->realText(rand(100, 1000)) : null),
|
||||
'happened_at' => $date,
|
||||
'contacts' => [$this->contact->id],
|
||||
];
|
||||
|
||||
$activity = app(CreateActivity::class)->execute($request);
|
||||
|
||||
$request = [
|
||||
'account_id' => $this->account->id,
|
||||
'activity_id' => $activity->id,
|
||||
'contacts' => [$this->contact->id],
|
||||
];
|
||||
|
||||
app(AttachContactToActivity::class)->execute($request);
|
||||
|
||||
DB::table('journal_entries')->insertGetId([
|
||||
'account_id' => $this->account->id,
|
||||
'date' => $date,
|
||||
'journalable_id' => $activity->id,
|
||||
'journalable_type' => 'App\Models\Account\Activity',
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function populateTasks()
|
||||
{
|
||||
if (rand(1, 2) == 1) {
|
||||
for ($j = 0; $j < rand(1, 10); $j++) {
|
||||
$task = $this->contact->tasks()->create([
|
||||
'title' => $this->faker->realText(rand(40, 100)),
|
||||
'description' => $this->faker->realText(rand(100, 1000)),
|
||||
'completed' => (rand(1, 2) == 1 ? 0 : 1),
|
||||
'completed_at' => (rand(1, 2) == 1 ? $this->faker->dateTimeThisCentury() : null),
|
||||
'account_id' => $this->account->id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function populateDebts()
|
||||
{
|
||||
if (rand(1, 2) == 1) {
|
||||
for ($j = 0; $j < rand(1, 6); $j++) {
|
||||
$this->contact->debts()->create([
|
||||
'in_debt' => (rand(1, 2) == 1 ? 'yes' : 'no'),
|
||||
'amount' => rand(321, 39391),
|
||||
'reason' => $this->faker->realText(rand(100, 1000)),
|
||||
'status' => 'inprogress',
|
||||
'account_id' => $this->account->id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function populateGifts()
|
||||
{
|
||||
if (rand(1, 2) == 1) {
|
||||
for ($j = 0; $j < rand(1, 31); $j++) {
|
||||
app(CreateGift::class)->execute([
|
||||
'account_id' => $this->account->id,
|
||||
'contact_id' => $this->contact->id,
|
||||
'status' => (rand(1, 3) == 1 ? 'offered' : 'idea'),
|
||||
'name' => $this->faker->realText(rand(10, 100)),
|
||||
'comment' => $this->faker->realText(rand(1000, 5000)),
|
||||
'url' => $this->faker->url,
|
||||
'amount' => rand(12, 120),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function populateAddresses()
|
||||
{
|
||||
if (rand(1, 3) == 1) {
|
||||
$request = [
|
||||
'account_id' => $this->account->id,
|
||||
'contact_id' => $this->contact->id,
|
||||
'country' => $this->getRandomCountry(),
|
||||
'name' => $this->faker->word,
|
||||
'street' => (rand(1, 3) == 1) ? $this->faker->streetAddress : null,
|
||||
'city' => (rand(1, 3) == 1) ? $this->faker->city : null,
|
||||
'province' => (rand(1, 3) == 1) ? $this->faker->state : null,
|
||||
'postal_code' => (rand(1, 3) == 1) ? $this->faker->postcode : null,
|
||||
];
|
||||
|
||||
app(CreateAddress::class)->execute($request);
|
||||
}
|
||||
}
|
||||
|
||||
private function getRandomCountry()
|
||||
{
|
||||
if ($this->countries == null) {
|
||||
$this->countries = CountriesHelper::getAll();
|
||||
}
|
||||
|
||||
return $this->countries->random()['id'];
|
||||
}
|
||||
|
||||
public function populateContactFields()
|
||||
{
|
||||
if (rand(1, 3) == 1) {
|
||||
|
||||
// Fetch number of types
|
||||
$numberOfTypes = ContactFieldType::where('account_id', $this->account->id)->count();
|
||||
|
||||
for ($j = 0; $j < rand(1, $numberOfTypes); $j++) {
|
||||
// Retrieve random ContactFieldType
|
||||
$contactFieldType = ContactFieldType::where('account_id', $this->account->id)->orderBy(DB::raw('RAND()'))->firstOrFail();
|
||||
|
||||
// Fake data according to type
|
||||
$data = null;
|
||||
switch ($contactFieldType->name) {
|
||||
case 'Email':
|
||||
$data = $this->faker->email;
|
||||
break;
|
||||
case 'Phone':
|
||||
$data = $this->faker->phoneNumber;
|
||||
break;
|
||||
case 'Facebook':
|
||||
$data = 'https://facebook.com/'.$this->faker->userName;
|
||||
break;
|
||||
case 'Twitter':
|
||||
$data = 'https://twitter.com/'.$this->faker->userName;
|
||||
break;
|
||||
case 'Whatsapp':
|
||||
$data = $this->faker->phoneNumber;
|
||||
break;
|
||||
case 'Telegram':
|
||||
$data = $this->faker->phoneNumber;
|
||||
break;
|
||||
default:
|
||||
$data = $this->faker->url;
|
||||
break;
|
||||
}
|
||||
|
||||
$this->contact->contactFields()->create([
|
||||
'contact_field_type_id' => $contactFieldType->id,
|
||||
'data' => $data,
|
||||
'account_id' => $this->account->id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function populateEntries()
|
||||
{
|
||||
for ($j = 0; $j < rand(10, 100); $j++) {
|
||||
$date = $this->faker->dateTimeThisYear();
|
||||
|
||||
$entryId = DB::table('entries')->insertGetId([
|
||||
'account_id' => $this->account->id,
|
||||
'title' => $this->faker->realText(rand(12, 20)),
|
||||
'post' => $this->faker->realText(rand(400, 500)),
|
||||
'created_at' => $date,
|
||||
]);
|
||||
|
||||
DB::table('journal_entries')->insertGetId([
|
||||
'account_id' => $this->account->id,
|
||||
'date' => $date,
|
||||
'journalable_id' => $entryId,
|
||||
'journalable_type' => 'App\Models\Journal\Entry',
|
||||
'created_at' => now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function populatePets()
|
||||
{
|
||||
if (rand(1, 3) == 1) {
|
||||
for ($j = 0; $j < rand(1, 3); $j++) {
|
||||
$date = $this->faker->dateTimeThisYear();
|
||||
|
||||
DB::table('pets')->insertGetId([
|
||||
'account_id' => $this->account->id,
|
||||
'contact_id' => $this->contact->id,
|
||||
'pet_category_id' => rand(1, 11),
|
||||
'name' => (rand(1, 3) == 1) ? $this->faker->firstName : null,
|
||||
'created_at' => $date,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function populateDayRatings()
|
||||
{
|
||||
for ($j = 0; $j < rand(10, 100); $j++) {
|
||||
$date = $this->faker->dateTimeThisYear();
|
||||
|
||||
$dayId = DB::table('days')->insertGetId([
|
||||
'account_id' => $this->account->id,
|
||||
'rate' => rand(1, 3),
|
||||
'date' => $date,
|
||||
'created_at' => $date,
|
||||
]);
|
||||
|
||||
DB::table('journal_entries')->insertGetId([
|
||||
'account_id' => $this->account->id,
|
||||
'date' => $date,
|
||||
'journalable_id' => $dayId,
|
||||
'journalable_type' => 'App\Models\Journal\Day',
|
||||
'created_at' => now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function changeUpdatedAt()
|
||||
{
|
||||
$this->contact->last_consulted_at = Carbon::instance($this->faker->dateTimeThisYear());
|
||||
$this->contact->save();
|
||||
}
|
||||
|
||||
public function populateCalls()
|
||||
{
|
||||
if (rand(1, 3) == 1) {
|
||||
$this->contact->calls()->create([
|
||||
'account_id' => $this->account->id,
|
||||
'called_at' => $this->faker->dateTimeThisYear(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function populateConversations()
|
||||
{
|
||||
if (rand(1, 3) == 1) {
|
||||
for ($j = 0; $j < rand(1, 20); $j++) {
|
||||
$contactFieldType = ContactFieldType::where('account_id', $this->account->id)->orderBy(DB::raw('RAND()'))->firstOrFail();
|
||||
|
||||
$conversation = app(CreateConversation::class)->execute([
|
||||
'happened_at' => $this->faker->dateTimeThisCentury(),
|
||||
'contact_id' => $this->contact->id,
|
||||
'contact_field_type_id' => $contactFieldType->id,
|
||||
'account_id' => $this->account->id,
|
||||
]);
|
||||
|
||||
for ($k = 0; $k < rand(1, 20); $k++) {
|
||||
app(AddMessageToConversation::class)->execute([
|
||||
'account_id' => $this->account->id,
|
||||
'contact_id' => $this->contact->id,
|
||||
'conversation_id' => $conversation->id,
|
||||
'written_at' => $this->faker->dateTimeThisCentury(),
|
||||
'written_by_me' => (rand(1, 2) == 1),
|
||||
'content' => $this->faker->realText(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function populateLifeEvents()
|
||||
{
|
||||
if (rand(1, 3) == 1) {
|
||||
for ($j = 0; $j < rand(1, 20); $j++) {
|
||||
$lifeEventType = LifeEventType::where('account_id', $this->account->id)->orderBy(DB::raw('RAND()'))->firstOrFail();
|
||||
|
||||
app(CreateLifeEvent::class)->execute([
|
||||
'account_id' => $this->account->id,
|
||||
'contact_id' => $this->contact->id,
|
||||
'life_event_type_id' => $lifeEventType->id,
|
||||
'happened_at' => $this->faker->dateTimeThisCentury(),
|
||||
'name' => $this->faker->realText(),
|
||||
'note' => $this->faker->realText(),
|
||||
'has_reminder' => false,
|
||||
'happened_at_month_unknown' => false,
|
||||
'happened_at_day_unknown' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getRandomGender()
|
||||
{
|
||||
return $this->account->genders->random();
|
||||
}
|
||||
|
||||
public function confirmUser($user)
|
||||
{
|
||||
$user->markEmailAsVerified();
|
||||
}
|
||||
}
|
||||
45
app/Console/Commands/Tests/SetupFrontEndTestUser.php
Normal file
45
app/Console/Commands/Tests/SetupFrontEndTestUser.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Tests;
|
||||
|
||||
use App\Models\User\User;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Services\User\AcceptPolicy;
|
||||
|
||||
class SetupFrontEndTestUser extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'setup:frontendtestuser {--database= : The database connection to use}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a user exclusively for front-end testing with Cypress.';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
app('migrator')->setConnection($this->option('database'));
|
||||
|
||||
$user = factory(User::class)->create();
|
||||
$user->account->populateDefaultFields();
|
||||
|
||||
app(AcceptPolicy::class)->execute([
|
||||
'account_id' => $user->account->id,
|
||||
'user_id' => $user->id,
|
||||
'ip_address' => null,
|
||||
]);
|
||||
|
||||
$this->info($user->getKey());
|
||||
}
|
||||
}
|
||||
64
app/Console/Commands/Tests/UpdateLangs.php
Normal file
64
app/Console/Commands/Tests/UpdateLangs.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Tests;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use function Safe\json_decode;
|
||||
use function Safe\json_encode;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class UpdateLangs extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'setup:updatelang';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Update langage files.';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
if (! File::exists(base_path('vendor/laravel-lang/lang'))) {
|
||||
throw new \Exception("This command requires laravel-lang/lang package. Run 'composer require --dev laravel-lang/lang' to install it.");
|
||||
}
|
||||
|
||||
$en = json_decode(File::get(lang_path('en.json')), true);
|
||||
|
||||
foreach (File::directories(lang_path()) as $lang) {
|
||||
$lang = basename($lang);
|
||||
$locale = Str::of($lang)->replace('-', '_');
|
||||
switch ($lang) {
|
||||
case 'zh':
|
||||
$locale = 'zh_CN';
|
||||
break;
|
||||
case 'en':
|
||||
continue 2;
|
||||
}
|
||||
if (File::exists($orig_path = lang_path("$lang.json"))
|
||||
&& File::exists($trans_path = base_path("vendor/laravel-lang/lang/locales/$locale/$locale.json"))) {
|
||||
$lang_orig = json_decode(File::get($orig_path), true);
|
||||
$lang_trans = json_decode(File::get($trans_path), true);
|
||||
foreach ($en as $key) {
|
||||
$lang_orig[$key] = $lang_trans[$key];
|
||||
}
|
||||
File::put($orig_path, json_encode($lang_orig, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)."\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
116
app/Console/Commands/Update.php
Normal file
116
app/Console/Commands/Update.php
Normal file
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Helpers\DBHelper;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
|
||||
class Update extends Command
|
||||
{
|
||||
use ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'monica:update
|
||||
{--force : Force the operation to run when in production.}
|
||||
{--composer-install : Updating composer dependencies.}
|
||||
{--skip-storage-link : Skip storage link create.}
|
||||
{--dev : Install dev dependencies too.}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Update monica dependencies and migrations after a new release';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
if ($this->confirmToProceed()) {
|
||||
try {
|
||||
$this->artisan('✓ Maintenance mode: on', 'down', [
|
||||
'--retry' => '10',
|
||||
]);
|
||||
|
||||
// Clear or rebuild all cache
|
||||
if (config('cache.default') != 'database' || Schema::hasTable(config('cache.stores.database.table'))) {
|
||||
$this->artisan('✓ Resetting application cache', 'cache:clear');
|
||||
}
|
||||
|
||||
if ($this->getLaravel()->environment() == 'production') {
|
||||
$this->artisan('✓ Clear config cache', 'config:clear');
|
||||
$this->artisan('✓ Resetting route cache', 'route:cache');
|
||||
if ($this->getLaravel()->version() > '5.6') {
|
||||
$this->artisan('✓ Resetting view cache', 'view:cache');
|
||||
} else {
|
||||
$this->artisan('✓ Resetting view cache', 'view:clear');
|
||||
}
|
||||
} else {
|
||||
$this->artisan('✓ Clear config cache', 'config:clear');
|
||||
$this->artisan('✓ Clear route cache', 'route:clear');
|
||||
$this->artisan('✓ Clear view cache', 'view:clear');
|
||||
}
|
||||
|
||||
if ($this->option('composer-install') === true) {
|
||||
$this->exec('✓ Updating composer dependencies', 'composer install --no-interaction'.($this->option('dev') === false ? ' --no-dev' : ''));
|
||||
}
|
||||
|
||||
if ($this->option('skip-storage-link') !== true && $this->getLaravel()->environment() != 'testing' && ! file_exists(public_path('storage'))) {
|
||||
$this->artisan('✓ Symlink the storage folder', 'storage:link');
|
||||
}
|
||||
|
||||
if ($this->migrateCollationTest()) {
|
||||
$this->artisan('✓ Performing collation migrations', 'migrate:collation', ['--force']);
|
||||
}
|
||||
|
||||
$this->artisan('✓ Performing migrations', 'migrate', ['--force']);
|
||||
|
||||
$this->artisan('✓ Check for encryption keys', 'monica:passport', ['--force']);
|
||||
|
||||
$this->artisan('✓ Ping for new version', 'monica:ping', ['--force']);
|
||||
|
||||
// Cache config
|
||||
if ($this->getLaravel()->environment() == 'production'
|
||||
&& (config('cache.default') != 'database' || Schema::hasTable(config('cache.stores.database.table')))) {
|
||||
$this->artisan('✓ Cache configuraton', 'config:cache');
|
||||
}
|
||||
} finally {
|
||||
$this->artisan('✓ Maintenance mode: off', 'up');
|
||||
}
|
||||
|
||||
$this->line('Monica v'.config('monica.app_version').' is set up, enjoy.');
|
||||
}
|
||||
}
|
||||
|
||||
private function migrateCollationTest()
|
||||
{
|
||||
$connection = DBHelper::connection();
|
||||
|
||||
if ($connection->getDriverName() != 'mysql') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$databasename = $connection->getDatabaseName();
|
||||
|
||||
$schemata = DB::select(
|
||||
'select DEFAULT_CHARACTER_SET_NAME from information_schema.schemata where schema_name = ?',
|
||||
[$databasename]
|
||||
);
|
||||
|
||||
$schema = $schemata[0]->DEFAULT_CHARACTER_SET_NAME;
|
||||
|
||||
return config('database.use_utf8mb4') && $schema == 'utf8'
|
||||
|| ! config('database.use_utf8mb4') && $schema == 'utf8mb4';
|
||||
}
|
||||
}
|
||||
33
app/Console/Commands/UpdateGravatars.php
Normal file
33
app/Console/Commands/UpdateGravatars.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Jobs\Avatars\UpdateAllGravatars;
|
||||
|
||||
class UpdateGravatars extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'monica:updategravatars';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Update all gravatars';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
UpdateAllGravatars::dispatch();
|
||||
}
|
||||
}
|
||||
75
app/Console/Kernel.php
Normal file
75
app/Console/Kernel.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console;
|
||||
|
||||
use App\Console\Scheduling\CronEvent;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
|
||||
class Kernel extends ConsoleKernel
|
||||
{
|
||||
/**
|
||||
* The Artisan commands provided by your application.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $commands = [
|
||||
];
|
||||
|
||||
/**
|
||||
* Register the Closure based commands for the application.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function commands()
|
||||
{
|
||||
$this->load(__DIR__.'/Commands');
|
||||
$this->load(__DIR__.'/Commands/OneTime');
|
||||
|
||||
if ($this->app->environment() != 'production') {
|
||||
$this->load(__DIR__.'/Commands/Tests');
|
||||
}
|
||||
|
||||
require base_path('routes/console.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the application's command schedule.
|
||||
*
|
||||
* @param \Illuminate\Console\Scheduling\Schedule $schedule
|
||||
* @return void
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
protected function schedule(Schedule $schedule)
|
||||
{
|
||||
$this->scheduleCommand($schedule, 'queue:prune-batches', 'daily');
|
||||
$this->scheduleCommand($schedule, 'send:reminders', 'hourly');
|
||||
$this->scheduleCommand($schedule, 'send:stay_in_touch', 'hourly');
|
||||
$this->scheduleCommand($schedule, 'monica:davclients', 'hourly');
|
||||
$this->scheduleCommand($schedule, 'monica:calculatestatistics', 'daily');
|
||||
$this->scheduleCommand($schedule, 'monica:ping', 'daily');
|
||||
$this->scheduleCommand($schedule, 'monica:clean', 'daily');
|
||||
$this->scheduleCommand($schedule, 'monica:updategravatars', 'weekly');
|
||||
if (config('app.cloudflare')) {
|
||||
$this->scheduleCommand($schedule, 'cloudflare:reload', 'daily');
|
||||
}
|
||||
$this->scheduleCommand($schedule, 'model:prune', 'daily');
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a new schedule command with a frequency.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
private function scheduleCommand(Schedule $schedule, string $command, $frequency)
|
||||
{
|
||||
$schedule->command($command)->when(function () use ($command, $frequency) {
|
||||
$event = CronEvent::command($command);
|
||||
if ($frequency) {
|
||||
$event = $event->$frequency();
|
||||
}
|
||||
|
||||
return $event->isDue();
|
||||
});
|
||||
}
|
||||
}
|
||||
128
app/Console/Scheduling/CronEvent.php
Normal file
128
app/Console/Scheduling/CronEvent.php
Normal file
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Scheduling;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use App\Models\Instance\Cron;
|
||||
|
||||
class CronEvent
|
||||
{
|
||||
/**
|
||||
* The cron model.
|
||||
*
|
||||
* @var Cron
|
||||
*/
|
||||
private $cron;
|
||||
|
||||
/**
|
||||
* Frequency to run the command in minutes.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $minutes = 1;
|
||||
|
||||
/**
|
||||
* Frequency to run the command in days.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $days = 0;
|
||||
|
||||
public function __construct(Cron $cron)
|
||||
{
|
||||
$this->cron = $cron;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the command.
|
||||
*
|
||||
* @param string $command
|
||||
* @return self
|
||||
*/
|
||||
public static function command(string $command): self
|
||||
{
|
||||
/** @var \App\Models\Instance\Cron $cron */
|
||||
$cron = Cron::firstOrCreate(['command' => $command]);
|
||||
|
||||
return new self($cron);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current Cron.
|
||||
*
|
||||
* @return Cron
|
||||
*/
|
||||
public function cron()
|
||||
{
|
||||
return $this->cron;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the command once per hour.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function hourly(): self
|
||||
{
|
||||
$this->minutes = 60;
|
||||
$this->days = 0;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the command once per day.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function daily(): self
|
||||
{
|
||||
$this->minutes = 0;
|
||||
$this->days = 1;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the command once a week.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function weekly(): self
|
||||
{
|
||||
$this->minutes = 0;
|
||||
$this->days = 7;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the command is due to run.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isDue(): bool
|
||||
{
|
||||
$now = now();
|
||||
|
||||
if ($this->cron->last_run !== null) {
|
||||
$t = $this->cron->last_run;
|
||||
|
||||
if ($this->minutes !== 0) {
|
||||
$next_run = Carbon::create($t->year, $t->month, $t->day, $t->hour, (int) floor($t->minute / $this->minutes) * $this->minutes, 0)
|
||||
->addMinutes($this->minutes);
|
||||
} elseif ($this->days !== 0) {
|
||||
$next_run = Carbon::create($t->year, $t->month, (int) floor($t->day / $this->days) * $this->days, 0, 0, 0)
|
||||
->addDays($this->days);
|
||||
}
|
||||
|
||||
if (! isset($next_run) || $next_run > $now) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$this->cron->update(['last_run' => $now]);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
8
app/Events/Event.php
Normal file
8
app/Events/Event.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
abstract class Event
|
||||
{
|
||||
//
|
||||
}
|
||||
29
app/Events/MoveAvatarEvent.php
Normal file
29
app/Events/MoveAvatarEvent.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use App\Models\Contact\Contact;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class MoveAvatarEvent extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* The contact.
|
||||
*
|
||||
* @var Contact
|
||||
*/
|
||||
public $contact;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param Contact $contact
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($contact)
|
||||
{
|
||||
$this->contact = $contact;
|
||||
}
|
||||
}
|
||||
30
app/Events/RecoveryLogin.php
Normal file
30
app/Events/RecoveryLogin.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class RecoveryLogin extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* The authenticated user.
|
||||
*
|
||||
* @var \Illuminate\Contracts\Auth\Authenticatable
|
||||
*/
|
||||
public $user;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Auth\Authenticatable $user
|
||||
* @return void
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function __construct($user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
||||
31
app/Events/TokenDeleteEvent.php
Normal file
31
app/Events/TokenDeleteEvent.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use App\Models\User\SyncToken;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class TokenDeleteEvent extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* The deleted token.
|
||||
*
|
||||
* @var SyncToken
|
||||
*/
|
||||
public $token;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param SyncToken $token
|
||||
* @return void
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function __construct($token)
|
||||
{
|
||||
$this->token = $token;
|
||||
}
|
||||
}
|
||||
30
app/Exceptions/FileNotFoundException.php
Normal file
30
app/Exceptions/FileNotFoundException.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Illuminate\Contracts\Filesystem\FileNotFoundException as FileNotFoundExceptionBase;
|
||||
|
||||
class FileNotFoundException extends FileNotFoundExceptionBase
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $fileName;
|
||||
|
||||
/**
|
||||
* Create a new instance.
|
||||
*
|
||||
* @param string $fileName
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($fileName)
|
||||
{
|
||||
$this->fileName = $fileName;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return 'File not found: '.$this->fileName;
|
||||
}
|
||||
}
|
||||
64
app/Exceptions/Handler.php
Normal file
64
app/Exceptions/Handler.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Throwable;
|
||||
use Illuminate\Session\TokenMismatchException;
|
||||
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
|
||||
class Handler extends ExceptionHandler
|
||||
{
|
||||
/**
|
||||
* A list of the exception types that should not be reported.
|
||||
*
|
||||
* @var array<int, class-string<Throwable>>
|
||||
*/
|
||||
protected $dontReport = [
|
||||
OAuthServerException::class,
|
||||
WrongIdException::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* Register the exception handling callbacks for the application.
|
||||
*
|
||||
* @return void
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
if (config('monica.sentry_support') && config('app.env') == 'production') {
|
||||
$this->reportable(function (Throwable $e) {
|
||||
if ($this->shouldReport($e) && app()->bound('sentry')) {
|
||||
app('sentry')->captureException($e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render an exception into an HTTP response.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Throwable $e
|
||||
* @return \Illuminate\Http\Response|\Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function render($request, Throwable $e)
|
||||
{
|
||||
// hopefully catches those pesky token expiries
|
||||
// and send them back to login.
|
||||
if ($e instanceof TokenMismatchException) {
|
||||
return redirect()->route('loginRedirect');
|
||||
}
|
||||
|
||||
// Convert all non-http exceptions to a proper 500 http exception
|
||||
// if we don't do this exceptions are shown as a default template
|
||||
// instead of our own view in resources/views/errors/500.blade.php
|
||||
if ($this->shouldReport($e) && ! $this->isHttpException($e) && ! config('app.debug')) {
|
||||
$e = new HttpException(500, $e->getMessage());
|
||||
}
|
||||
|
||||
return parent::render($request, $e);
|
||||
}
|
||||
}
|
||||
12
app/Exceptions/MissingEnvVariableException.php
Normal file
12
app/Exceptions/MissingEnvVariableException.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Exception thrown if the env variable is not set or does not exist.
|
||||
*/
|
||||
class MissingEnvVariableException extends RuntimeException
|
||||
{
|
||||
}
|
||||
12
app/Exceptions/NoAccountException.php
Normal file
12
app/Exceptions/NoAccountException.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Exception thrown if the env variable is not set or does not exist.
|
||||
*/
|
||||
class NoAccountException extends RuntimeException
|
||||
{
|
||||
}
|
||||
9
app/Exceptions/NoCoordinatesException.php
Normal file
9
app/Exceptions/NoCoordinatesException.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class NoCoordinatesException extends RuntimeException
|
||||
{
|
||||
}
|
||||
13
app/Exceptions/RateLimitedSecondException.php
Normal file
13
app/Exceptions/RateLimitedSecondException.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class RateLimitedSecondException extends RuntimeException
|
||||
{
|
||||
public function __construct($e)
|
||||
{
|
||||
parent::__construct('', 429, $e);
|
||||
}
|
||||
}
|
||||
12
app/Exceptions/StripeException.php
Normal file
12
app/Exceptions/StripeException.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Exception thrown by Stripe.
|
||||
*/
|
||||
class StripeException extends RuntimeException
|
||||
{
|
||||
}
|
||||
12
app/Exceptions/WrongIdException.php
Normal file
12
app/Exceptions/WrongIdException.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Exception thrown by IdHasher if a desired key item is wrong.
|
||||
*/
|
||||
class WrongIdException extends RuntimeException
|
||||
{
|
||||
}
|
||||
12
app/Exceptions/WrongValueException.php
Normal file
12
app/Exceptions/WrongValueException.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Exception thrown if the given value is not in the list of authorized values.
|
||||
*/
|
||||
class WrongValueException extends RuntimeException
|
||||
{
|
||||
}
|
||||
63
app/ExportResources/Account/Account.php
Normal file
63
app/ExportResources/Account/Account.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources\Account;
|
||||
|
||||
use App\Models\Contact\Gender;
|
||||
use App\ExportResources\User\User;
|
||||
use App\ExportResources\User\Module;
|
||||
use App\ExportResources\ExportResource;
|
||||
use App\ExportResources\Contact\Contact;
|
||||
use App\ExportResources\Contact\Document;
|
||||
use App\ExportResources\Instance\AuditLog;
|
||||
use App\ExportResources\Journal\JournalEntry;
|
||||
use App\ExportResources\Contact\ContactFieldType;
|
||||
use App\ExportResources\Relationship\Relationship;
|
||||
use App\ExportResources\Contact\Gender as GenderResource;
|
||||
|
||||
class Account extends ExportResource
|
||||
{
|
||||
protected $columns = [
|
||||
'uuid',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
protected $properties = [
|
||||
'number_of_invitations_sent',
|
||||
];
|
||||
|
||||
public function data(): ?array
|
||||
{
|
||||
return [
|
||||
'data' => [
|
||||
User::countCollection($this->users),
|
||||
Contact::countCollection($this->allContacts),
|
||||
Relationship::countCollection($this->relationships),
|
||||
Addressbook::countCollection($this->addressBooks),
|
||||
AddressbookSubscription::countCollection($this->addressBookSubscriptions),
|
||||
Photo::countCollection($this->photos),
|
||||
Document::countCollection($this->documents),
|
||||
Activity::countCollection($this->activities),
|
||||
],
|
||||
'properties' => [
|
||||
'default_gender' => $this->when($this->default_gender_id !== null, function () {
|
||||
$defaultGender = Gender::where(['account_id' => $this->id])->find($this->default_gender_id);
|
||||
|
||||
return $defaultGender->uuid;
|
||||
}),
|
||||
'journal_entries' => JournalEntry::collection($this->journalEntries()->entry()->get()),
|
||||
'modules' => Module::collection($this->modules),
|
||||
'reminder_rules' => ReminderRule::collection($this->reminderRules),
|
||||
'audit_logs' => AuditLog::collection($this->auditLogs),
|
||||
],
|
||||
'instance' => [
|
||||
'activity_types' => ActivityType::collection($this->activityTypes),
|
||||
'activity_type_categories' => ActivityTypeCategory::collection($this->activityTypeCategories),
|
||||
'contact_field_types' => ContactFieldType::collection($this->contactFieldTypes),
|
||||
'genders' => GenderResource::collection($this->genders),
|
||||
'life_event_types' => LifeEventType::collection($this->lifeEventTypes),
|
||||
'life_event_categories' => LifeEventCategory::collection($this->lifeEventCategories),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
33
app/ExportResources/Account/Activity.php
Normal file
33
app/ExportResources/Account/Activity.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources\Account;
|
||||
|
||||
use App\ExportResources\ExportResource;
|
||||
|
||||
class Activity extends ExportResource
|
||||
{
|
||||
protected $columns = [
|
||||
'uuid',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
protected $properties = [
|
||||
'summary',
|
||||
'description',
|
||||
'happened_at',
|
||||
];
|
||||
|
||||
public function data(): ?array
|
||||
{
|
||||
return [
|
||||
'properties' => [
|
||||
$this->mergeWhen($this->type !== null, function () {
|
||||
return [
|
||||
'type' => $this->type->uuid,
|
||||
];
|
||||
}),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
33
app/ExportResources/Account/ActivityType.php
Normal file
33
app/ExportResources/Account/ActivityType.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources\Account;
|
||||
|
||||
use App\ExportResources\ExportResource;
|
||||
|
||||
class ActivityType extends ExportResource
|
||||
{
|
||||
protected $columns = [
|
||||
'uuid',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
protected $properties = [
|
||||
'translation_key',
|
||||
'name',
|
||||
'location_type',
|
||||
];
|
||||
|
||||
public function data(): ?array
|
||||
{
|
||||
return [
|
||||
'properties' => [
|
||||
$this->mergeWhen($this->category !== null, function () {
|
||||
return [
|
||||
'category' => $this->category->uuid,
|
||||
];
|
||||
}),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
19
app/ExportResources/Account/ActivityTypeCategory.php
Normal file
19
app/ExportResources/Account/ActivityTypeCategory.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources\Account;
|
||||
|
||||
use App\ExportResources\ExportResource;
|
||||
|
||||
class ActivityTypeCategory extends ExportResource
|
||||
{
|
||||
protected $columns = [
|
||||
'uuid',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
protected $properties = [
|
||||
'translation_key',
|
||||
'name',
|
||||
];
|
||||
}
|
||||
27
app/ExportResources/Account/Addressbook.php
Normal file
27
app/ExportResources/Account/Addressbook.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources\Account;
|
||||
|
||||
use App\ExportResources\ExportResource;
|
||||
|
||||
class Addressbook extends ExportResource
|
||||
{
|
||||
protected $columns = [
|
||||
'uuid',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
protected $properties = [
|
||||
'name',
|
||||
'description',
|
||||
];
|
||||
|
||||
public function data(): ?array
|
||||
{
|
||||
return [
|
||||
'user' => $this->user->uuid,
|
||||
'contacts' => $this->contacts->mapUuid(),
|
||||
];
|
||||
}
|
||||
}
|
||||
44
app/ExportResources/Account/AddressbookSubscription.php
Normal file
44
app/ExportResources/Account/AddressbookSubscription.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources\Account;
|
||||
|
||||
use App\Models\User\SyncToken;
|
||||
use App\ExportResources\ExportResource;
|
||||
use App\ExportResources\User\SyncToken as SyncTokenResource;
|
||||
|
||||
class AddressbookSubscription extends ExportResource
|
||||
{
|
||||
protected $columns = [
|
||||
'uuid',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
protected $properties = [
|
||||
'name',
|
||||
'uri',
|
||||
'username',
|
||||
'readonly',
|
||||
'active',
|
||||
'capabilities',
|
||||
'frequency',
|
||||
'last_syncronized_at',
|
||||
];
|
||||
|
||||
public function data(): ?array
|
||||
{
|
||||
return [
|
||||
'properties' => [
|
||||
'addressbook' => $this->addressBook->uuid,
|
||||
'sync_token' => $this->syncToken,
|
||||
$this->merge(function () {
|
||||
$localSyncToken = SyncToken::where('account_id', $this->account_id)->find($this->localSyncToken);
|
||||
|
||||
return [
|
||||
'local_sync_token' => new SyncTokenResource($localSyncToken),
|
||||
];
|
||||
}),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
27
app/ExportResources/Account/LifeEventCategory.php
Normal file
27
app/ExportResources/Account/LifeEventCategory.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources\Account;
|
||||
|
||||
use App\ExportResources\ExportResource;
|
||||
|
||||
class LifeEventCategory extends ExportResource
|
||||
{
|
||||
protected $columns = [
|
||||
'uuid',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
protected $properties = [
|
||||
'core_monica_data',
|
||||
];
|
||||
|
||||
public function data(): ?array
|
||||
{
|
||||
return [
|
||||
'properties' => [
|
||||
'translation_key' => $this->default_life_event_category_key,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
34
app/ExportResources/Account/LifeEventType.php
Normal file
34
app/ExportResources/Account/LifeEventType.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources\Account;
|
||||
|
||||
use App\ExportResources\ExportResource;
|
||||
|
||||
class LifeEventType extends ExportResource
|
||||
{
|
||||
protected $columns = [
|
||||
'uuid',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
protected $properties = [
|
||||
'name',
|
||||
'core_monica_data',
|
||||
'specific_information_structure',
|
||||
];
|
||||
|
||||
public function data(): ?array
|
||||
{
|
||||
return [
|
||||
'properties' => [
|
||||
'translation_key' => $this->default_life_event_type_key,
|
||||
$this->mergeWhen($this->lifeEventCategory !== null, function () {
|
||||
return [
|
||||
'category' => $this->lifeEventCategory->uuid,
|
||||
];
|
||||
}),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
29
app/ExportResources/Account/Photo.php
Normal file
29
app/ExportResources/Account/Photo.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources\Account;
|
||||
|
||||
use App\ExportResources\ExportResource;
|
||||
|
||||
class Photo extends ExportResource
|
||||
{
|
||||
protected $columns = [
|
||||
'uuid',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
protected $properties = [
|
||||
'original_filename',
|
||||
'filesize',
|
||||
'mime_type',
|
||||
];
|
||||
|
||||
public function data(): ?array
|
||||
{
|
||||
return [
|
||||
'properties' => [
|
||||
'dataUrl' => $this->dataUrl(),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
18
app/ExportResources/Account/ReminderRule.php
Normal file
18
app/ExportResources/Account/ReminderRule.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources\Account;
|
||||
|
||||
use App\ExportResources\ExportResource;
|
||||
|
||||
class ReminderRule extends ExportResource
|
||||
{
|
||||
protected $columns = [
|
||||
'number_of_days_before',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
protected $properties = [
|
||||
'active',
|
||||
];
|
||||
}
|
||||
33
app/ExportResources/Contact/Address.php
Normal file
33
app/ExportResources/Contact/Address.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources\Contact;
|
||||
|
||||
use App\ExportResources\ExportResource;
|
||||
|
||||
class Address extends ExportResource
|
||||
{
|
||||
protected $columns = [
|
||||
'uuid',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
protected $properties = [
|
||||
'name',
|
||||
];
|
||||
|
||||
public function data(): ?array
|
||||
{
|
||||
return [
|
||||
'properties' => [
|
||||
'street' => $this->place->street,
|
||||
'city' => $this->place->city,
|
||||
'province' => $this->place->province,
|
||||
'postal_code' => $this->place->postal_code,
|
||||
'latitude' => $this->place->latitude,
|
||||
'longitude' => $this->place->longitude,
|
||||
'country' => $this->place->country,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
33
app/ExportResources/Contact/Call.php
Normal file
33
app/ExportResources/Contact/Call.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources\Contact;
|
||||
|
||||
use App\ExportResources\ExportResource;
|
||||
|
||||
class Call extends ExportResource
|
||||
{
|
||||
protected $columns = [
|
||||
'uuid',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
protected $properties = [
|
||||
'called_at',
|
||||
'content',
|
||||
'contact_called',
|
||||
];
|
||||
|
||||
public function data(): ?array
|
||||
{
|
||||
return [
|
||||
'properties' => [
|
||||
$this->mergeWhen($this->emotions->count() > 0, [
|
||||
'emotions' => $this->emotions->map(function ($emotion) {
|
||||
return $emotion->name;
|
||||
})->toArray(),
|
||||
]),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
106
app/ExportResources/Contact/Contact.php
Normal file
106
app/ExportResources/Contact/Contact.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources\Contact;
|
||||
|
||||
use App\ExportResources\Account\Photo;
|
||||
use App\ExportResources\ExportResource;
|
||||
use App\ExportResources\Account\Activity;
|
||||
use App\ExportResources\Instance\SpecialDate;
|
||||
use App\Models\Contact\Reminder as ContactReminder;
|
||||
|
||||
class Contact extends ExportResource
|
||||
{
|
||||
protected $columns = [
|
||||
'uuid',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
protected $properties = [
|
||||
'first_name',
|
||||
'middle_name',
|
||||
'last_name',
|
||||
'nickname',
|
||||
'description',
|
||||
'is_starred',
|
||||
'is_partial',
|
||||
'is_active',
|
||||
'is_dead',
|
||||
'job',
|
||||
'company',
|
||||
'food_preferences',
|
||||
'last_talked_to',
|
||||
'last_consulted_at',
|
||||
'number_of_views',
|
||||
'stay_in_touch_frequency',
|
||||
'stay_in_touch_trigger_date',
|
||||
'vcard',
|
||||
'distant_etag',
|
||||
];
|
||||
|
||||
public function data(): ?array
|
||||
{
|
||||
return [
|
||||
'properties' => [
|
||||
'avatar' => [
|
||||
'avatar_source' => $this->avatar_source,
|
||||
'avatar_gravatar_url' => $this->avatar_gravatar_url,
|
||||
'avatar_adorable_uuid' => $this->avatar_adorable_uuid,
|
||||
'avatar_default_url' => $this->avatar_default_url,
|
||||
$this->mergeWhen($this->avatarPhoto !== null, function () {
|
||||
return ['avatar_photo' => $this->avatarPhoto->uuid];
|
||||
}),
|
||||
'has_avatar' => $this->has_avatar,
|
||||
'avatar_external_url' => $this->avatar_external_url,
|
||||
'avatar_file_name' => $this->avatar_file_name,
|
||||
'avatar_location' => $this->avatar_location,
|
||||
'gravatar_url' => $this->gravatar_url,
|
||||
'default_avatar_color' => $this->default_avatar_color,
|
||||
],
|
||||
'tags' => $this->when($this->tags->count() > 0, function () {
|
||||
return $this->tags->map(function ($tag) {
|
||||
return $tag->name;
|
||||
})->toArray();
|
||||
}),
|
||||
$this->mergeWhen($this->gender !== null, function () {
|
||||
return ['gender' => $this->gender->uuid];
|
||||
}),
|
||||
$this->mergeWhen($this->birthdate, [
|
||||
'birthdate' => new SpecialDate($this->birthdate),
|
||||
]),
|
||||
$this->mergeWhen($this->deceasedDate, [
|
||||
'deceased_date' => new SpecialDate($this->deceasedDate),
|
||||
]),
|
||||
$this->mergeWhen($this->deceased_reminder_id, function () {
|
||||
return ['deceased_reminder' => new Reminder(ContactReminder::find($this->deceased_reminder_id))];
|
||||
}),
|
||||
$this->mergeWhen($this->firstMetDate, [
|
||||
'first_met_date' => new SpecialDate($this->firstMetDate),
|
||||
]),
|
||||
$this->mergeWhen($this->getIntroducer() !== null, function () {
|
||||
return ['first_met_through' => $this->getIntroducer()->uuid];
|
||||
}),
|
||||
$this->mergeWhen($this->first_met_reminder_id !== null, function () {
|
||||
return ['first_met_reminder' => new Reminder(ContactReminder::find($this->first_met_reminder_id))];
|
||||
}),
|
||||
],
|
||||
'data' => [
|
||||
Call::countCollection($this->calls),
|
||||
ContactField::countCollection($this->contactFields),
|
||||
Debt::countCollection($this->debts),
|
||||
Gift::countCollection($this->gifts),
|
||||
Note::countCollection($this->notes),
|
||||
Reminder::countCollection($this->reminders),
|
||||
Task::countCollection($this->tasks),
|
||||
Address::countCollection($this->addresses),
|
||||
Pet::countCollection($this->pets),
|
||||
Conversation::countCollection($this->conversations),
|
||||
LifeEvent::countCollection($this->lifeEvents),
|
||||
Activity::uuidCollection($this->activities),
|
||||
Photo::uuidCollection($this->photos),
|
||||
Document::uuidCollection($this->documents),
|
||||
// Occupation::collection($this->occupations),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
29
app/ExportResources/Contact/ContactField.php
Normal file
29
app/ExportResources/Contact/ContactField.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources\Contact;
|
||||
|
||||
use App\ExportResources\ExportResource;
|
||||
|
||||
class ContactField extends ExportResource
|
||||
{
|
||||
protected $columns = [
|
||||
'uuid',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
protected $properties = [
|
||||
'data',
|
||||
];
|
||||
|
||||
public function data(): ?array
|
||||
{
|
||||
return [
|
||||
'properties' => [
|
||||
$this->mergeWhen($this->contactFieldType !== null, function () {
|
||||
return ['type' => $this->contactFieldType->uuid];
|
||||
}),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
22
app/ExportResources/Contact/ContactFieldType.php
Normal file
22
app/ExportResources/Contact/ContactFieldType.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources\Contact;
|
||||
|
||||
use App\ExportResources\ExportResource;
|
||||
|
||||
class ContactFieldType extends ExportResource
|
||||
{
|
||||
protected $columns = [
|
||||
'uuid',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
protected $properties = [
|
||||
'name',
|
||||
'fontawesome_icon',
|
||||
'protocol',
|
||||
'delible',
|
||||
'type',
|
||||
];
|
||||
}
|
||||
30
app/ExportResources/Contact/Conversation.php
Normal file
30
app/ExportResources/Contact/Conversation.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources\Contact;
|
||||
|
||||
use App\ExportResources\ExportResource;
|
||||
|
||||
class Conversation extends ExportResource
|
||||
{
|
||||
protected $columns = [
|
||||
'uuid',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
protected $properties = [
|
||||
'happened_at',
|
||||
];
|
||||
|
||||
public function data(): ?array
|
||||
{
|
||||
return [
|
||||
'properties' => [
|
||||
$this->mergeWhen($this->contactFieldType !== null, function () {
|
||||
return ['contact_field_type' => $this->contactFieldType->uuid];
|
||||
}),
|
||||
'messages' => Message::collection($this->messages),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
29
app/ExportResources/Contact/Debt.php
Normal file
29
app/ExportResources/Contact/Debt.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources\Contact;
|
||||
|
||||
use App\ExportResources\ExportResource;
|
||||
|
||||
class Debt extends ExportResource
|
||||
{
|
||||
protected $columns = [
|
||||
'uuid',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
protected $properties = [
|
||||
'amount',
|
||||
'currency',
|
||||
'status',
|
||||
];
|
||||
|
||||
public function data(): ?array
|
||||
{
|
||||
return [
|
||||
'properties' => [
|
||||
'in_debt' => $this->in_debt === 'yes',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
33
app/ExportResources/Contact/Document.php
Normal file
33
app/ExportResources/Contact/Document.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources\Contact;
|
||||
|
||||
use App\ExportResources\ExportResource;
|
||||
|
||||
class Document extends ExportResource
|
||||
{
|
||||
protected $columns = [
|
||||
'uuid',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
protected $properties = [
|
||||
'original_filename',
|
||||
'filesize',
|
||||
'type',
|
||||
'mime_type',
|
||||
'number_of_downloads',
|
||||
];
|
||||
|
||||
public function data(): ?array
|
||||
{
|
||||
return [
|
||||
'properties' => [
|
||||
$this->mergeWhen(($dataUrl = $this->dataUrl()) !== null, [
|
||||
'dataUrl' => $dataUrl,
|
||||
]),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
19
app/ExportResources/Contact/Gender.php
Normal file
19
app/ExportResources/Contact/Gender.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources\Contact;
|
||||
|
||||
use App\ExportResources\ExportResource;
|
||||
|
||||
class Gender extends ExportResource
|
||||
{
|
||||
protected $columns = [
|
||||
'uuid',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
protected $properties = [
|
||||
'name',
|
||||
'type',
|
||||
];
|
||||
}
|
||||
37
app/ExportResources/Contact/Gift.php
Normal file
37
app/ExportResources/Contact/Gift.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources\Contact;
|
||||
|
||||
use App\ExportResources\ExportResource;
|
||||
|
||||
class Gift extends ExportResource
|
||||
{
|
||||
protected $columns = [
|
||||
'uuid',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
protected $properties = [
|
||||
'name',
|
||||
'comment',
|
||||
'url',
|
||||
'amount',
|
||||
'status',
|
||||
'date',
|
||||
];
|
||||
|
||||
public function data(): ?array
|
||||
{
|
||||
return [
|
||||
'properties' => [
|
||||
$this->mergeWhen($this->recipient !== null, function () {
|
||||
return ['recipient' => $this->recipient->uuid];
|
||||
}),
|
||||
$this->mergeWhen($this->photos->count() > 0, [
|
||||
'photos' => $this->photos->mapUuid(),
|
||||
]),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
32
app/ExportResources/Contact/LifeEvent.php
Normal file
32
app/ExportResources/Contact/LifeEvent.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources\Contact;
|
||||
|
||||
use App\ExportResources\ExportResource;
|
||||
|
||||
class LifeEvent extends ExportResource
|
||||
{
|
||||
protected $columns = [
|
||||
'uuid',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
protected $properties = [
|
||||
'name',
|
||||
'note',
|
||||
'happened_at',
|
||||
'specific_information',
|
||||
];
|
||||
|
||||
public function data(): ?array
|
||||
{
|
||||
return [
|
||||
'properties' => [
|
||||
$this->mergeWhen($this->lifeEventType != null, function () {
|
||||
return ['type' => $this->lifeEventType->uuid];
|
||||
}),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
20
app/ExportResources/Contact/Message.php
Normal file
20
app/ExportResources/Contact/Message.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources\Contact;
|
||||
|
||||
use App\ExportResources\ExportResource;
|
||||
|
||||
class Message extends ExportResource
|
||||
{
|
||||
protected $columns = [
|
||||
'uuid',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
protected $properties = [
|
||||
'content',
|
||||
'written_at',
|
||||
'written_by_me',
|
||||
];
|
||||
}
|
||||
20
app/ExportResources/Contact/Note.php
Normal file
20
app/ExportResources/Contact/Note.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources\Contact;
|
||||
|
||||
use App\ExportResources\ExportResource;
|
||||
|
||||
class Note extends ExportResource
|
||||
{
|
||||
protected $columns = [
|
||||
'uuid',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
protected $properties = [
|
||||
'body',
|
||||
'is_favorite',
|
||||
'favorited_at',
|
||||
];
|
||||
}
|
||||
29
app/ExportResources/Contact/Pet.php
Normal file
29
app/ExportResources/Contact/Pet.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources\Contact;
|
||||
|
||||
use App\ExportResources\ExportResource;
|
||||
|
||||
class Pet extends ExportResource
|
||||
{
|
||||
protected $columns = [
|
||||
'uuid',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
protected $properties = [
|
||||
'name',
|
||||
];
|
||||
|
||||
public function data(): ?array
|
||||
{
|
||||
return [
|
||||
'properties' => [
|
||||
$this->mergeWhen($this->petCategory !== null, function () {
|
||||
return ['category' => $this->petCategory->name];
|
||||
}),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
24
app/ExportResources/Contact/Reminder.php
Normal file
24
app/ExportResources/Contact/Reminder.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources\Contact;
|
||||
|
||||
use App\ExportResources\ExportResource;
|
||||
|
||||
class Reminder extends ExportResource
|
||||
{
|
||||
protected $columns = [
|
||||
'uuid',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
protected $properties = [
|
||||
'initial_date',
|
||||
'title',
|
||||
'description',
|
||||
'frequency_type',
|
||||
'frequency_number',
|
||||
'delible',
|
||||
'inactive',
|
||||
];
|
||||
}
|
||||
21
app/ExportResources/Contact/Task.php
Normal file
21
app/ExportResources/Contact/Task.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources\Contact;
|
||||
|
||||
use App\ExportResources\ExportResource;
|
||||
|
||||
class Task extends ExportResource
|
||||
{
|
||||
protected $columns = [
|
||||
'uuid',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
protected $properties = [
|
||||
'title',
|
||||
'description',
|
||||
'completed',
|
||||
'completed_at',
|
||||
];
|
||||
}
|
||||
24
app/ExportResources/CountResourceCollection.php
Normal file
24
app/ExportResources/CountResourceCollection.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
|
||||
class CountResourceCollection extends AnonymousResourceCollection
|
||||
{
|
||||
/**
|
||||
* Transform the resource into a JSON array.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
||||
*/
|
||||
public function toArray($request)
|
||||
{
|
||||
return [
|
||||
'count' => $this->count(),
|
||||
'type' => Str::of($this->collects)->afterLast('\\')->kebab()->replace('-', '_'),
|
||||
'values' => parent::toArray($request),
|
||||
];
|
||||
}
|
||||
}
|
||||
137
app/ExportResources/ExportResource.php
Normal file
137
app/ExportResources/ExportResource.php
Normal file
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Http\Resources\MissingValue;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class ExportResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* The resource instance.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $columns = [];
|
||||
|
||||
/**
|
||||
* The resource instance.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $properties = null;
|
||||
|
||||
/**
|
||||
* Create a new resource instance.
|
||||
*
|
||||
* @param mixed $resource
|
||||
* @return void
|
||||
*/
|
||||
final public function __construct($resource)
|
||||
{
|
||||
$this->resource = $resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new anonymous resource collection.
|
||||
*
|
||||
* @param mixed $resource
|
||||
* @return CountResourceCollection|MissingValue
|
||||
*/
|
||||
public static function countCollection($resource)
|
||||
{
|
||||
if ($resource->count() === 0) {
|
||||
return new MissingValue();
|
||||
}
|
||||
|
||||
return tap(new CountResourceCollection($resource, static::class), function ($collection) {
|
||||
if (property_exists(static::class, 'preserveKeys')) {
|
||||
$collection->preserveKeys = (new static([]))->preserveKeys === true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new anonymous resource collection.
|
||||
*
|
||||
* @param mixed $resource
|
||||
* @return MapUuidResourceCollection|MissingValue
|
||||
*/
|
||||
public static function uuidCollection($resource)
|
||||
{
|
||||
if ($resource->count() === 0) {
|
||||
return new MissingValue();
|
||||
}
|
||||
|
||||
return tap(new MapUuidResourceCollection($resource, static::class), function ($collection) {
|
||||
if (property_exists(static::class, 'preserveKeys')) {
|
||||
$collection->preserveKeys = (new static([]))->preserveKeys === true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
||||
*/
|
||||
public function toArray($request)
|
||||
{
|
||||
if (is_null($this->resource)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return is_array($this->resource)
|
||||
? $this->resource
|
||||
: $this->export($this->columns, $this->properties, $this->data());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|null
|
||||
*/
|
||||
public function data(): ?array
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the Insert query for the given table.
|
||||
*
|
||||
* @param array $columns
|
||||
* @param array $properties
|
||||
* @param array $data
|
||||
* @return array|null
|
||||
*/
|
||||
protected function export(array $columns, array $properties = null, array $data = null): ?array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
if (! $this->resource->exists()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($columns as $column) {
|
||||
$result[$column] = $this->{$column};
|
||||
}
|
||||
|
||||
if ($data !== null) {
|
||||
foreach ($data as $key => $value) {
|
||||
if (isset($result[$key]) && is_array($result[$key])) {
|
||||
$result[$key] = array_merge($result[$key], $value);
|
||||
} else {
|
||||
$result[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($properties !== null) {
|
||||
$result['properties'] = array_merge(collect($properties)->mapWithKeys(function ($item, $key) {
|
||||
return ($value = $this->{$item}) !== null ? [$item => $value] : new MissingValue();
|
||||
})->toArray(), Arr::get($result, 'properties', []));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
35
app/ExportResources/Instance/AuditLog.php
Normal file
35
app/ExportResources/Instance/AuditLog.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources\Instance;
|
||||
|
||||
use App\ExportResources\ExportResource;
|
||||
|
||||
class AuditLog extends ExportResource
|
||||
{
|
||||
protected $columns = [
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
protected $properties = [
|
||||
'author_name',
|
||||
'action',
|
||||
'objects',
|
||||
'audited_at',
|
||||
'should_appear_on_dashboard',
|
||||
];
|
||||
|
||||
public function data(): ?array
|
||||
{
|
||||
return [
|
||||
'properties' => [
|
||||
'author' => $this->when($this->author !== null, function () {
|
||||
return $this->author->uuid;
|
||||
}),
|
||||
'contact' => $this->when($this->contact !== null, function () {
|
||||
return $this->contact->uuid;
|
||||
}),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
27
app/ExportResources/Instance/Emotion/Emotion.php
Normal file
27
app/ExportResources/Instance/Emotion/Emotion.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources\Instance\Emotion;
|
||||
|
||||
use App\ExportResources\ExportResource;
|
||||
|
||||
class Emotion extends ExportResource
|
||||
{
|
||||
protected $columns = [
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
protected $properties = [
|
||||
'name',
|
||||
];
|
||||
|
||||
public function data(): ?array
|
||||
{
|
||||
return [
|
||||
'properties' => [
|
||||
'primary' => $this->primary->name,
|
||||
'secondary' => $this->secondary->name,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
17
app/ExportResources/Instance/SpecialDate.php
Normal file
17
app/ExportResources/Instance/SpecialDate.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources\Instance;
|
||||
|
||||
use App\ExportResources\ExportResource;
|
||||
|
||||
class SpecialDate extends ExportResource
|
||||
{
|
||||
protected $columns = [
|
||||
'uuid',
|
||||
'is_age_based',
|
||||
'is_year_unknown',
|
||||
'date',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
}
|
||||
50
app/ExportResources/Journal/JournalEntry.php
Normal file
50
app/ExportResources/Journal/JournalEntry.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources\Journal;
|
||||
|
||||
use App\ExportResources\ExportResource;
|
||||
|
||||
class JournalEntry extends ExportResource
|
||||
{
|
||||
protected $columns = [
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
protected $properties = [
|
||||
'date',
|
||||
];
|
||||
|
||||
public function data(): ?array
|
||||
{
|
||||
$data = $this->getObjectData();
|
||||
if ($data !== null) {
|
||||
switch ($data['type']) {
|
||||
case 'entry':
|
||||
return [
|
||||
'uuid' => $this->journalable->uuid,
|
||||
'properties' => [
|
||||
'type' => $data['type'],
|
||||
'title' => $data['title'],
|
||||
'post' => $data['post'],
|
||||
'date' => $data['date'],
|
||||
],
|
||||
];
|
||||
case 'day':
|
||||
return [
|
||||
'uuid' => $this->journalable->uuid,
|
||||
'properties' => [
|
||||
'type' => $data['type'],
|
||||
'rate' => $data['rate'],
|
||||
'comment' => $data['comment'],
|
||||
'day' => $data['day'],
|
||||
'month' => $data['month'],
|
||||
'year' => $data['year'],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
24
app/ExportResources/MapUuidResourceCollection.php
Normal file
24
app/ExportResources/MapUuidResourceCollection.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
|
||||
class MapUuidResourceCollection extends AnonymousResourceCollection
|
||||
{
|
||||
/**
|
||||
* Transform the resource into a JSON array.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
||||
*/
|
||||
public function toArray($request)
|
||||
{
|
||||
return [
|
||||
'count' => $this->count(),
|
||||
'type' => Str::of($this->collects)->afterLast('\\')->kebab()->replace('-', '_'),
|
||||
'values' => $this->collection->mapUuid(),
|
||||
];
|
||||
}
|
||||
}
|
||||
25
app/ExportResources/Relationship/Relationship.php
Normal file
25
app/ExportResources/Relationship/Relationship.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources\Relationship;
|
||||
|
||||
use App\ExportResources\ExportResource;
|
||||
|
||||
class Relationship extends ExportResource
|
||||
{
|
||||
protected $columns = [
|
||||
'uuid',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
public function data(): ?array
|
||||
{
|
||||
return [
|
||||
'properties' => [
|
||||
'type' => $this->relationshipType->name,
|
||||
'contact_is' => $this->contactIs->uuid,
|
||||
'of_contact' => $this->ofContact->uuid,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
20
app/ExportResources/User/Module.php
Normal file
20
app/ExportResources/User/Module.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources\User;
|
||||
|
||||
use App\ExportResources\ExportResource;
|
||||
|
||||
class Module extends ExportResource
|
||||
{
|
||||
protected $columns = [
|
||||
'key',
|
||||
'translation_key',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
protected $properties = [
|
||||
'active',
|
||||
'delible',
|
||||
];
|
||||
}
|
||||
18
app/ExportResources/User/SyncToken.php
Normal file
18
app/ExportResources/User/SyncToken.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources\User;
|
||||
|
||||
use App\ExportResources\ExportResource;
|
||||
|
||||
class SyncToken extends ExportResource
|
||||
{
|
||||
protected $columns = [
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
protected $properties = [
|
||||
'name',
|
||||
'timestamp',
|
||||
];
|
||||
}
|
||||
61
app/ExportResources/User/User.php
Normal file
61
app/ExportResources/User/User.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\ExportResources\User;
|
||||
|
||||
use App\Models\Contact\Contact;
|
||||
use App\ExportResources\ExportResource;
|
||||
use Illuminate\Http\Resources\MissingValue;
|
||||
|
||||
class User extends ExportResource
|
||||
{
|
||||
protected $columns = [
|
||||
'uuid',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'email',
|
||||
'email_verified_at',
|
||||
'google2fa_secret',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
protected $properties = [
|
||||
'locale',
|
||||
'metric',
|
||||
'fluid_container',
|
||||
'contacts_sort_order',
|
||||
'name_order',
|
||||
'dashboard_active_tab',
|
||||
'gifts_active_tab',
|
||||
'profile_active_tab',
|
||||
'timezone',
|
||||
'profile_new_life_event_badge_seen',
|
||||
'temperature_scale',
|
||||
];
|
||||
|
||||
public function data(): ?array
|
||||
{
|
||||
return [
|
||||
'properties' => [
|
||||
$this->mergeWhen($this->currency !== null, [
|
||||
'currency' => $this->currency->iso,
|
||||
]),
|
||||
$this->mergeWhen($this->invited_by_user_id !== null, function () {
|
||||
try {
|
||||
$invited_by_user = Contact::where('account_id', $this->account_id)
|
||||
->findOrFail($this->invited_by_user_id);
|
||||
|
||||
return [
|
||||
'invited_by_user' => $invited_by_user->uuid,
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
return new MissingValue();
|
||||
}
|
||||
}),
|
||||
$this->mergeWhen($this->me !== null, function () {
|
||||
return ['me_contact' => $this->me->uuid];
|
||||
}),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
219
app/Helpers/AccountHelper.php
Normal file
219
app/Helpers/AccountHelper.php
Normal file
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use App\Models\Contact\Gender;
|
||||
use App\Models\Account\Account;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class AccountHelper
|
||||
{
|
||||
/**
|
||||
* Indicates whether the given account has limitations with her current
|
||||
* plan.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function hasLimitations(Account $account): bool
|
||||
{
|
||||
if ($account->has_access_to_paid_version_for_free) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! config('monica.requires_subscription')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($account->isSubscribed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate whether an account has reached the contact limit if the account
|
||||
* is on a free trial.
|
||||
*
|
||||
* @param Account $account
|
||||
* @return bool
|
||||
*/
|
||||
public static function hasReachedContactLimit(Account $account): bool
|
||||
{
|
||||
return $account->allContacts()->real()->active()->count() >= config('monica.number_of_allowed_contacts_free_account');
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate whether an account has not reached the contact limit of free accounts.
|
||||
*
|
||||
* @param Account $account
|
||||
* @return bool
|
||||
*/
|
||||
public static function isBelowContactLimit(Account $account): bool
|
||||
{
|
||||
return $account->allContacts()->real()->active()->count() <= config('monica.number_of_allowed_contacts_free_account');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the account can be downgraded, based on a set of rules.
|
||||
*
|
||||
* @param Account $account
|
||||
* @return bool
|
||||
*/
|
||||
public static function canDowngrade(Account $account): bool
|
||||
{
|
||||
$canDowngrade = true;
|
||||
$numberOfUsers = $account->users()->count();
|
||||
$numberPendingInvitations = $account->invitations()->count();
|
||||
$numberActiveContacts = $account->allContacts()->active()->count();
|
||||
|
||||
// number of users in the account should be == 1
|
||||
if ($numberOfUsers > 1) {
|
||||
$canDowngrade = false;
|
||||
}
|
||||
|
||||
// there should not be any pending user invitations
|
||||
if ($numberPendingInvitations > 0) {
|
||||
$canDowngrade = false;
|
||||
}
|
||||
|
||||
// there should not be more than the number of contacts allowed
|
||||
if ($numberActiveContacts > config('monica.number_of_allowed_contacts_free_account')) {
|
||||
$canDowngrade = false;
|
||||
}
|
||||
|
||||
return $canDowngrade;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default gender for this account.
|
||||
*
|
||||
* @param Account $account
|
||||
* @return string
|
||||
*/
|
||||
public static function getDefaultGender(Account $account): string
|
||||
{
|
||||
$defaultGenderType = Gender::UNKNOWN;
|
||||
|
||||
if ($account->default_gender_id) {
|
||||
$defaultGender = Gender::where([
|
||||
'account_id' => $account->id,
|
||||
])->find($account->default_gender_id);
|
||||
|
||||
if ($defaultGender) {
|
||||
$defaultGenderType = $defaultGender->type;
|
||||
}
|
||||
}
|
||||
|
||||
return $defaultGenderType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the reminders for the month given in parameter.
|
||||
* - 0 means current month
|
||||
* - 1 means month+1
|
||||
* - 2 means month+2...
|
||||
*
|
||||
* @param Account $account
|
||||
* @param int $month
|
||||
*/
|
||||
public static function getUpcomingRemindersForMonth(Account $account, int $month)
|
||||
{
|
||||
$startOfMonth = now(DateHelper::getTimezone())->addMonthsNoOverflow($month)->startOfMonth();
|
||||
|
||||
// don't get reminders for past events:
|
||||
if ($startOfMonth->isPast()) {
|
||||
$startOfMonth = now(DateHelper::getTimezone());
|
||||
}
|
||||
|
||||
$endOfMonth = now(DateHelper::getTimezone())->addMonthsNoOverflow($month)->endOfMonth();
|
||||
|
||||
return $account->reminderOutboxes()
|
||||
->with(['reminder', 'reminder.contact'])
|
||||
->whereBetween('planned_date', [$startOfMonth, $endOfMonth])
|
||||
->where([
|
||||
'user_id' => auth()->user()->id,
|
||||
'nature' => 'reminder',
|
||||
])
|
||||
->orderBy('planned_date', 'asc')
|
||||
->get()
|
||||
->filter(function ($reminderOutbox) {
|
||||
return $reminderOutbox->reminder->contact !== null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of activities grouped by year.
|
||||
*
|
||||
* @param Account $account
|
||||
* @return Collection
|
||||
*/
|
||||
public static function getYearlyActivitiesStatistics(Account $account): Collection
|
||||
{
|
||||
$activitiesStatistics = collect([]);
|
||||
$activities = $account->activities()
|
||||
->select('happened_at')
|
||||
->latest('happened_at')
|
||||
->get();
|
||||
$years = [];
|
||||
|
||||
foreach ($activities as $activity) {
|
||||
$yearStatistic = $activity->happened_at->format('Y');
|
||||
$foundInYear = false;
|
||||
|
||||
foreach ($years as $year => $number) {
|
||||
if ($year == $yearStatistic) {
|
||||
$years[$year] = $number + 1;
|
||||
$foundInYear = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $foundInYear) {
|
||||
$years[$yearStatistic] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($years as $year => $number) {
|
||||
$activitiesStatistics->put($year, $number);
|
||||
}
|
||||
|
||||
return $activitiesStatistics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of calls grouped by year.
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public static function getYearlyCallStatistics(Account $account): Collection
|
||||
{
|
||||
$callsStatistics = collect([]);
|
||||
$calls = $account->calls()
|
||||
->select('called_at')
|
||||
->latest('called_at')
|
||||
->get();
|
||||
$years = [];
|
||||
|
||||
foreach ($calls as $call) {
|
||||
$yearStatistic = $call->called_at->format('Y');
|
||||
$foundInYear = false;
|
||||
|
||||
foreach ($years as $year => $number) {
|
||||
if ($year == $yearStatistic) {
|
||||
$years[$year] = $number + 1;
|
||||
$foundInYear = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $foundInYear) {
|
||||
$years[$yearStatistic] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($years as $year => $number) {
|
||||
$callsStatistics->put($year, $number);
|
||||
}
|
||||
|
||||
return $callsStatistics;
|
||||
}
|
||||
}
|
||||
53
app/Helpers/AuditLogHelper.php
Normal file
53
app/Helpers/AuditLogHelper.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use App\Models\Contact\Contact;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
|
||||
class AuditLogHelper
|
||||
{
|
||||
/**
|
||||
* Prepare a collection of audit logs that is displayed on the Settings page.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Pagination\LengthAwarePaginator|Collection<array-key, \App\Models\Instance\AuditLog> $logs
|
||||
* @return Collection
|
||||
*/
|
||||
public static function getCollectionOfAudits($logs): Collection
|
||||
{
|
||||
$logsCollection = collect();
|
||||
|
||||
foreach ($logs as $log) {
|
||||
$object = null;
|
||||
$link = null;
|
||||
|
||||
// the log is about a contact
|
||||
if (isset($log->object->{'contact_id'})) {
|
||||
try {
|
||||
// check if the contact that the log is about still exists
|
||||
// in that case, we will display a link to point to this contact
|
||||
$contact = Contact::findOrFail($log->object->{'contact_id'});
|
||||
$object = $contact->name;
|
||||
$link = route('people.show', ['contact' => $contact]);
|
||||
} catch (ModelNotFoundException $e) {
|
||||
// the contact doesn't exist anymore, we don't need a link, we'll only display a name
|
||||
$object = $log->object->{'contact_name'};
|
||||
}
|
||||
$description = trans('logs.settings_log_'.$log->action.'_with_name', ['name' => $object]);
|
||||
} else {
|
||||
$description = trans('logs.settings_log_'.$log->action, ['name' => $log->object->{'name'}]);
|
||||
}
|
||||
|
||||
$logsCollection->push([
|
||||
'author_name' => ($log->author) ? $log->author->name : $log->author_name,
|
||||
'description' => $description,
|
||||
'link' => $link,
|
||||
'object' => $object,
|
||||
'audited_at' => $log->audited_at,
|
||||
]);
|
||||
}
|
||||
|
||||
return $logsCollection;
|
||||
}
|
||||
}
|
||||
106
app/Helpers/CollectionHelper.php
Normal file
106
app/Helpers/CollectionHelper.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class CollectionHelper
|
||||
{
|
||||
/**
|
||||
* Sort the collection using the given callback.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection $collect
|
||||
* @param callable|string $callback
|
||||
* @param int $options
|
||||
* @param bool $descending
|
||||
* @return Collection
|
||||
*/
|
||||
public static function sortByCollator($collect, $callback, $options = \Collator::SORT_STRING, $descending = false)
|
||||
{
|
||||
$results = [];
|
||||
|
||||
$callback = static::valueRetriever($callback);
|
||||
|
||||
// First we will loop through the items and get the comparator from a callback
|
||||
// function which we were given. Then, we will sort the returned values and
|
||||
// and grab the corresponding values for the sorted keys from this array.
|
||||
foreach ($collect->all() as $key => $value) {
|
||||
$results[$key] = $callback($value, $key);
|
||||
}
|
||||
|
||||
// Using Collator to sort the array, with locale-sensitive sort ordering support.
|
||||
static::getCollator()->asort($results, $options);
|
||||
if ($descending) {
|
||||
$results = array_reverse($results);
|
||||
}
|
||||
|
||||
// Once we have sorted all of the keys in the array, we will loop through them
|
||||
// and grab the corresponding model so we can set the underlying items list
|
||||
// to the sorted version. Then we'll just return the collection instance.
|
||||
foreach (array_keys($results) as $key) {
|
||||
$results[$key] = $collect->get($key);
|
||||
}
|
||||
|
||||
return new Collection($results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Collator object for the locale or current locale.
|
||||
*
|
||||
* @param string $locale
|
||||
* @return \Collator
|
||||
*/
|
||||
public static function getCollator($locale = null)
|
||||
{
|
||||
static $collators = [];
|
||||
|
||||
if (! $locale) {
|
||||
$locale = app()->getLocale();
|
||||
}
|
||||
if (! Arr::has($collators, $locale)) {
|
||||
$collator = new \Collator($locale);
|
||||
|
||||
if (LocaleHelper::getLang($locale) == 'fr') {
|
||||
$collator->setAttribute(\Collator::FRENCH_COLLATION, \Collator::ON);
|
||||
}
|
||||
|
||||
$collators[$locale] = $collator;
|
||||
|
||||
return $collator;
|
||||
}
|
||||
|
||||
return $collators[$locale];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value retrieving callback.
|
||||
*
|
||||
* @param string|callable $value
|
||||
* @return callable
|
||||
*/
|
||||
private static function valueRetriever($value)
|
||||
{
|
||||
if (! is_string($value) && is_callable($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return function ($item) use ($value) {
|
||||
return data_get($item, $value);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Group collection based on a specific property from its items.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection $collection
|
||||
* @param string $property
|
||||
* @return mixed
|
||||
*/
|
||||
public static function groupByItemsProperty($collection, $property)
|
||||
{
|
||||
return $collection->mapToGroups(function ($item) use ($property) {
|
||||
return [data_get($item, $property) => $item];
|
||||
});
|
||||
}
|
||||
}
|
||||
45
app/Helpers/ComplianceHelper.php
Normal file
45
app/Helpers/ComplianceHelper.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use App\Models\User\User;
|
||||
use App\Models\Settings\Term;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ComplianceHelper
|
||||
{
|
||||
/**
|
||||
* Give the status for the given term for the given user.
|
||||
*
|
||||
* @param User $user
|
||||
* @param Term $term
|
||||
* @return bool
|
||||
*/
|
||||
public static function hasSignedGivenTerm(User $user, Term $term): bool
|
||||
{
|
||||
$termUser = DB::table('term_user')->where('user_id', $user->id)
|
||||
->where('account_id', $user->account_id)
|
||||
->where('term_id', $term->id)
|
||||
->first();
|
||||
|
||||
if (! $termUser) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate if the user has accepted the most recent terms and privacy.
|
||||
* This really is a shortcut of the `hasSignedGivenTerm` method.
|
||||
*
|
||||
* @param User $user
|
||||
* @return bool
|
||||
*/
|
||||
public static function isCompliantWithCurrentTerm(User $user): bool
|
||||
{
|
||||
$latestTerm = Term::latest()->first();
|
||||
|
||||
return self::hasSignedGivenTerm($user, $latestTerm);
|
||||
}
|
||||
}
|
||||
58
app/Helpers/ComposerScripts.php
Normal file
58
app/Helpers/ComposerScripts.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
class ComposerScripts
|
||||
{
|
||||
const CONFIG = 'bootstrap/cache/config.php';
|
||||
|
||||
/**
|
||||
* Handle the pre-install Composer event.
|
||||
*
|
||||
* @param mixed $event
|
||||
* @return void
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public static function preInstall($event)
|
||||
{
|
||||
try {
|
||||
if (file_exists('vendor')) {
|
||||
\Illuminate\Foundation\ComposerScripts::postInstall($event);
|
||||
}
|
||||
static::clear();
|
||||
} catch (\Throwable $e) {
|
||||
// catch all
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the pre-update Composer event.
|
||||
*
|
||||
* @param mixed $event
|
||||
* @return void
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public static function preUpdate($event)
|
||||
{
|
||||
try {
|
||||
if (file_exists('vendor')) {
|
||||
\Illuminate\Foundation\ComposerScripts::postUpdate($event);
|
||||
}
|
||||
static::clear();
|
||||
} catch (\Throwable $e) {
|
||||
// catch all
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
protected static function clear()
|
||||
{
|
||||
if (file_exists(self::CONFIG)) {
|
||||
\unlink(self::CONFIG); /** @phpstan-ignore-line */
|
||||
}
|
||||
}
|
||||
}
|
||||
203
app/Helpers/CountriesHelper.php
Normal file
203
app/Helpers/CountriesHelper.php
Normal file
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use Rinvex\Country\Country;
|
||||
use Rinvex\Country\CountryLoader;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
class CountriesHelper
|
||||
{
|
||||
/**
|
||||
* Get list of countries.
|
||||
*
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public static function getAll(): Collection
|
||||
{
|
||||
$x = collect(countries(true, true));
|
||||
$countries = $x->map(function (Country $item) {
|
||||
return [
|
||||
'id' => $item->getIsoAlpha2(),
|
||||
'country' => static::getCommonNameLocale($item),
|
||||
];
|
||||
});
|
||||
|
||||
return collect($countries->sortByCollator('country'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get country name.
|
||||
*
|
||||
* @param string $iso code of the country
|
||||
* @return string common name (localized) of the country
|
||||
*/
|
||||
public static function get($iso): string
|
||||
{
|
||||
$country = self::getCountry($iso);
|
||||
if (is_null($country)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return static::getCommonNameLocale($country);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a country by the (english) name of the country.
|
||||
*
|
||||
* @param string $name Common name of a country
|
||||
* @return string iso_3166_1_alpha2 code of the country
|
||||
*/
|
||||
public static function find($name): string
|
||||
{
|
||||
$country = collect(CountryLoader::where('name.common', $name));
|
||||
if ($country->count() === 0) {
|
||||
$country = collect(CountryLoader::where('iso_3166_1_alpha2', mb_strtoupper($name)));
|
||||
}
|
||||
if ($country->count() === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return (new Country($country->first()))->getIsoAlpha2();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the common name of country, in locale version.
|
||||
*
|
||||
* @param \Rinvex\Country\Country $country
|
||||
* @return string
|
||||
*/
|
||||
private static function getCommonNameLocale(Country $country): string
|
||||
{
|
||||
$locale = App::getLocale();
|
||||
$lang = LocaleHelper::getLocaleAlpha($locale);
|
||||
|
||||
return $country->getTranslation($lang)['common'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get country for a specific iso code.
|
||||
*
|
||||
* @param string $iso
|
||||
* @return \Rinvex\Country\Country|null the Country element
|
||||
*/
|
||||
public static function getCountry($iso): ?Country
|
||||
{
|
||||
$country = collect(CountryLoader::where('iso_3166_1_alpha2', mb_strtoupper($iso)));
|
||||
if ($country->count() === 0) {
|
||||
$country = collect(CountryLoader::where('alt_spellings', mb_strtoupper($iso)));
|
||||
}
|
||||
if ($country->count() === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Country($country->first());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get country for a specific language.
|
||||
*
|
||||
* @param string $locale language code (iso)
|
||||
* @return \Rinvex\Country\Country|null the Country element
|
||||
*/
|
||||
public static function getCountryFromLocale($locale): ?Country
|
||||
{
|
||||
$countryCode = LocaleHelper::extractCountry($locale);
|
||||
if (empty($countryCode)) {
|
||||
$countryCode = self::getDefaultCountryFromLocale($locale);
|
||||
}
|
||||
|
||||
if (is_null($countryCode)) {
|
||||
$lang = LocaleHelper::getLocaleAlpha($locale);
|
||||
$country = collect(CountryLoader::where("languages.$lang", '>', '0'));
|
||||
if ($country->count() === 0) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
$country = collect(CountryLoader::where('iso_3166_1_alpha2', mb_strtoupper($countryCode)));
|
||||
}
|
||||
|
||||
return new Country($country->first());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default country for a language.
|
||||
*
|
||||
* @param string $locale language code (iso)
|
||||
* @return string|null iso_3166_1_alpha2 code
|
||||
*/
|
||||
private static function getDefaultCountryFromLocale($locale): ?string
|
||||
{
|
||||
switch (mb_strtolower($locale)) {
|
||||
case 'cs':
|
||||
$country = 'CZ';
|
||||
break;
|
||||
case 'en':
|
||||
$country = 'US';
|
||||
break;
|
||||
case 'he':
|
||||
$country = 'IL';
|
||||
break;
|
||||
case 'zh':
|
||||
$country = 'CN';
|
||||
break;
|
||||
case 'de':
|
||||
case 'es':
|
||||
case 'fr':
|
||||
case 'hr':
|
||||
case 'it':
|
||||
case 'nl':
|
||||
case 'pt':
|
||||
case 'ru':
|
||||
case 'tr':
|
||||
$country = mb_strtoupper($locale);
|
||||
break;
|
||||
default:
|
||||
$country = null;
|
||||
break;
|
||||
}
|
||||
|
||||
return $country;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default timezone for the country.
|
||||
*
|
||||
* @param mixed $country Country element
|
||||
* @return string timezone fo this sountry
|
||||
*/
|
||||
public static function getDefaultTimezone($country): string
|
||||
{
|
||||
// https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
// https://en.wikipedia.org/wiki/List_of_time_zones_by_country
|
||||
switch ($country->getIsoAlpha3()) {
|
||||
case 'AUS':
|
||||
$timezone = 'Australia/Melbourne';
|
||||
break;
|
||||
case 'CHN':
|
||||
$timezone = 'Asia/Shanghai';
|
||||
break;
|
||||
case 'ESP':
|
||||
$timezone = 'Europe/Madrid';
|
||||
break;
|
||||
case 'PRT':
|
||||
$timezone = 'Europe/Lisbon';
|
||||
break;
|
||||
case 'RUS':
|
||||
$timezone = 'Europe/Moscow';
|
||||
break;
|
||||
case 'CAN':
|
||||
$timezone = 'America/Toronto';
|
||||
break;
|
||||
case 'USA':
|
||||
$timezone = 'America/Chicago';
|
||||
break;
|
||||
default:
|
||||
$timezone = collect($country->getTimezones())->first();
|
||||
break;
|
||||
}
|
||||
|
||||
return $timezone ?? config('app.timezone');
|
||||
}
|
||||
}
|
||||
67
app/Helpers/DBHelper.php
Normal file
67
app/Helpers/DBHelper.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use PDO;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Database\Connection;
|
||||
|
||||
class DBHelper
|
||||
{
|
||||
/**
|
||||
* Get connection.
|
||||
*
|
||||
* @param string $name
|
||||
* @return \Illuminate\Database\Connection
|
||||
*/
|
||||
public static function connection($name = null): Connection
|
||||
{
|
||||
return DB::connection($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the version of DB engine.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public static function version(): ?string
|
||||
{
|
||||
try {
|
||||
return static::connection()->getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION);
|
||||
} catch (\Exception $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if db version if greater than $version param.
|
||||
*
|
||||
* @param string $version
|
||||
* @return bool
|
||||
*/
|
||||
public static function testVersion($version)
|
||||
{
|
||||
return version_compare(static::version(), $version) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of tables on this instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getTables()
|
||||
{
|
||||
return DB::select('SELECT table_name as `table_name`
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = :table_schema
|
||||
AND table_name LIKE :table_prefix', [
|
||||
'table_schema' => static::connection()->getDatabaseName(),
|
||||
'table_prefix' => '%'.static::connection()->getTablePrefix().'%',
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getTable($name)
|
||||
{
|
||||
return '`'.static::connection()->getTablePrefix().$name.'`';
|
||||
}
|
||||
}
|
||||
366
app/Helpers/DateHelper.php
Normal file
366
app/Helpers/DateHelper.php
Normal file
@@ -0,0 +1,366 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use function Safe\date;
|
||||
use function Safe\strtotime;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class DateHelper
|
||||
{
|
||||
/**
|
||||
* Creates a Carbon object from DateTime format.
|
||||
* If timezone is given, it parse the date with this timezone.
|
||||
* Always return a date with default timezone (UTC).
|
||||
*
|
||||
* @param \DateTime|Carbon|string|null $date
|
||||
* @param string $timezone
|
||||
* @return Carbon|null
|
||||
*/
|
||||
public static function parseDateTime($date, $timezone = null): ?Carbon
|
||||
{
|
||||
if (is_null($date)) {
|
||||
return null;
|
||||
}
|
||||
if ($date instanceof Carbon) {
|
||||
// ok
|
||||
} elseif ($date instanceof \DateTimeInterface) {
|
||||
$date = Carbon::instance($date);
|
||||
} else {
|
||||
try {
|
||||
$date = Carbon::parse($date, $timezone);
|
||||
} catch (\Exception $e) {
|
||||
// Parse error
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
$appTimezone = config('app.timezone');
|
||||
if ($date->timezone !== $appTimezone) {
|
||||
$date->setTimezone($appTimezone);
|
||||
}
|
||||
|
||||
return $date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Carbon object from Date format.
|
||||
* If timezone is given, it parse the date with this timezone.
|
||||
* Always return a date with default timezone (UTC).
|
||||
*
|
||||
* @param Carbon|string $date
|
||||
* @param string $timezone
|
||||
* @return Carbon|null
|
||||
*/
|
||||
public static function parseDate($date, $timezone = null): ?Carbon
|
||||
{
|
||||
if (! $date instanceof Carbon) {
|
||||
try {
|
||||
$date = Carbon::parse($date);
|
||||
} catch (\Exception $e) {
|
||||
// Parse error
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
$date = Carbon::create($date->year, $date->month, $date->day, 0, 0, 0, $timezone ?? $date->timezone);
|
||||
|
||||
$appTimezone = config('app.timezone');
|
||||
if ($date->timezone !== $appTimezone) {
|
||||
$date->setTimezone($appTimezone);
|
||||
}
|
||||
|
||||
return $date === false ? null : $date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return timestamp date format.
|
||||
*
|
||||
* @param Carbon|\App\Models\Instance\SpecialDate|string|null $date
|
||||
* @return string|null
|
||||
*/
|
||||
public static function getTimestamp($date): ?string
|
||||
{
|
||||
if (is_null($date)) {
|
||||
return null;
|
||||
}
|
||||
if ($date instanceof \App\Models\Instance\SpecialDate) {
|
||||
$date = $date->date;
|
||||
}
|
||||
if (! $date instanceof Carbon) {
|
||||
$date = Carbon::parse($date);
|
||||
}
|
||||
|
||||
return $date->translatedFormat(config('api.timestamp_format'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return date timestamp format.
|
||||
*
|
||||
* @param Carbon|\App\Models\Instance\SpecialDate|string|null $date
|
||||
* @return string|null
|
||||
*/
|
||||
public static function getDate($date): ?string
|
||||
{
|
||||
if (is_null($date)) {
|
||||
return null;
|
||||
}
|
||||
if ($date instanceof \App\Models\Instance\SpecialDate) {
|
||||
$date = $date->date;
|
||||
}
|
||||
if (! $date instanceof Carbon) {
|
||||
$date = Carbon::parse($date);
|
||||
}
|
||||
|
||||
return $date->translatedFormat(config('api.date_timestamp_format'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the timezone of the current user, or null.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public static function getTimezone(): ?string
|
||||
{
|
||||
return Auth::check() ? Auth::user()->timezone : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a date in a short format like "Oct 29, 1981".
|
||||
*
|
||||
* @param Carbon $date
|
||||
* @return string
|
||||
*/
|
||||
public static function getShortDate(Carbon $date): string
|
||||
{
|
||||
return self::formatDate($date, 'format.short_date_year');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a date in a full format like "October 29, 1981".
|
||||
*
|
||||
* @param Carbon $date
|
||||
* @return string
|
||||
*/
|
||||
public static function getFullDate(Carbon $date): string
|
||||
{
|
||||
return self::formatDate($date, 'format.full_date_year');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the month of the date like "Oct", or "Dec".
|
||||
*
|
||||
* @param Carbon $date
|
||||
* @return string
|
||||
*/
|
||||
public static function getShortMonth(Carbon $date): string
|
||||
{
|
||||
return self::formatDate($date, 'format.short_month');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the month and year of the date like "October 2010",
|
||||
* or "March 2032".
|
||||
*
|
||||
* @param Carbon $date
|
||||
* @return string
|
||||
*/
|
||||
public static function getFullMonthAndDate(Carbon $date): string
|
||||
{
|
||||
return self::formatDate($date, 'format.full_month_year');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the day of the date like "Mon", or "Wed".
|
||||
*
|
||||
* @param Carbon $date
|
||||
* @return string
|
||||
*/
|
||||
public static function getShortDay(Carbon $date): string
|
||||
{
|
||||
return self::formatDate($date, 'format.short_day');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a date in a short format
|
||||
* like "Oct 29".
|
||||
*
|
||||
* @param Carbon $date
|
||||
* @return string
|
||||
*/
|
||||
public static function getShortDateWithoutYear(Carbon $date): string
|
||||
{
|
||||
return self::formatDate($date, 'format.short_date');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a date and the time according to the timezone of the user, in a short format
|
||||
* like "Oct 29, 1981 19:32".
|
||||
*
|
||||
* @param Carbon $date
|
||||
* @return string
|
||||
*/
|
||||
public static function getShortDateWithTime(Carbon $date): string
|
||||
{
|
||||
return self::formatDate($date, 'format.short_date_year_time', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a date in a given format.
|
||||
*
|
||||
* @param Carbon $date
|
||||
* @param string $format
|
||||
* @param bool $withTimezone
|
||||
* @return string
|
||||
*/
|
||||
private static function formatDate(Carbon $date, string $format, bool $withTimezone = false): string
|
||||
{
|
||||
$format = trans($format, [], Carbon::getLocale());
|
||||
if ($withTimezone) {
|
||||
$date = $date->setTimezone(static::getTimezone());
|
||||
}
|
||||
|
||||
return $date->translatedFormat($format) ?: '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a given number of week/month/year to a date.
|
||||
*
|
||||
* @param Carbon $date the start date
|
||||
* @param string $frequency week/month/year
|
||||
* @param int $number the number of week/month/year to increment to
|
||||
* @return Carbon
|
||||
*/
|
||||
public static function addTimeAccordingToFrequencyType(Carbon $date, string $frequency, int $number): Carbon
|
||||
{
|
||||
switch ($frequency) {
|
||||
case 'week':
|
||||
$date = $date->addWeeks($number);
|
||||
break;
|
||||
case 'month':
|
||||
$date = $date->addMonths($number);
|
||||
break;
|
||||
default:
|
||||
$date = $date->addYears($number);
|
||||
break;
|
||||
}
|
||||
|
||||
return $date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the month and year of a given date with a given number
|
||||
* of months more.
|
||||
*
|
||||
* @param int $month
|
||||
* @return string
|
||||
*/
|
||||
public static function getMonthAndYear(int $month): string
|
||||
{
|
||||
$date = Carbon::now(static::getTimezone())->addMonthsNoOverflow($month);
|
||||
$format = trans('format.short_month_year', [], Carbon::getLocale());
|
||||
|
||||
return $date->translatedFormat($format) ?: '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next theoritical billing date.
|
||||
* This is used on the Upgrade page to tell the user when the next billing
|
||||
* date would be if he subscribed.
|
||||
*
|
||||
* @param string $interval
|
||||
* @return Carbon
|
||||
*/
|
||||
public static function getNextTheoriticalBillingDate(string $interval): Carbon
|
||||
{
|
||||
if ($interval == 'monthly') {
|
||||
return now(static::getTimezone())->addMonth();
|
||||
}
|
||||
|
||||
return now(static::getTimezone())->addYear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of all the year from min to max (0 is the current year).
|
||||
*
|
||||
* @param int $max
|
||||
* @param int $min
|
||||
* @return Collection
|
||||
*/
|
||||
public static function getListOfYears($max = 120, $min = 0): Collection
|
||||
{
|
||||
$years = collect([]);
|
||||
$maxYear = now(static::getTimezone())->subYears($min)->year;
|
||||
$minYear = now(static::getTimezone())->subYears($max)->year;
|
||||
|
||||
for ($year = $maxYear; $year >= $minYear; $year--) {
|
||||
$years->push([
|
||||
'id' => $year,
|
||||
'name' => $year,
|
||||
]);
|
||||
}
|
||||
|
||||
return $years;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of all the months in a year.
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public static function getListOfMonths(): Collection
|
||||
{
|
||||
$months = collect([]);
|
||||
$currentDate = Carbon::parse('2000-01-01');
|
||||
$format = trans('format.full_month', [], Carbon::getLocale());
|
||||
|
||||
for ($month = 1; $month <= 12; $month++) {
|
||||
$currentDate->month = $month;
|
||||
$months->push([
|
||||
'id' => $month,
|
||||
'name' => mb_convert_case($currentDate->translatedFormat($format), MB_CASE_TITLE, 'UTF-8'),
|
||||
]);
|
||||
}
|
||||
|
||||
return $months;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of all the days in a month.
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public static function getListOfDays(): Collection
|
||||
{
|
||||
$days = collect([]);
|
||||
for ($day = 1; $day <= 31; $day++) {
|
||||
$days->push(['id' => $day, 'name' => $day]);
|
||||
}
|
||||
|
||||
return $days;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of all the hours in a day.
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public static function getListOfHours(): Collection
|
||||
{
|
||||
$currentDate = Carbon::parse('2000-01-01 00:00:00');
|
||||
$format = trans('format.full_hour', [], Carbon::getLocale());
|
||||
|
||||
$hours = collect([]);
|
||||
for ($hour = 1; $hour <= 24; $hour++) {
|
||||
$currentDate->hour = $hour;
|
||||
$hours->push([
|
||||
'id' => date('H:i', strtotime("$hour:00")),
|
||||
'name' => $currentDate->translatedFormat($format),
|
||||
]);
|
||||
}
|
||||
|
||||
return $hours;
|
||||
}
|
||||
}
|
||||
31
app/Helpers/FormHelper.php
Normal file
31
app/Helpers/FormHelper.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use App\Models\User\User;
|
||||
|
||||
class FormHelper
|
||||
{
|
||||
/**
|
||||
* Get the name order that will be used when rendered the Add/Edit forms
|
||||
* about contacts.
|
||||
*
|
||||
* @param User $user
|
||||
* @return string
|
||||
*/
|
||||
public static function getNameOrderForForms(User $user): string
|
||||
{
|
||||
$nameOrder = 'firstname';
|
||||
|
||||
switch ($user->name_order) {
|
||||
case 'lastname_firstname':
|
||||
case 'lastname_firstname_nickname':
|
||||
case 'lastname_nickname_firstname':
|
||||
case 'nickname_lastname_firstname':
|
||||
$nameOrder = 'lastname';
|
||||
break;
|
||||
}
|
||||
|
||||
return $nameOrder;
|
||||
}
|
||||
}
|
||||
48
app/Helpers/GenderHelper.php
Normal file
48
app/Helpers/GenderHelper.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use App\Models\Contact\Gender;
|
||||
use App\Models\Account\Account;
|
||||
use App\Models\Contact\Contact;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class GenderHelper
|
||||
{
|
||||
/**
|
||||
* Return a collection of genders.
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public static function getGendersInput()
|
||||
{
|
||||
$genders = auth()->user()->account->genders->map(function (Gender $gender): array {
|
||||
return [
|
||||
'id' => $gender->id,
|
||||
'name' => $gender->name,
|
||||
];
|
||||
});
|
||||
$genders = CollectionHelper::sortByCollator($genders, 'name');
|
||||
$genders->prepend(['id' => '', 'name' => trans('app.gender_no_gender')]);
|
||||
|
||||
return $genders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces a specific gender of all the contacts in the account with another
|
||||
* gender.
|
||||
*
|
||||
* @param Account $account
|
||||
* @param Gender $genderToDelete
|
||||
* @param Gender $genderToReplaceWith
|
||||
* @return bool
|
||||
*/
|
||||
public static function replace(Account $account, Gender $genderToDelete, Gender $genderToReplaceWith): bool
|
||||
{
|
||||
Contact::where('account_id', $account->id)
|
||||
->where('gender_id', $genderToDelete->id)
|
||||
->update(['gender_id' => $genderToReplaceWith->id]);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
118
app/Helpers/InstanceHelper.php
Normal file
118
app/Helpers/InstanceHelper.php
Normal file
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use function Safe\json_decode;
|
||||
use App\Models\Account\Account;
|
||||
use App\Models\Instance\Instance;
|
||||
use App\Models\Settings\Currency;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use function Safe\file_get_contents;
|
||||
|
||||
class InstanceHelper
|
||||
{
|
||||
/**
|
||||
* Get the number of paid accounts in the instance.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getNumberOfPaidSubscribers()
|
||||
{
|
||||
return Account::where('stripe_id', '!=', null)->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the plan information for the given time period.
|
||||
*
|
||||
* @param string $timePeriod Accepted values: 'monthly', 'annual'
|
||||
* @return array|null
|
||||
*/
|
||||
public static function getPlanInformationFromConfig(string $timePeriod): ?array
|
||||
{
|
||||
$timePeriod = strtolower($timePeriod);
|
||||
|
||||
if ($timePeriod != 'monthly' && $timePeriod != 'annual') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$currency = Currency::where('iso', strtoupper(config('cashier.currency')))->first();
|
||||
$amount = MoneyHelper::format(config('monica.paid_plan_'.$timePeriod.'_price'), $currency);
|
||||
|
||||
return [
|
||||
'type' => $timePeriod,
|
||||
'name' => config('monica.paid_plan_'.$timePeriod.'_friendly_name'),
|
||||
'id' => config('monica.paid_plan_'.$timePeriod.'_id'),
|
||||
'price' => config('monica.paid_plan_'.$timePeriod.'_price'),
|
||||
'friendlyPrice' => $amount,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the plan information for the given time period.
|
||||
*
|
||||
* @param \Laravel\Cashier\Subscription $subscription
|
||||
* @return array|null
|
||||
*/
|
||||
public static function getPlanInformationFromSubscription(\Laravel\Cashier\Subscription $subscription): ?array
|
||||
{
|
||||
try {
|
||||
$stripeSubscription = $subscription->asStripeSubscription();
|
||||
$plan = $stripeSubscription->plan;
|
||||
} catch (\Stripe\Exception\ApiErrorException $e) {
|
||||
$stripeSubscription = null;
|
||||
$plan = null;
|
||||
}
|
||||
|
||||
if (is_null($stripeSubscription) || is_null($plan)) {
|
||||
return [
|
||||
'type' => $subscription->stripe_price,
|
||||
'name' => $subscription->name,
|
||||
'id' => $subscription->stripe_id,
|
||||
'price' => '?',
|
||||
'friendlyPrice' => '?',
|
||||
'nextBillingDate' => '',
|
||||
];
|
||||
}
|
||||
|
||||
$currency = Currency::where('iso', strtoupper($plan->currency))->first();
|
||||
$amount = MoneyHelper::format($plan->amount, $currency);
|
||||
|
||||
return [
|
||||
'type' => $plan->interval === 'month' ? 'monthly' : 'annual',
|
||||
'name' => $subscription->name,
|
||||
'id' => $plan->id,
|
||||
'price' => $plan->amount,
|
||||
'friendlyPrice' => $amount,
|
||||
'nextBillingDate' => DateHelper::getFullDate(Carbon::createFromTimestamp($stripeSubscription->current_period_end)),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get changelogs entries.
|
||||
*
|
||||
* @param int $limit
|
||||
* @return array
|
||||
*/
|
||||
public static function getChangelogEntries($limit = null)
|
||||
{
|
||||
$json = public_path('changelog.json');
|
||||
$changelogs = json_decode(file_get_contents($json), true)['entries'];
|
||||
|
||||
if ($limit) {
|
||||
$changelogs = array_slice($changelogs, 0, $limit);
|
||||
}
|
||||
|
||||
return $changelogs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the instance has at least one account.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function hasAtLeastOneAccount(): bool
|
||||
{
|
||||
return DB::table('accounts')->count() > 0;
|
||||
}
|
||||
}
|
||||
29
app/Helpers/JournalHelper.php
Normal file
29
app/Helpers/JournalHelper.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use App\Models\User\User;
|
||||
use App\Models\Journal\Day;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
|
||||
class JournalHelper
|
||||
{
|
||||
/**
|
||||
* Get the number of paid accounts in the instance.
|
||||
*
|
||||
* @param User $user
|
||||
* @return bool
|
||||
*/
|
||||
public static function hasAlreadyRatedToday(User $user): bool
|
||||
{
|
||||
try {
|
||||
Day::where('account_id', $user->account_id)
|
||||
->where('date', now($user->timezone)->toDateString())
|
||||
->firstOrFail();
|
||||
} catch (ModelNotFoundException $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
210
app/Helpers/LocaleHelper.php
Normal file
210
app/Helpers/LocaleHelper.php
Normal file
@@ -0,0 +1,210 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use Matriphe\ISO639\ISO639;
|
||||
use function Safe\preg_match;
|
||||
use function Safe\preg_split;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use libphonenumber\PhoneNumberUtil;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use libphonenumber\PhoneNumberFormat;
|
||||
use libphonenumber\NumberParseException;
|
||||
|
||||
class LocaleHelper
|
||||
{
|
||||
private const LANG_SPLIT = '/(-|_)/';
|
||||
|
||||
/**
|
||||
* Get the current or default locale.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getLocale()
|
||||
{
|
||||
if (Auth::check()) {
|
||||
$locale = Auth::user()->locale;
|
||||
} else {
|
||||
$locale = app('language.detector')->detect() ?: config('app.locale');
|
||||
}
|
||||
|
||||
return $locale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current lang from locale.
|
||||
*
|
||||
* @return string lang, lowercase form.
|
||||
*/
|
||||
public static function getLang($locale = null)
|
||||
{
|
||||
if (is_null($locale)) {
|
||||
$locale = App::getLocale();
|
||||
}
|
||||
if (preg_match(self::LANG_SPLIT, $locale)) {
|
||||
$locale = preg_split(self::LANG_SPLIT, $locale, 2)[0];
|
||||
}
|
||||
|
||||
return mb_strtolower($locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current country from locale.
|
||||
*
|
||||
* @return string country, uppercase form.
|
||||
*/
|
||||
public static function getCountry($locale = null)
|
||||
{
|
||||
$countryCode = self::extractCountry($locale);
|
||||
|
||||
if (is_null($countryCode)) {
|
||||
$country = CountriesHelper::getCountryFromLocale($locale);
|
||||
$countryCode = $country->getIsoAlpha2();
|
||||
}
|
||||
|
||||
return mb_strtoupper($countryCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the current country from locale, i.e. 'en-US' will return 'US'.
|
||||
* If no country is present in the locale, it will return null.
|
||||
*
|
||||
* @return string|null country, uppercase form.
|
||||
*/
|
||||
public static function extractCountry($locale = null): ?string
|
||||
{
|
||||
if (is_null($locale)) {
|
||||
$locale = App::getLocale();
|
||||
}
|
||||
if (preg_match(self::LANG_SPLIT, $locale)) {
|
||||
$locale = preg_split(self::LANG_SPLIT, $locale, 2)[1];
|
||||
|
||||
return mb_strtoupper($locale);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of avalaible languages.
|
||||
*
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public static function getLocaleList()
|
||||
{
|
||||
return collect(config('lang-detector.languages'))->map(function (string $lang): array {
|
||||
return [
|
||||
'lang' => $lang,
|
||||
'name' => self::getLocaleName($lang),
|
||||
'name-orig' => self::getLocaleName($lang, $lang),
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of one language.
|
||||
*
|
||||
* @param string $lang
|
||||
* @param string $locale
|
||||
* @return string
|
||||
*/
|
||||
private static function getLocaleName($lang, $locale = null): string
|
||||
{
|
||||
$name = trans('settings.locale_'.$lang, [], $locale);
|
||||
if ($name == 'settings.locale_'.$lang) {
|
||||
// The name of the new language is not already set, even in english
|
||||
$name = $lang;
|
||||
}
|
||||
|
||||
return (string) Str::of($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the direction: left to right/right to left.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getDirection()
|
||||
{
|
||||
$lang = self::getLang();
|
||||
switch ($lang) {
|
||||
// Source: https://meta.wikimedia.org/wiki/Template:List_of_language_names_ordered_by_code
|
||||
case 'ar':
|
||||
case 'arc':
|
||||
case 'dv':
|
||||
case 'fa':
|
||||
case 'ha':
|
||||
case 'he':
|
||||
case 'khw':
|
||||
case 'ks':
|
||||
case 'ku':
|
||||
case 'ps':
|
||||
case 'ur':
|
||||
case 'yi':
|
||||
return 'rtl';
|
||||
default:
|
||||
return 'ltr';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Association ISO-639-1 => ISO-639-2.
|
||||
*
|
||||
* @var array<string,string>
|
||||
*/
|
||||
private static $locales = [];
|
||||
|
||||
/**
|
||||
* Get ISO-639-2/t (three-letter codes) from ISO-639-1 (two-letters code).
|
||||
*
|
||||
* @param string $locale
|
||||
* @return string
|
||||
*/
|
||||
public static function getLocaleAlpha($locale)
|
||||
{
|
||||
if (Arr::has(static::$locales, $locale)) {
|
||||
return Arr::get(static::$locales, $locale);
|
||||
}
|
||||
$locale = mb_strtolower($locale);
|
||||
$languages = (new ISO639)->allLanguages();
|
||||
$lang = '';
|
||||
foreach ($languages as $l) {
|
||||
if ($l[0] == $locale) {
|
||||
$lang = $l[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
static::$locales[$locale] = $lang;
|
||||
|
||||
return $lang;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format phone number by country.
|
||||
*
|
||||
* @param string $tel
|
||||
* @param string|null $iso
|
||||
* @param int $format
|
||||
* @return string
|
||||
*/
|
||||
public static function formatTelephoneNumberByISO(string $tel, $iso, int $format = PhoneNumberFormat::INTERNATIONAL): string
|
||||
{
|
||||
if (empty($iso)) {
|
||||
return $tel;
|
||||
}
|
||||
|
||||
try {
|
||||
$phoneUtil = PhoneNumberUtil::getInstance();
|
||||
|
||||
$phoneInstance = $phoneUtil->parse($tel, mb_strtoupper($iso));
|
||||
|
||||
$tel = $phoneUtil->format($phoneInstance, $format);
|
||||
} catch (NumberParseException $e) {
|
||||
// Do nothing if the number cannot be parsed successfully
|
||||
}
|
||||
|
||||
return $tel;
|
||||
}
|
||||
}
|
||||
26
app/Helpers/MailHelper.php
Normal file
26
app/Helpers/MailHelper.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use App\Models\User\User;
|
||||
use App\Interfaces\MailNotification;
|
||||
|
||||
class MailHelper
|
||||
{
|
||||
/**
|
||||
* Get the HTML view that is rendered by the default markdown Laravel
|
||||
* notification class.
|
||||
* Yes, this is weird, but it's the only way to do it (as of Laravel 5.7).
|
||||
*
|
||||
* @param MailNotification $notification
|
||||
* @param User $user
|
||||
* @return string
|
||||
*/
|
||||
public static function emailView($notification, $user)
|
||||
{
|
||||
$message = $notification->toMail($user);
|
||||
$markdown = new \Illuminate\Mail\Markdown(view(), config('mail.markdown'));
|
||||
|
||||
return $markdown->render($message->markdown, $message->toArray());
|
||||
}
|
||||
}
|
||||
139
app/Helpers/MoneyHelper.php
Normal file
139
app/Helpers/MoneyHelper.php
Normal file
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use Money\Money;
|
||||
use App\Models\Settings\Currency;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Money\Currencies\ISOCurrencies;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Money\Currency as MoneyCurrency;
|
||||
use Money\Parser\DecimalMoneyParser;
|
||||
use Money\Formatter\IntlMoneyFormatter;
|
||||
use Money\Formatter\DecimalMoneyFormatter;
|
||||
|
||||
class MoneyHelper
|
||||
{
|
||||
/**
|
||||
* Format a monetary amount with currency symbol.
|
||||
* The value is formatted using current langage, as per the currency symbol.
|
||||
*
|
||||
* If the currency parameter is not passed, then the currency specified in
|
||||
* the users's settings will be used. If the currency setting is not
|
||||
* defined, then the amount will be returned without a currency symbol.
|
||||
*
|
||||
* @param int|null $amount Amount value in storable format (ex: 100 for 1,00€).
|
||||
* @param Currency|int|null $currency Currency of amount.
|
||||
* @return string Formatted amount for display with currency symbol (ex '1,235.87 €').
|
||||
*/
|
||||
public static function format($amount, $currency = null): string
|
||||
{
|
||||
if (is_null($amount)) {
|
||||
$amount = 0;
|
||||
}
|
||||
|
||||
$currency = self::getCurrency($currency);
|
||||
|
||||
if (! $currency || ! $currency->iso) {
|
||||
$numberFormatter = new \NumberFormatter(App::getLocale(), \NumberFormatter::DECIMAL);
|
||||
|
||||
return $numberFormatter->format($amount);
|
||||
}
|
||||
|
||||
$moneyCurrency = new MoneyCurrency($currency->iso);
|
||||
$money = new Money($amount, $moneyCurrency);
|
||||
$numberFormatter = new \NumberFormatter(App::getLocale(), \NumberFormatter::CURRENCY);
|
||||
$moneyFormatter = new IntlMoneyFormatter($numberFormatter, new ISOCurrencies());
|
||||
|
||||
return $moneyFormatter->format($money);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a monetary amount, without the currency.
|
||||
* The value is formatted using current langage.
|
||||
*
|
||||
* @param int|null $amount Amount value in storable format (ex: 100 for 1,00€).
|
||||
* @param Currency|int|null $currency
|
||||
* @return string Formatted amount for display without currency symbol (ex: '1234.50').
|
||||
*/
|
||||
public static function getValue($amount, $currency = null): string
|
||||
{
|
||||
$currency = self::getCurrency($currency);
|
||||
|
||||
if (! $currency || ! $currency->iso) {
|
||||
return (string) ($amount / 100);
|
||||
}
|
||||
|
||||
$moneyCurrency = new MoneyCurrency($currency->iso);
|
||||
$money = new Money($amount ?? 0, $moneyCurrency);
|
||||
$numberFormatter = new \NumberFormatter(App::getLocale(), \NumberFormatter::PATTERN_DECIMAL);
|
||||
$moneyFormatter = new IntlMoneyFormatter($numberFormatter, new ISOCurrencies());
|
||||
|
||||
return $moneyFormatter->format($money);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a monetary exchange value as storable integer.
|
||||
* Currency is used to know the precision of this currency.
|
||||
*
|
||||
* @param mixed|null $exchange Amount value in exchange format (ex: 1.00).
|
||||
* @param Currency|int|null $currency
|
||||
* @return int Amount as storable format (ex: 14500).
|
||||
*/
|
||||
public static function parseInput($exchange, $currency): int
|
||||
{
|
||||
$currency = self::getCurrency($currency);
|
||||
|
||||
if (! $currency || ! $currency->iso) {
|
||||
return (int) ((float) $exchange * 100);
|
||||
}
|
||||
|
||||
$moneyParser = new DecimalMoneyParser(new ISOCurrencies());
|
||||
$money = $moneyParser->parse((string) $exchange, new MoneyCurrency($currency->iso));
|
||||
|
||||
return (int) $money->getAmount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a monetary value as exchange value.
|
||||
* Exchange value is the amount to be entered in an input by a user,
|
||||
* using ordinary format.
|
||||
*
|
||||
* @param int|null $amount Amount value in storable format (ex: 100 for 1,00€).
|
||||
* @param Currency|int|null $currency
|
||||
* @return string Real value of amount in exchange format (ex: 1.24).
|
||||
*/
|
||||
public static function exchangeValue($amount, $currency): string
|
||||
{
|
||||
$currency = self::getCurrency($currency);
|
||||
|
||||
if (! $currency || ! $currency->iso) {
|
||||
return (string) ($amount / 100);
|
||||
}
|
||||
|
||||
$moneyCurrency = new MoneyCurrency($currency->iso);
|
||||
$money = new Money($amount ?? 0, $moneyCurrency);
|
||||
$moneyFormatter = new DecimalMoneyFormatter(new ISOCurrencies());
|
||||
|
||||
return $moneyFormatter->format($money);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get currency object.
|
||||
*
|
||||
* @param Currency|int|null $currency
|
||||
* @return Currency|null
|
||||
*/
|
||||
public static function getCurrency($currency): ?Currency
|
||||
{
|
||||
if (is_int($currency)) {
|
||||
$currency = Currency::find($currency);
|
||||
}
|
||||
|
||||
if (! $currency && Auth::check()) {
|
||||
$currency = Auth::user()->currency;
|
||||
}
|
||||
|
||||
return $currency;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user