merge v5-dev

This commit is contained in:
David Bomba 2021-06-14 20:47:03 +10:00
commit 95c9eb03a9
509 changed files with 312288 additions and 278059 deletions

View File

@ -51,11 +51,13 @@ TRUSTED_PROXIES=
NINJA_ENVIRONMENT=selfhost
PHANTOMJS_PDF_GENERATION=true
#options - snappdf / phantom / hosted_ninja
PDF_GENERATOR=phantom
PHANTOMJS_KEY='a-demo-key-with-low-quota-per-ip-address'
PHANTOMJS_SECRET=secret
UPDATE_SECRET=
UPDATE_SECRET=secret
COMPOSER_AUTH='{"github-oauth": {"github.com": "${{ secrets.GITHUB_TOKEN }}"}}'
SENTRY_LARAVEL_DSN=https://cc7e8e2c678041689e53e409b7dba236@sentry.invoicing.co/5

View File

@ -1,7 +1,21 @@
# Release notes
## [Unreleased (daily channel)](https://github.com/invoiceninja/invoiceninja/tree/v5-develop)
## [v5.2.0-release](https://github.com/invoiceninja/invoiceninja/releases/tag/v5.2.0-release)
## Added:
- Timezone Offset: Schedule emails based on timezone and time offsets.
- Force client country to system country if none is set.
- GMail Oauth via web
## Fixed:
- Add Cache-control: no-cache to prevent overaggressive caching of assets
- Improved labelling in the settings (client portal)
- Client portal: Multiple accounts access improvements (#5703)
- Client portal: "Credits" updates (#5734)
- Client portal: Make sidebar white color, in order to make logo displaying more simple. (#5753)
- Inject small delay into emails to allow all resources to be produced (ie PDFs) prior to sending
- Fixes for endless reminders not firing
## [v5.1.56-release](https://github.com/invoiceninja/invoiceninja/releases/tag/v5.1.56-release)
## Fixed:

View File

@ -45,3 +45,5 @@ AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
For more information regarding the interpretation of this license please see here: https://invoiceninja.github.io/docs/legal/license/

View File

@ -1 +1 @@
5.1.60
5.2.1

View File

@ -12,6 +12,7 @@
namespace App\Console\Commands;
use App;
use App\Factory\ClientContactFactory;
use App\Models\Account;
use App\Models\Client;
use App\Models\ClientContact;
@ -27,6 +28,7 @@ use Exception;
use Illuminate\Console\Command;
use Mail;
use Symfony\Component\Console\Input\InputOption;
use Illuminate\Support\Str;
/*
@ -65,7 +67,7 @@ class CheckData extends Command
/**
* @var string
*/
protected $name = 'ninja:check-data';
protected $signature = 'ninja:check-data {--database=} {--fix=} {--client_id=}';
/**
* @var string
@ -78,7 +80,10 @@ class CheckData extends Command
public function handle()
{
$this->logMessage(date('Y-m-d h:i:s').' Running CheckData...');
$database_connection = $this->option('database') ? $this->option('database') : 'Connected to Default DB';
$fix_status = $this->option('fix') ? "Fixing Issues" : "Just checking issues ";
$this->logMessage(date('Y-m-d h:i:s').' Running CheckData... on ' . $database_connection . " Fix Status = {$fix_status}");
if ($database = $this->option('database')) {
config(['database.default' => $database]);
@ -208,14 +213,13 @@ class CheckData extends Command
if ($this->option('fix') == 'true') {
foreach ($clients as $client) {
$contact = new ClientContact();
$contact->company_id = $client->company_id;
$contact->user_id = $client->user_id;
$contact->client_id = $client->id;
$contact->is_primary = true;
$contact->send_invoice = true;
$contact->contact_key = str_random(config('ninja.key_length'));
$contact->save();
$this->logMessage("Fixing missing contacts #{$client->id}");
$new_contact = ClientContactFactory::create($client->company_id, $client->user_id);
$new_contact->client_id = $client->id;
$new_contact->contact_key = Str::random(40);
$new_contact->is_primary = true;
$new_contact->save();
}
}
@ -233,9 +237,21 @@ class CheckData extends Command
$clients->where('clients.id', '=', $this->option('client_id'));
}
$clients = $clients->get(['clients.id', DB::raw('count(client_contacts.id)')]);
$clients = $clients->get(['clients.id', 'clients.user_id', 'clients.company_id']);
$this->logMessage($clients->count().' clients without a single primary contact');
if ($this->option('fix') == 'true') {
foreach ($clients as $client) {
$this->logMessage("Fixing missing primary contacts #{$client->id}");
$new_contact = ClientContactFactory::create($client->company_id, $client->user_id);
$new_contact->client_id = $client->id;
$new_contact->contact_key = Str::random(40);
$new_contact->is_primary = true;
$new_contact->save();
}
}
if ($clients->count() > 0) {
$this->isValid = false;
}
@ -295,17 +311,17 @@ class CheckData extends Command
Client::withTrashed()->where('is_deleted', 0)->cursor()->each(function ($client) use ($wrong_paid_to_dates, $credit_total_applied) {
$total_invoice_payments = 0;
foreach ($client->invoices->where('is_deleted', false)->where('status_id', '>', 1) as $invoice) {
foreach ($client->invoices()->where('is_deleted', false)->where('status_id', '>', 1)->get() as $invoice) {
$total_amount = $invoice->payments->where('is_deleted', false)->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])->sum('pivot.amount');
$total_refund = $invoice->payments->where('is_deleted', false)->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])->sum('pivot.refunded');
$total_amount = $invoice->payments()->where('is_deleted', false)->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])->get()->sum('pivot.amount');
$total_refund = $invoice->payments()->where('is_deleted', false)->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])->get()->sum('pivot.refunded');
$total_invoice_payments += ($total_amount - $total_refund);
}
// 10/02/21
foreach ($client->payments as $payment) {
$credit_total_applied += $payment->paymentables->where('paymentable_type', App\Models\Credit::class)->sum(DB::raw('amount'));
$credit_total_applied += $payment->paymentables()->where('paymentable_type', App\Models\Credit::class)->get()->sum(DB::raw('amount'));
}
if ($credit_total_applied < 0) {
@ -316,7 +332,7 @@ class CheckData extends Command
if (round($total_invoice_payments, 2) != round($client->paid_to_date, 2)) {
$wrong_paid_to_dates++;
$this->logMessage($client->present()->name.'id = # '.$client->id." - Paid to date does not match Client Paid To Date = {$client->paid_to_date} - Invoice Payments = {$total_invoice_payments}");
$this->logMessage($client->present()->name.' id = # '.$client->id." - Paid to date does not match Client Paid To Date = {$client->paid_to_date} - Invoice Payments = {$total_invoice_payments}");
$this->isValid = false;
}
@ -331,10 +347,11 @@ class CheckData extends Command
$wrong_paid_to_dates = 0;
Client::cursor()->where('is_deleted', 0)->each(function ($client) use ($wrong_balances) {
$client->invoices->where('is_deleted', false)->whereIn('status_id', '!=', Invoice::STATUS_DRAFT)->each(function ($invoice) use ($wrong_balances, $client) {
$total_amount = $invoice->payments->whereIn('status_id', [Payment::STATUS_PAID, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED])->sum('pivot.amount');
$total_refund = $invoice->payments->whereIn('status_id', [Payment::STATUS_PAID, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED])->sum('pivot.refunded');
$total_credit = $invoice->credits->sum('amount');
$total_amount = $invoice->payments()->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED])->get()->sum('pivot.amount');
$total_refund = $invoice->payments()->get()->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED])->sum('pivot.refunded');
$total_credit = $invoice->credits()->get()->sum('amount');
$total_paid = $total_amount - $total_refund;
$calculated_paid_amount = $invoice->amount - $invoice->balance - $total_credit;
@ -347,6 +364,7 @@ class CheckData extends Command
$this->isValid = false;
}
});
});
$this->logMessage("{$wrong_balances} clients with incorrect invoice balances");
@ -370,7 +388,7 @@ class CheckData extends Command
if ($ledger && (string) $invoice_balance != (string) $client->balance) {
$wrong_paid_to_dates++;
$this->logMessage($client->present()->name.' - '.$client->id." - calculated client balances do not match {$invoice_balance} - ".rtrim($client->balance, '0'));
$this->logMessage($client->present()->name.' - '.$client->id." - calculated client balances do not match Invoice Balances = {$invoice_balance} - Client Balance = ".rtrim($client->balance, '0'). " Ledger balance = {$ledger->balance}");
$this->isValid = false;
}
@ -379,6 +397,12 @@ class CheckData extends Command
$this->logMessage("{$wrong_paid_to_dates} clients with incorrect client balances");
}
//fix for client balances =
//$adjustment = ($invoice_balance-$client->balance)
//$client->balance += $adjustment;
//$ledger_adjustment = $ledger->balance - $client->balance;
//$ledger->balance += $ledger_adjustment
private function checkInvoiceBalances()
{
@ -386,8 +410,8 @@ class CheckData extends Command
$wrong_paid_to_dates = 0;
foreach (Client::where('is_deleted', 0)->cursor() as $client) {
$invoice_balance = $client->invoices->where('is_deleted', false)->where('status_id', '>', 1)->sum('balance');
$credit_balance = $client->credits->where('is_deleted', false)->sum('balance');
$invoice_balance = $client->invoices()->where('is_deleted', false)->where('status_id', '>', 1)->get()->sum('balance');
$credit_balance = $client->credits()->where('is_deleted', false)->get()->sum('balance');
// if($client->balance != $invoice_balance)
// $invoice_balance -= $credit_balance;//doesn't make sense to remove the credit amount

View File

@ -0,0 +1,165 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Console\Commands;
use App;
use App\Factory\ClientContactFactory;
use App\Models\Account;
use App\Models\Activity;
use App\Models\Backup;
use App\Models\Client;
use App\Models\ClientContact;
use App\Models\ClientGatewayToken;
use App\Models\Company;
use App\Models\CompanyGateway;
use App\Models\CompanyLedger;
use App\Models\CompanyToken;
use App\Models\CompanyUser;
use App\Models\Contact;
use App\Models\Credit;
use App\Models\CreditInvitation;
use App\Models\Design;
use App\Models\Document;
use App\Models\Expense;
use App\Models\ExpenseCategory;
use App\Models\Gateway;
use App\Models\GroupSetting;
use App\Models\Invoice;
use App\Models\InvoiceInvitation;
use App\Models\Payment;
use App\Models\PaymentHash;
use App\Models\Paymentable;
use App\Models\Product;
use App\Models\Project;
use App\Models\Quote;
use App\Models\QuoteInvitation;
use App\Models\RecurringInvoice;
use App\Models\RecurringInvoiceInvitation;
use App\Models\Subscription;
use App\Models\SystemLog;
use App\Models\Task;
use App\Models\TaskStatus;
use App\Models\TaxRate;
use App\Models\User;
use App\Models\Vendor;
use App\Models\VendorContact;
use App\Models\Webhook;
use App\Utils\Ninja;
use DB;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Str;
use Mail;
use Symfony\Component\Console\Input\InputOption;
/**
* Class CheckDb.
*/
class CheckDb extends Command
{
/**
* @var string
*/
protected $signature = 'ninja:check-db';
/**
* @var string
*/
protected $description = 'Check MultiDB';
protected $log = '';
private $entities = [
Account::class,
Activity::class,
Backup::class,
Client::class,
ClientContact::class,
ClientGatewayToken::class,
Company::class,
CompanyGateway::class,
CompanyLedger::class,
CompanyToken::class,
CompanyUser::class,
Credit::class,
CreditInvitation::class,
Design::class,
Document::class,
Expense::class,
ExpenseCategory::class,
Gateway::class,
GroupSetting::class,
Invoice::class,
InvoiceInvitation::class,
Payment::class,
Paymentable::class,
PaymentHash::class,
Product::class,
Project::class,
Quote::class,
QuoteInvitation::class,
RecurringInvoice::class,
RecurringInvoiceInvitation::class,
Subscription::class,
SystemLog::class,
Task::class,
TaskStatus::class,
TaxRate::class,
User::class,
Vendor::class,
VendorContact::class,
WebHook::class,
];
public function handle()
{
$this->LogMessage("Checking - V5_DB1");
foreach($this->entities as $entity) {
$count_db_1 = $entity::on('db-ninja-01')->count();
$count_db_2 = $entity::on('db-ninja-02a')->count();
$diff = $count_db_1 - $count_db_2;
if($diff != 0)
$this->logMessage("{$entity} DB1: {$count_db_1} - DB2: {$count_db_2} - diff = {$diff}");
}
$this->LogMessage("Checking - V5_DB2");
foreach($this->entities as $entity) {
$count_db_1 = $entity::on('db-ninja-02')->count();
$count_db_2 = $entity::on('db-ninja-01a')->count();
$diff = $count_db_1 - $count_db_2;
if($diff != 0)
$this->logMessage("{$entity} DB1: {$count_db_1} - DB2: {$count_db_2} - diff = {$diff}");
}
}
private function logMessage($str)
{
$str = date('Y-m-d h:i:s').' '.$str;
$this->info($str);
$this->log .= $str."\n";
}
}

View File

@ -105,6 +105,9 @@ class CreateAccount extends Command
'password' => Hash::make($password),
'confirmation_code' => $this->createDbHash(config('database.default')),
'email_verified_at' => now(),
'first_name' => 'New',
'last_name' => 'User',
'phone' => '',
]);
$company_token = new CompanyToken;

View File

@ -34,6 +34,7 @@ use App\Models\Project;
use App\Models\Quote;
use App\Models\RecurringInvoice;
use App\Models\Task;
use App\Models\TaxRate;
use App\Models\User;
use App\Models\Vendor;
use App\Models\VendorContact;
@ -105,8 +106,18 @@ class CreateSingleAccount extends Command
'account_id' => $account->id,
'slack_webhook_url' => config('ninja.notification.slack'),
'default_password_timeout' => 30*60000,
'portal_mode' => 'domain',
'portal_domain' => 'http://ninja.test:8000',
]);
$settings = $company->settings;
$settings->invoice_terms = 'Default company invoice terms';
$settings->quote_terms = 'Default company quote terms';
$settings->invoice_footer = 'Default invoice footer';
$company->settings = $settings;
$company->save();
$account->default_company_id = $company->id;
$account->save();
@ -144,6 +155,29 @@ class CreateSingleAccount extends Command
'company_id' => $company->id,
]);
TaxRate::factory()->create([
'user_id' => $user->id,
'company_id' => $company->id,
'name' => 'GST',
'rate' => 10
]);
TaxRate::factory()->create([
'user_id' => $user->id,
'company_id' => $company->id,
'name' => 'VAT',
'rate' => 17.5
]);
TaxRate::factory()->create([
'user_id' => $user->id,
'company_id' => $company->id,
'name' => 'CA Sales Tax',
'rate' => 5
]);
$this->info('Creating '.$this->count.' clients');
for ($x = 0; $x < $this->count; $x++) {

View File

@ -128,7 +128,7 @@ class DemoMode extends Command
{
$faker = \Faker\Factory::create();
$this->count = 50;
$this->count = 25;
$this->info('Creating Small Account and Company');
@ -486,7 +486,7 @@ class DemoMode extends Command
if (rand(0, 1)) {
$invoice->assigned_user_id = $assigned_user_id;
}
$invoice->number = $this->getNextRecurringInvoiceNumber($client);
$invoice->save();
}

View File

@ -0,0 +1,61 @@
<?php
namespace App\Console\Commands;
use App\Models\ClientContact;
use App\Models\Company;
use App\Models\User;
use App\Utils\Ninja;
use Illuminate\Console\Command;
class HostedUsers extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'ninja:sync-users';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Syncs Invoice Ninja Users';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
Company::on('db-ninja-01')->each(function ($company){
if(Ninja::isHosted())
\Modules\Admin\Jobs\Account\NinjaUser::dispatchNow([], $company);
});
Company::on('db-ninja-02')->each(function ($company){
if(Ninja::isHosted())
\Modules\Admin\Jobs\Account\NinjaUser::dispatchNow([], $company);
});
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace App\Console\Commands;
use App\Models\Company;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
class S3Cleanup extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'ninja:s3-cleanup';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Remove orphan folders';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$c1 = Company::on('db-ninja-01')->pluck('company_key');
$c2 = Company::on('db-ninja-02')->pluck('company_key');
$merged = $c1->merge($c2)->toArray();
$directories = Storage::disk(config('filesystems.default'))->directories();
$this->LogMessage("Disk Cleanup");
foreach($directories as $dir)
{
if(!in_array($dir, $merged))
{
$this->logMessage("Deleting $dir");
Storage::disk(config('filesystems.default'))->deleteDirectory($dir);
}
}
$this->logMessage("exiting");
}
private function logMessage($str)
{
$str = date('Y-m-d h:i:s').' '.$str;
$this->info($str);
$this->log .= $str."\n";
}
}

