1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-16 08:02:40 +01:00

Merge pull request #5968 from turbo124/v5-stable

v5.1.72
This commit is contained in:
David Bomba 2021-06-08 22:31:00 +10:00 committed by GitHub
commit 0a2f9069cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
101 changed files with 154251 additions and 153097 deletions

View File

@ -1 +1 @@
5.1.70
5.1.72

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;
/*
@ -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;
}
@ -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;
}
@ -332,8 +348,8 @@ class CheckData extends Command
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_amount = $invoice->payments->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED])->sum('pivot.amount');
$total_refund = $invoice->payments->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED])->sum('pivot.refunded');
$total_credit = $invoice->credits->sum('amount');
$total_paid = $total_amount - $total_refund;
@ -370,7 +386,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 +395,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()
{

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

@ -16,6 +16,7 @@ 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;
@ -47,6 +48,8 @@ class Kernel extends ConsoleKernel
$schedule->job(new VersionCheck)->daily();
$schedule->job(new DiskCleanup)->daily()->withoutOverlapping();
$schedule->command('ninja:check-data --database=db-ninja-01')->daily()->withoutOverlapping();
$schedule->job(new ReminderJob)->daily()->withoutOverlapping();

View File

@ -254,7 +254,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

View File

@ -50,6 +50,7 @@ class Handler extends ExceptionHandler
//Swift_TransportException::class,
MaxAttemptsExceededException::class,
CommandNotFoundException::class,
ValidationException::class,
];
/**

View File

@ -15,6 +15,7 @@ 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;
@ -34,11 +35,19 @@ class ContactLoginController extends Controller
public function showLoginForm(Request $request)
{
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]);
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 = '')

View File

@ -38,6 +38,7 @@ 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;
@ -212,15 +213,24 @@ class LoginController extends BaseController
if(!$cu->exists())
return response()->json(['message' => 'User not linked to any companies'], 403);
$cu->first()->account->companies->each(function ($company) use($cu, $request){
/* Ensure the user has a valid token */
$user->company_users->each(function ($company_user) use($request){
if($company->tokens()->where('is_system', true)->count() == 0)
{
CreateCompanyToken::dispatchNow($company, $cu->first()->user, $request->server('HTTP_USER_AGENT'));
if($company_user->tokens->count() == 0){
CreateCompanyToken::dispatchNow($company_user->company, $company_user->user, $request->server('HTTP_USER_AGENT'));
}
});
// $cu->first()->account->companies->each(function ($company) use($cu, $request){
// if($company->tokens()->where('is_system', true)->count() == 0)
// {
// CreateCompanyToken::dispatchNow($company, $cu->first()->user, $request->server('HTTP_USER_AGENT'));
// }
// });
return $this->timeConstrainedResponse($cu);
@ -489,49 +499,37 @@ class LoginController extends BaseController
if (request()->has('code')) {
return $this->handleProviderCallback($provider);
} else {
return Socialite::driver($provider)->scopes($scopes)->redirect();
return Socialite::driver($provider)->with(['redirect_uri' => config('ninja.app_url')."/auth/google"])->scopes($scopes)->redirect();
}
}
public function handleProviderCallback(string $provider)
{
$socialite_user = Socialite::driver($provider)
->stateless()
->user();
// if($user = OAuth::handleAuth($socialite_user, $provider))
// {
// Auth::login($user, true);
if($user = OAuth::handleAuth($socialite_user, $provider))
{
// return redirect($this->redirectTo);
// }
// else if(MultiDB::checkUserEmailExists($socialite_user->getEmail()))
// {
// Session::flash('error', 'User exists in system, but not with this authentication method'); //todo add translations
nlog('found user and updating their user record');
// return view('auth.login');
// }
// else {
// //todo
// $name = OAuth::splitName($socialite_user->getName());
$update_user = [
'first_name' => $name[0],
'last_name' => $name[1],
'password' => '',
'email' => $socialite_user->getEmail(),
'oauth_user_id' => $socialite_user->getId(),
'oauth_provider_id' => $provider,
'oauth_user_token' => $socialite_user->refreshToken,
];
// $new_account = [
// 'first_name' => $name[0],
// 'last_name' => $name[1],
// 'password' => '',
// 'email' => $socialite_user->getEmail(),
// 'oauth_user_id' => $socialite_user->getId(),
// 'oauth_provider_id' => $provider
// ];
$user->update($update_user);
// $account = CreateAccount::dispatchNow($new_account);
// Auth::login($account->default_company->owner(), true);
// $cookie = cookie('db', $account->default_company->db);
// return redirect($this->redirectTo)->withCookie($cookie);
// }
}
else {
nlog("user not found for oauth");
}
return redirect('/#/');
}
}

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')->loginUsingId($invitation->contact->id, 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')->loginUsingId($invitation->contact->id, true);
nlog("else - default - login contact");
auth()->guard('contact')->login($client_contact, 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

@ -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

@ -18,6 +18,7 @@ use App\Utils\Traits\MakesHash;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
use ZipArchive;
class ImportJsonController extends BaseController
{
@ -56,7 +57,7 @@ class ImportJsonController extends BaseController
* ),
* )
*/
public function index(ImportJsonRequest $request)
public function import(ImportJsonRequest $request)
{
$import_file = $request->file('files');
@ -64,10 +65,12 @@ class ImportJsonController extends BaseController
$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->all());
CompanyImport::dispatch(auth()->user()->getCompany(), auth()->user(), $hash, $request->except('files'))->delay(now()->addMinutes(1));
return response()->json(['message' => 'Processing'], 200);
@ -86,11 +89,11 @@ class ImportJsonController extends BaseController
if (! file_exists($file_location))
throw new NonExistingMigrationFile('Backup file does not exist, or is corrupted.');
$data = json_decode(file_get_contents($file_location));
$data = file_get_contents($file_location);
unlink($file_contents);
unlink($file_location);
return $data
return $data;
}
}

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

@ -70,7 +70,7 @@ class Kernel extends HttpKernel
TrimStrings::class,
ConvertEmptyStringsToNull::class,
TrustProxies::class,
//\Fruitcake\Cors\HandleCors::class,
// \Fruitcake\Cors\HandleCors::class,
Cors::class,
];
@ -85,7 +85,6 @@ class Kernel extends HttpKernel
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
SubstituteBindings::class,
@ -96,7 +95,6 @@ class Kernel extends HttpKernel
'throttle:300,1',
'bindings',
'query_logging',
Cors::class,
],
'contact' => [
'throttle:60,1',
@ -154,7 +152,7 @@ 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,
@ -164,6 +162,7 @@ class Kernel extends HttpKernel
protected $middlewarePriority = [
Cors::class,
SetDomainNameDb::class,
SetDb::class,
SetWebDb::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')) {
@ -182,8 +199,8 @@ class BillingPortalPurchase extends Component
$this->validate();
$contact = ClientContact::where('email', $this->email)
->where('company_id', $this->subscription->company_id)
->first();
->where('company_id', $this->subscription->company_id)
->first();
if ($contact && $this->steps['existing_user'] === false) {
return $this->steps['existing_user'] = true;
@ -262,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;
@ -272,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;
@ -332,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');
}
@ -370,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;
@ -434,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';

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

@ -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
{

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()

View File

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

View File

@ -39,7 +39,25 @@ class ContactKeyLogin
Auth::guard('contact')->logout();
}
if ($request->segment(3) && config('ninja.db.multi_db_enabled')) {
if ($request->segment(2) && $request->segment(2) == 'magic_link' && $request->segment(3)) {
$payload = Cache::get($request->segment(3));
$contact_email = $payload['email'];
if($client_contact = ClientContact::where('email', $contact_email)->where('company_id', $payload['company_id'])->first()){
if(empty($client_contact->email))
$client_contact->email = Str::random(15) . "@example.com"; $client_contact->save();
auth()->guard('contact')->login($client_contact, true);
if ($request->query('redirect') && !empty($request->query('redirect'))) {
return redirect()->to($request->query('redirect'));
}
return redirect()->to('client/dashboard');
}
}
elseif ($request->segment(3) && config('ninja.db.multi_db_enabled')) {
if (MultiDB::findAndSetDbByContactKey($request->segment(3))) {
if($client_contact = ClientContact::where('contact_key', $request->segment(3))->first()){
@ -84,21 +102,6 @@ class ContactKeyLogin
auth()->guard('contact')->login($primary_contact, true);
return redirect()->to('client/dashboard');
}
} elseif ($request->segment(2) && $request->segment(2) == 'magic_link' && $request->segment(3)) {
$contact_email = Cache::get($request->segment(3));
if($client_contact = ClientContact::where('email', $contact_email)->first()){
if(empty($client_contact->email))
$client_contact->email = Str::random(6) . "@example.com"; $client_contact->save();
auth()->guard('contact')->login($client_contact, true);
if ($request->query('redirect') && !empty($request->query('redirect'))) {
return redirect()->to($request->query('redirect'));
}
return redirect()->to('client/dashboard');
}
}

View File

@ -19,17 +19,44 @@ class ContactRegister
*/
public function handle($request, Closure $next)
{
// Resolving based on subdomain. Used in version 5 hosted platform.
if ($request->subdomain) {
$company = Company::where('subdomain', $request->subdomain)->firstOrFail();
abort_unless($company->getSetting('enable_client_registration'), 404);
if (strpos($request->getHost(), 'invoicing.co') !== false)
{
$subdomain = explode('.', $request->getHost())[0];
$query = [
'subdomain' => $subdomain,
'portal_mode' => 'subdomain',
];
$company = Company::where($query)->first();
if($company)
{
abort_unless($company->client_can_register, 404);
$request->merge(['key' => $company->company_key]);
return $next($request);
}
}
$query = [
'portal_domain' => $request->getSchemeAndHttpHost(),
'portal_mode' => 'domain',
];
if($company = Company::where($query)->first())
{
abort_unless($company->client_can_register, 404);
$request->merge(['key' => $company->company_key]);
return $next($request);
}
// For self-hosted platforms with multiple companies, resolving is done using company key
// if it doesn't resolve using a domain.
if ($request->route()->parameter('company_key') && Ninja::isSelfHost()) {

View File

@ -16,7 +16,7 @@ class Cors
// ALLOW OPTIONS METHOD
$headers = [
'Access-Control-Allow-Methods'=> 'POST, GET, OPTIONS, PUT, DELETE',
'Access-Control-Allow-Headers'=> 'X-API-COMPANY-KEY,X-CLIENT-VERSION,X-API-SECRET,X-API-TOKEN,X-API-PASSWORD,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range',
'Access-Control-Allow-Headers'=> 'X-API-COMPANY-KEY,X-CLIENT-VERSION,X-API-SECRET,X-API-TOKEN,X-API-PASSWORD,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,X-CSRF-TOKEN,X-XSRF-TOKEN,X-LIVEWIRE',
];
return Response::make('OK', 200, $headers);
@ -26,11 +26,11 @@ class Cors
$response->headers->set('Access-Control-Allow-Origin', '*');
$response->headers->set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
$response->headers->set('Access-Control-Allow-Headers', 'X-API-COMPANY-KEY,X-API-SECRET,X-API-TOKEN,X-API-PASSWORD,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range');
$response->headers->set('Access-Control-Allow-Headers', 'X-API-COMPANY-KEY,X-API-SECRET,X-API-TOKEN,X-API-PASSWORD,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,X-CSRF-TOKEN,X-XSRF-TOKEN,X-LIVEWIRE');
$response->headers->set('Access-Control-Expose-Headers', 'X-APP-VERSION,X-MINIMUM-CLIENT-VERSION');
$response->headers->set('X-APP-VERSION', config('ninja.app_version'));
$response->headers->set('X-MINIMUM-CLIENT-VERSION', config('ninja.minimum_client_version'));
return $response;
}
}
}

View File

@ -86,6 +86,7 @@ class SetDomainNameDb
}
// config(['app.url' => $request->getSchemeAndHttpHost()]);
return $next($request);
}

View File

@ -28,6 +28,6 @@ class VerifyCsrfToken extends Middleware
* @var array
*/
protected $except = [
//
// 'livewire/message/*'
];
}

View File

@ -0,0 +1,57 @@
<?php
namespace App\Http\Requests\Gateways\Checkout3ds;
use App\Models\Client;
use App\Models\Company;
use App\Models\CompanyGateway;
use App\Models\PaymentHash;
use App\Utils\Traits\MakesHash;
use Illuminate\Foundation\Http\FormRequest;
class Checkout3dsRequest extends FormRequest
{
use MakesHash;
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
//
];
}
public function getCompany()
{
return Company::where('company_key', $this->company_key)->first();
}
public function getCompanyGateway()
{
return CompanyGateway::find($this->decodePrimaryKey($this->company_gateway_id));
}
public function getPaymentHash()
{
return PaymentHash::where('hash', $this->hash)->first();
}
public function getClient()
{
return Client::find($this->getPaymentHash()->data->client_id);
}
}