View File

@ -12,12 +12,12 @@
namespace App\Console\Commands;
use App\Jobs\Ninja\SendReminders;
use App\Jobs\Util\WebHookHandler;
use App\Libraries\MultiDB;
use App\Models\Invoice;
use App\Models\Quote;
use App\Models\Webhook;
use Illuminate\Console\Command;
use App\Jobs\Util\WebhookHandler;
class SendRemindersCron extends Command
{
@ -54,8 +54,8 @@ class SendRemindersCron extends Command
{
SendReminders::dispatchNow();
$this->webHookOverdueInvoices();
$this->webHookExpiredQuotes();
$this->webHookOverdueInvoices();
$this->webHookExpiredQuotes();
}
private function webHookOverdueInvoices()
@ -89,7 +89,8 @@ class SendRemindersCron extends Command
->cursor();
$invoices->each(function ($invoice) {
WebHookHandler::dispatch(Webhook::EVENT_LATE_INVOICE, $invoice, $invoice->company);
WebhookHandler::dispatch(Webhook::EVENT_LATE_INVOICE, $invoice, $invoice->company);
});
$quotes = Quote::where('is_deleted', 0)
@ -98,7 +99,7 @@ class SendRemindersCron extends Command
->cursor();
$quotes->each(function ($quote) {
WebHookHandler::dispatch(Webhook::EVENT_EXPIRED_QUOTE, $quote, $quote->company);
WebhookHandler::dispatch(Webhook::EVENT_EXPIRED_QUOTE, $quote, $quote->company);
});
}

View File

@ -17,6 +17,9 @@ use App\Factory\ClientFactory;
use App\Factory\InvoiceFactory;
use App\Factory\InvoiceInvitationFactory;
use App\Jobs\Invoice\CreateEntityPdf;
use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject;
use App\Mail\Migration\MaxCompanies;
use App\Mail\TemplateEmail;
use App\Models\Account;
use App\Models\Client;
@ -59,101 +62,40 @@ class SendTestEmails extends Command
* @return mixed
*/
public function handle()
{
$this->sendTemplateEmails('plain');
$this->sendTemplateEmails('light');
$this->sendTemplateEmails('dark');
}
private function sendTemplateEmails($template)
{
$faker = Factory::create();
$message = [
'title' => 'Invoice XJ-3838',
'body' => '<div>"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?"</div>',
'subject' => 'The Test Subject',
'footer' => 'Lovely Footer Texts',
];
$account = Account::factory()->create();
$user = User::whereEmail('user@example.com')->first();
$user = User::factory()->create([
'account_id' => $account->id,
'confirmation_code' => '123',
'email' => $faker->safeEmail,
'first_name' => 'John',
'last_name' => 'Doe',
]);
if (! $user) {
$account = Account::factory()->create();
$company = Company::factory()->create([
'account_id' => $account->id,
]);
$user = User::factory()->create([
'account_id' => $account->id,
'confirmation_code' => '123',
'email' => $faker->safeEmail,
'first_name' => 'John',
'last_name' => 'Doe',
]);
$user->companies()->attach($company->id, [
'account_id' => $account->id,
'is_owner' => 1,
'is_admin' => 1,
'is_locked' => 0,
'permissions' => '',
'notifications' => CompanySettings::notificationDefaults(),
//'settings' => DefaultSettings::userSettings(),
'settings' => null,
]);
$company = Company::factory()->create([
'account_id' => $account->id,
]);
$user->companies()->attach($company->id, [
'account_id' => $account->id,
'is_owner' => 1,
'is_admin' => 1,
'is_locked' => 0,
'permissions' => '',
'notifications' => CompanySettings::notificationDefaults(),
//'settings' => DefaultSettings::userSettings(),
'settings' => null,
]);
} else {
$company = $user->company_users->first()->company;
$account = $company->account;
}
$client = Client::all()->first();
if (! $client) {
$client = ClientFactory::create($company->id, $user->id);
$client->save();
ClientContact::factory()->create([
'user_id' => $user->id,
'client_id' => $client->id,
'company_id' => $company->id,
'is_primary' => 1,
'send_email' => true,
'email' => $faker->safeEmail,
]);
ClientContact::factory()->create([
'user_id' => $user->id,
'client_id' => $client->id,
'company_id' => $company->id,
'send_email' => true,
'email' => $faker->safeEmail,
]);
}
$invoice = InvoiceFactory::create($company->id, $user->id);
$invoice->client_id = $client->id;
$invoice->setRelation('client', $client);
$invoice->save();
$ii = InvoiceInvitationFactory::create($invoice->company_id, $invoice->user_id);
$ii->invoice_id = $invoice->id;
$ii->client_contact_id = $client->primary_contact()->first()->id;
$ii->save();
$invoice->setRelation('invitations', $ii);
$invoice->service()->markSent()->save();
CreateEntityPdf::dispatch($invoice->invitations()->first());
$cc_emails = [config('ninja.testvars.test_email')];
$bcc_emails = [config('ninja.testvars.test_email')];
$email_builder->setFooter($message['footer'])
->setSubject($message['subject'])
->setBody($message['body']);
$nmo = new NinjaMailerObject;
$nmo->mailable = new MaxCompanies($user->account->companies()->first());
$nmo->company = $user->account->companies()->first();
$nmo->settings = $user->account->companies()->first()->settings;
$nmo->to_user = $user;
NinjaMailerJob::dispatchNow($nmo);
}
}

View File

@ -11,10 +11,12 @@
namespace App\Console;
use App\Jobs\Cron\SubscriptionCron;
use App\Jobs\Cron\AutoBillCron;
use App\Jobs\Cron\RecurringInvoicesCron;
use App\Jobs\Cron\SubscriptionCron;
use App\Jobs\Ninja\AdjustEmailQuota;
use App\Jobs\Ninja\CompanySizeCheck;
use App\Jobs\Util\DiskCleanup;
use App\Jobs\Util\ReminderJob;
use App\Jobs\Util\SchedulerCheck;
use App\Jobs\Util\SendFailedEmails;
@ -46,9 +48,11 @@ class Kernel extends ConsoleKernel
$schedule->job(new VersionCheck)->daily();
$schedule->command('ninja:check-data')->daily()->withoutOverlapping();
$schedule->job(new DiskCleanup)->daily()->withoutOverlapping();
$schedule->job(new ReminderJob)->daily()->withoutOverlapping();
$schedule->command('ninja:check-data --database=db-ninja-01')->daily()->withoutOverlapping();
$schedule->job(new ReminderJob)->hourly()->withoutOverlapping();
$schedule->job(new CompanySizeCheck)->daily()->withoutOverlapping();
@ -58,6 +62,8 @@ class Kernel extends ConsoleKernel
$schedule->job(new RecurringInvoicesCron)->hourly()->withoutOverlapping();
$schedule->job(new AutoBillCron)->dailyAt('00:30')->withoutOverlapping();
$schedule->job(new SchedulerCheck)->everyFiveMinutes();
/* Run hosted specific jobs */
@ -65,12 +71,15 @@ class Kernel extends ConsoleKernel
$schedule->job(new AdjustEmailQuota)->daily()->withoutOverlapping();
$schedule->job(new SendFailedEmails)->daily()->withoutOverlapping();
$schedule->command('ninja:check-data --database=db-ninja-02')->daily()->withoutOverlapping();
}
if(config('queue.default') == 'database' && Ninja::isSelfHost()) {
if(config('queue.default') == 'database' && Ninja::isSelfHost() && config('ninja.internal_queue_enabled') && !config('ninja.is_docker')) {
$schedule->command('queue:work')->everyMinute()->withoutOverlapping();
$schedule->command('queue:restart')->everyFiveMinutes()->withoutOverlapping();
}
}

View File

@ -65,10 +65,12 @@ class CompanySettings extends BaseSettings
public $auto_convert_quote = true; //@implemented
public $auto_email_invoice = true; //@only used for Recurring Invoices, if set to false, we never send?
public $entity_send_time = 0;
public $inclusive_taxes = false; //@implemented
public $quote_footer = ''; //@implmented
public $translations; //@TODO not used anywhere
public $translations;
public $counter_number_applied = 'when_saved'; // when_saved , when_sent //@implemented
public $quote_number_applied = 'when_saved'; // when_saved , when_sent //@implemented
@ -202,7 +204,7 @@ class CompanySettings extends BaseSettings
public $schedule_reminder2 = ''; // (enum: after_invoice_date, before_due_date, after_due_date) implmemented
public $schedule_reminder3 = ''; // (enum: after_invoice_date, before_due_date, after_due_date) implmemented
public $reminder_send_time = 32400; //number of seconds from UTC +0 to send reminders @TODO
public $reminder_send_time = 0; //number of seconds from UTC +0 to send reminders @TODO
public $late_fee_amount1 = 0; //@implemented
public $late_fee_amount2 = 0; //@implemented
@ -245,8 +247,8 @@ class CompanySettings extends BaseSettings
public $hide_paid_to_date = false; //@TODO where?
public $embed_documents = false; //@TODO where?
public $all_pages_header = false; //@implemented
public $all_pages_footer = false; //@implemented
public $all_pages_header = false; //@deprecated 31-05-2021
public $all_pages_footer = false; //@deprecated 31-05-2021
public $pdf_variables = ''; //@implemented
public $portal_custom_head = ''; //@TODO @BEN
@ -254,7 +256,7 @@ class CompanySettings extends BaseSettings
public $portal_custom_footer = ''; //@TODO @BEN
public $portal_custom_js = ''; //@TODO @BEN
public $client_can_register = false; //@implemented
public $client_can_register = false; //@deorecated 04/06/2021
public $client_portal_terms = ''; //@TODO @BEN
public $client_portal_privacy_policy = ''; //@TODO @BEN
public $client_portal_enable_uploads = false; //@implemented
@ -266,6 +268,7 @@ class CompanySettings extends BaseSettings
public $hide_empty_columns_on_pdf = false;
public static $casts = [
'entity_send_time' => 'int',
'shared_invoice_credit_counter' => 'bool',
'reply_to_name' => 'string',
'hide_empty_columns_on_pdf' => 'bool',
@ -667,8 +670,9 @@ class CompanySettings extends BaseSettings
'$custom_surcharge4',
'$total_taxes',
'$line_taxes',
'$paid_to_date',
'$total',
'$paid_to_date',
'$outstanding',
],
];

View File

@ -0,0 +1,47 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Events\RecurringInvoice;
use App\Models\Company;
use App\Models\RecurringInvoice;
use Illuminate\Queue\SerializesModels;
/**
* Class RecurringInvoiceWasArchived.
*/
class RecurringInvoiceWasArchived
{
use SerializesModels;
/**
* @var Invoice
*/
public $recurring_invoice;
public $company;
public $event_vars;
/**
* Create a new event instance.
*
* @param Invoice $recurring_invoice
* @param Company $company
* @param array $event_vars
*/
public function __construct(RecurringInvoice $recurring_invoice, Company $company, array $event_vars)
{
$this->recurring_invoice = $recurring_invoice;
$this->company = $company;
$this->event_vars = $event_vars;
}
}

View File

@ -0,0 +1,47 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Events\RecurringInvoice;
use App\Models\Company;
use App\Models\RecurringInvoice;
use Illuminate\Queue\SerializesModels;
/**
* Class RecurringInvoiceWasCreated.
*/
class RecurringInvoiceWasCreated
{
use SerializesModels;
/**
* @var RecurringInvoice
*/
public $recurring_invoice;
public $company;
public $event_vars;
/**
* Create a new event instance.
*
* @param RecurringInvoice $recurring_invoice
* @param Company $company
* @param array $event_vars
*/
public function __construct(RecurringInvoice $recurring_invoice, Company $company, array $event_vars)
{
$this->recurring_invoice = $recurring_invoice;
$this->company = $company;
$this->event_vars = $event_vars;
}
}

View File

@ -0,0 +1,47 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Events\RecurringInvoice;
use App\Models\Company;
use App\Models\RecurringInvoice;
use Illuminate\Queue\SerializesModels;
/**
* Class RecurringInvoiceWasDeleted.
*/
class RecurringInvoiceWasDeleted
{
use SerializesModels;
/**
* @var RecurringInvoice
*/
public $recurring_invoice;
public $company;
public $event_vars;
/**
* Create a new event instance.
*
* @param Invoice $invoice
* @param Company $company
* @param array $event_vars
*/
public function __construct(RecurringInvoice $recurring_invoice, Company $company, array $event_vars)
{
$this->recurring_invoice = $recurring_invoice;
$this->company = $company;
$this->event_vars = $event_vars;
}
}

View File

@ -0,0 +1,51 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Events\RecurringInvoice;
use App\Models\Company;
use App\Models\RecurringInvoice;
use Illuminate\Queue\SerializesModels;
/**
* Class RecurringInvoiceWasRestored.
*/
class RecurringInvoiceWasRestored
{
use SerializesModels;
/**
* @var RecurringInvoice
*/
public $recurring_invoice;
public $fromDeleted;
public $company;
public $event_vars;
/**
* Create a new event instance.
*
* @param Invoice $invoice
* @param $fromDeleted
* @param Company $company
* @param array $event_vars
*/
public function __construct(RecurringInvoice $recurring_invoice, $fromDeleted, Company $company, array $event_vars)
{
$this->recurring_invoice = $recurring_invoice;
$this->fromDeleted = $fromDeleted;
$this->company = $company;
$this->event_vars = $event_vars;
}
}

View File

@ -0,0 +1,49 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Events\RecurringInvoice;
use App\Models\Company;
use App\Models\RecurringInvoice;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
/**
* Class RecurringInvoiceWasUpdated.
*/
class RecurringInvoiceWasUpdated
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* @var Invoice
*/
public $recurring_invoice;
public $company;
public $event_vars;
/**
* Create a new event instance.
*
* @param RecurringInvoice $recurring_invoice
* @param Company $company
* @param array $event_vars
*/
public function __construct(RecurringInvoice $recurring_invoice, Company $company, array $event_vars)
{
$this->recurring_invoice = $recurring_invoice;
$this->company = $company;
$this->event_vars = $event_vars;
}
}

View File

@ -14,6 +14,7 @@ namespace App\Exceptions;
use App\Exceptions\FilePermissionsFailure;
use App\Exceptions\InternalPDFFailure;
use App\Exceptions\PhantomPDFFailure;
use App\Utils\Ninja;
use Exception;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Auth\AuthenticationException;
@ -49,6 +50,7 @@ class Handler extends ExceptionHandler
//Swift_TransportException::class,
MaxAttemptsExceededException::class,
CommandNotFoundException::class,
ValidationException::class,
];
/**
@ -75,7 +77,28 @@ class Handler extends ExceptionHandler
return;
}
if (app()->bound('sentry') && $this->shouldReport($exception)) {
if(Ninja::isHosted()){
app('sentry')->configureScope(function (Scope $scope): void {
if(auth()->guard('contact') && auth()->guard('contact')->user())
$key = auth()->guard('contact')->user()->company->account->key;
elseif (auth()->guard('user') && auth()->guard('user')->user())
$key = auth()->user()->account->key;
else
$key = 'Anonymous';
$scope->setUser([
'id' => 'Hosted_User',
'email' => 'hosted@invoiceninja.com',
'name' => $key,
]);
});
app('sentry')->captureException($exception);
}
elseif (app()->bound('sentry') && $this->shouldReport($exception)) {
app('sentry')->configureScope(function (Scope $scope): void {
if (auth()->guard('contact') && auth()->guard('contact')->user() && auth()->guard('contact')->user()->company->account->report_errors) {
$scope->setUser([

View File

@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class ImportCompanyFailed extends Exception
{
// ..
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class NonExistingBackupFile extends Exception
{
// ..
}

View File

@ -33,9 +33,6 @@ class ClientFactory
$client->client_hash = Str::random(40);
$client->settings = ClientSettings::defaults();
// $client_contact = ClientContactFactory::create($company_id, $user_id);
// $client->contacts->add($client_contact);
return $client;
}
}

View File

@ -12,7 +12,9 @@
namespace App\Factory;
use App\DataMapper\CompanySettings;
use App\Libraries\MultiDB;
use App\Models\Company;
use App\Utils\Ninja;
use App\Utils\Traits\MakesHash;
class CompanyFactory
@ -33,7 +35,12 @@ class CompanyFactory
$company->db = config('database.default');
//$company->custom_fields = (object) ['invoice1' => '1', 'invoice2' => '2', 'client1'=>'3'];
$company->custom_fields = (object) [];
$company->subdomain = '';
if(Ninja::isHosted())
$company->subdomain = MultiDB::randomSubdomainGenerator();
else
$company->subdomain = '';
$company->enabled_modules = config('ninja.enabled_modules'); //32767;//8191; //4095
$company->default_password_timeout = 1800000;

View File

@ -21,6 +21,8 @@ class CompanyGatewayFactory
$company_gateway = new CompanyGateway;
$company_gateway->company_id = $company_id;
$company_gateway->user_id = $user_id;
$company_gateway->require_billing_address = false;
$company_gateway->require_shipping_address = false;
// $company_gateway->fees_and_limits = new FeesAndLimits;
return $company_gateway;

View File

@ -37,10 +37,10 @@ class CreditFactory
$credit->tax_rate1 = 0;
$credit->tax_name2 = '';
$credit->tax_rate2 = 0;
$credit->custom_value1 = 0;
$credit->custom_value2 = 0;
$credit->custom_value3 = 0;
$credit->custom_value4 = 0;
$credit->custom_value1 = '';
$credit->custom_value2 = '';
$credit->custom_value3 = '';
$credit->custom_value4 = '';
$credit->amount = 0;
$credit->balance = 0;
$credit->partial = 0;

View File

@ -38,10 +38,10 @@ class InvoiceFactory
$invoice->tax_rate2 = 0;
$invoice->tax_name3 = '';
$invoice->tax_rate3 = 0;
$invoice->custom_value1 = 0;
$invoice->custom_value2 = 0;
$invoice->custom_value3 = 0;
$invoice->custom_value4 = 0;
$invoice->custom_value1 = '';
$invoice->custom_value2 = '';
$invoice->custom_value3 = '';
$invoice->custom_value4 = '';
$invoice->amount = 0;
$invoice->balance = 0;
$invoice->paid_to_date = 0;

View File

@ -36,10 +36,10 @@ class RecurringInvoiceFactory
$invoice->tax_rate1 = 0;
$invoice->tax_name2 = '';
$invoice->tax_rate2 = 0;
$invoice->custom_value1 = 0;
$invoice->custom_value2 = 0;
$invoice->custom_value3 = 0;
$invoice->custom_value4 = 0;
$invoice->custom_value1 = '';
$invoice->custom_value2 = '';
$invoice->custom_value3 = '';
$invoice->custom_value4 = '';
$invoice->amount = 0;
$invoice->balance = 0;
$invoice->partial = 0;

View File

@ -35,10 +35,10 @@ class RecurringQuoteFactory
$quote->tax_rate1 = 0;
$quote->tax_name2 = '';
$quote->tax_rate2 = 0;
$quote->custom_value1 = 0;
$quote->custom_value2 = 0;
$quote->custom_value3 = 0;
$quote->custom_value4 = 0;
$quote->custom_value1 = '';
$quote->custom_value2 = '';
$quote->custom_value3 = '';
$quote->custom_value4 = '';
$quote->amount = 0;
$quote->balance = 0;
$quote->partial = 0;

View File

@ -160,4 +160,36 @@ abstract class QueryFilters
return $this->builder->whereClientId(auth('contact')->user()->client->id);
}
}
public function created_at($value)
{
$created_at = $value ? $value : 0;
$created_at = date('Y-m-d H:i:s', $value);
return $this->builder->where('created_at', '>=', $created_at);
}
public function is_deleted($value)
{
return $this->builder->where('is_deleted', $value);
}
public function filter_deleted_clients($value)
{
if($value == 'true'){
return $this->builder->whereHas('client', function (Builder $query) {
$query->where('is_deleted', 0);
});
}
return $this->builder;
}
}

View File

@ -30,7 +30,7 @@ function isActive($page, bool $boolean = false)
}
if ($page == $current_page) {
return 'bg-primary-darken';
return 'bg-gray-200';
}
return false;

View File

@ -30,6 +30,7 @@ function nlog($output, $context = []): void
}
$trace = debug_backtrace();
//nlog( debug_backtrace()[1]['function']);
// \Illuminate\Support\Facades\Log::channel('invoiceninja')->info(print_r($trace[1]['class'],1), []);
\Illuminate\Support\Facades\Log::channel('invoiceninja')->info($output, $context);

View File

@ -74,14 +74,12 @@ class GmailTransport extends Transport
}
}
$this->gmail->send();
$this->sendPerformed($message);
return $this->numberOfRecipients($message);
}
}

View File

@ -1,47 +0,0 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Helpers\Mail;
use App\Libraries\MultiDB;
use App\Mail\SupportMessageSent;
use App\Models\User;
use App\Providers\MailServiceProvider;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Mail;
use Laravel\Socialite\Facades\Socialite;
/**
* GmailTransportConfig.
*/
class GmailTransportConfig
{
public function test()
{
/********************* We may need to fetch a new token on behalf of the client ******************************/
$query = [
'email' => 'david@invoiceninja.com',
];
$user = MultiDB::hasUser($query);
// $oauth_user = Socialite::driver('google')->stateless()->userFromToken($user->oauth_user_token);
// $user->oauth_user_token = $oauth_user->refreshToken;
// $user->save();
Config::set('mail.driver', 'gmail');
Config::set('services.gmail.token', $user->oauth_user_token);
(new MailServiceProvider(app()))->register();
Mail::to('david@romulus.com.au')
->send(new SupportMessageSent('a cool message'));
}
}

View File