View File

@ -100,14 +100,15 @@ class PaymentWebhookRequest extends Request
/**
* Resolve client from payment hash.
*
* @return null|\App\Models\Client
* @return null|\App\Models\Client|bool
*/
public function getClient()
{
$hash = $this->getPaymentHash();
if($hash)
if($hash) {
return Client::find($hash->data->client_id)->firstOrFail();
}
return false;
}

View File

@ -79,7 +79,6 @@ class CreateAccount
$sp035a66 = CreateCompany::dispatchNow($this->request, $sp794f3f);
$sp035a66->load('account');
$sp035a66->settings = $this->processSettings($sp035a66->settings);
$sp794f3f->default_company_id = $sp035a66->id;
$sp794f3f->save();
@ -116,11 +115,12 @@ class CreateAccount
private function processSettings($settings)
{
if(Ninja::isHosted() && Cache::get('currencies') && $data = unserialize(@file_get_contents('http://www.geoplugin.net/php.gp?ip=' . $this->client_ip)))
if(Ninja::isHosted() && Cache::get('currencies'))
{
$currency_code = strtolower($data['geoplugin_currencyCode']);
$country_code = strtolower($data['geoplugin_countryCode']);
//&& $data = unserialize(@file_get_contents('http://www.geoplugin.net/php.gp?ip=' . $this->client_ip))
// $currency_code = strtolower($data['geoplugin_currencyCode']);
// $country_code = strtolower($data['geoplugin_countryCode']);
$currency = Cache::get('currencies')->filter(function ($item) use ($currency_code) {
return strtolower($item->code) == $currency_code;
@ -146,7 +146,7 @@ class CreateAccount
$settings->language_id = (string)$language->id;
}
$timezone = Timezone::where('name', $data['geoplugin_timezone'])->first();
//$timezone = Timezone::where('name', $data['geoplugin_timezone'])->first();
if($timezone) {
$settings->timezone_id = (string)$timezone->id;

View File

@ -168,7 +168,7 @@ class CompanyExport implements ShouldQueue
$this->export_data['company'] = $this->company->toArray();
$this->export_data['company_gateways'] = $this->company->company_gateways->map(function ($company_gateway){
$this->export_data['company_gateways'] = $this->company->company_gateways()->withTrashed()->cursor()->map(function ($company_gateway){
$company_gateway = $this->transformArrayOfKeys($company_gateway, ['company_id', 'user_id']);
$company_gateway->config = decrypt($company_gateway->config);
@ -480,6 +480,7 @@ class CompanyExport implements ShouldQueue
$file_name = date('Y-m-d').'_'.str_replace(' ', '_', $this->company->present()->name() . '_' . $this->company->company_key .'.zip');
Storage::makeDirectory(public_path('storage/backups/'), 0775);
$zip_path = public_path('storage/backups/'.$file_name);
$zip = new \ZipArchive();
@ -502,8 +503,6 @@ class CompanyExport implements ShouldQueue
NinjaMailerJob::dispatch($nmo);
UnlinkFile::dispatch(config('filesystems.default'), 'backups/'.$file_name)->delay(now()->addHours(1));
UnlinkFile::dispatch('public', 'backups/'.$file_name)->delay(now()->addHours(1));
}
}

View File

@ -19,7 +19,9 @@ use App\Jobs\Util\UnlinkFile;
use App\Libraries\MultiDB;
use App\Mail\DownloadBackup;
use App\Mail\DownloadInvoices;
use App\Mail\Import\CompanyImportFailure;
use App\Models\Activity;
use App\Models\Backup;
use App\Models\Client;
use App\Models\ClientContact;
use App\Models\ClientGatewayToken;
@ -40,6 +42,7 @@ use App\Models\Payment;
use App\Models\PaymentTerm;
use App\Models\Paymentable;
use App\Models\Product;
use App\Models\Project;
use App\Models\Quote;
use App\Models\QuoteInvitation;
use App\Models\RecurringInvoice;
@ -59,6 +62,7 @@ use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use ZipArchive;
@ -85,6 +89,10 @@ class CompanyImport implements ShouldQueue
private $request_array = [];
public $message = '';
public $pre_flight_checks_pass = true;
private $importables = [
// 'company',
'users',
@ -145,28 +153,128 @@ class CompanyImport implements ShouldQueue
$this->company = Company::where('company_key', $this->company->company_key)->firstOrFail();
$this->account = $this->company->account;
nlog("Company ID = {$this->company->id}");
nlog("Hash ID = {$this->hash}");
$this->backup_file = Cache::get($this->hash);
if ( empty( $this->backup_file ) )
throw new \Exception('No import data found, has the cache expired?');
$this->backup_file = base64_decode($this->backup_file);
$this->backup_file = json_decode(base64_decode($this->backup_file));
// nlog($this->backup_file);
if(array_key_exists('import_settings', $request) && $request['import_settings'] == 'true') {
$this->preFlightChecks()->importSettings();
if(array_key_exists('import_settings', $this->request_array) && $this->request_array['import_settings'] == 'true') {
$this->checkUserCount()->preFlightChecks()->importSettings();
}
if(array_key_exists('import_data', $request) && $request['import_data'] == 'true') {
if(array_key_exists('import_data', $this->request_array) && $this->request_array['import_data'] == 'true') {
$this->preFlightChecks()
->purgeCompanyData()
->importData();
try{
$this->checkUserCount()
->preFlightChecks()
->purgeCompanyData()
->importData();
}
catch(\Exception $e){
info($e->getMessage());
}
}
}
/**
* On the hosted platform we cannot allow the
* import to start if there are users > plan number
* due to entity user_id dependencies
*
* @return bool
*/
private function checkUserCount()
{
if(Ninja::isSelfHost())
$this->pre_flight_checks_pass = true;
$backup_users = $this->backup_file->users;
$company_users = $this->company->users;
$company_owner = $this->company->owner();
if($this->company->account->isFreeHostedClient()){
nlog("This is a free account");
nlog("Backup user count = ".count($backup_users));
if(count($backup_users) > 1){
$this->message = 'Only one user can be in the import for a Free Account';
$this->pre_flight_checks_pass = false;
}
nlog("backup users email = " . $backup_users[0]->email);
if(count($backup_users) == 1 && $company_owner->email != $backup_users[0]->email) {
$this->message = 'Account emails do not match. Account owner email must match backup user email';
$this->pre_flight_checks_pass = false;
}
$backup_users_emails = array_column($backup_users, 'email');
$company_users_emails = $company_users->pluck('email')->toArray();
$existing_user_count = count(array_intersect($backup_users_emails, $company_users_emails));
nlog("existing user count = {$existing_user_count}");
if($existing_user_count > 1){
if($this->account->plan == 'pro'){
$this->message = 'Pro plan is limited to one user, you have multiple users in the backup file';
$this->pre_flight_checks_pass = false;
}
if($this->account->plan == 'enterprise'){
$total_import_users = count($backup_users_emails);
$account_plan_num_user = $this->account->num_users;
if($total_import_users > $account_plan_num_user){
$this->message = "Total user count ({$total_import_users}) greater than your plan allows ({$account_plan_num_user})";
$this->pre_flight_checks_pass = false;
}
}
}
if($this->company->account->isFreeHostedClient() && count($this->backup_file->clients) > config('ninja.quotas.free.clients')){
nlog("client quota busted");
$client_count = count($this->backup_file->clients);
$client_limit = config('ninja.quotas.free.clients');
$this->message = "You are attempting to import ({$client_count}) clients, your current plan allows a total of ({$client_limit})";
$this->pre_flight_checks_pass = false;
}
}
nlog($this->message);
nlog($this->pre_flight_checks_pass);
return $this;
}
//check if this is a complete company import OR if it is selective
/*
@ -177,12 +285,23 @@ class CompanyImport implements ShouldQueue
private function preFlightChecks()
{
//check the file version and perform any necessary adjustments to the file in order to proceed - needed when we change schema
if($this->current_app_version != $this->backup_file->app_version)
{
//perform some magic here
}
if($this->pre_flight_checks_pass === false)
{
$nmo = new NinjaMailerObject;
$nmo->mailable = new CompanyImportFailure($this->company, $this->message);
$nmo->company = $this->company;
$nmo->settings = $this->company->settings;
$nmo->to_user = $this->company->owner();
NinjaMailerJob::dispatchNow($nmo);
nlog($this->message);
throw new \Exception($this->message);
}
return $this;
}
@ -236,10 +355,14 @@ class CompanyImport implements ShouldQueue
$method = "import_{$import}";
nlog($method);
$this->{$method}();
}
nlog("finished importing company data");
return $this;
}
@ -278,6 +401,8 @@ class CompanyImport implements ShouldQueue
$obj_array,
);
$new_obj->company_id = $this->company->id;
$new_obj->user_id = $user_id;
$new_obj->save(['timestamps' => false]);
}
@ -328,8 +453,8 @@ class CompanyImport implements ShouldQueue
{
$this->genericImport(ClientContact::class,
['user_id', 'assigned_user_id', 'company_id', 'id', 'hashed_id'],
[['users' => 'user_id'], ['users' => 'assigned_user_id']],
['user_id', 'company_id', 'id', 'hashed_id'],
[['users' => 'user_id'], ['clients' => 'client_id']],
'client_contacts',
'email');
@ -644,24 +769,8 @@ class CompanyImport implements ShouldQueue
$this->backup_file->activities = $activities;
$this->genericImport(Activity::class,
$this->genericNewClassImport(Activity::class,
[
'user_id',
'company_id',
'client_id',
'client_contact_id',
'project_id',
'vendor_id',
'payment_id',
'invoice_id',
'credit_id',
'invitation_id',
'task_id',
'expense_id',
'token_id',
'quote_id',
'subscription_id',
'recurring_invoice_id',
'hashed_id',
'company_id',
],
@ -681,8 +790,7 @@ class CompanyImport implements ShouldQueue
['recurring_invoices' => 'recurring_invoice_id'],
['invitations' => 'invitation_id'],
],
'activities',
'created_at');
'activities');
return $this;
@ -692,7 +800,7 @@ class CompanyImport implements ShouldQueue
{
$this->genericImportWithoutCompany(Backup::class,
['activity_id','hashed_id'],
['hashed_id','id'],
[
['activities' => 'activity_id'],
],
@ -711,7 +819,7 @@ class CompanyImport implements ShouldQueue
[
['users' => 'user_id'],
['clients' => 'client_id'],
['activities' => 'activity_id'],
// ['activities' => 'activity_id'],
],
'company_ledger',
'created_at');
@ -802,9 +910,14 @@ class CompanyImport implements ShouldQueue
if(User::where('email', $user->email)->where('account_id', '!=', $this->account->id)->exists())
throw new ImportCompanyFailed("{$user->email} is already in the system attached to a different account");
$user_array = (array)$user;
unset($user_array['laravel_through_key']);
unset($user_array['hashed_id']);
unset($user_array['id']);
$new_user = User::firstOrNew(
['email' => $user->email],
(array)$user,
$user_array,
);
$new_user->account_id = $this->account->id;
@ -824,11 +937,14 @@ class CompanyImport implements ShouldQueue
foreach($this->backup_file->company_users as $cu)
{
$user_id = $this->transformId($cu->user_id);
$user_id = $this->transformId('users', $cu->user_id);
$cu_array = (array)$cu;
unset($cu_array['id']);
$new_cu = CompanyUser::firstOrNew(
['user_id' => $user_id, 'company_id', $this->company->id],
(array)$cu,
['user_id' => $user_id, 'company_id' => $this->company->id],
$cu_array,
);
$new_cu->account_id = $this->account->id;
@ -945,7 +1061,7 @@ class CompanyImport implements ShouldQueue
$activity_invitation_key = false;
if($class instanceof Activity){
if($class == 'App\Models\Activity'){
if(isset($obj->invitation_id)){
@ -955,6 +1071,7 @@ class CompanyImport implements ShouldQueue
$activity_invitation_key = 'quote_invitations';
elseif($isset($obj->credit_id))
$activity_invitation_key = 'credit_invitations';
}
}
@ -964,14 +1081,15 @@ class CompanyImport implements ShouldQueue
{
foreach($transform as $key => $value)
{
if($class instanceof Activity && $activity_invitation_key)
if($class == 'App\Models\Activity' && $activity_invitation_key && $key == 'invitations'){
$key = $activity_invitation_key;
}
$obj_array["{$value}"] = $this->transformId($key, $obj->{$value});
}
}
if($class instanceof CompanyGateway) {
if($class == 'App\Models\CompanyGateway') {
$obj_array['config'] = encrypt($obj_array['config']);
}
@ -1105,10 +1223,13 @@ class CompanyImport implements ShouldQueue
return null;
if (! array_key_exists($resource, $this->ids)) {
// nlog($this->ids);
throw new \Exception("Resource {$resource} not available.");
}
if (! array_key_exists("{$old}", $this->ids[$resource])) {
// nlog($this->ids[$resource]);
nlog("searching for {$old} in {$resource}");
throw new \Exception("Missing {$resource} key: {$old}");
}

View File

@ -127,8 +127,8 @@ class CheckCompanyData implements ShouldQueue
$this->company->clients->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_amount = $invoice->payments->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED])->sum('pivot.amount');
$total_refund = $invoice->payments->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED])->sum('pivot.refunded');
$total_credit = $invoice->credits->sum('amount');
$total_paid = $total_amount - $total_refund;

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\Jobs\Util;
use App\Jobs\Util\UnlinkFile;
use App\Models\Account;
use App\Utils\Ninja;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Storage;
class DiskCleanup implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct()
{
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
// Get all files in a directory
$files = Storage::allFiles(config('filesystems.default'), 'backups/');
Storage::delete($files);
$files = Storage::allFiles('public', 'backups/');
Storage::delete($files);
}
}

View File

@ -928,6 +928,10 @@ class Import implements ShouldQueue
}
$modified['client_id'] = $this->transformId('clients', $resource['client_id']);
if(array_key_exists('invoice_id', $resource) && $this->tryTransformingId('invoices', $resource['invoice_id']))
$modified['invoice_id'] = $this->transformId('invoices', $resource['invoice_id']);
$modified['user_id'] = $this->processUserId($resource);
$modified['company_id'] = $this->company->id;
@ -1566,6 +1570,7 @@ class Import implements ShouldQueue
*/
public function transformId($resource, string $old): int
{
if (! array_key_exists($resource, $this->ids)) {
info(print_r($resource, 1));
throw new Exception("Resource {$resource} not available.");
@ -1650,76 +1655,13 @@ class Import implements ShouldQueue
return $response->getBody();
}
private function buildNewUserPlan()
{
$current_db = config('database.default');
nlog($this->company);
$local_company = Company::on($current_db)->where('company_key', $this->company->company_key)->first();
MultiDB::setDb('db-ninja-01');
$ninja_company = Company::find(config('ninja.ninja_default_company_id'));
/* If we already have a record of this user - move along. */
if($client_contact = ClientContact::where(['email' => $this->user->email, 'company_id' => $ninja_company->id])->first())
return $client_contact->client;
$ninja_client = ClientFactory::create($ninja_company->id, $ninja_company->owner()->id);
$ninja_client->name = $this->user->present()->name();
$ninja_client->address1 = $local_company->settings->address1;
$ninja_client->address2 = $local_company->settings->address2;
$ninja_client->city = $local_company->settings->city;
$ninja_client->postal_code = $local_company->settings->postal_code;
$ninja_client->state = $local_company->settings->state;
$ninja_client->country_id = $local_company->settings->country_id;
$ninja_client->custom_value1 = $local_company->company_key;
$ninja_client->save();
$ninja_client_contact = ClientContactFactory::create($ninja_company->id, $ninja_company->owner()->id);
$ninja_client_contact->first_name = $this->user->first_name;
$ninja_client_contact->last_name = $this->user->last_name;
$ninja_client_contact->client_id = $ninja_client->id;
$ninja_client_contact->email = $this->user->email;
$ninja_client_contact->phone = $this->user->phone;
$ninja_client_contact->save();
MultiDB::setDb($current_db);
return $ninja_client;
}
private function processNinjaTokens(array $data)
{
$current_db = config('database.default');
$local_company = Company::on($current_db)->where('company_key', $this->company->company_key)->first();
if(Ninja::isHosted())
\Modules\Admin\Jobs\Account\NinjaUser::dispatch($data, $this->company);
MultiDB::setDb('db-ninja-01');
if($existing_client = Client::where('custom_value1', $local_company->company_key)->first())
$ninja_client = $existing_client;
else
$ninja_client = $this->buildNewUserPlan();
foreach($data as $token)
{
//get invoiceninja company_id
$ninja_company = Company::where('id', config('ninja.ninja_default_company_id'))->first();
$token['company_id'] = $ninja_company->id;
$token['client_id'] = $ninja_client->id;/////
$token['user_id'] = $ninja_company->owner()->id;
$token['company_gateway_id'] = config('ninja.ninja_default_company_gateway_id');
//todo
ClientGatewayToken::unguard();
$cgt = ClientGatewayToken::Create($token);
ClientGatewayToken::reguard();
}
MultiDB::setDb($current_db);
}

View File

@ -57,9 +57,9 @@ class UpdateUserLastLogin implements ShouldQueue
if($user->ip != $ip)
{
$nmo = new NinjaMailerObject;
$nmo->mailable = new UserLoggedIn($user, $user->account->companies()->first(), $ip);
$nmo->company = $user->account->companies()->first();
$nmo->settings = $user->account->companies()->first()->settings;
$nmo->mailable = new UserLoggedIn($user, $user->account->companies->first(), $ip);
$nmo->company = $user->account->companies->first();
$nmo->settings = $user->account->companies->first()->settings;
$nmo->to_user = $user;
NinjaMailerJob::dispatch($nmo);

View File

@ -12,6 +12,7 @@
namespace App\Mail;
use App\Models\Company;
use App\Utils\ClientPortal\MagicLink;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
@ -20,24 +21,25 @@ use Illuminate\Queue\SerializesModels;
class ContactPasswordlessLogin extends Mailable
{
use Queueable, SerializesModels;
/**
* @var string
*/
public $email;
public $url;
/**
* Create a new message instance.
*
* @param string $email
* @param string $redirect
*/
public function __construct(string $email, string $redirect = '')
public function __construct(string $email, $company_id, string $redirect = '')
{
$this->email = $email;
$this->url = MagicLink::create($email, $redirect);
$this->url = MagicLink::create($email, $company_id, $redirect);
}
/**

View File

@ -0,0 +1,65 @@
<?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\Mail\Import;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class CompanyImportFailure extends Mailable
{
// use Queueable, SerializesModels;
public $company;
public $settings;
public $logo;
public $title;
public $message;
public $whitelabel;
public $user_message;
/**
* Create a new message instance.
*
* @return void
*/
public function __construct($company, $user_message)
{
$this->company = $company;
$this->user_message = $user_message;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
$this->settings = $this->company->settings;
$this->logo = $this->company->present()->logo();
$this->title = ctrans('texts.company_import_failure_subject', ['company' => $this->company->present()->name()]);
$this->whitelabel = $this->company->account->isPaid();
nlog($this->user_message);
return $this->from(config('mail.from.address'), config('mail.from.name'))
->subject(ctrans('texts.company_import_failure_subject', ['company' => $this->company->present()->name()]))
->view('email.import.import_failure', ['user_message' => $this->user_message, 'title' => $this->title]);
}
}

View File

@ -41,8 +41,8 @@ class MigrationCompleted extends Mailable
$result = $this->from(config('mail.from.address'), config('mail.from.name'))
->view('email.import.completed', $data);
if($this->company->invoices->count() >=1)
$result->attach($this->company->invoices->first()->pdf_file_path());
// if($this->company->invoices->count() >=1)
// $result->attach($this->company->invoices->first()->pdf_file_path());
return $result;
}

View File

@ -207,7 +207,7 @@ class Account extends BaseModel
return false;
}
return $this->plan == 'free';
return $this->plan == 'free' || is_null($this->plan);
}
public function isEnterpriseClient()

View File

@ -133,7 +133,7 @@ class Company extends BaseModel
public function all_documents()
{
return $this->HasMany(Document::class);
return $this->hasMany(Document::class);
}
public function getEntityType()

View File

@ -81,7 +81,7 @@ class User extends Authenticatable implements MustVerifyEmail
'custom_value3',
'custom_value4',
'is_deleted',
'google_2fa_secret',
// 'google_2fa_secret',
];
/**

View File

@ -244,6 +244,10 @@ class BaseDriver extends AbstractPaymentDriver
if (property_exists($this->payment_hash->data, 'billing_context')) {
$billing_subscription = \App\Models\Subscription::find($this->payment_hash->data->billing_context->subscription_id);
// To access campaign hash => $this->payment_hash->data->billing_context->campaign;
// To access campaign data => Cache::get(CAMPAIGN_HASH)
// To access utm data => session()->get('utm-' . CAMPAIGN_HASH);
(new SubscriptionService($billing_subscription))->completePurchase($this->payment_hash);
}

View File

@ -144,7 +144,7 @@ class CreditCard
if ($this->checkout->client->currency()->code == 'EUR' || $this->checkout->company_gateway->getConfigField('threeds')) {
$payment->{'3ds'} = ['enabled' => true];
$payment->{'success_url'} = route('payment_webhook', [
$payment->{'success_url'} = route('checkout.3ds_redirect', [
'company_key' => $this->checkout->client->company->company_key,
'company_gateway_id' => $this->checkout->company_gateway->hashed_id,
'hash' => $this->checkout->payment_hash->hash,

View File

@ -13,6 +13,7 @@
namespace App\PaymentDrivers;
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
use App\Http\Requests\Gateways\Checkout3ds\Checkout3dsRequest;
use App\Http\Requests\Payments\PaymentWebhookRequest;
use App\Jobs\Mail\PaymentFailureMailer;
use App\Jobs\Util\SystemLogger;
@ -287,6 +288,11 @@ class CheckoutComPaymentDriver extends BaseDriver
}
public function processWebhookRequest(PaymentWebhookRequest $request, Payment $payment = null)
{
return true;
}
public function process3dsConfirmation(Checkout3dsRequest $request)
{
$this->init();
$this->setPaymentHash($request->getPaymentHash());

View File

@ -187,7 +187,7 @@ class PayPalExpressPaymentDriver extends BaseDriver
'cancelUrl' => $this->client->company->domain() . '/client/invoices',
'description' => implode(',', collect($this->payment_hash->data->invoices)
->map(function ($invoice) {
return sprintf('%s: %s', ctrans('texts.invoice_number'), $invoice->number);
return sprintf('%s: %s', ctrans('texts.invoice_number'), $invoice->invoice_number);
})->toArray()),
'transactionId' => $this->payment_hash->hash . '-' . time(),
'ButtonSource' => 'InvoiceNinja_SP',

View File

@ -11,9 +11,22 @@
namespace App\Repositories;
use App\Models\Design;
use Illuminate\Support\Str;
/**
* Class for DesignRepository .
*/
class DesignRepository extends BaseRepository
{
public function delete($design) :Design
{
$design->name = $design->name . "_deleted_" . Str::random(5);
parent::delete($design);
return $design;
}
}

View File

@ -346,6 +346,8 @@ class Design extends BaseDesign
$items = $this->transformLineItems($this->entity->line_items, $type);
// $this->processMarkdownOnLineItems($items);
if (count($items) == 0) {
return [];
}

View File

@ -321,10 +321,27 @@ document.addEventListener('DOMContentLoaded', function() {
public static function parseMarkdownToHtml(string $markdown): ?string
{
// Use setting to determinate if parsing should be done.
// 'parse_markdown_on_pdfs'
$converter = new CommonMarkConverter([
'allow_unsafe_links' => false,
]);
return $converter->convertToHtml($markdown);
}
public function processMarkdownOnLineItems(array &$items)
{
// Use setting to determinate if parsing should be done.
// 'parse_markdown_on_pdfs'
foreach ($items as $key => $item) {
foreach ($item as $variable => $value) {
$item[$variable] = DesignHelpers::parseMarkdownToHtml($value ?? '');
}
$items[$key] = $item;
}
}
}

View File

@ -92,7 +92,10 @@ trait PdfMakerUtilities
$contains_html = false;
if (isset($child['content'])) {
$child['content'] = nl2br($child['content']);
// Commented cause it keeps adding <br> at the end, if markdown parsing is turned on.
// Should update with 'parse_markdown_on_pdfs' setting.
$child['content'] = nl2br($child['content']);
}
// "/\/[a-z]*>/i" -> checks for HTML-like tags:

View File

@ -52,8 +52,8 @@ class SubscriptionService
$this->subscription = $subscription;
}
/*
Performs the initial purchase of a
/*
Performs the initial purchase of a
one time or recurring product
*/
public function completePurchase(PaymentHash $payment_hash)
@ -184,13 +184,13 @@ class SubscriptionService
/**
* Returns an upgrade price when moving between plans
*
* However we only allow people to move between plans
* However we only allow people to move between plans
* if their account is in good standing.
*
* @param RecurringInvoice $recurring_invoice
* @param Subscription $target
*
* @return float
*
* @param RecurringInvoice $recurring_invoice
* @param Subscription $target
*
* @return float
*/
public function calculateUpgradePrice(RecurringInvoice $recurring_invoice, Subscription $target) :?float
{
@ -210,7 +210,7 @@ class SubscriptionService
->where('client_id', $recurring_invoice->client_id)
->where('is_deleted', 0)
->orderBy('id', 'desc')
->first();
->first();
if ($outstanding->count() == 0){
//nothing outstanding
@ -232,15 +232,15 @@ class SubscriptionService
/**
* We refund unused days left.
*
* @param Invoice $invoice
* @return float
*
* @param Invoice $invoice
* @return float
*/
private function calculateProRataRefund($invoice) :float
{
if(!$this->invoice->date)
if(!$invoice->date)
return 0;
$start_date = Carbon::parse($invoice->date);
$current_date = now();
@ -259,9 +259,9 @@ class SubscriptionService
* Returns refundable set of line items
* transformed for direct injection into
* the invoice
*
* @param Invoice $invoice
* @return array
*
* @param Invoice $invoice
* @return array
*/
private function calculateProRataRefundItems($invoice, $is_credit = false) :array
{
@ -285,14 +285,14 @@ class SubscriptionService
if($item->product_key != ctrans('texts.refund'))
{
$item->cost = ($item->cost*$ratio*$multiplier);
$item->product_key = ctrans('texts.refund');
$item->notes = ctrans('texts.refund') . ": ". $item->notes;
$line_items[] = $item;
}
}
@ -302,13 +302,13 @@ class SubscriptionService
/**
* We only charge for the used days
*
* @param Invoice $invoice
* @return float
*
* @param Invoice $invoice
* @return float
*/
private function calculateProRataCharge($invoice) :float
{
$start_date = Carbon::parse($invoice->date);
$current_date = now();
@ -317,10 +317,10 @@ class SubscriptionService
$days_in_frequency = $this->getDaysInFrequency();
nlog("days to charge = {$days_to_charge} fays in frequency = {$days_in_frequency}");
nlog("days to charge = {$days_to_charge} days in frequency = {$days_in_frequency}");
$pro_rata_charge = round(($days_to_charge/$days_in_frequency) * $invoice->amount ,2);
nlog("pro rata charge = {$pro_rata_charge}");
return $pro_rata_charge;
@ -329,15 +329,15 @@ class SubscriptionService
/**
* When downgrading, we may need to create
* a credit
*
* @param array $data
*
* @param array $data
*/
public function createChangePlanCredit($data)
{
$recurring_invoice = $data['recurring_invoice'];
$old_subscription = $data['subscription'];
$target_subscription = $data['target'];
$pro_rata_charge_amount = 0;
$pro_rata_refund_amount = 0;
@ -346,9 +346,9 @@ class SubscriptionService
->where('is_deleted', 0)
->withTrashed()
->orderBy('id', 'desc')
->first();
->first();
if($last_invoice->balance > 0)
if($last_invoice->balance > 0)
{
$pro_rata_charge_amount = $this->calculateProRataCharge($last_invoice, $old_subscription);
nlog("pro rata charge = {$pro_rata_charge_amount}");
@ -364,7 +364,7 @@ class SubscriptionService
nlog("total payable = {$total_payable}");
$credit = $this->createCredit($last_invoice, $target_subscription);
$new_recurring_invoice = $this->createNewRecurringInvoice($recurring_invoice);
$context = [
@ -386,9 +386,9 @@ class SubscriptionService
/**
* When changing plans, we need to generate a pro rata invoice
*
* @param array $data
* @return Invoice
*
* @param array $data
* @return Invoice
*/
public function createChangePlanInvoice($data)
{
@ -404,9 +404,9 @@ class SubscriptionService
->where('is_deleted', 0)
->withTrashed()
->orderBy('id', 'desc')
->first();
->first();
if($last_invoice->balance > 0)
if($last_invoice->balance > 0)
{
$pro_rata_charge_amount = $this->calculateProRataCharge($last_invoice, $old_subscription);
nlog("pro rata charge = {$pro_rata_charge_amount}");
@ -424,10 +424,10 @@ class SubscriptionService
}
/**
* Response from payment service on
* Response from payment service on
* return from a plan change
*
* @param PaymentHash $payment_hash
*
* @param PaymentHash $payment_hash
*/
private function handlePlanChange($payment_hash)
{
@ -457,9 +457,9 @@ class SubscriptionService
/**
* Creates a new recurring invoice when changing
* plans
*
* @param RecurringInvoice $old_recurring_invoice
* @return RecurringInvoice
*
* @param RecurringInvoice $old_recurring_invoice
* @return RecurringInvoice
*/
private function createNewRecurringInvoice($old_recurring_invoice) :RecurringInvoice
{
@ -512,10 +512,10 @@ class SubscriptionService
/**
* Creates a credit note if the plan change requires
*
* @param Invoice $last_invoice
* @param Subscription $target
* @return Credit
*
* @param Invoice $last_invoice
* @param Subscription $target
* @return Credit
*/
private function createCredit($last_invoice, $target)
{
@ -544,10 +544,10 @@ class SubscriptionService
/**
* When changing plans we need to generate a pro rata
* invoice which takes into account any credits.
*
* @param Invoice $last_invoice
* @param Subscription $target
* @return Invoice
*
* @param Invoice $last_invoice
* @param Subscription $target
* @return Invoice
*/
private function proRataInvoice($last_invoice, $target)
{
@ -576,9 +576,9 @@ class SubscriptionService
/**
* Generates the first invoice when a subscription is purchased
*
* @param array $data
* @return Invoice
*
* @param array $data
* @return Invoice
*/
public function createInvoice($data): ?\App\Models\Invoice
{
@ -603,9 +603,9 @@ class SubscriptionService
/**
* Generates a recurring invoice based on
* the specifications of the subscription
*
*
* @param int $client_id The Client Id
* @return RecurringInvoice
* @return RecurringInvoice
*/
public function convertInvoiceToRecurring($client_id) :RecurringInvoice
{
@ -626,13 +626,12 @@ class SubscriptionService
/**
* Hit a 3rd party API if defined in the subscription
*
* @param array $context
* @param array $context
*/
public function triggerWebhook($context)
{
/* If no webhooks have been set, then just return gracefully */
if(!array_key_exists('post_purchase_url', $this->subscription->webhook_configuration) || !array_key_exists('post_purchase_rest_method', $this->subscription->webhook_configuration)) {
return true;
if (empty($this->subscription->webhook_configuration['post_purchase_url']) || empty($this->subscription->webhook_configuration['post_purchase_rest_method'])) {
return ["message" => "Success", "status_code" => 200];
}
$response = false;
@ -653,10 +652,7 @@ class SubscriptionService
}
else {
$status = $response->getStatusCode();
//$response_body = $response->getReasonPhrase();
//$body = array_merge($body, ['status' => $status, 'response_body' => $response_body]);
$body = $response->getStatusCode();
}
@ -667,11 +663,14 @@ class SubscriptionService
SystemLog::CATEGORY_WEBHOOK,
SystemLog::EVENT_WEBHOOK_RESPONSE,
SystemLog::TYPE_WEBHOOK_RESPONSE,
$client,
$client,
$client->company,
);
return $response;
if(is_array($body))
return $response;
else
return ['message' => 'There was a problem encountered with the webhook', 'status_code' => 500];
}
@ -718,9 +717,9 @@ class SubscriptionService
/**
* Handle the cancellation of a subscription
*
* @param RecurringInvoice $recurring_invoice
*
*
* @param RecurringInvoice $recurring_invoice
*
*/
public function handleCancellation(RecurringInvoice $recurring_invoice)
{
@ -730,7 +729,7 @@ class SubscriptionService
->where('client_id', $recurring_invoice->client_id)
->where('is_deleted', 0)
->orderBy('id', 'desc')
->first();
->first();
$invoice_start_date = Carbon::parse($outstanding_invoice->date);
$refund_end_date = $invoice_start_date->addSeconds($this->subscription->refund_period);
@ -807,15 +806,15 @@ class SubscriptionService
default:
return 0;
}
}
/**
* 'email' => $this->email ?? $this->contact->email,
* 'quantity' => $this->quantity,
* 'contact_id' => $this->contact->id,
*/
*/
public function handleNoPaymentRequired(array $data)
{
@ -827,7 +826,7 @@ class SubscriptionService
// Hit the redirect
return $this->handleRedirect($context['redirect_url']);
}
/**
@ -840,5 +839,5 @@ class SubscriptionService
return redirect($this->subscription->webhook_configuration['return_url']);
return redirect($default_redirect);
}
}
}

View File

@ -15,6 +15,7 @@ use App\Models\Account;
use App\Models\Company;
use App\Models\CompanyUser;
use App\Models\User;
use App\Utils\Ninja;
use App\Utils\Traits\MakesHash;
/**

View File

@ -18,12 +18,17 @@ class MagicLink
{
//return a magic login link URL
public static function create($email, $url = null) :string
public static function create($email, $company_id, $url = null) :string
{
$magic_key = Str::random(64);
$timeout = 600; //seconds
Cache::add($magic_key, $email, $timeout);
$payload = [
'email' => $email,
'company_id' => $company_id,
];
Cache::add($magic_key, $payload, $timeout);
return route('client.contact_magic_link', ['magic_link' => $magic_key, 'redirect' => $url]);
}

View File

@ -17,6 +17,7 @@ use App\Models\CreditInvitation;
use App\Models\InvoiceInvitation;
use App\Models\QuoteInvitation;
use App\Models\RecurringInvoiceInvitation;
use App\Services\PdfMaker\Designs\Utilities\DesignHelpers;
use App\Utils\Traits\MakesDates;
use Exception;
use Illuminate\Support\Facades\App;
@ -214,6 +215,7 @@ class HtmlEngine
$data['$quote_no'] = &$data['$quote.number'];
$data['$quote.quote_no'] = &$data['$quote.number'];
$data['$quote.valid_until'] = ['value' => $this->translateDate($this->entity->due_date, $this->client->date_format(), $this->entity->client->locale()), 'label' => ctrans('texts.valid_until')];
$data['$valid_until'] = &$data['$quote.valid_until'];
$data['$credit_amount'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: '&nbsp;', 'label' => ctrans('texts.credit_amount')];
$data['$credit_balance'] = ['value' => Number::formatMoney($this->entity->balance, $this->client) ?: '&nbsp;', 'label' => ctrans('texts.credit_balance')];
$data['$quote.custom1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice1', $this->entity->custom_value1, $this->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice1')];
@ -231,6 +233,10 @@ class HtmlEngine
$data['$client2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'client2', $this->client->custom_value2, $this->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'client2')];
$data['$client3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'client3', $this->client->custom_value3, $this->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'client3')];
$data['$client4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'client4', $this->client->custom_value4, $this->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'client4')];
$data['$client.custom1'] = &$data['$client1'];
$data['$client.custom2'] = &$data['$client2'];
$data['$client.custom3'] = &$data['$client3'];
$data['$client.custom4'] = &$data['$client4'];
$data['$address1'] = ['value' => $this->client->address1 ?: '&nbsp;', 'label' => ctrans('texts.address1')];
$data['$address2'] = ['value' => $this->client->address2 ?: '&nbsp;', 'label' => ctrans('texts.address2')];
$data['$id_number'] = ['value' => $this->client->id_number ?: '&nbsp;', 'label' => ctrans('texts.id_number')];
@ -246,6 +252,7 @@ class HtmlEngine
$data['$client.address2'] = &$data['$address2'];
$data['$client_address'] = ['value' => $this->client->present()->address() ?: '&nbsp;', 'label' => ctrans('texts.address')];
$data['$client.address'] = &$data['$client_address'];
$data['$client.postal_code'] = ['value' => $this->client->postal_code ?: '&nbsp;', 'label' => ctrans('texts.postal_code')];
$data['$client.id_number'] = &$data['$id_number'];
$data['$client.vat_number'] = &$data['$vat_number'];
$data['$client.website'] = &$data['$website'];
@ -306,6 +313,11 @@ class HtmlEngine
$data['$company3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'company3', $this->settings->custom_value3, $this->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'company3')];
$data['$company4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'company4', $this->settings->custom_value4, $this->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'company4')];
$data['$company.custom1'] = &$data['$company1'];
$data['$company.custom2'] = &$data['$company2'];
$data['$company.custom3'] = &$data['$company3'];
$data['$company.custom4'] = &$data['$company4'];
$data['$custom_surcharge1'] = ['value' => Number::formatMoney($this->entity->custom_surcharge1, $this->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'surcharge1')];
$data['$custom_surcharge2'] = ['value' => Number::formatMoney($this->entity->custom_surcharge2, $this->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'surcharge2')];
$data['$custom_surcharge3'] = ['value' => Number::formatMoney($this->entity->custom_surcharge3, $this->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'surcharge3')];
@ -379,6 +391,8 @@ class HtmlEngine
$data['$page_size'] = ['value' => $this->settings->page_size, 'label' => ''];
$data['$page_layout'] = ['value' => property_exists($this->settings, 'page_layout') ? $this->settings->page_layout : 'Portrait', 'label' => ''];
$data['$tech_hero_image'] = ['value' => asset('images/pdf-designs/tech-hero-image.jpg'), 'label' => ''];
$arrKeysLength = array_map('strlen', array_keys($data));
array_multisort($arrKeysLength, SORT_DESC, $data);

View File

@ -40,14 +40,13 @@ trait SubscriptionHooker
RequestOptions::JSON => ['body' => $body], RequestOptions::ALLOW_REDIRECTS => false
]);
return array_merge($body, ['exception' => json_decode($response->getBody(),true), 'status_code' => $response->getStatusCode()]);
return array_merge($body, json_decode($response->getBody(),true));
}
catch(\Exception $e)
{
//;
// dd($e);
$body = array_merge($body, ['exception' => ['message' => $e->getMessage(), 'status_code' => 500]]);
return $body;
return array_merge($body, ['message' => $e->getMessage(), 'status_code' => 500]);
}
}

View File

@ -30,7 +30,7 @@
"ext-dom": "*",
"ext-json": "*",
"ext-libxml": "*",
"asm/php-ansible": "dev-master",
"asm/php-ansible": "dev-main",
"authorizenet/authorizenet": "^2.0",
"bacon/bacon-qr-code": "^2.0",
"beganovich/snappdf": "^1.0",
@ -43,6 +43,7 @@
"doctrine/dbal": "^2.10",
"fakerphp/faker": "^1.14",
"fideloper/proxy": "^4.2",
"fruitcake/laravel-cors": "^2.0",
"google/apiclient": "^2.7",
"guzzlehttp/guzzle": "^7.0.1",
"hashids/hashids": "^4.0",
@ -58,7 +59,7 @@
"league/flysystem-cached-adapter": "^1.1",
"league/fractal": "^0.17.0",
"league/omnipay": "^3.1",
"livewire/livewire": "^2.0",
"livewire/livewire": "^2.4",
"maennchen/zipstream-php": "^1.2",
"nwidart/laravel-modules": "^8.0",
"omnipay/paypal": "^3.0",
@ -137,4 +138,4 @@
},
"minimum-stability": "dev",
"prefer-stable": true
}
}

948
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -15,7 +15,7 @@ return [
|
*/
'paths' => ['api/*'],
'paths' => ['*'],
'allowed_methods' => ['*'],
@ -23,9 +23,9 @@ return [
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'allowed_headers' => ['X-API-COMPANY-KEY,X-API-SECRET,X-API-TOKEN,X-API-PASSWORD,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,X-CSRF-TOKEN,X-XSRF-TOKEN,X-LIVEWIRE'],
'exposed_headers' => [],
'exposed_headers' => ['X-APP-VERSION,X-MINIMUM-CLIENT-VERSION,X-CSRF-TOKEN,X-XSRF-TOKEN,X-LIVEWIRE'],
'max_age' => 0,

View File

@ -14,8 +14,8 @@ return [
'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => '5.1.70',
'app_tag' => '5.1.70-release',
'app_version' => '5.1.72',
'app_tag' => '5.1.72-release',
'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', ''),
@ -152,6 +152,7 @@ return [
'ninja_stripe_client_id' => env('NINJA_STRIPE_CLIENT_ID', null),
'ninja_default_company_id' => env('NINJA_COMPANY_ID', null),
'ninja_default_company_gateway_id' => env('NINJA_COMPANY_GATEWAY_ID', null),
'ninja_hosted_secret' => env('NINJA_HOSTED_SECRET', null),
'pdf_generator' => env('PDF_GENERATOR', false),
'internal_queue_enabled' => env('INTERNAL_QUEUE_ENABLED', true),
];

View File

@ -0,0 +1,49 @@
<?php
use App\Models\Design;
use App\Utils\Ninja;
use Illuminate\Database\Migrations\Migration;
class AddTechDesign extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
if (Ninja::isHosted()) {
$design = new Design();
$design->id = 10;
$design->name = 'Tech';
$design->is_custom = false;
$design->design = '';
$design->is_active = true;
$design->save();
} elseif (Design::count() !== 0) {
$design = new Design();
$design->name = 'Tech';
$design->is_custom = false;
$design->design = '';
$design->is_active = true;
$design->save();
}
\Illuminate\Support\Facades\Artisan::call('ninja:design-update');
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

View File

@ -36,6 +36,7 @@ class DesignSeeder extends Seeder
['id' => 7, 'name' => 'Elegant', 'user_id' => null, 'company_id' => null, 'is_custom' => false, 'design' => '', 'is_active' => true],
['id' => 8, 'name' => 'Hipster', 'user_id' => null, 'company_id' => null, 'is_custom' => false, 'design' => '', 'is_active' => true],
['id' => 9, 'name' => 'Playful', 'user_id' => null, 'company_id' => null, 'is_custom' => false, 'design' => '', 'is_active' => true],
['id' => 10, 'name' => 'Tech', 'user_id' => null, 'company_id' => null, 'is_custom' => false, 'design' => '', 'is_active' => true],
];
foreach ($designs as $design) {

View File

@ -5,7 +5,7 @@ const CACHE_NAME = 'flutter-app-cache';
const RESOURCES = {
"version.json": "9fe5b22a16f39b766c8fdc35a24b3efa",
"favicon.ico": "51636d3a390451561744c42188ccd628",
"main.dart.js": "4ed39edfd85c4ac1e202b7c83715f20d",
"main.dart.js": "9f05b24849e19debf0c8286556e368e6",
"/": "23224b5e03519aaa87594403d54412cf",
"assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "174c02fc4609e8fc4389f5d21f16a296",
"assets/AssetManifest.json": "659dcf9d1baf3aed3ab1b9c42112bf8f",

Binary file not shown.

After

Width:  |  Height:  |  Size: 594 KiB

152488
public/main.dart.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

151170
public/main.foss.dart.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -4254,6 +4254,8 @@ $LANG = array(
'account_passwordless_login' => 'Account passwordless login',
'user_duplicate_error' => 'Cannot add the same user to the same company',
'user_cross_linked_error' => 'User exists but cannot be crossed linked to multiple accounts',
'company_import_failure_subject' => 'Error importing :company',
'company_import_failure_body' => 'There was an error importing the company data, the error message was:',
);
return $LANG;

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
@section('meta_title', ctrans('texts.purchase'))
@section('body')
@livewire('billing-portal-purchase', ['subscription' => $subscription, 'contact' => auth('contact')->user(), 'hash' => $hash, 'request_data' => $request_data])
@livewire('billing-portal-purchase', ['subscription' => $subscription, 'company' => $subscription->company, 'contact' => auth('contact')->user(), 'hash' => $hash, 'request_data' => $request_data, 'campaign' => request()->query('campaign') ?? null])
@stop
@push('footer')

View File

@ -70,9 +70,11 @@
<p><b>{{ ctrans('texts.documents') }}:</b> {{ count($company->documents) }} </p>
@endif
@if($check_data)
<p><b>Data Quality:</b></p>
<p> {!! $check_data !!} </p>
@endif
@if(!empty($errors) )
<p>{{ ctrans('texts.errors') }}:</p>
<table>

View File

@ -0,0 +1,22 @@
@component('email.template.master', ['design' => 'light', 'settings' => $settings])
@slot('header')
@include('email.components.header', ['logo' => $logo])
@endslot
<h2>{{ $title }}</h2>
<p>{{ctrans('texts.company_import_failure_body')}}</p>
@if($user_message)
<p>{{ $user_message }}</p>
@endif
@if(isset($whitelabel) && !$whitelabel)
@slot('footer')
@component('email.components.footer', ['url' => 'https://invoiceninja.com', 'url_text' => '&copy; InvoiceNinja'])
For any info, please visit InvoiceNinja.
@endcomponent
@endslot
@endif
@endcomponent

View File

@ -0,0 +1,296 @@
<style id="style">
:root {
--primary-color: $primary_color;
--secondary-color: $secondary_color;
}
body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-family: Arial, Helvetica, sans-serif;
font-size: "$font_size";
zoom: 80%;
margin: 0;
}
@page {
margin: 0;
size: $page_size $page_layout;
}
@media print {
.header-wrapper,
.body-wrapper {
margin: $global_margin;
}
}
p {
margin: 0;
padding: 0;
}
/** End of global, standard CSS */
.header-wrapper {
display: flex;
flex-direction: column;
}
.top-right-side-section {
display: flex;
flex-direction: row;
justify-content: flex-end;
}
.top-right-side-section > * {
margin-left: 1.5rem;
}
.logo-and-partial-entity-info {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.company-logo {
zoom: 50%;
}
.header-invoice-number {
color: var(--primary-color);
}
.header-payment-due-label,
.header-amount-due-label {
text-transform: uppercase;
}
.header-amount-due-value {
color: var(--primary-color);
}
.hero-section {
margin-top: 50px;
min-width: 100% !important;
background: url('$tech_hero_image'); /** If you want to replace the image, this is the place to do it. */
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
display: grid;
grid-template-columns: 1fr 1.5fr;
}
.hero-contact-section {
background-color: rgba(255, 255, 255, 0.98);
margin: 1rem;
padding: 1rem;
border-radius: 5px;
}
.client-details,
.company-details {
display: flex;
flex-direction: row;
justify-items: start;
}
.client-details-to-label {
text-transform: uppercase;
font-weight: bold;
color: var(--primary-color);
margin-right: 10px;
}
#client-details,
#company-details,
#company-address {
display: flex;
flex-direction: column;
}
.company-details-wrapper {
display: flex;
flex-direction: column;
}
.company-details {
margin-top: 20px;
}
.body-wrapper {}
#product-table,
#delivery-note-table,
#task-table {
min-width: 100%;
table-layout: fixed;
overflow-wrap: break-word;
margin-top: 3rem;
}
#product-table > thead > tr > th,
#delivery-note-table > thead > tr > th,
#task-table > thead > tr > th {
text-transform: uppercase;
font-weight: normal;
padding: 1rem;
border-bottom: 5px solid black;
text-align: left;
font-size: 1.1rem;
}
#product-table > thead > tr > th:last-child,
#delivery-note-table > thead > tr > th:last-child,
#task-table > thead > tr > th:last-child {
text-align: right;
}
#product-table > tbody > tr > td,
#delivery-note-table > tbody > tr > td,
#task-table > tbody > tr > td {
border-bottom: 1px solid #e6e6e6;
padding: 1rem;
}
#product-table > tbody > tr > td:first-child,
#delivery-note-table > tbody > tr > td:first-child,
#task-table > tbody > tr > td:first-child {
color: var(--primary-color);
}
#product-table > tbody > tr > td:last-child,
#delivery-note-table > tbody > tr > td:last-child,
#task-table > tbody > tr > td:last-child {
text-align: right;
}
.task-time-details {
display: block;
margin-top: 5px;
color: grey;
}
#table-totals {
page-break-inside: avoid;
}
#table-totals {
margin-top: 2rem;
display: grid;
grid-template-columns: 2fr 1fr;
gap: 80px;
}
#table-totals .totals-table-right-side > * {
display: grid;
grid-template-columns: 1fr 1fr;
}
#table-totals > .totals-table-right-side > * > :nth-child(1) {
text-align: left;
padding: 7px;
}
#table-totals > .totals-table-right-side > * > :nth-child(2) {
text-align: right;
padding: 7px;
}
#table-totals
> *
[data-element='total-table-balance-due-label'],
#table-totals
> *
[data-element='total-table-balance-due'] {
font-weight: bold;
}
#table-totals > * > :last-child {
text-align: right;
padding-right: 1rem;
}
[data-ref="total_table-footer"] {
padding-left: 1rem
}
[data-ref="totals_table-outstanding-label"],
[data-ref="totals_table-outstanding"] {
background-color: var(--primary-color);
color: white;
}
#footer {
margin-top: 30px;
}
</style>
<div id="header"></div>
<div id="body">
<div class="header-wrapper">
<div class="top-right-side-section">
<span class="header-invoice-number">$entity_number_label: $entity_number</span>
<span>$date_label: $date</span>
</div>
<div class="logo-and-partial-entity-info">
<img class="company-logo" src="$company.logo"
alt="$company.name logo">
<div class="top-right-side-section">
<section>
<span class="header-payment-due-label">$payment_due_label:</span>
<span>$payment_due</span>
</section>
<section>
<span class="header-amount-due-label">$amount_due_label:</span>
<span class="header-amount-due-value">$amount_due</span>
</section>
</div>
</div>
</div>
<div class="hero-section">
<div class="hero-contact-section">
<div class="client-details">
<span class="client-details-to-label">$from_label:</span>
<div id="client-details"></div>
</div>
<div class="company-details">
<span class="client-details-to-label">$to_label:</span>
<div class="company-details-wrapper">
<div id="company-details"></div>
<div id="company-address"></div>
</div>
</div>
</div>
</div>
<div class="body-wrapper">
<table id="product-table" cellspacing="0"></table>
<table id="task-table" cellspacing="0"></table>
<table id="delivery-note-table" cellspacing="0"></table>
<div id="table-totals" cellspacing="0"></div>
</div>
</div>
<div id="footer">
<p data-ref="total_table-footer">$entity_footer</p>
<script>
// Clear up space a bit, if [product-table, tasks-table, delivery-note-table] isn't present.
document.addEventListener('DOMContentLoaded', () => {
['product-table', 'task-table', 'delivery-note-table'].forEach((tableIdentifier) => {
document.getElementById(tableIdentifier).childElementCount === 0
? document.getElementById(tableIdentifier).style.display = 'none'
: '';
});
});
</script>
</div>

View File

@ -9,20 +9,21 @@
@section('body')
<div class="grid lg:grid-cols-3">
@if($account && !$account->isPaid())
<div class="hidden lg:block col-span-1 bg-red-100 h-screen">
<img src="{{ asset('images/client-portal-new-image.jpg') }}"
class="w-full h-screen object-cover"
alt="Background image">
</div>
<div class="hidden lg:block col-span-1 bg-red-100 h-screen">
<img src="{{ asset('images/client-portal-new-image.jpg') }}"
class="w-full h-screen object-cover"
alt="Background image">
</div>
@endif
<div class="col-span-2 h-screen flex">
<div class="m-auto md:w-1/2 lg:w-1/4">
@if($account && !$account->isPaid())
<div>
<img src="{{ asset('images/invoiceninja-black-logo-2.png') }}" class="border-b border-gray-100 h-18 pb-4" alt="Invoice Ninja logo">
</div>
@endif
@if($account && !$account->isPaid())
<div>
<img src="{{ asset('images/invoiceninja-black-logo-2.png') }}"
class="border-b border-gray-100 h-18 pb-4" alt="Invoice Ninja logo">
</div>
@endif
<div class="flex flex-col">
<h1 class="text-center text-3xl">{{ ctrans('texts.client_portal') }}</h1>
@ -61,9 +62,14 @@
</button>
</div>
</form>
@if(!is_null($company) && $company->client_can_register)
<div class="mt-5 text-center">
<a class="button-link text-sm" href="{{ route('client.register') }}">{{ ctrans('texts.register_label') }}</a>
</div>
@endif
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -5,7 +5,7 @@
<div class="grid lg:grid-cols-3">
@if($account && !$account->isPaid())
<div class="hidden lg:block col-span-1 bg-red-100 h-screen">
<img src="https://www.invoiceninja.com/wp-content/uploads/2018/04/bg-home2018b.jpg"
<img src="{{ asset('images/client-portal-new-image.jpg') }}"
class="w-full h-screen object-cover"
alt="Background image">
</div>

View File

@ -5,7 +5,7 @@
<div class="grid lg:grid-cols-3">
@if($account && !$account->isPaid())
<div class="hidden lg:block col-span-1 bg-red-100 h-screen">
<img src="https://www.invoiceninja.com/wp-content/uploads/2018/04/bg-home2018b.jpg"
<img src="{{ asset('images/client-portal-new-image.jpg') }}"
class="w-full h-screen object-cover"
alt="Background image">
</div>

View File

@ -13,6 +13,6 @@
@section('body')
<div class="flex flex-col">
@livewire('credits-table')
@livewire('credits-table', ['company' => $company])
</div>
@endsection

View File

@ -14,5 +14,5 @@
@csrf
</form>
@livewire('documents-table', ['client' => $client])
@livewire('documents-table', ['client' => $client, 'company' => $company])
@endsection

View File

@ -20,6 +20,6 @@
</form>
</div>
<div class="flex flex-col mt-4">
@livewire('invoices-table')
@livewire('invoices-table', ['company' => $company])
</div>
@endsection

View File

@ -19,7 +19,7 @@
<div class="col-span-6 md:col-start-2 md:col-span-4">
<div class="flex justify-end">
<div class="flex justify-end mb-2">
@livewire('pay-now-dropdown', ['total' => $total])
@livewire('pay-now-dropdown', ['total' => $total, 'company' => $company])
</div>
</div>

View File

@ -47,7 +47,7 @@
@if($settings->client_portal_allow_under_payment || $settings->client_portal_allow_over_payment)
<button class="button button-primary bg-primary">{{ ctrans('texts.pay_now') }}</button>
@else
@livewire('pay-now-dropdown', ['total' => $invoice->partial > 0 ? $invoice->partial : $invoice->balance])
@livewire('pay-now-dropdown', ['total' => $invoice->partial > 0 ? $invoice->partial : $invoice->balance, 'company' => $company])
@endif
</div>
</div>

View File

@ -43,7 +43,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="@yield('meta_description')"/>
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">

View File

@ -11,7 +11,7 @@
@endpush
@section('body')
@livewire('required-client-info', ['fields' => method_exists($gateway, 'getClientRequiredFields') ? $gateway->getClientRequiredFields() : [], 'contact' => auth('contact')->user(), 'countries' => $countries])
@livewire('required-client-info', ['fields' => method_exists($gateway, 'getClientRequiredFields') ? $gateway->getClientRequiredFields() : [], 'contact' => auth('contact')->user(), 'countries' => $countries, 'company' => $company])
<div class="container mx-auto grid grid-cols-12 opacity-25 pointer-events-none" data-ref="gateway-container">
<div class="col-span-12 lg:col-span-6 lg:col-start-4 overflow-hidden bg-white shadow rounded-lg">

View File

@ -3,6 +3,6 @@
@section('body')
<div class="flex flex-col">
@livewire('payment-methods-table', ['client' => $client])
@livewire('payment-methods-table', ['client' => $client, 'company' => $company])
</div>
@endsection

View File

@ -3,6 +3,6 @@
@section('body')
<div class="flex flex-col">
@livewire('payments-table')
@livewire('payments-table', ['company' => $company])
</div>
@endsection

View File

@ -22,6 +22,6 @@
</form>
</div>
<div class="flex flex-col mt-4">
@livewire('quotes-table')
@livewire('quotes-table', ['company' => $company])
</div>
@endsection

View File

@ -34,7 +34,7 @@
</div>
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<div class="flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto">
@livewire('recurring-invoice-cancellation', ['invoice' => $invoice])
@livewire('recurring-invoice-cancellation', ['invoice' => $invoice, 'company' => $company])
</div>
<div class="mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto">
<button @click="open = false" type="button" class="button button-secondary button-block">

View File

@ -3,6 +3,6 @@
@section('body')
<div class="flex flex-col">
@livewire('recurring-invoices-table')
@livewire('recurring-invoices-table', ['company' => $company])
</div>
@endsection

View File

@ -3,6 +3,6 @@
@section('body')
<div class="flex flex-col">
@livewire('subscription-recurring-invoices-table')
@livewire('subscription-recurring-invoices-table', ['company' => $company])
</div>
@endsection

View File

@ -42,7 +42,7 @@
</div>
<!-- Payment box -->
@livewire('subscription-plan-switch', compact('recurring_invoice', 'subscription', 'target', 'contact', 'amount'))
@livewire('subscription-plan-switch', compact('recurring_invoice', 'subscription', 'target', 'contact', 'amount', 'company'))
</div>
@endsection

View File

@ -3,6 +3,6 @@
@section('body')
<div class="flex flex-col">
@livewire('tasks-table')
@livewire('tasks-table', ['company' => $company])
</div>
@endsection

View File

@ -163,6 +163,7 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a
Route::get('settings/enable_two_factor', 'TwoFactorController@setupTwoFactor');
Route::post('settings/enable_two_factor', 'TwoFactorController@enableTwoFactor');
Route::post('settings/disable_two_factor', 'TwoFactorController@disableTwoFactor');
Route::resource('vendors', 'VendorController'); // name = (vendors. index / create / show / update / destroy / edit
Route::post('vendors/bulk', 'VendorController@bulk')->name('vendors.bulk');

View File

@ -7,13 +7,13 @@ Route::get('client', 'Auth\ContactLoginController@showLoginForm')->name('client.
Route::get('client/login', 'Auth\ContactLoginController@showLoginForm')->name('client.login')->middleware(['domain_db', 'contact_account','locale']);
Route::post('client/login', 'Auth\ContactLoginController@login')->name('client.login.submit');
Route::get('client/register/{company_key?}', 'Auth\ContactRegisterController@showRegisterForm')->name('client.register')->middleware(['domain_db', 'contact_account','locale']);
Route::post('client/register/{company_key?}', 'Auth\ContactRegisterController@register');
Route::get('client/register/{company_key?}', 'Auth\ContactRegisterController@showRegisterForm')->name('client.register')->middleware(['domain_db', 'contact_account', 'contact_register','locale']);
Route::post('client/register/{company_key?}', 'Auth\ContactRegisterController@register')->middleware(['domain_db', 'contact_account', 'contact_register', 'locale']);
Route::get('client/password/reset', 'Auth\ContactForgotPasswordController@showLinkRequestForm')->name('client.password.request')->middleware(['domain_db', 'contact_account','locale']);
Route::post('client/password/email', 'Auth\ContactForgotPasswordController@sendResetLinkEmail')->name('client.password.email')->middleware('locale');
Route::get('client/password/reset/{token}', 'Auth\ContactResetPasswordController@showResetForm')->name('client.password.reset')->middleware(['domain_db', 'contact_account','locale']);
Route::post('client/password/reset', 'Auth\ContactResetPasswordController@reset')->name('client.password.update')->middleware('locale');
Route::post('client/password/reset', 'Auth\ContactResetPasswordController@reset')->name('client.password.update')->middleware(['domain_db', 'contact_account','locale']);
Route::get('view/{entity_type}/{invitation_key}', 'ClientPortal\EntityViewController@index')->name('client.entity_view');
Route::get('view/{entity_type}/{invitation_key}/password', 'ClientPortal\EntityViewController@password')->name('client.entity_view.password');
@ -21,8 +21,8 @@ Route::post('view/{entity_type}/{invitation_key}/password', 'ClientPortal\Entity
Route::get('tmp_pdf/{hash}', 'ClientPortal\TempRouteController@index')->name('tmp_pdf');
Route::get('client/key_login/{contact_key}', 'ClientPortal\ContactHashLoginController@login')->name('client.contact_login')->middleware(['contact_key_login']);
Route::get('client/magic_link/{magic_link}', 'ClientPortal\ContactHashLoginController@magicLink')->name('client.contact_magic_link')->middleware(['contact_key_login']);
Route::get('client/key_login/{contact_key}', 'ClientPortal\ContactHashLoginController@login')->name('client.contact_login')->middleware(['domain_db','contact_key_login']);
Route::get('client/magic_link/{magic_link}', 'ClientPortal\ContactHashLoginController@magicLink')->name('client.contact_magic_link')->middleware(['domain_db','contact_key_login']);
Route::get('documents/{document_hash}', 'ClientPortal\DocumentController@publicDownload')->name('documents.public_download');
Route::get('error', 'ClientPortal\ContactHashLoginController@errorPage')->name('client.error');

View File

@ -36,4 +36,6 @@ Route::group(['middleware' => ['url_db']], function () {
});
Route::get('stripe/signup/{token}', 'StripeConnectController@initialize')->name('stripe_connect.initialization');
Route::get('stripe/completed', 'StripeConnectController@completed')->name('stripe_connect.return');
Route::get('stripe/completed', 'StripeConnectController@completed')->name('stripe_connect.return');
Route::get('checkout/3ds_redirect/{company_key}/{company_gateway_id}/{hash}', 'Gateways\Checkout3dsController@index')->name('checkout.3ds_redirect');

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