@ -142,7 +142,7 @@ class AccountController extends BaseController
*/
public function store(CreateAccountRequest $request)
{
$account = CreateAccount::dispatchNow($request->all());
$account = CreateAccount::dispatchNow($request->all(), $request->getClientIp());
if (! ($account instanceof Account)) {
return $account;

View File

@ -141,10 +141,10 @@ class ActivityController extends BaseController
return response()->json(['message'=> ctrans('texts.no_backup_exists'), 'errors' => new stdClass], 404);
}
if (config('ninja.phantomjs_pdf_generation')) {
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
$pdf = (new Phantom)->convertHtmlToPdf($backup->html_backup);
}
elseif(config('ninja.invoiceninja_hosted_pdf_generation')){
elseif(config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja'){
$pdf = (new NinjaPdf())->build($backup->html_backup);
}
else {

View File

@ -13,6 +13,7 @@ namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Libraries\MultiDB;
use App\Models\Account;
use Illuminate\Contracts\View\Factory;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Http\Request;
@ -50,11 +51,15 @@ class ContactForgotPasswordController extends Controller
*
* @return Factory|View
*/
public function showLinkRequestForm()
public function showLinkRequestForm(Request $request)
{
$account_id = $request->get('account_id');
$account = Account::find($account_id);
return $this->render('auth.passwords.request', [
'title' => 'Client Password Reset',
'passwordEmailRoute' => 'client.password.email',
'account' => $account
]);
}
@ -72,7 +77,7 @@ class ContactForgotPasswordController extends Controller
{
//MultiDB::userFindAndSetDb($request->input('email'));
$user = MultiDB::hasContact(['email' => $request->input('email')]);
$user = MultiDB::hasContact($request->input('email'));
$this->validateEmail($request);
@ -84,6 +89,10 @@ class ContactForgotPasswordController extends Controller
);
if ($request->ajax()) {
if($response == Password::RESET_THROTTLED)
return response()->json(['message' => ctrans('passwords.throttled'), 'status' => false], 429);
return $response == Password::RESET_LINK_SENT
? response()->json(['message' => 'Reset link sent to your email.', 'status' => true], 201)
: response()->json(['message' => 'Email not found', 'status' => false], 401);

View File

@ -13,7 +13,9 @@ namespace App\Http\Controllers\Auth;
use App\Events\Contact\ContactLoggedIn;
use App\Http\Controllers\Controller;
use App\Models\Account;
use App\Models\ClientContact;
use App\Models\Company;
use App\Utils\Ninja;
use Auth;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
@ -31,9 +33,21 @@ class ContactLoginController extends Controller
$this->middleware('guest:contact', ['except' => ['logout']]);
}
public function showLoginForm()
public function showLoginForm(Request $request)
{
return $this->render('auth.login');
if ($request->subdomain) {
$company = Company::where('subdomain', $request->subdomain)->first();
} elseif (Ninja::isSelfHost()) {
$company = Account::first()->default_company;
} else {
$company = null;
}
$account_id = $request->get('account_id');
$account = Account::find($account_id);
return $this->render('auth.login', ['account' => $account, 'company' => $company]);
}
public function login(Request $request)

View File

@ -15,7 +15,7 @@ class ContactRegisterController extends Controller
{
public function __construct()
{
$this->middleware(['guest', 'contact.register']);
$this->middleware(['guest']);
}
public function showRegisterForm(string $company_key = '')
@ -24,7 +24,7 @@ class ContactRegisterController extends Controller
$company = Company::where('company_key', $key)->firstOrFail();
return render('auth.register', ['company' => $company]);
return render('auth.register', ['company' => $company, 'account' => $company->account]);
}
public function register(RegisterRequest $request)

View File

@ -12,6 +12,7 @@
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\Account;
use Illuminate\Contracts\View\Factory;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Http\Request;
@ -62,8 +63,11 @@ class ContactResetPasswordController extends Controller
*/
public function showResetForm(Request $request, $token = null)
{
$account_id = $request->get('account_id');
$account = Account::find($account_id);
return $this->render('auth.passwords.reset')->with(
['token' => $token, 'email' => $request->email]
['token' => $token, 'email' => $request->email, 'account' => $account]
);
}

View File

@ -13,6 +13,7 @@ namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Libraries\MultiDB;
use App\Models\Account;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
@ -104,8 +105,7 @@ class ForgotPasswordController extends Controller
*/
public function sendResetLinkEmail(Request $request)
{
//MultiDB::userFindAndSetDb($request->input('email'));
MultiDB::userFindAndSetDb($request->input('email'));
$user = MultiDB::hasUser(['email' => $request->input('email')]);
$this->validateEmail($request);
@ -115,9 +115,13 @@ class ForgotPasswordController extends Controller
// need to show to the user. Finally, we'll send out a proper response.
$response = $this->broker()->sendResetLink(
$this->credentials($request)
);
);
if ($request->ajax()) {
if($response == Password::RESET_THROTTLED)
return response()->json(['message' => ctrans('passwords.throttled'), 'status' => false], 429);
return $response == Password::RESET_LINK_SENT
? response()->json(['message' => 'Reset link sent to your email.', 'status' => true], 201)
: response()->json(['message' => 'Email not found', 'status' => false], 401);
@ -128,8 +132,11 @@ class ForgotPasswordController extends Controller
: $this->sendResetLinkFailedResponse($request, $response);
}
public function showLinkRequestForm()
public function showLinkRequestForm(Request $request)
{
return $this->render('auth.passwords.request', ['root' => 'themes']);
$account_id = $request->get('account_id');
$account = Account::find($account_id);
return $this->render('auth.passwords.request', ['root' => 'themes', 'account' => $account]);
}
}

View File

@ -13,24 +13,32 @@ namespace App\Http\Controllers\Auth;
use App\DataMapper\Analytics\LoginFailure;
use App\DataMapper\Analytics\LoginSuccess;
use App\Events\User\UserLoggedIn;
use App\Http\Controllers\BaseController;
use App\Http\Controllers\Controller;
use App\Jobs\Account\CreateAccount;
use App\Jobs\Company\CreateCompanyToken;
use App\Jobs\Util\SystemLogger;
use App\Libraries\MultiDB;
use App\Libraries\OAuth\OAuth;
use App\Libraries\OAuth\Providers\Google;
use App\Models\Client;
use App\Models\Company;
use App\Models\CompanyToken;
use App\Models\CompanyUser;
use App\Models\SystemLog;
use App\Models\User;
use App\Transformers\CompanyUserTransformer;
use App\Utils\Ninja;
use App\Utils\Traits\UserSessionAttributes;
use App\Utils\Traits\User\LoginCache;
use Google_Client;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
use Laravel\Socialite\Facades\Socialite;
use PragmaRX\Google2FA\Google2FA;
use Turbo124\Beacon\Facades\LightLogs;
@ -49,6 +57,7 @@ class LoginController extends BaseController
use AuthenticatesUsers;
use UserSessionAttributes;
use LoginCache;
protected $entity_type = CompanyUser::class;
@ -170,8 +179,9 @@ class LoginController extends BaseController
$user = $this->guard()->user();
//if user has 2fa enabled - lets check this now:
event(new UserLoggedIn($user, $user->account->default_company, Ninja::eventVars($user->id)));
//2FA
if($user->google_2fa_secret && $request->has('one_time_password'))
{
$google2fa = new Google2FA();
@ -195,29 +205,29 @@ class LoginController extends BaseController
$user->setCompany($user->account->default_company);
$timeout = $user->company()->default_password_timeout;
if($timeout == 0)
$timeout = 30*60*1000*1000;
else
$timeout = $timeout/1000;
Cache::put($user->hashed_id.'_logged_in', Str::random(64), $timeout);
$this->setLoginCache($user);
$cu = CompanyUser::query()
->where('user_id', auth()->user()->id);
$cu->first()->account->companies->each(function ($company) use($cu, $request){
if(!$cu->exists())
return response()->json(['message' => 'User not linked to any companies'], 403);
if($company->tokens()->where('is_system', true)->count() == 0)
{
CreateCompanyToken::dispatchNow($company, $cu->first()->user, $request->server('HTTP_USER_AGENT'));
/* Ensure the user has a valid token */
$user->company_users->each(function ($company_user) use($request){
if($company_user->tokens->count() == 0){
CreateCompanyToken::dispatchNow($company_user->company, $company_user->user, $request->server('HTTP_USER_AGENT'));
}
});
/*On the hosted platform, only owners can login for free/pro accounts*/
if(Ninja::isHosted() && !$cu->first()->is_owner && !$user->account->isEnterpriseClient())
return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403);
return $this->timeConstrainedResponse($cu);
// return $this->listResponse($cu);
} else {
@ -225,6 +235,15 @@ class LoginController extends BaseController
->increment()
->batch();
SystemLogger::dispatch(
json_encode(['ip' => request()->getClientIp()]),
SystemLog::CATEGORY_SECURITY,
SystemLog::EVENT_USER,
SystemLog::TYPE_LOGIN_FAILURE,
null,
Company::first(),
);
$this->incrementLoginAttempts($request);
return response()
@ -294,6 +313,9 @@ class LoginController extends BaseController
if($request->has('current_company') && $request->input('current_company') == 'true')
$cu->where("company_id", $company_token->company_id);
if(Ninja::isHosted() && !$cu->first()->is_owner && !$cu->first()->user->account->isEnterpriseClient())
return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403);
return $this->refreshResponse($cu);
}
@ -331,6 +353,7 @@ class LoginController extends BaseController
if (is_array($user)) {
//
$query = [
'oauth_user_id' => $google->harvestSubField($user),
'oauth_provider_id'=> 'google',
@ -341,15 +364,7 @@ class LoginController extends BaseController
Auth::login($existing_user, true);
$existing_user->setCompany($existing_user->account->default_company);
$timeout = $existing_user->company()->default_password_timeout;
if($timeout == 0)
$timeout = 30*60*1000*1000;
else
$timeout = $timeout/1000;
Cache::put($existing_user->hashed_id.'_logged_in', Str::random(64), $timeout);
$this->setLoginCache($existing_user);
$cu = CompanyUser::query()
->where('user_id', auth()->user()->id);
@ -362,13 +377,80 @@ class LoginController extends BaseController
}
});
if(Ninja::isHosted() && !$cu->first()->is_owner && !$existing_user->account->isEnterpriseClient())
return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403);
return $this->timeConstrainedResponse($cu);
}
//If this is a result user/email combo - lets add their OAuth details details
if($existing_login_user = MultiDB::hasUser(['email' => $google->harvestEmail($user)]))
{
Auth::login($existing_login_user, true);
$existing_login_user->setCompany($existing_login_user->account->default_company);
$this->setLoginCache($existing_login_user);
auth()->user()->update([
'oauth_user_id' => $google->harvestSubField($user),
'oauth_provider_id'=> 'google',
]);
$cu = CompanyUser::query()
->where('user_id', auth()->user()->id);
$cu->first()->account->companies->each(function ($company) use($cu){
if($company->tokens()->where('is_system', true)->count() == 0)
{
CreateCompanyToken::dispatchNow($company, $cu->first()->user, request()->server('HTTP_USER_AGENT'));
}
});
if(Ninja::isHosted() && !$cu->first()->is_owner && !$existing_login_user->account->isEnterpriseClient())
return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403);
return $this->timeConstrainedResponse($cu);
}
}
if ($user) {
//check the user doesn't already exist in some form
if($existing_login_user = MultiDB::hasUser(['email' => $google->harvestEmail($user)]))
{
Auth::login($existing_login_user, true);
$existing_login_user->setCompany($existing_login_user->account->default_company);
$this->setLoginCache($existing_login_user);
auth()->user()->update([
'oauth_user_id' => $google->harvestSubField($user),
'oauth_provider_id'=> 'google',
]);
$cu = CompanyUser::query()
->where('user_id', auth()->user()->id);
$cu->first()->account->companies->each(function ($company) use($cu){
if($company->tokens()->where('is_system', true)->count() == 0)
{
CreateCompanyToken::dispatchNow($company, $cu->first()->user, request()->server('HTTP_USER_AGENT'));
}
});
if(Ninja::isHosted() && !$cu->first()->is_owner && !$existing_login_user->account->isEnterpriseClient())
return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403);
return $this->timeConstrainedResponse($cu);
}
//user not found anywhere - lets sign them up.
$name = OAuth::splitName($google->harvestName($user));
$new_account = [
@ -384,22 +466,14 @@ class LoginController extends BaseController
MultiDB::setDefaultDatabase();
$account = CreateAccount::dispatchNow($new_account);
$account = CreateAccount::dispatchNow($new_account, request()->getClientIp());
Auth::login($account->default_company->owner(), true);
auth()->user()->email_verified_at = now();
auth()->user()->save();
$timeout = auth()->user()->company()->default_password_timeout;
if($timeout == 0)
$timeout = 30*60*1000*1000;
else
$timeout = $timeout/1000;
Cache::put(auth()->user()->hashed_id.'_logged_in', Str::random(64), $timeout);
$this->setLoginCache(auth()->user());
$cu = CompanyUser::whereUserId(auth()->user()->id);
@ -411,6 +485,9 @@ class LoginController extends BaseController
}
});
if(Ninja::isHosted() && !$cu->first()->is_owner && !auth()->user()->account->isEnterpriseClient())
return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403);
return $this->timeConstrainedResponse($cu);
}
@ -419,4 +496,66 @@ class LoginController extends BaseController
->header('X-App-Version', config('ninja.app_version'))
->header('X-Api-Version', config('ninja.minimum_client_version'));
}
public function redirectToProvider(string $provider)
{
$scopes = [];
$parameters = [];
if($provider == 'google'){
$scopes = ['https://www.googleapis.com/auth/gmail.send','email','profile','openid'];
$parameters = ['access_type' => 'offline', "prompt" => "consent select_account", 'redirect_uri' => config('ninja.app_url')."/auth/google"];
}
if (request()->has('code')) {
return $this->handleProviderCallback($provider);
} else {
return Socialite::driver($provider)->with($parameters)->scopes($scopes)->redirect();
}
}
public function handleProviderCallback(string $provider)
{
$socialite_user = Socialite::driver($provider)->user();
$oauth_user_token = '';
if($socialite_user->refreshToken){
$client = new Google_Client();
$client->setClientId(config('ninja.auth.google.client_id'));
$client->setClientSecret(config('ninja.auth.google.client_secret'));
$client->fetchAccessTokenWithRefreshToken($socialite_user->refreshToken);
$oauth_user_token = $client->getAccessToken();
}
if($user = OAuth::handleAuth($socialite_user, $provider))
{
nlog('found user and updating their user record');
$name = OAuth::splitName($socialite_user->getName());
$update_user = [
'first_name' => $name[0],
'last_name' => $name[1],
'email' => $socialite_user->getEmail(),
'oauth_user_id' => $socialite_user->getId(),
'oauth_provider_id' => $provider,
'oauth_user_token' => $oauth_user_token,
'oauth_user_refresh_token' => $socialite_user->refreshToken
];
$user->update($update_user);
}
else {
nlog("user not found for oauth");
}
return redirect('/#/');
}
}

View File

@ -12,6 +12,7 @@
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\Account;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
@ -52,7 +53,10 @@ class ResetPasswordController extends Controller
public function showResetForm(Request $request, $token = null)
{
return $this->render('auth.passwords.reset', ['root' => 'themes', 'token' => $token]);
$account_id = $request->get('account_id');
$account = Account::find($account_id);
return $this->render('auth.passwords.reset', ['root' => 'themes', 'token' => $token, 'account' => $account]);
}
/**

View File

@ -164,7 +164,7 @@ class BaseController extends Controller
*/
public function notFoundClient()
{
return abort(404);
abort(404, 'Page not found in client portal.');
}
/**
@ -189,10 +189,7 @@ class BaseController extends Controller
{
$user = auth()->user();
if ($user->getCompany()->is_large)
$this->manager->parseIncludes($this->mini_load);
else
$this->manager->parseIncludes($this->first_load);
$this->manager->parseIncludes($this->first_load);
$this->serializer = request()->input('serializer') ?: EntityTransformer::API_SERIALIZER_ARRAY;
@ -312,10 +309,6 @@ class BaseController extends Controller
},
'company.tax_rates' => function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at);
if(!$user->isAdmin())
$query->where('tax_rates.user_id', $user->id);
},
'company.vendors'=> function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at)->with('contacts', 'documents');
@ -326,17 +319,9 @@ class BaseController extends Controller
},
'company.expense_categories'=> function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at);
if(!$user->isAdmin())
$query->where('expense_categories.user_id', $user->id);
},
'company.task_statuses'=> function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at);
if(!$user->isAdmin())
$query->where('task_statuses.user_id', $user->id);
},
'company.activities'=> function ($query) use($user) {
@ -368,13 +353,92 @@ class BaseController extends Controller
return $this->response($this->manager->createData($resource)->toArray());
}
protected function miniLoadResponse($query)
{
$user = auth()->user();
$this->serializer = request()->input('serializer') ?: EntityTransformer::API_SERIALIZER_ARRAY;
if ($this->serializer === EntityTransformer::API_SERIALIZER_JSON) {
$this->manager->setSerializer(new JsonApiSerializer());
} else {
$this->manager->setSerializer(new ArraySerializer());
}
$transformer = new $this->entity_transformer($this->serializer);
$created_at = request()->has('created_at') ? request()->input('created_at') : 0;
$created_at = date('Y-m-d H:i:s', $created_at);
$query->with(
[
'company' => function ($query) use ($created_at, $user) {
$query->whereNotNull('created_at')->with('documents');
},
'company.designs'=> function ($query) use ($created_at, $user) {
$query->where('created_at', '>=', $created_at)->with('company');
if(!$user->isAdmin())
$query->where('designs.user_id', $user->id);
},
'company.documents'=> function ($query) use ($created_at, $user) {
$query->where('created_at', '>=', $created_at);
},
'company.groups' => function ($query) use ($created_at, $user) {
$query->where('created_at', '>=', $created_at);
if(!$user->isAdmin())
$query->where('group_settings.user_id', $user->id);
},
'company.payment_terms'=> function ($query) use ($created_at, $user) {
$query->where('created_at', '>=', $created_at);
if(!$user->isAdmin())
$query->where('payment_terms.user_id', $user->id);
},
'company.tax_rates' => function ($query) use ($created_at, $user) {
$query->where('created_at', '>=', $created_at);
if(!$user->isAdmin())
$query->where('tax_rates.user_id', $user->id);
},
'company.activities'=> function ($query) use($user) {
if(!$user->isAdmin())
$query->where('activities.user_id', $user->id);
}
]
);
if ($query instanceof Builder) {
$limit = request()->input('per_page', 20);
$paginator = $query->paginate($limit);
$query = $paginator->getCollection();
$resource = new Collection($query, $transformer, $this->entity_type);
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
} else {
$resource = new Collection($query, $transformer, $this->entity_type);
}
return $this->response($this->manager->createData($resource)->toArray());
}
protected function timeConstrainedResponse($query)
{
$user = auth()->user();
if ($user->getCompany()->is_large)
if ($user->getCompany()->is_large){
$this->manager->parseIncludes($this->mini_load);
return $this->miniLoadResponse($query);
}
else
$this->manager->parseIncludes($this->first_load);
@ -518,9 +582,6 @@ class BaseController extends Controller
'company.task_statuses'=> function ($query) use ($created_at, $user) {
$query->where('created_at', '>=', $created_at);
if(!$user->isAdmin())
$query->where('task_statuses.user_id', $user->id);
},
'company.activities'=> function ($query) use($user) {

View File

@ -15,6 +15,7 @@ use App\Events\Client\ClientWasCreated;
use App\Events\Client\ClientWasUpdated;
use App\Factory\ClientFactory;
use App\Filters\ClientFilters;
use App\Http\Requests\Client\AdjustClientLedgerRequest;
use App\Http\Requests\Client\CreateClientRequest;
use App\Http\Requests\Client\DestroyClientRequest;
use App\Http\Requests\Client\EditClientRequest;
@ -282,7 +283,7 @@ class ClientController extends BaseController
$this->uploadLogo($request->file('company_logo'), $client->company, $client);
event(new ClientWasUpdated($client, $client->company, Ninja::eventVars(auth()->user()->id)));
event(new ClientWasUpdated($client, $client->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
return $this->itemResponse($client->fresh());
}
@ -378,9 +379,18 @@ class ClientController extends BaseController
$client->load('contacts', 'primary_contact');
/* Set the client country to the company if none is set */
if(!$client->country_id && strlen($client->company->settings->country_id) > 1){
$client->country_id = $client->company->settings->country_id;
$client->save();
}
$this->uploadLogo($request->file('company_logo'), $client->company, $client);
event(new ClientWasCreated($client, $client->company, Ninja::eventVars(auth()->user()->id)));
event(new ClientWasCreated($client, $client->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
return $this->itemResponse($client);
}
@ -585,4 +595,64 @@ class ClientController extends BaseController
}
/**
* Update the specified resource in storage.
*
* @param UploadClientRequest $request
* @param Client $client
* @return Response
*
*
*
* @OA\Put(
* path="/api/v1/clients/{id}/adjust_ledger",
* operationId="adjustLedger",
* tags={"clients"},
* summary="Adjust the client ledger to rebalance",
* description="Adjust the client ledger to rebalance",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The Client Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the client object",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/Client"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
//@deprecated - not available
public function adjustLedger(AdjustClientLedgerRequest $request, Client $client)
{
// $adjustment = $request->input('adjustment');
// $notes = $request->input('notes');
// $client->service()->updateBalance
}
}

View File

@ -24,11 +24,16 @@ class ContactHashLoginController extends Controller
*/
public function login(string $contact_key)
{
return redirect('/client/login');
return redirect('/client/invoices');
}
public function magicLink(string $magic_link)
{
return redirect('/client/login');
return redirect('/client/invoices');
}
public function errorPage()
{
return render('generic.error', ['title' => session()->get('title'), 'notification' => session()->get('notification')]);
}
}

View File

@ -22,8 +22,15 @@ class CreditController extends Controller
public function show(ShowCreditRequest $request, Credit $credit)
{
return $this->render('credits.show', [
'credit' => $credit,
]);
set_time_limit(0);
$data = ['credit' => $credit];
if ($request->query('mode') === 'fullscreen') {
return render('credits.show-fullscreen', $data);
}
return $this->render('credits.show', $data);
}
}

View File

@ -68,7 +68,7 @@ class DocumentController extends Controller
$documents->map(function ($document) {
if (auth()->user('contact')->client->id != $document->documentable->id) {
abort(401);
abort(401, 'Permission denied');
}
});

View File

@ -31,7 +31,7 @@ class EntityViewController extends Controller
public function index(string $entity_type, string $invitation_key)
{
if (! in_array($entity_type, $this->entity_types)) {
abort(404);
abort(404, 'Entity not found');
}
$invitation_entity = sprintf('App\\Models\\%sInvitation', ucfirst($entity_type));
@ -91,7 +91,7 @@ class EntityViewController extends Controller
public function handlePassword(string $entity_type, string $invitation_key)
{
if (! in_array($entity_type, $this->entity_types)) {
abort(404);
abort(404, 'Entity not found');
}
$invitation_entity = sprintf('App\\Models\\%sInvitation', ucfirst($entity_type));

View File

@ -55,18 +55,21 @@ class InvitationController extends Controller
->firstOrFail();
/* Return early if we have the correct client_hash embedded */
$client_contact = $invitation->contact;
if(empty($client_contact->email))
$client_contact->email = Str::random(15) . "@example.com"; $client_contact->save();
if (request()->has('client_hash') && request()->input('client_hash') == $invitation->contact->client->client_hash) {
auth()->guard('contact')->login($invitation->contact, true);
auth()->guard('contact')->login($client_contact, true);
} elseif ((bool) $invitation->contact->client->getSetting('enable_client_portal_password') !== false) {
//If no contact password is set - this will cause a 401 error - instead redirect to the client.login route
$this->middleware('auth:contact');
return redirect()->route('client.login');
} else {
auth()->guard('contact')->login($invitation->contact, true);
nlog("else - default - login contact");
auth()->guard('contact')->login($client_contact, true);
}

View File

@ -57,7 +57,7 @@ class InvoiceController extends Controller
];
if ($request->query('mode') === 'fullscreen') {
return response()->file($invoice->pdf_file_path(null, 'path'));
return render('invoices.show-fullscreen', $data);
}
return $this->render('invoices.show', $data);
@ -127,6 +127,8 @@ class InvoiceController extends Controller
$payment_methods = auth()->user()->client->service()->getPaymentMethods($total);
//if there is only one payment method -> lets return straight to the payment page
$data = [
'settings' => auth()->user()->client->getMergedSettings(),
'invoices' => $invoices,
@ -136,8 +138,6 @@ class InvoiceController extends Controller
'total' => $total,
];
// nlog($data);
return $this->render('invoices.payment', $data);
}
@ -164,10 +164,11 @@ class InvoiceController extends Controller
//if only 1 pdf, output to buffer for download
if ($invoices->count() == 1) {
return response()->streamDownload(function () use ($invoices) {
echo file_get_contents($invoices->first()->pdf_file_path());
}, basename($invoices->first()->pdf_file_path()), ['Cache-Control:' => 'no-cache']);
//return response()->download(TempFile::path($invoices->first()->pdf_file_path()), basename($invoices->first()->pdf_file_path()));
$invoice = $invoices->first();
$invitation = $invoice->invitations->first();
$file = $invoice->pdf_file_path($invitation);
return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);;
}
// enable output of HTTP headers

View File

@ -243,7 +243,7 @@ class PaymentController extends Controller
->get();
}
$hash_data = ['invoices' => $payable_invoices->toArray(), 'credits' => $credit_totals];
$hash_data = ['invoices' => $payable_invoices->toArray(), 'credits' => $credit_totals, 'amount_with_fee' => max(0, (($invoice_totals + $fee_totals) - $credit_totals))];
if ($request->query('hash')) {
$hash_data['billing_context'] = Cache::get($request->query('hash'));
@ -290,7 +290,8 @@ class PaymentController extends Controller
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_ERROR,
SystemLog::TYPE_FAILURE,
auth('contact')->user()->client
auth('contact')->user()->client,
auth('contact')->user()->client->company
);
throw new PaymentFailed($e->getMessage());
@ -303,24 +304,12 @@ class PaymentController extends Controller
$payment_hash = PaymentHash::whereRaw('BINARY `hash`= ?', [$request->payment_hash])->first();
try {
return $gateway
->driver(auth()->user()->client)
->setPaymentMethod($request->input('payment_method_id'))
->setPaymentHash($payment_hash)
->checkRequirements()
->processPaymentResponse($request);
} catch (\Exception $e) {
SystemLogger::dispatch(
$e->getMessage(),
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_FAILURE,
SystemLog::TYPE_FAILURE,
auth('contact')->user()->client
);
throw new PaymentFailed($e->getMessage());
}
}
/**

View File

@ -149,6 +149,6 @@ class PaymentMethodController extends Controller
return $gateway = auth()->user()->client->getBankTransferGateway();
}
return abort(404);
abort(404, 'Gateway not found.');
}
}

View File

@ -44,7 +44,7 @@ class QuoteController extends Controller
];
if ($request->query('mode') === 'fullscreen') {
return response()->file($quote->pdf_file_path(null, 'path'));
return render('quotes.show-fullscreen', $data);
}
return $this->render('quotes.show', $data);
@ -76,10 +76,9 @@ class QuoteController extends Controller
}
if ($quotes->count() == 1) {
return response()->streamDownload(function () use ($invoices) {
echo file_get_contents($invoices->first()->pdf_file_path());
}, basename($invoices->first()->pdf_file_path()), ['Cache-Control:' => 'no-cache']);
//return response()->download(TempFile::path($invoices->first()->pdf_file_path()), basename($quotes->first()->pdf_file_path()));
$file = $quotes->first()->pdf_file_path();
return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
}
// enable output of HTTP headers

View File

@ -0,0 +1,34 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Controllers\ClientPortal;
use App\Http\Controllers\Controller;
use App\Http\Requests\ClientPortal\Tasks\ShowTasksRequest;
class TaskController extends Controller
{
/**
* Show the tasks in the client portal.
*
* @param ShowTasksRequest $request
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function index(ShowTasksRequest $request)
{
\Carbon\Carbon::setLocale(
auth('contact')->user()->preferredLocale()
);
return render('tasks.index');
}
}

View File

@ -480,12 +480,9 @@ class CompanyController extends BaseController
if ($company_count == 1) {
$company->company_users->each(function ($company_user) {
$company_user->user->forceDelete();
$company_user->forceDelete();
});
// if (Ninja::isHosted()) {
// RefundCancelledAccount::dispatchNow($account);
// }
$account->delete();
LightLogs::create(new AccountDeleted())
@ -493,6 +490,11 @@ class CompanyController extends BaseController
->batch();
} else {
$company_id = $company->id;
$company->company_users->each(function ($company_user){
$company_user->forceDelete();
});
$company->delete();
//If we are deleting the default companies, we'll need to make a new company the default.

View File

@ -17,6 +17,7 @@ use App\Models\CompanyUser;
use App\Models\User;
use App\Transformers\CompanyUserTransformer;
use App\Transformers\UserTransformer;
use App\Utils\Traits\User\LoginCache;
use Google_Client;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
@ -24,6 +25,7 @@ use Illuminate\Support\Str;
class ConnectedAccountController extends BaseController
{
use LoginCache;
protected $entity_type = User::class;
@ -113,9 +115,8 @@ class ConnectedAccountController extends BaseController
auth()->user()->email_verified_at = now();
auth()->user()->save();
$timeout = auth()->user()->company()->default_password_timeout;
Cache::put(auth()->user()->hashed_id.'_logged_in', Str::random(64), $timeout);
$this->setLoginCache(auth()->user());
return $this->itemResponse(auth()->user());
}
@ -160,6 +161,9 @@ class ConnectedAccountController extends BaseController
'email_verified_at' =>now()
];
if(auth()->user()->email != $google->harvestEmail($user))
return response()->json(['message' => 'Primary Email differs to OAuth email. Emails must match.'], 400);
auth()->user()->update($connected_account);
auth()->user()->email_verified_at = now();
auth()->user()->save();

View File

@ -93,11 +93,16 @@ class LoginController extends BaseController
public function redirectToProvider(string $provider)
{
//'https://www.googleapis.com/auth/gmail.send','email','profile','openid'
//
$scopes = [];
if($provider == 'google'){
$scopes = ['https://www.googleapis.com/auth/gmail.send','email','profile','openid'];
}
if (request()->has('code')) {
return $this->handleProviderCallback($provider);
} else {
return Socialite::driver($provider)->scopes()->redirect();
return Socialite::driver($provider)->scopes($scopes)->redirect();
}
}
@ -231,43 +236,5 @@ class LoginController extends BaseController
}
}
/*
* Received the returning object from the provider
* which we will use to resolve the user, we return the response in JSON format
*
* @return json
public function handleProviderCallbackApiUser(string $provider)
{
$socialite_user = Socialite::driver($provider)->stateless()->user();
if($user = OAuth::handleAuth($socialite_user, $provider))
{
return $this->itemResponse($user);
}
else if(MultiDB::checkUserEmailExists($socialite_user->getEmail()))
{
return $this->errorResponse(['message'=>'User exists in system, but not with this authentication method'], 400);
}
else {
//todo
$name = OAuth::splitName($socialite_user->getName());
$new_account = [
'first_name' => $name[0],
'last_name' => $name[1],
'password' => '',
'email' => $socialite_user->getEmail(),
];
$account = CreateAccount::dispatchNow($new_account);
return $this->itemResponse($account->default_company->owner());
}
}
*/
}

View File

@ -201,7 +201,7 @@ class CreditController extends BaseController
->fillDefaults()
->save();
event(new CreditWasCreated($credit, $credit->company, Ninja::eventVars(auth()->user()->id)));
event(new CreditWasCreated($credit, $credit->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
return $this->itemResponse($credit);
}
@ -378,7 +378,7 @@ class CreditController extends BaseController
$credit->service()->deletePdf();
event(new CreditWasUpdated($credit, $credit->company, Ninja::eventVars(auth()->user()->id)));
event(new CreditWasUpdated($credit, $credit->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
return $this->itemResponse($credit);
}
@ -535,11 +535,9 @@ class CreditController extends BaseController
return $this->itemResponse($credit);
}
break;
case 'download':
return response()->streamDownload(function () use ($credit) {
echo file_get_contents($credit->pdf_file_path());
}, basename($credit->pdf_file_path()), ['Cache-Control:' => 'no-cache']);
//return response()->download(TempFile::path($credit->pdf_file_path()), basename($credit->pdf_file_path()));
case 'download':
$file = $credit->pdf_file_path();
return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
break;
case 'archive':
$this->credit_repository->archive($credit);
@ -589,7 +587,7 @@ class CreditController extends BaseController
$file_path = $credit->service()->getCreditPdf($invitation);
return response()->download($file_path, basename($file_path), ['Cache-Control:' => 'no-cache']);
return response()->download($file_path, basename($file_path), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
}
/**

View File

@ -25,6 +25,7 @@ use App\Transformers\DesignTransformer;
use App\Utils\Traits\MakesHash;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Str;
/**
* Class DesignController.
@ -412,6 +413,7 @@ class DesignController extends BaseController
{
//may not need these destroy routes as we are using actions to 'archive/delete'
$design->is_deleted = true;
$design->name = $design->name . "_deleted_" . Str::random(5);
$design->delete();
$design->save();

View File

@ -132,7 +132,7 @@ class EmailController extends BaseController
$entity_obj->service()->markSent()->save();
EmailEntity::dispatch($invitation->fresh(), $invitation->company, $template, $data)
->delay(now()->addSeconds(5));
->delay(now()->addSeconds(30));
}
@ -157,7 +157,7 @@ class EmailController extends BaseController
$this->entity_transformer = QuoteTransformer::class;
if ($entity_obj->invitations->count() >= 1)
event(new QuoteWasEmailed($entity_obj->invitations->first(), $entity_obj->company, Ninja::eventVars(auth()->user()->id), 'quote'));
event(new QuoteWasEmailed($entity_obj->invitations->first(), $entity_obj->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null), 'quote'));
}
@ -166,7 +166,7 @@ class EmailController extends BaseController
$this->entity_transformer = CreditTransformer::class;
if ($entity_obj->invitations->count() >= 1)
event(new CreditWasEmailed($entity_obj->invitations->first(), $entity_obj->company, Ninja::eventVars(auth()->user()->id), 'credit'));
event(new CreditWasEmailed($entity_obj->invitations->first(), $entity_obj->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null), 'credit'));
}

View File

@ -279,7 +279,7 @@ class ExpenseController extends BaseController
$this->uploadLogo($request->file('company_logo'), $expense->company, $expense);
event(new ExpenseWasUpdated($expense, $expense->company, Ninja::eventVars(auth()->user()->id)));
event(new ExpenseWasUpdated($expense, $expense->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
return $this->itemResponse($expense->fresh());
}
@ -373,7 +373,7 @@ class ExpenseController extends BaseController
{
$expense = $this->expense_repo->save($request->all(), ExpenseFactory::create(auth()->user()->company()->id, auth()->user()->id));
event(new ExpenseWasCreated($expense, $expense->company, Ninja::eventVars(auth()->user()->id)));
event(new ExpenseWasCreated($expense, $expense->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
return $this->itemResponse($expense);
}

View File

@ -0,0 +1,64 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Controllers;
use App\Http\Requests\Export\StoreExportRequest;
use App\Jobs\Company\CompanyExport;
use App\Utils\Traits\MakesHash;
use Illuminate\Http\Response;
class ExportController extends BaseController
{
use MakesHash;
public function __construct()
{
parent::__construct();
}
/**
* @OA\Post(
* path="/api/v1/export",
* operationId="getExport",
* tags={"export"},
* summary="Export data from the system",
* description="Export data from the system",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Response(
* response=200,
* description="success",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function index(StoreExportRequest $request)
{
CompanyExport::dispatch(auth()->user()->getCompany(), auth()->user());
return response()->json(['message' => 'Processing'], 200);
}
}

View File

@ -0,0 +1,42 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Controllers\Gateways;
use App\Http\Controllers\Controller;
use App\Http\Requests\Gateways\Checkout3ds\Checkout3dsRequest;
class Checkout3dsController extends Controller
{
public function index(Checkout3dsRequest $request, string $company_key, string $company_gateway_id, string $hash)
{
if (!$request->getCompany()) {
return response()->json(['message' => 'Company record not found.', 'company_key' => $company_key]);
}
if (!$request->getCompanyGateway()) {
return response()->json(['message' => 'Company gateway record not found.', 'company_gateway_id' => $company_gateway_id]);
}
if (!$request->getPaymentHash()) {
return response()->json(['message' => 'Hash record not found.', 'hash' => $hash]);
}
if (!$request->getClient()) {
return response()->json(['message' => 'Client record not found.']);
}
return $request->getCompanyGateway()
->driver($request->getClient())
->process3dsConfirmation($request);
}
}

View File

@ -0,0 +1,99 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Controllers;
use App\Http\Requests\Import\ImportJsonRequest;
use App\Jobs\Company\CompanyExport;
use App\Jobs\Company\CompanyImport;
use App\Utils\Traits\MakesHash;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
use ZipArchive;
class ImportJsonController extends BaseController
{
use MakesHash;
public function __construct()
{
parent::__construct();
}
/**
* @OA\Post(
* path="/api/v1/import_json",
* operationId="getImportJson",
* tags={"import"},
* summary="Import data from the system",
* description="Import data from the system",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Response(
* response=200,
* description="success",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function import(ImportJsonRequest $request)
{
$import_file = $request->file('files');
$contents = $this->unzipFile($import_file->getPathname());
$hash = Str::random(32);
nlog($hash);
Cache::put( $hash, base64_encode( $contents ), 3600 );
CompanyImport::dispatch(auth()->user()->getCompany(), auth()->user(), $hash, $request->except('files'))->delay(now()->addMinutes(1));
return response()->json(['message' => 'Processing'], 200);
}
private function unzipFile($file_contents)
{
$zip = new ZipArchive();
$archive = $zip->open($file_contents);
$filename = pathinfo($file_contents, PATHINFO_FILENAME);
$zip->extractTo(public_path("storage/backups/{$filename}"));
$zip->close();
$file_location = public_path("storage/backups/$filename/backup.json");
if (! file_exists($file_location))
throw new NonExistingMigrationFile('Backup file does not exist, or is corrupted.');
$data = file_get_contents($file_location);
unlink($file_contents);
unlink($file_location);
return $data;
}
}

View File

@ -218,7 +218,7 @@ class InvoiceController extends BaseController
->triggeredActions($request)
->save();
event(new InvoiceWasCreated($invoice, $invoice->company, Ninja::eventVars(auth()->user()->id)));
event(new InvoiceWasCreated($invoice, $invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
return $this->itemResponse($invoice);
}
@ -399,7 +399,7 @@ class InvoiceController extends BaseController
$invoice->service()->deletePdf();
event(new InvoiceWasUpdated($invoice, $invoice->company, Ninja::eventVars(auth()->user()->id)));
event(new InvoiceWasUpdated($invoice, $invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
return $this->itemResponse($invoice);
}
@ -671,10 +671,10 @@ class InvoiceController extends BaseController
}
break;
case 'download':
return response()->streamDownload(function () use ($invoice) {
echo file_get_contents($invoice->pdf_file_path());
}, basename($invoice->pdf_file_path()), ['Cache-Control:' => 'no-cache']);
//return response()->download(TempFile::path($invoice->pdf_file_path()), basename($invoice->pdf_file_path()));
$file = $invoice->pdf_file_path();
return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
break;
case 'restore':
$this->invoice_repo->restore($invoice);
@ -793,9 +793,10 @@ class InvoiceController extends BaseController
$contact = $invitation->contact;
$invoice = $invitation->invoice;
$file_path = $invoice->service()->getInvoicePdf($contact);
$file = $invoice->service()->getInvoicePdf($contact);
return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);;
return response()->download($file_path, basename($file_path), ['Cache-Control:' => 'no-cache']);
}
/**
@ -844,16 +845,11 @@ class InvoiceController extends BaseController
*/
public function deliveryNote(ShowInvoiceRequest $request, Invoice $invoice)
{
$file_path = $invoice->service()->getInvoiceDeliveryNote($invoice, $invoice->invitations->first()->contact);
try {
$file = public_path("storage/{$file_path}");
$file = $invoice->service()->getInvoiceDeliveryNote($invoice, $invoice->invitations->first()->contact);
return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache']);
} catch (\Exception $e) {
return response(['message' => 'Oops, something went wrong. Make sure you have symlink to storage/ in public/ directory.'], 500);
}
}
/**

View File

@ -107,6 +107,12 @@ class LicenseController extends BaseController
'errors' => new stdClass,
];
$account->plan_term = Account::PLAN_TERM_YEARLY;
$account->plan_paid = null;
$account->plan_expires = null;
$account->plan = Account::PLAN_FREE;
$account->save();
return response()->json($error, 400);
} else {
$account = auth()->user()->company()->account;

View File

@ -21,6 +21,7 @@ use App\Mail\ExistingMigration;
use App\Mail\Migration\MaxCompanies;
use App\Models\Company;
use App\Models\CompanyToken;
use App\Utils\Ninja;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
@ -378,14 +379,15 @@ class MigrationController extends BaseController
return;
}
// try {
// StartMigration::dispatch(base_path("storage/app/public/$migration_file"), $user, $fresh_company)->delay(now()->addSeconds(5));
nlog("starting migration job");
nlog($migration_file);
StartMigration::dispatch($migration_file, $user, $fresh_company)->onQueue('migration');
// } catch (\Exception $e) {
// nlog($e->getMessage());
// }
if(Ninja::isHosted())
StartMigration::dispatch($migration_file, $user, $fresh_company)->onQueue('migration');
else
StartMigration::dispatch($migration_file, $user, $fresh_company);
}
}

View File

@ -25,7 +25,6 @@ class OneTimeTokenController extends BaseController
{
private $contexts = [
'stripe_connect' => 'https://connect.stripe.com/oauth/authorize?response_type=code&client_id=ca_J2Fh2tZfMlaaItUfbUwBBx4JPss8jCz9&scope=read_write'
];
public function __construct()

View File

@ -385,7 +385,7 @@ class PaymentController extends BaseController
$payment = $this->payment_repo->save($request->all(), $payment);
event(new PaymentWasUpdated($payment, $payment->company, Ninja::eventVars(auth()->user()->id)));
event(new PaymentWasUpdated($payment, $payment->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
return $this->itemResponse($payment);
}

View File

@ -21,13 +21,18 @@ class PaymentWebhookController extends Controller
public function __invoke(PaymentWebhookRequest $request, string $company_key, string $company_gateway_id)
{
MultiDB::findAndSetDbByCompanyKey($company_key);
// MultiDB::findAndSetDbByCompanyKey($company_key);
$payment = $request->getPayment();
if(!$payment)
return response()->json(['message' => 'Payment record not found.'], 400);
$client = is_null($payment) ? $request->getClient() : $payment->client;
// $contact= $client->primary_contact()->first();
// Auth::guard('contact')->login($contact, true);
if(!$client)
return response()->json(['message' => 'Client record not found.'], 400);
return $request->getCompanyGateway()
->driver($client)

View File

@ -125,7 +125,13 @@ class PostMarkController extends BaseController
$this->invitation->email_status = 'delivered';
$this->invitation->save();
SystemLogger::dispatch($request->all(), SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_DELIVERY, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client);
SystemLogger::dispatch($request->all(),
SystemLog::CATEGORY_MAIL,
SystemLog::EVENT_MAIL_DELIVERY,
SystemLog::TYPE_WEBHOOK_RESPONSE,
$this->invitation->contact->client,
$this->invitation->company
);
}
// {
@ -167,7 +173,7 @@ class PostMarkController extends BaseController
LightLogs::create($bounce)->batch();
SystemLogger::dispatch($request->all(), SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_BOUNCED, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client);
SystemLogger::dispatch($request->all(), SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_BOUNCED, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client, $this->invitation->company);
}
// {
@ -209,7 +215,7 @@ class PostMarkController extends BaseController
LightLogs::create($bounce)->batch();
SystemLogger::dispatch($request->all(), SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_SPAM_COMPLAINT, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client);
SystemLogger::dispatch($request->all(), SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_SPAM_COMPLAINT, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client, $this->invitation->company);
}
private function discoverInvitation($message_id)

View File

@ -99,9 +99,10 @@ class PreviewController extends BaseController
$entity_obj->load('client');
App::setLocale($entity_obj->client->primary_contact()->preferredLocale());
App::forgetInstance('translator');
Lang::replace(Ninja::transformTranslations($entity_obj->client->getMergedSettings()));
$t = app('translator');
App::setLocale($entity_obj->client->primary_contact()->preferredLocale());
$t->replace(Ninja::transformTranslations($entity_obj->client->getMergedSettings()));
$html = new HtmlEngine($entity_obj->invitations()->first());
@ -131,11 +132,11 @@ class PreviewController extends BaseController
}
//if phantom js...... inject here..
if (config('ninja.phantomjs_pdf_generation')) {
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
return (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true));
}
if(config('ninja.invoiceninja_hosted_pdf_generation')){
if(config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja'){
return (new NinjaPdf())->build($maker->getCompiledHTML(true));
}
@ -151,7 +152,8 @@ class PreviewController extends BaseController
private function blankEntity()
{
App::forgetInstance('translator');
Lang::replace(Ninja::transformTranslations(auth()->user()->company()->settings));
$t = app('translator');
$t->replace(Ninja::transformTranslations(auth()->user()->company()->settings));
DB::beginTransaction();

View File

@ -211,7 +211,7 @@ class QuoteController extends BaseController
$quote = $quote->service()->fillDefaults()->save();
event(new QuoteWasCreated($quote, $quote->company, Ninja::eventVars(auth()->user()->id)));
event(new QuoteWasCreated($quote, $quote->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
return $this->itemResponse($quote);
}
@ -389,7 +389,7 @@ class QuoteController extends BaseController
$quote->service()->deletePdf();
event(new QuoteWasUpdated($quote, $quote->company, Ninja::eventVars(auth()->user()->id)));
event(new QuoteWasUpdated($quote, $quote->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
return $this->itemResponse($quote);
}
@ -675,10 +675,10 @@ class QuoteController extends BaseController
// code...
break;
case 'download':
return response()->streamDownload(function () use ($quote) {
echo file_get_contents($quote->pdf_file_path());
}, basename($quote->pdf_file_path()), ['Cache-Control:' => 'no-cache']);
//return response()->download(TempFile::path($quote->pdf_file_path()), basename($quote->pdf_file_path()));
$file = $quote->pdf_file_path();
return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
break;
case 'restore':
$this->quote_repo->restore($quote);
@ -730,7 +730,7 @@ class QuoteController extends BaseController
$file_path = $quote->service()->getQuotePdf($contact);
return response()->download($file_path, basename($file_path), ['Cache-Control:' => 'no-cache']);
return response()->download($file_path, basename($file_path), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
}
/**

View File

@ -11,6 +11,8 @@
namespace App\Http\Controllers;
use App\Events\RecurringInvoice\RecurringInvoiceWasCreated;
use App\Events\RecurringInvoice\RecurringInvoiceWasUpdated;
use App\Factory\RecurringInvoiceFactory;
use App\Filters\RecurringInvoiceFilters;
use App\Http\Requests\RecurringInvoice\ActionRecurringInvoiceRequest;
@ -25,8 +27,10 @@ use App\Models\Account;
use App\Models\RecurringInvoice;
use App\Repositories\RecurringInvoiceRepository;
use App\Transformers\RecurringInvoiceTransformer;
use App\Utils\Ninja;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\SavesDocuments;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
@ -200,6 +204,12 @@ class RecurringInvoiceController extends BaseController
{
$recurring_invoice = $this->recurring_invoice_repo->save($request->all(), RecurringInvoiceFactory::create(auth()->user()->company()->id, auth()->user()->id));
event(new RecurringInvoiceWasCreated($recurring_invoice, $recurring_invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
$offset = $recurring_invoice->client->timezone_offset();
$recurring_invoice->next_send_date = Carbon::parse($recurring_invoice->next_send_date)->startOfDay()->addSeconds($offset);
$recurring_invoice->save();
return $this->itemResponse($recurring_invoice);
}
@ -376,6 +386,8 @@ class RecurringInvoiceController extends BaseController
$recurring_invoice->service()->deletePdf()->save();
event(new RecurringInvoiceWasUpdated($recurring_invoice, $recurring_invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
return $this->itemResponse($recurring_invoice);
}
@ -490,7 +502,7 @@ class RecurringInvoiceController extends BaseController
$file_path = $recurring_invoice->service()->getInvoicePdf($contact);
return response()->download($file_path, basename($file_path), ['Cache-Control:' => 'no-cache']);
return response()->download($file_path, basename($file_path), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
}
/**

View File

@ -79,7 +79,6 @@ class SelfUpdateController extends BaseController
if (file_exists($cacheServices)) { unlink ($cacheServices); }
Artisan::call('clear-compiled');
Artisan::call('cache:clear');
Artisan::call('route:clear');
Artisan::call('view:clear');
Artisan::call('config:clear');

View File

@ -163,7 +163,7 @@ class SetupController extends Controller
/* Create the first account. */
if (Account::count() == 0) {
CreateAccount::dispatchNow($request->all());
CreateAccount::dispatchNow($request->all(), $request->getClientIp());
}
VersionCheck::dispatchNow();
@ -275,7 +275,9 @@ class SetupController extends Controller
public function update()
{
// if(Ninja::isHosted())
// return redirect('/');
// if( Ninja::isNinja() || !request()->has('secret') || (request()->input('secret') != config('ninja.update_secret')) )
if(!request()->has('secret') || (request()->input('secret') != config('ninja.update_secret')) )
return redirect('/');
@ -290,6 +292,11 @@ class SetupController extends Controller
unlink ($cacheServices);
}
$cacheRoute = base_path('bootstrap/cache/routes-v7.php');
if (file_exists($cacheRoute)) {
unlink ($cacheRoute);
}
Artisan::call('clear-compiled');
Artisan::call('route:clear');
Artisan::call('view:clear');

View File

@ -12,11 +12,16 @@
namespace App\Http\Controllers;
use App\DataMapper\FeesAndLimits;
use App\Factory\CompanyGatewayFactory;
use App\Http\Requests\StripeConnect\InitializeStripeConnectRequest;
use App\Libraries\MultiDB;
use App\Models\Client;
use App\Models\Company;
use App\Models\CompanyGateway;
use App\Models\GatewayType;
use App\PaymentDrivers\Stripe\Connect\Account;
use Illuminate\Http\Request;
use Stripe\Exception\ApiErrorException;
class StripeConnectController extends BaseController
@ -36,40 +41,128 @@ class StripeConnectController extends BaseController
MultiDB::findAndSetDbByCompanyKey($request->getTokenContent()['company_key']);
$data = [
'type' => 'standard',
'email' => $request->getContact()->email,
'country' => $request->getCompany()->country()->iso_3166_2,
];
$company = Company::where('company_key', $request->getTokenContent()['company_key'])->first();
$exists = CompanyGateway::query()
$company_gateway = CompanyGateway::query()
->where('gateway_key', 'd14dd26a47cecc30fdd65700bfb67b34')
->where('company_id', $request->getCompany()->id)
->first();
if ($exists) {
return render('gateways.stripe.connect.existing');
if ($company_gateway) {
$config = $company_gateway->getConfig();
if(property_exists($config, 'account_id'))
return view('auth.connect.existing');
}
$account = Account::create($data);
$stripe_client_id = config('ninja.ninja_stripe_client_id');
$redirect_uri = 'https://invoicing.co/stripe/completed';
$endpoint = "https://connect.stripe.com/oauth/authorize?response_type=code&client_id={$stripe_client_id}&redirect_uri={$redirect_uri}&scope=read_write&state={$token}";
$link = Account::link($account->id, $token);
// if($email = $request->getContact()->email)
// $endpoint .= "&stripe_user[email]={$email}";
$company_gateway = CompanyGatewayFactory::create($request->getCompany()->id, $request->getContact()->id);
// $company_name = str_replace(" ", "_", $company->present()->name());
// $endpoint .= "&stripe_user[business_name]={$company_name}";
$company_gateway->fill([
'gateway_key' => 'd14dd26a47cecc30fdd65700bfb67b34',
'fees_and_limits' => [],
'config' => encrypt(json_encode(['account_id' => $account->id]))
]);
return redirect($endpoint);
}
public function completed(InitializeStripeConnectRequest $request)
{
\Stripe\Stripe::setApiKey(config('ninja.ninja_stripe_key'));
try {
$response = \Stripe\OAuth::token([
'grant_type' => 'authorization_code',
'code' => $request->input('code'),
]);
}catch(\Exception $e)
{
nlog($e->getMessage());
}
// nlog($response);
$company = Company::where('company_key', $request->getTokenContent()['company_key'])->first();
$company_gateway = CompanyGatewayFactory::create($company->id, $company->owner()->id);
$fees_and_limits = new \stdClass;
$fees_and_limits->{GatewayType::CREDIT_CARD} = new FeesAndLimits;
$company_gateway->gateway_key = 'd14dd26a47cecc30fdd65700bfb67b34';
$company_gateway->fees_and_limits = $fees_and_limits;
$company_gateway->setConfig([]);
// $company_gateway->save();
$payload = [
'account_id' => $response->stripe_user_id,
"token_type" => 'bearer',
"stripe_publishable_key" => $response->stripe_publishable_key,
"scope" => $response->scope,
"livemode" => $response->livemode,
"stripe_user_id" => $response->stripe_user_id,
"refresh_token" => $response->refresh_token,
"access_token" => $response->access_token
];
/* Link account if existing account exists */
// if($account_id = $this->checkAccountAlreadyLinkToEmail($company_gateway, $request->getContact()->email)) {
// $payload['account_id'] = $account_id;
// $payload['stripe_user_id'] = $account_id;
// $company_gateway->setConfig($payload);
// $company_gateway->save();
// return view('auth.connect.existing');
// }
$company_gateway->setConfig($payload);
$company_gateway->save();
return redirect($link['url']);
//response here
return view('auth.connect.completed');
}
public function completed()
private function checkAccountAlreadyLinkToEmail($company_gateway, $email)
{
return render('gateways.stripe.connect.completed');
$client = Client::first() ? Client::first() : new Client;
//Pull the list of Stripe Accounts and see if we match
$accounts = $company_gateway->driver($client)->getAllConnectedAccounts()->data;
foreach($accounts as $account)
{
if($account['email'] == $email)
return $account['id'];
}
return false;
}
/*********************************
* Stripe OAuth
*/
// public function initialize(InitializeStripeConnectRequest $request, string $token)
// {
// $stripe_key = config('ninja.ninja_stripe_key');
// $endpoint = "https://connect.stripe.com/oauth/authorize?response_type=code&client_id={$stripe_key}&scope=read_write";
// return redirect($endpoint);
// }
}

View File

@ -0,0 +1,52 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Controllers;
use App\Jobs\Util\ImportStripeCustomers;
use App\Jobs\Util\StripeUpdatePaymentMethods;
class StripeController extends BaseController
{
public function update()
{
if(auth()->user()->isAdmin())
{
StripeUpdatePaymentMethods::dispatch(auth()->user()->company());
return response()->json(['message' => 'Processing'], 200);
}
return response()->json(['message' => 'Unauthorized'], 403);
}
public function import()
{
if(auth()->user()->isAdmin())
{
ImportStripeCustomers::dispatch(auth()->user()->company());
return response()->json(['message' => 'Processing'], 200);
}
return response()->json(['message' => 'Unauthorized'], 403);
}
}

View File

@ -41,7 +41,7 @@ class SubdomainController extends BaseController
public function index()
{
if(in_array(request()->input('subdomain'), $this->protected) || MultiDB::findAndSetDbByDomain(request()->input('subdomain')))
if(in_array(request()->input('subdomain'), $this->protected) || MultiDB::findAndSetDbByDomain(['subdomain' => request()->input('subdomain')]))
return response()->json(['message' => 'Domain not available'] , 401);
return response()->json(['message' => 'Domain available'], 200);

View File

@ -177,7 +177,7 @@ class SubscriptionController extends BaseController
{
$subscription = $this->subscription_repo->save($request->all(), SubscriptionFactory::create(auth()->user()->company()->id, auth()->user()->id));
event(new SubscriptionWasCreated($subscription, $subscription->company, Ninja::eventVars(auth()->user()->id)));
event(new SubscriptionWasCreated($subscription, $subscription->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
return $this->itemResponse($subscription);
}
@ -352,7 +352,7 @@ class SubscriptionController extends BaseController
$subscription = $this->subscription_repo->save($request->all(), $subscription);
event(new SubscriptionWasUpdated($subscription, $subscription->company, Ninja::eventVars(auth()->user()->id)));
event(new SubscriptionWasUpdated($subscription, $subscription->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
return $this->itemResponse($subscription);
}

View File

@ -284,7 +284,7 @@ class TaskController extends BaseController
if($task->status_order != $old_task->status_order)
$this->task_repo->sortStatuses($old_task, $task);
event(new TaskWasUpdated($task, $task->company, Ninja::eventVars(auth()->user()->id)));
event(new TaskWasUpdated($task, $task->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
return $this->itemResponse($task->fresh());
}
@ -378,7 +378,7 @@ class TaskController extends BaseController
{
$task = $this->task_repo->save($request->all(), TaskFactory::create(auth()->user()->company()->id, auth()->user()->id));
event(new TaskWasCreated($task, $task->company, Ninja::eventVars(auth()->user()->id)));
event(new TaskWasCreated($task, $task->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
return $this->itemResponse($task);
}
@ -631,6 +631,7 @@ class TaskController extends BaseController
$task_status = TaskStatus::where('id', $this->decodePrimaryKey($task_status_hashed_id))
->where('company_id', auth()->user()->company()->id)
->withTrashed()
->first();
$task_status->status_order = $key;
@ -643,18 +644,13 @@ class TaskController extends BaseController
$sort_status_id = $this->decodePrimaryKey($key);
// nlog($task_list);
foreach ($task_list as $key => $task)
{
// nlog($task);
$task_record = Task::where('id', $this->decodePrimaryKey($task))
->where('company_id', auth()->user()->company()->id)
->withTrashed()
->first();
// nlog($task_record->id);
$task_record->status_order = $key;
$task_record->status_id = $sort_status_id;
@ -663,6 +659,6 @@ class TaskController extends BaseController
}
return response()->json(['message' => 'Ok'],200);
return response()->json(['message' => 'Ok'], 200);
}
}

View File

@ -71,4 +71,12 @@ class TwoFactorController extends BaseController
}
public function disableTwoFactor()
{
$user = auth()->user();
$user->google_2fa_secret = null;
$user->save();
return $this->itemResponse($user);
}
}

View File

@ -63,9 +63,11 @@ class UserController extends BaseController
*/
public function __construct(UserRepository $user_repo)
{
parent::__construct();
$this->user_repo = $user_repo;
}
/**
@ -209,11 +211,12 @@ class UserController extends BaseController
$ct = CreateCompanyToken::dispatchNow($company, $user, $user_agent);
nlog("in the store method of the usercontroller class");
event(new UserWasCreated($user, auth()->user(), $company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
event(new UserWasCreated($user, auth()->user(), $company, Ninja::eventVars(auth()->user()->id)));
return $this->itemResponse($user->fresh());
$user->setCompany($company);
$user->company_id = $company->id;
return $this->itemResponse($user);
}
/**
@ -376,7 +379,6 @@ class UserController extends BaseController
*/
public function update(UpdateUserRequest $request, User $user)
{
$old_company_user = $user->company_user;
$old_user = json_encode($user);
$old_user_email = $user->getOriginal('email');
@ -401,7 +403,7 @@ class UserController extends BaseController
$user->company_user()->update(["permissions_updated_at" => now()]);
}
event(new UserWasUpdated($user, auth()->user(), auth()->user()->company, Ninja::eventVars(auth()->user()->id)));
event(new UserWasUpdated($user, auth()->user(), auth()->user()->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
return $this->itemResponse($user);
}
@ -474,7 +476,7 @@ class UserController extends BaseController
/* If the user passes the company user we archive the company user */
$user = $this->user_repo->delete($request->all(), $user);
event(new UserWasDeleted($user, auth()->user(), auth()->user()->company, Ninja::eventVars(auth()->user()->id)));
event(new UserWasDeleted($user, auth()->user(), auth()->user()->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
return $this->itemResponse($user->fresh());
}

View File

@ -278,7 +278,7 @@ class VendorController extends BaseController
$this->uploadLogo($request->file('company_logo'), $vendor->company, $vendor);
event(new VendorWasUpdated($vendor, $vendor->company, Ninja::eventVars(auth()->user()->id)));
event(new VendorWasUpdated($vendor, $vendor->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
return $this->itemResponse($vendor->fresh());
}
@ -376,7 +376,7 @@ class VendorController extends BaseController
$this->uploadLogo($request->file('company_logo'), $vendor->company, $vendor);
event(new VendorWasCreated($vendor, $vendor->company, Ninja::eventVars(auth()->user()->id)));
event(new VendorWasCreated($vendor, $vendor->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
return $this->itemResponse($vendor);
}

View File

@ -16,6 +16,7 @@ use App\Http\Middleware\Authenticate;
use App\Http\Middleware\CheckClientExistence;
use App\Http\Middleware\CheckForMaintenanceMode;
use App\Http\Middleware\ClientPortalEnabled;
use App\Http\Middleware\ContactAccount;
use App\Http\Middleware\ContactKeyLogin;
use App\Http\Middleware\ContactRegister;
use App\Http\Middleware\ContactSetDb;
@ -69,7 +70,7 @@ class Kernel extends HttpKernel
TrimStrings::class,
ConvertEmptyStringsToNull::class,
TrustProxies::class,
//\Fruitcake\Cors\HandleCors::class,
// \Fruitcake\Cors\HandleCors::class,
Cors::class,
];
@ -84,7 +85,6 @@ class Kernel extends HttpKernel
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
SubstituteBindings::class,
@ -95,7 +95,6 @@ class Kernel extends HttpKernel
'throttle:300,1',
'bindings',
'query_logging',
Cors::class,
],
'contact' => [
'throttle:60,1',
@ -110,7 +109,6 @@ class Kernel extends HttpKernel
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
SubstituteBindings::class,
//\App\Http\Middleware\StartupCheck::class,
QueryLogging::class,
],
'shop' => [
@ -142,6 +140,7 @@ class Kernel extends HttpKernel
'api_secret_check' => ApiSecretCheck::class,
'contact_token_auth' => ContactTokenAuth::class,
'contact_db' => ContactSetDb::class,
'contact_account' => ContactAccount::class,
'domain_db' => SetDomainNameDb::class,
'email_db' => SetEmailDb::class,
'invite_db' => SetInviteDb::class,
@ -153,11 +152,37 @@ class Kernel extends HttpKernel
'api_db' => SetDb::class,
'company_key_db' => SetDbByCompanyKey::class,
'locale' => Locale::class,
'contact.register' => ContactRegister::class,
'contact_register' => ContactRegister::class,
'shop_token_auth' => ShopTokenAuth::class,
'phantom_secret' => PhantomSecret::class,
'contact_key_login' => ContactKeyLogin::class,
'check_client_existence' => CheckClientExistence::class,
'user_verified' => UserVerified::class,
];
protected $middlewarePriority = [
Cors::class,
SetDomainNameDb::class,
SetDb::class,
SetWebDb::class,
UrlSetDb::class,
ContactSetDb::class,
SetEmailDb::class,
SetInviteDb::class,
SetDbByCompanyKey::class,
TokenAuth::class,
ContactTokenAuth::class,
ContactKeyLogin::class,
Authenticate::class,
ShopTokenAuth::class,
ContactRegister::class,
PhantomSecret::class,
CheckClientExistence::class,
ClientPortalEnabled::class,
PasswordProtection::class,
Locale::class,
SubstituteBindings::class,
ContactAccount::class,
];
}

View File

@ -14,11 +14,12 @@ namespace App\Http\Livewire;
use App\Factory\ClientFactory;
use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject;
use App\Libraries\MultiDB;
use App\Mail\ContactPasswordlessLogin;
use App\Models\Client;
use App\Models\Subscription;
use App\Models\ClientContact;
use App\Models\Invoice;
use App\Models\Subscription;
use App\Repositories\ClientContactRepository;
use App\Repositories\ClientRepository;
use Illuminate\Support\Facades\App;
@ -162,8 +163,24 @@ class BillingPortalPurchase extends Component
*/
public $passwordless_login_btn = false;
/**
* Instance of company.
*
* @var Company
*/
public $company;
/**
* Campaign reference.
*
* @var string|null
*/
public $campaign;
public function mount()
{
MultiDB::setDb($this->company->db);
$this->price = $this->subscription->price;
if (request()->query('coupon')) {
@ -181,14 +198,16 @@ class BillingPortalPurchase extends Component
{
$this->validate();
$contact = ClientContact::where('email', $this->email)->first();
$contact = ClientContact::where('email', $this->email)
->where('company_id', $this->subscription->company_id)
->first();
if ($contact && $this->steps['existing_user'] === false) {
return $this->steps['existing_user'] = true;
}
if ($contact && $this->steps['existing_user']) {
$attempt = Auth::guard('contact')->attempt(['email' => $this->email, 'password' => $this->password]);
$attempt = Auth::guard('contact')->attempt(['email' => $this->email, 'password' => $this->password, 'company_id' => $this->subscription->company_id]);
return $attempt
? $this->getPaymentMethods($contact)
@ -260,7 +279,7 @@ class BillingPortalPurchase extends Component
*/
protected function getPaymentMethods(ClientContact $contact): self
{
Auth::guard('contact')->login($contact);
Auth::guard('contact')->login($contact, true);
$this->contact = $contact;
@ -270,8 +289,8 @@ class BillingPortalPurchase extends Component
return $this;
}
if((int)$this->subscription->price == 0)
if ((int)$this->subscription->price == 0)
$this->steps['payment_required'] = false;
else
$this->steps['fetched_payment_methods'] = true;
@ -330,22 +349,22 @@ class BillingPortalPurchase extends Component
$is_eligible = $this->subscription->service()->isEligible($this->contact);
if ($is_eligible['exception']['message'] != 'Success') {
if (is_array($is_eligible) && $is_eligible['message'] != 'Success') {
$this->steps['not_eligible'] = true;
$this->steps['not_eligible_message'] = $is_eligible['exception']['message'];
$this->steps['not_eligible_message'] = $is_eligible['message'];
$this->steps['show_loading_bar'] = false;
return;
}
Cache::put($this->hash, [
'subscription_id' => $this->subscription->id,
'email' => $this->email ?? $this->contact->email,
'client_id' => $this->contact->client->id,
'invoice_id' => $this->invoice->id,
'context' => 'purchase',
now()->addMinutes(60)]
);
'subscription_id' => $this->subscription->id,
'email' => $this->email ?? $this->contact->email,
'client_id' => $this->contact->client->id,
'invoice_id' => $this->invoice->id,
'context' => 'purchase',
'campaign' => $this->campaign,
], now()->addMinutes(60));
$this->emit('beforePaymentEventsCompleted');
}
@ -368,11 +387,11 @@ class BillingPortalPurchase extends Component
public function handlePaymentNotRequired()
{
$is_eligible = $this->subscription->service()->isEligible($this->contact);
$is_eligible = $this->subscription->service()->isEligible($this->contact);
if ($is_eligible['status_code'] != 200) {
$this->steps['not_eligible'] = true;
$this->steps['not_eligible_message'] = $is_eligible['exception']['message'];
$this->steps['not_eligible_message'] = $is_eligible['message'];
$this->steps['show_loading_bar'] = false;
return;
@ -432,7 +451,7 @@ class BillingPortalPurchase extends Component
->first();
$mailer = new NinjaMailerObject();
$mailer->mailable = new ContactPasswordlessLogin($this->email, (string)route('client.subscription.purchase', $this->subscription->hashed_id) . '?coupon=' . $this->coupon);
$mailer->mailable = new ContactPasswordlessLogin($this->email, $this->subscription->company->id, (string)route('client.subscription.purchase', $this->subscription->hashed_id) . '?coupon=' . $this->coupon);
$mailer->company = $this->subscription->company;
$mailer->settings = $this->subscription->company->settings;
$mailer->to_user = $contact;

View File

@ -12,6 +12,7 @@
namespace App\Http\Livewire;
use App\Libraries\MultiDB;
use App\Models\Credit;
use App\Utils\Traits\WithSorting;
use Livewire\Component;
@ -24,6 +25,13 @@ class CreditsTable extends Component
public $per_page = 10;
public $company;
public function mount()
{
MultiDB::setDb($this->company->db);
}
public function render()
{
$query = Credit::query()

View File

@ -12,6 +12,7 @@
namespace App\Http\Livewire;
use App\Libraries\MultiDB;
use App\Models\Client;
use App\Utils\Traits\WithSorting;
use Livewire\Component;
@ -25,8 +26,13 @@ class DocumentsTable extends Component
public $per_page = 10;
public $company;
public function mount($client)
{
MultiDB::setDb($this->company->db);
$this->client = $client;
}

View File

@ -12,6 +12,7 @@
namespace App\Http\Livewire;
use App\Libraries\MultiDB;
use App\Models\Invoice;
use App\Utils\Traits\WithSorting;
use Carbon\Carbon;
@ -26,8 +27,12 @@ class InvoicesTable extends Component
public $status = [];
public $company;
public function mount()
{
MultiDB::setDb($this->company->db);
$this->sort_asc = false;
$this->sort_field = 'date';
@ -38,7 +43,8 @@ class InvoicesTable extends Component
$local_status = [];
$query = Invoice::query()
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc');
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
->where('is_deleted', false);
if (in_array('paid', $this->status)) {
$local_status[] = Invoice::STATUS_PAID;

View File

@ -12,6 +12,7 @@
namespace App\Http\Livewire;
use App\Libraries\MultiDB;
use Livewire\Component;
class PayNowDropdown extends Component
@ -20,8 +21,12 @@ class PayNowDropdown extends Component
public $methods;
public $company;
public function mount(int $total)
{
MultiDB::setDb($this->company->db);
$this->total = $total;
$this->methods = auth()->user()->client->service()->getPaymentMethods($total);

View File

@ -5,6 +5,7 @@
namespace App\Http\Livewire;
use App\Libraries\MultiDB;
use App\Models\ClientGatewayToken;
use App\Utils\Traits\WithSorting;
use Livewire\Component;
@ -16,10 +17,16 @@ class PaymentMethodsTable extends Component
use WithSorting;
public $per_page = 10;
public $client;
public $company;
public function mount($client)
{
MultiDB::setDb($this->company->db);
$this->client = $client;
}

View File

@ -12,6 +12,7 @@
namespace App\Http\Livewire;
use App\Libraries\MultiDB;
use App\Models\Payment;
use App\Utils\Traits\WithSorting;
use Livewire\Component;
@ -23,11 +24,17 @@ class PaymentsTable extends Component
use WithPagination;
public $per_page = 10;
public $user;
public $company;
public function mount()
{
MultiDB::setDb($this->company->db);
$this->user = auth()->user();
}
public function render()

View File

@ -9,6 +9,7 @@ class NameWebsiteLogo extends Component
public $profile;
public $name;
public $vat_number;
public $website;
public $phone;
@ -16,6 +17,7 @@ class NameWebsiteLogo extends Component
public $rules = [
'name' => ['sometimes', 'min:3'],
'vat_number' => ['sometimes'],
'website' => ['sometimes'],
'phone' => ['sometimes', 'string', 'max:255'],
];
@ -25,6 +27,7 @@ class NameWebsiteLogo extends Component
$this->fill([
'profile' => auth()->user('contact')->client,
'name' => auth()->user('contact')->client->present()->name,
'vat_number' => auth()->user('contact')->client->present()->vat_number,
'website' => auth()->user('contact')->client->present()->website,
'phone' => auth()->user('contact')->client->present()->phone,
'saved' => ctrans('texts.save'),
@ -41,6 +44,7 @@ class NameWebsiteLogo extends Component
$data = $this->validate($this->rules);
$this->profile->name = $data['name'];
$this->profile->vat_number = $data['vat_number'];
$this->profile->website = $data['website'];
$this->profile->phone = $data['phone'];

View File

@ -12,6 +12,7 @@
namespace App\Http\Livewire;
use App\Libraries\MultiDB;
use App\Models\Quote;
use App\Utils\Traits\WithSorting;
use Livewire\Component;
@ -23,8 +24,17 @@ class QuotesTable extends Component
use WithPagination;
public $per_page = 10;
public $status = [];
public $company;
public function mount()
{
MultiDB::setDb($this->company->db);
}
public function render()
{
$query = Quote::query()

View File

@ -12,6 +12,7 @@
namespace App\Http\Livewire;
use App\Libraries\MultiDB;
use App\Models\RecurringInvoice;
use Livewire\Component;
@ -22,6 +23,18 @@ class RecurringInvoiceCancellation extends Component
*/
public $invoice;
public $company;
public function mount()
{
MultiDB::setDb($this->company->db);
}
public function render()
{
return render('components.livewire.recurring-invoice-cancellation');
}
public function processCancellation()
{
if ($this->invoice->subscription) {
@ -31,8 +44,5 @@ class RecurringInvoiceCancellation extends Component
return redirect()->route('client.recurring_invoices.request_cancellation', ['recurring_invoice' => $this->invoice->hashed_id]);
}
public function render()
{
return render('components.livewire.recurring-invoice-cancellation');
}
}

View File

@ -13,6 +13,7 @@
namespace App\Http\Livewire;
use App\Libraries\MultiDB;
use App\Models\ClientContact;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
@ -65,7 +66,12 @@ class RequiredClientInfo extends Component
public $show_form = false;
public function mount() {}
public $company;
public function mount()
{
MultiDB::setDb($this->company->db);
}
public function handleSubmit(array $data): bool
{
@ -88,7 +94,9 @@ class RequiredClientInfo extends Component
}
if ($this->updateClientDetails($data)) {
$this->emit('passed-required-fields-check');
$this->emit('passed-required-fields-check', [
'client_postal_code' => $this->contact->client->postal_code,
]);
return true;
}

View File

@ -12,6 +12,7 @@
namespace App\Http\Livewire;
use App\Libraries\MultiDB;
use App\Models\ClientContact;
use App\Models\Subscription;
use Illuminate\Support\Facades\Cache;
@ -71,8 +72,12 @@ class SubscriptionPlanSwitch extends Component
*/
public $hash;
public $company;
public function mount()
{
MultiDB::setDb($this->company->db);
$this->total = $this->amount;
$this->methods = $this->contact->client->service()->getPaymentMethods($this->amount);

View File

@ -12,6 +12,7 @@
namespace App\Http\Livewire;
use App\Libraries\MultiDB;
use App\Models\RecurringInvoice;
use App\Utils\Traits\WithSorting;
use Livewire\Component;
@ -24,6 +25,13 @@ class SubscriptionRecurringInvoicesTable extends Component
public $per_page = 10;
public $company;
public function mount()
{
MultiDB::setDb($this->company->db);
}
public function render()
{
$query = RecurringInvoice::query()

Some files were not shown because too many files have changed in this diff Show More