1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-16 16:13:20 +01:00
This commit is contained in:
David Bomba 2021-06-01 07:41:49 +10:00
commit 7fb5f8abe2
310 changed files with 294889 additions and 251624 deletions

View File

@ -2,6 +2,10 @@
## [Unreleased (daily channel)](https://github.com/invoiceninja/invoiceninja/tree/v5-develop)
- Add Cache-control: no-cache to prevent overaggressive caching of assets
- Improved labelling in the settings (client portal)
- Client portal: Multiple accounts access improvements (#5703)
- Client portal: "Credits" updates (#5734)
- Client portal: Make sidebar white color, in order to make logo displaying more simple. (#5753)
## [v5.1.56-release](https://github.com/invoiceninja/invoiceninja/releases/tag/v5.1.56-release)
## Fixed:

View File

@ -1 +1 @@
5.1.62
5.1.68

View File

@ -65,7 +65,7 @@ class CheckData extends Command
/**
* @var string
*/
protected $name = 'ninja:check-data';
protected $signature = 'ninja:check-data {--database=} {--fix=} {--client_id=}';
/**
* @var string

View File

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

View File

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

View File

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

View File

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

View File

@ -11,8 +11,9 @@
namespace App\Console;
use App\Jobs\Cron\SubscriptionCron;
use App\Jobs\Cron\AutoBillCron;
use App\Jobs\Cron\RecurringInvoicesCron;
use App\Jobs\Cron\SubscriptionCron;
use App\Jobs\Ninja\AdjustEmailQuota;
use App\Jobs\Ninja\CompanySizeCheck;
use App\Jobs\Util\ReminderJob;
@ -46,7 +47,7 @@ class Kernel extends ConsoleKernel
$schedule->job(new VersionCheck)->daily();
$schedule->command('ninja:check-data')->daily()->withoutOverlapping();
$schedule->command('ninja:check-data --database=db-ninja-01')->daily()->withoutOverlapping();
$schedule->job(new ReminderJob)->daily()->withoutOverlapping();
@ -58,6 +59,8 @@ class Kernel extends ConsoleKernel
$schedule->job(new RecurringInvoicesCron)->hourly()->withoutOverlapping();
$schedule->job(new AutoBillCron)->dailyAt('00:30')->withoutOverlapping();
$schedule->job(new SchedulerCheck)->everyFiveMinutes();
/* Run hosted specific jobs */
@ -65,6 +68,7 @@ class Kernel extends ConsoleKernel
$schedule->job(new AdjustEmailQuota)->daily()->withoutOverlapping();
$schedule->job(new SendFailedEmails)->daily()->withoutOverlapping();
$schedule->command('ninja:check-data --database=db-ninja-02')->daily()->withoutOverlapping();
}

View File

@ -68,7 +68,7 @@ class CompanySettings extends BaseSettings
public $inclusive_taxes = false; //@implemented
public $quote_footer = ''; //@implmented
public $translations; //@TODO not used anywhere
public $translations;
public $counter_number_applied = 'when_saved'; // when_saved , when_sent //@implemented
public $quote_number_applied = 'when_saved'; // when_saved , when_sent //@implemented
@ -202,7 +202,7 @@ class CompanySettings extends BaseSettings
public $schedule_reminder2 = ''; // (enum: after_invoice_date, before_due_date, after_due_date) implmemented
public $schedule_reminder3 = ''; // (enum: after_invoice_date, before_due_date, after_due_date) implmemented
public $reminder_send_time = 32400; //number of seconds from UTC +0 to send reminders @TODO
public $reminder_send_time = 0; //number of seconds from UTC +0 to send reminders @TODO
public $late_fee_amount1 = 0; //@implemented
public $late_fee_amount2 = 0; //@implemented
@ -245,8 +245,8 @@ class CompanySettings extends BaseSettings
public $hide_paid_to_date = false; //@TODO where?
public $embed_documents = false; //@TODO where?
public $all_pages_header = false; //@implemented
public $all_pages_footer = false; //@implemented
public $all_pages_header = false; //@deprecated 31-05-2021
public $all_pages_footer = false; //@deprecated 31-05-2021
public $pdf_variables = ''; //@implemented
public $portal_custom_head = ''; //@TODO @BEN
@ -667,8 +667,9 @@ class CompanySettings extends BaseSettings
'$custom_surcharge4',
'$total_taxes',
'$line_taxes',
'$paid_to_date',
'$total',
'$paid_to_date',
'$outstanding',
],
];

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -169,4 +169,27 @@ abstract class QueryFilters
return $this->builder->where('created_at', '>=', $created_at);
}
public function is_deleted($value)
{
return $this->builder->where('is_deleted', $value);
}
public function filter_deleted_clients($value)
{
if($value == 'true'){
return $this->builder->whereHas('client', function (Builder $query) {
$query->where('is_deleted', 0);
});
}
return $this->builder;
}
}

View File

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

View File

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

View File

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

View File

@ -13,6 +13,7 @@ namespace App\Http\Controllers\Auth;
use App\Events\Contact\ContactLoggedIn;
use App\Http\Controllers\Controller;
use App\Models\Account;
use App\Models\ClientContact;
use App\Utils\Ninja;
use Auth;
@ -31,9 +32,13 @@ class ContactLoginController extends Controller
$this->middleware('guest:contact', ['except' => ['logout']]);
}
public function showLoginForm()
public function showLoginForm(Request $request)
{
return $this->render('auth.login');
$account_id = $request->get('account_id');
$account = Account::find($account_id);
return $this->render('auth.login', ['account' => $account]);
}
public function login(Request $request)

View File

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

View File

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

View File

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

View File

@ -23,6 +23,7 @@ use App\Libraries\MultiDB;
use App\Libraries\OAuth\OAuth;
use App\Libraries\OAuth\Providers\Google;
use App\Models\Client;
use App\Models\Company;
use App\Models\CompanyToken;
use App\Models\CompanyUser;
use App\Models\SystemLog;
@ -30,6 +31,7 @@ use App\Models\User;
use App\Transformers\CompanyUserTransformer;
use App\Utils\Ninja;
use App\Utils\Traits\UserSessionAttributes;
use App\Utils\Traits\User\LoginCache;
use Google_Client;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
@ -54,6 +56,7 @@ class LoginController extends BaseController
use AuthenticatesUsers;
use UserSessionAttributes;
use LoginCache;
protected $entity_type = CompanyUser::class;
@ -177,8 +180,7 @@ class LoginController extends BaseController
event(new UserLoggedIn($user, $user->account->default_company, Ninja::eventVars($user->id)));
//if user has 2fa enabled - lets check this now:
//2FA
if($user->google_2fa_secret && $request->has('one_time_password'))
{
$google2fa = new Google2FA();
@ -202,14 +204,7 @@ class LoginController extends BaseController
$user->setCompany($user->account->default_company);
$timeout = $user->company()->default_password_timeout;
if($timeout == 0)
$timeout = 30*60*1000*1000;
else
$timeout = $timeout/1000;
Cache::put($user->hashed_id.'_logged_in', Str::random(64), $timeout);
$this->setLoginCache($user);
$cu = CompanyUser::query()
->where('user_id', auth()->user()->id);
@ -227,7 +222,7 @@ class LoginController extends BaseController
});
return $this->timeConstrainedResponse($cu);
// return $this->listResponse($cu);
} else {
@ -236,11 +231,12 @@ class LoginController extends BaseController
->batch();
SystemLogger::dispatch(
request()->getClientIp(),
json_encode(['ip' => request()->getClientIp()]),
SystemLog::CATEGORY_SECURITY,
SystemLog::EVENT_USER,
SystemLog::TYPE_LOGIN_FAILURE,
Client::first(),
null,
Company::first(),
);
$this->incrementLoginAttempts($request);
@ -349,6 +345,7 @@ class LoginController extends BaseController
if (is_array($user)) {
//
$query = [
'oauth_user_id' => $google->harvestSubField($user),
'oauth_provider_id'=> 'google',
@ -359,15 +356,7 @@ class LoginController extends BaseController
Auth::login($existing_user, true);
$existing_user->setCompany($existing_user->account->default_company);
$timeout = $existing_user->company()->default_password_timeout;
if($timeout == 0)
$timeout = 30*60*1000*1000;
else
$timeout = $timeout/1000;
Cache::put($existing_user->hashed_id.'_logged_in', Str::random(64), $timeout);
$this->setLoginCache($existing_user);
$cu = CompanyUser::query()
->where('user_id', auth()->user()->id);
@ -383,10 +372,68 @@ class LoginController extends BaseController
return $this->timeConstrainedResponse($cu);
}
//If this is a result user/email combo - lets add their OAuth details details
if($existing_login_user = MultiDB::hasUser(['email' => $google->harvestEmail($user)]))
{
Auth::login($existing_login_user, true);
$existing_login_user->setCompany($existing_login_user->account->default_company);
$this->setLoginCache($existing_login_user);
auth()->user()->update([
'oauth_user_id' => $google->harvestSubField($user),
'oauth_provider_id'=> 'google',
]);
$cu = CompanyUser::query()
->where('user_id', auth()->user()->id);
$cu->first()->account->companies->each(function ($company) use($cu){
if($company->tokens()->where('is_system', true)->count() == 0)
{
CreateCompanyToken::dispatchNow($company, $cu->first()->user, request()->server('HTTP_USER_AGENT'));
}
});
return $this->timeConstrainedResponse($cu);
}
}
if ($user) {
//check the user doesn't already exist in some form
if($existing_login_user = MultiDB::hasUser(['email' => $google->harvestEmail($user)]))
{
Auth::login($existing_login_user, true);
$existing_login_user->setCompany($existing_login_user->account->default_company);
$this->setLoginCache($existing_login_user);
auth()->user()->update([
'oauth_user_id' => $google->harvestSubField($user),
'oauth_provider_id'=> 'google',
]);
$cu = CompanyUser::query()
->where('user_id', auth()->user()->id);
$cu->first()->account->companies->each(function ($company) use($cu){
if($company->tokens()->where('is_system', true)->count() == 0)
{
CreateCompanyToken::dispatchNow($company, $cu->first()->user, request()->server('HTTP_USER_AGENT'));
}
});
return $this->timeConstrainedResponse($cu);
}
//user not found anywhere - lets sign them up.
$name = OAuth::splitName($google->harvestName($user));
$new_account = [
@ -402,22 +449,14 @@ class LoginController extends BaseController
MultiDB::setDefaultDatabase();
$account = CreateAccount::dispatchNow($new_account);
$account = CreateAccount::dispatchNow($new_account, request()->getClientIp());
Auth::login($account->default_company->owner(), true);
auth()->user()->email_verified_at = now();
auth()->user()->save();
$timeout = auth()->user()->company()->default_password_timeout;
if($timeout == 0)
$timeout = 30*60*1000*1000;
else
$timeout = $timeout/1000;
Cache::put(auth()->user()->hashed_id.'_logged_in', Str::random(64), $timeout);
$this->setLoginCache(auth()->user());
$cu = CompanyUser::whereUserId(auth()->user()->id);
@ -437,4 +476,62 @@ class LoginController extends BaseController
->header('X-App-Version', config('ninja.app_version'))
->header('X-Api-Version', config('ninja.minimum_client_version'));
}
public function redirectToProvider(string $provider)
{
//'https://www.googleapis.com/auth/gmail.send','email','profile','openid'
$scopes = [];
if($provider == 'google'){
$scopes = ['https://www.googleapis.com/auth/gmail.send','email','profile','openid'];
}
if (request()->has('code')) {
return $this->handleProviderCallback($provider);
} else {
return Socialite::driver($provider)->scopes($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);
// 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
// return view('auth.login');
// }
// else {
// //todo
// $name = OAuth::splitName($socialite_user->getName());
// $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
// ];
// $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);
// }
}
}

View File

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

View File

@ -15,6 +15,7 @@ use App\Events\Client\ClientWasCreated;
use App\Events\Client\ClientWasUpdated;
use App\Factory\ClientFactory;
use App\Filters\ClientFilters;
use App\Http\Requests\Client\AdjustClientLedgerRequest;
use App\Http\Requests\Client\CreateClientRequest;
use App\Http\Requests\Client\DestroyClientRequest;
use App\Http\Requests\Client\EditClientRequest;
@ -585,4 +586,64 @@ class ClientController extends BaseController
}
/**
* Update the specified resource in storage.
*
* @param UploadClientRequest $request
* @param Client $client
* @return Response
*
*
*
* @OA\Put(
* path="/api/v1/clients/{id}/adjust_ledger",
* operationId="adjustLedger",
* tags={"clients"},
* summary="Adjust the client ledger to rebalance",
* description="Adjust the client ledger to rebalance",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The Client Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the client object",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/Client"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
//@deprecated - not available
public function adjustLedger(AdjustClientLedgerRequest $request, Client $client)
{
// $adjustment = $request->input('adjustment');
// $notes = $request->input('notes');
// $client->service()->updateBalance
}
}

View File

@ -24,12 +24,12 @@ class ContactHashLoginController extends Controller
*/
public function login(string $contact_key)
{
return redirect('/client/login');
return redirect('/client/invoices');
}
public function magicLink(string $magic_link)
{
return redirect('/client/login');
return redirect('/client/invoices');
}
public function errorPage()

View File

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

View File

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

View File

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

View File

@ -57,7 +57,7 @@ class InvitationController extends Controller
/* Return early if we have the correct client_hash embedded */
if (request()->has('client_hash') && request()->input('client_hash') == $invitation->contact->client->client_hash) {
auth()->guard('contact')->login($invitation->contact, true);
auth()->guard('contact')->loginUsingId($invitation->contact->id, true);
} elseif ((bool) $invitation->contact->client->getSetting('enable_client_portal_password') !== false) {
@ -66,7 +66,7 @@ class InvitationController extends Controller
return redirect()->route('client.login');
} else {
auth()->guard('contact')->login($invitation->contact, true);
auth()->guard('contact')->loginUsingId($invitation->contact->id, true);
}

View File

@ -164,10 +164,10 @@ class InvoiceController extends Controller
//if only 1 pdf, output to buffer for download
if ($invoices->count() == 1) {
return response()->streamDownload(function () use ($invoices) {
echo file_get_contents($invoices->first()->pdf_file_path());
}, basename($invoices->first()->pdf_file_path()), ['Cache-Control:' => 'no-cache']);
//return response()->download(TempFile::path($invoices->first()->pdf_file_path()), basename($invoices->first()->pdf_file_path()));
$file = $invoices->first()->pdf_file_path();
return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);;
}
// enable output of HTTP headers

View File

@ -290,7 +290,8 @@ class PaymentController extends Controller
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_ERROR,
SystemLog::TYPE_FAILURE,
auth('contact')->user()->client
auth('contact')->user()->client,
auth('contact')->user()->client->company
);
throw new PaymentFailed($e->getMessage());

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -535,11 +535,9 @@ class CreditController extends BaseController
return $this->itemResponse($credit);
}
break;
case 'download':
return response()->streamDownload(function () use ($credit) {
echo file_get_contents($credit->pdf_file_path());
}, basename($credit->pdf_file_path()), ['Cache-Control:' => 'no-cache']);
//return response()->download(TempFile::path($credit->pdf_file_path()), basename($credit->pdf_file_path()));
case 'download':
$file = $credit->pdf_file_path();
return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
break;
case 'archive':
$this->credit_repository->archive($credit);
@ -589,7 +587,7 @@ class CreditController extends BaseController
$file_path = $credit->service()->getCreditPdf($invitation);
return response()->download($file_path, basename($file_path), ['Cache-Control:' => 'no-cache']);
return response()->download($file_path, basename($file_path), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
}
/**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -99,9 +99,10 @@ class PreviewController extends BaseController
$entity_obj->load('client');
App::setLocale($entity_obj->client->primary_contact()->preferredLocale());
App::forgetInstance('translator');
Lang::replace(Ninja::transformTranslations($entity_obj->client->getMergedSettings()));
$t = app('translator');
App::setLocale($entity_obj->client->primary_contact()->preferredLocale());
$t->replace(Ninja::transformTranslations($entity_obj->client->getMergedSettings()));
$html = new HtmlEngine($entity_obj->invitations()->first());
@ -151,7 +152,8 @@ class PreviewController extends BaseController
private function blankEntity()
{
App::forgetInstance('translator');
Lang::replace(Ninja::transformTranslations(auth()->user()->company()->settings));
$t = app('translator');
$t->replace(Ninja::transformTranslations(auth()->user()->company()->settings));
DB::beginTransaction();

View File

@ -675,10 +675,10 @@ class QuoteController extends BaseController
// code...
break;
case 'download':
return response()->streamDownload(function () use ($quote) {
echo file_get_contents($quote->pdf_file_path());
}, basename($quote->pdf_file_path()), ['Cache-Control:' => 'no-cache']);
//return response()->download(TempFile::path($quote->pdf_file_path()), basename($quote->pdf_file_path()));
$file = $quote->pdf_file_path();
return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
break;
case 'restore':
$this->quote_repo->restore($quote);
@ -730,7 +730,7 @@ class QuoteController extends BaseController
$file_path = $quote->service()->getQuotePdf($contact);
return response()->download($file_path, basename($file_path), ['Cache-Control:' => 'no-cache']);
return response()->download($file_path, basename($file_path), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
}
/**

View File

@ -497,7 +497,7 @@ class RecurringInvoiceController extends BaseController
$file_path = $recurring_invoice->service()->getInvoicePdf($contact);
return response()->download($file_path, basename($file_path), ['Cache-Control:' => 'no-cache']);
return response()->download($file_path, basename($file_path), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
}
/**

View File

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

View File

@ -17,8 +17,11 @@ use App\Factory\CompanyGatewayFactory;
use App\Http\Requests\StripeConnect\InitializeStripeConnectRequest;
use App\Libraries\MultiDB;
use App\Models\Client;
use App\Models\Company;
use App\Models\CompanyGateway;
use App\Models\GatewayType;
use App\PaymentDrivers\Stripe\Connect\Account;
use Illuminate\Http\Request;
use Stripe\Exception\ApiErrorException;
class StripeConnectController extends BaseController
@ -38,6 +41,8 @@ class StripeConnectController extends BaseController
MultiDB::findAndSetDbByCompanyKey($request->getTokenContent()['company_key']);
$company = Company::where('company_key', $request->getTokenContent()['company_key'])->first();
$company_gateway = CompanyGateway::query()
->where('gateway_key', 'd14dd26a47cecc30fdd65700bfb67b34')
->where('company_id', $request->getCompany()->id)
@ -45,53 +50,84 @@ class StripeConnectController extends BaseController
if ($company_gateway) {
$config = decrypt($company_gateway->config);
$config = $company_gateway->getConfig();
if(property_exists($config, 'account_id'))
return render('gateways.stripe.connect.existing');
}
else
$company_gateway = CompanyGatewayFactory::create($request->getCompany()->id, $request->getContact()->id);
return view('auth.connect.existing');
/* Set Credit Card To Enabled */
$gateway_types = $company_gateway->driver(new Client)->gatewayTypes();
$fees_and_limits = new \stdClass;
$fees_and_limits->{$gateway_types[0]} = new FeesAndLimits;
$company_gateway->gateway_key = 'd14dd26a47cecc30fdd65700bfb67b34';
$company_gateway->fees_and_limits = $fees_and_limits;
$company_gateway->save();
/* Link account if existing account exists */
if($account_id = $this->checkAccountAlreadyLinkToEmail($company_gateway, $request->getContact()->email)) {
$config = json_decode(decrypt($company_gateway->config));
$config->account_id = $account_id;
$company_gateway->config = encrypt(json_encode($config));
$company_gateway->save();
return render('gateways.stripe.connect.existing');
}
$data = [
'type' => 'standard',
'email' => $request->getContact()->email,
'country' => $request->getCompany()->country()->iso_3166_2,
];
$stripe_client_id = config('ninja.ninja_stripe_client_id');
$redirect_uri = 'https://invoicing.co/stripe/completed';
$endpoint = "https://connect.stripe.com/oauth/authorize?response_type=code&client_id={$stripe_client_id}&redirect_uri={$redirect_uri}&scope=read_write&state={$token}";
$account = Account::create($data);
$link = Account::link($account->id, $token);
$company_gateway->config = encrypt(json_encode(['account_id' => $account->id]));
$company_gateway->save();
// if($email = $request->getContact()->email)
// $endpoint .= "&stripe_user[email]={$email}";
return redirect($link['url']);
// $company_name = str_replace(" ", "_", $company->present()->name());
// $endpoint .= "&stripe_user[business_name]={$company_name}";
return redirect($endpoint);
}
public function completed()
public function completed(InitializeStripeConnectRequest $request)
{
return render('gateways.stripe.connect.completed');
\Stripe\Stripe::setApiKey(config('ninja.ninja_stripe_key'));
try {
$response = \Stripe\OAuth::token([
'grant_type' => 'authorization_code',
'code' => $request->input('code'),
]);
}catch(\Exception $e)
{
nlog($e->getMessage());
}
// nlog($response);
$company = Company::where('company_key', $request->getTokenContent()['company_key'])->first();
$company_gateway = CompanyGatewayFactory::create($company->id, $company->owner()->id);
$fees_and_limits = new \stdClass;
$fees_and_limits->{GatewayType::CREDIT_CARD} = new FeesAndLimits;
$company_gateway->gateway_key = 'd14dd26a47cecc30fdd65700bfb67b34';
$company_gateway->fees_and_limits = $fees_and_limits;
$company_gateway->setConfig([]);
// $company_gateway->save();
$payload = [
'account_id' => $response->stripe_user_id,
"token_type" => 'bearer',
"stripe_publishable_key" => $response->stripe_publishable_key,
"scope" => $response->scope,
"livemode" => $response->livemode,
"stripe_user_id" => $response->stripe_user_id,
"refresh_token" => $response->refresh_token,
"access_token" => $response->access_token
];
/* Link account if existing account exists */
// if($account_id = $this->checkAccountAlreadyLinkToEmail($company_gateway, $request->getContact()->email)) {
// $payload['account_id'] = $account_id;
// $payload['stripe_user_id'] = $account_id;
// $company_gateway->setConfig($payload);
// $company_gateway->save();
// return view('auth.connect.existing');
// }
$company_gateway->setConfig($payload);
$company_gateway->save();
//response here
return view('auth.connect.completed');
}
@ -111,4 +147,22 @@ class StripeConnectController extends BaseController
return false;
}
/*********************************
* Stripe OAuth
*/
// public function initialize(InitializeStripeConnectRequest $request, string $token)
// {
// $stripe_key = config('ninja.ninja_stripe_key');
// $endpoint = "https://connect.stripe.com/oauth/authorize?response_type=code&client_id={$stripe_key}&scope=read_write";
// return redirect($endpoint);
// }
}

View File

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

View File

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

View File

@ -631,6 +631,7 @@ class TaskController extends BaseController
$task_status = TaskStatus::where('id', $this->decodePrimaryKey($task_status_hashed_id))
->where('company_id', auth()->user()->company()->id)
->withTrashed()
->first();
$task_status->status_order = $key;
@ -643,18 +644,13 @@ class TaskController extends BaseController
$sort_status_id = $this->decodePrimaryKey($key);
// nlog($task_list);
foreach ($task_list as $key => $task)
{
// nlog($task);
$task_record = Task::where('id', $this->decodePrimaryKey($task))
->where('company_id', auth()->user()->company()->id)
->withTrashed()
->first();
// nlog($task_record->id);
$task_record->status_order = $key;
$task_record->status_id = $sort_status_id;
@ -663,6 +659,6 @@ class TaskController extends BaseController
}
return response()->json(['message' => 'Ok'],200);
return response()->json(['message' => 'Ok'], 200);
}
}

View File

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

View File

@ -16,6 +16,7 @@ use App\Http\Middleware\Authenticate;
use App\Http\Middleware\CheckClientExistence;
use App\Http\Middleware\CheckForMaintenanceMode;
use App\Http\Middleware\ClientPortalEnabled;
use App\Http\Middleware\ContactAccount;
use App\Http\Middleware\ContactKeyLogin;
use App\Http\Middleware\ContactRegister;
use App\Http\Middleware\ContactSetDb;
@ -110,7 +111,6 @@ class Kernel extends HttpKernel
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
SubstituteBindings::class,
//\App\Http\Middleware\StartupCheck::class,
QueryLogging::class,
],
'shop' => [
@ -142,6 +142,7 @@ class Kernel extends HttpKernel
'api_secret_check' => ApiSecretCheck::class,
'contact_token_auth' => ContactTokenAuth::class,
'contact_db' => ContactSetDb::class,
'contact_account' => ContactAccount::class,
'domain_db' => SetDomainNameDb::class,
'email_db' => SetEmailDb::class,
'invite_db' => SetInviteDb::class,
@ -160,4 +161,29 @@ class Kernel extends HttpKernel
'check_client_existence' => CheckClientExistence::class,
'user_verified' => UserVerified::class,
];
protected $middlewarePriority = [
SetDomainNameDb::class,
SetDb::class,
SetWebDb::class,
UrlSetDb::class,
ContactSetDb::class,
SetEmailDb::class,
SetInviteDb::class,
SetDbByCompanyKey::class,
TokenAuth::class,
ContactTokenAuth::class,
ContactKeyLogin::class,
Authenticate::class,
ShopTokenAuth::class,
ContactRegister::class,
PhantomSecret::class,
CheckClientExistence::class,
ClientPortalEnabled::class,
PasswordProtection::class,
Locale::class,
SubstituteBindings::class,
ContactAccount::class,
];
}

View File

@ -181,14 +181,16 @@ class BillingPortalPurchase extends Component
{
$this->validate();
$contact = ClientContact::where('email', $this->email)->first();
$contact = ClientContact::where('email', $this->email)
->where('company_id', $this->subscription->company_id)
->first();
if ($contact && $this->steps['existing_user'] === false) {
return $this->steps['existing_user'] = true;
}
if ($contact && $this->steps['existing_user']) {
$attempt = Auth::guard('contact')->attempt(['email' => $this->email, 'password' => $this->password]);
$attempt = Auth::guard('contact')->attempt(['email' => $this->email, 'password' => $this->password, 'company_id' => $this->subscription->company_id]);
return $attempt
? $this->getPaymentMethods($contact)

View File

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

View File

@ -0,0 +1,39 @@
<?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\Livewire;
use App\Models\Task;
use App\Utils\Traits\WithSorting;
use Livewire\Component;
use Livewire\WithPagination;
class TasksTable extends Component
{
use WithSorting;
use WithPagination;
public $per_page = 10;
public function render()
{
$query = Task::query()
->where('client_id', auth('contact')->user()->client->id)
->whereNotNull('invoice_id')
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
->paginate($this->per_page);
return render('components.livewire.tasks-table', [
'tasks' => $query,
]);
}
}

View File

@ -11,6 +11,7 @@
namespace App\Http\Middleware;
use App\Utils\Ninja;
use Closure;
use Illuminate\Http\Request;
use stdClass;
@ -26,7 +27,7 @@ class ApiSecretCheck
*/
public function handle($request, Closure $next)
{
if (! config('ninja.api_secret')) {
if (! config('ninja.api_secret') || Ninja::isHosted()) {
return $next($request);
}

View File

@ -31,6 +31,7 @@ class CheckClientExistence
$multiple_contacts = ClientContact::query()
->where('email', auth('contact')->user()->email)
->whereNotNull('email')
->where('email', '<>', '')
->whereNull('deleted_at')
->distinct('company_id')
->distinct('email')
@ -38,6 +39,9 @@ class CheckClientExistence
->whereHas('client', function ($query) {
return $query->whereNull('deleted_at');
})
->whereHas('client.company', function ($query){
return $query->where('account_id', auth('contact')->user()->client->company->account->id);
})
->get();
if (count($multiple_contacts) == 0) {

View File

@ -0,0 +1,41 @@
<?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\Middleware;
use App\Libraries\MultiDB;
use App\Models\Account;
use App\Utils\Ninja;
use Closure;
use Illuminate\Http\Request;
class ContactAccount
{
/**
* Handle an incoming request.
*
* @param Request $request
* @param Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if(!Ninja::isHosted()) {
$account_id = Account::first()->id;
$request->attributes->add(['account_id' => $account_id]);
}
return $next($request);
}
}

View File

@ -18,6 +18,7 @@ use Auth;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
class ContactKeyLogin
{
@ -42,13 +43,20 @@ class ContactKeyLogin
if (MultiDB::findAndSetDbByContactKey($request->segment(3))) {
if($client_contact = ClientContact::where('contact_key', $request->segment(3))->first()){
auth()->guard('contact')->login($client_contact, true);
if(empty($client_contact->email))
$client_contact->email = Str::random(6) . "@example.com"; $client_contact->save();
Auth::guard('contact')->login($client_contact, true);
return redirect()->to('client/dashboard');
}
}
} elseif ($request->segment(2) && $request->segment(2) == 'key_login' && $request->segment(3)) {
if ($client_contact = ClientContact::where('contact_key', $request->segment(3))->first()) {
if(empty($client_contact->email))
$client_contact->email = Str::random(6) . "@example.com"; $client_contact->save();
auth()->guard('contact')->login($client_contact, true);
return redirect()->to('client/dashboard');
}
@ -56,19 +64,36 @@ class ContactKeyLogin
if (MultiDB::findAndSetDbByClientHash($request->input('client_hash'))) {
if($client = Client::where('client_hash', $request->input('client_hash'))->first()){
auth()->guard('contact')->login($client->primary_contact()->first(), true);
$primary_contact = $client->primary_contact()->first();
if(empty($primary_contact->email))
$primary_contact->email = Str::random(6) . "@example.com"; $primary_contact->save();
auth()->guard('contact')->login($primary_contact, true);
return redirect()->to('client/dashboard');
}
}
} elseif ($request->has('client_hash')) {
if ($client = Client::where('client_hash', $request->input('client_hash'))->first()) {
Auth::guard('contact')->login($client->primary_contact()->first(), true);
$primary_contact = $client->primary_contact()->first();
if(empty($primary_contact->email))
$primary_contact->email = Str::random(6) . "@example.com"; $primary_contact->save();
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()){
Auth::guard('contact')->login($client_contact, true);
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'));

View File

@ -52,6 +52,6 @@ class ContactRegister
return $next($request);
}
return abort(404);
abort(404, 'ContactRegister Middlware');
}
}

View File

@ -31,7 +31,7 @@ class PasswordProtection
*/
public function handle($request, Closure $next)
{
$error = [
'message' => 'Invalid Password',
'errors' => new stdClass,
@ -44,9 +44,9 @@ class PasswordProtection
else
$timeout = $timeout/1000;
if (Cache::get(auth()->user()->hashed_id.'_logged_in')) {
if (Cache::get(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in')) {
Cache::put(auth()->user()->hashed_id.'_logged_in', Str::random(64), $timeout);
Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout);
return $next($request);
@ -68,12 +68,13 @@ class PasswordProtection
//If OAuth and user also has a password set - check both
if ($existing_user = MultiDB::hasUser($query) && auth()->user()->has_password && Hash::check(auth()->user()->password, $request->header('X-API-PASSWORD'))) {
Cache::put(auth()->user()->hashed_id.'_logged_in', Str::random(64), $timeout);
Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout);
return $next($request);
}
elseif($existing_user = MultiDB::hasUser($query) && !auth()->user()->has_password){
Cache::put(auth()->user()->hashed_id.'_logged_in', Str::random(64), $timeout);
Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout);
return $next($request);
}
}
@ -83,7 +84,7 @@ class PasswordProtection
}elseif ($request->header('X-API-PASSWORD') && Hash::check($request->header('X-API-PASSWORD'), auth()->user()->password)) {
Cache::put(auth()->user()->hashed_id.'_logged_in', Str::random(64), $timeout);
Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout);
return $next($request);

View File

@ -34,14 +34,16 @@ class RedirectIfAuthenticated
}
break;
case 'user':
if (Auth::guard($guard)->check()) {
return redirect()->route('dashboard.index');
}
Auth::logout();
// if (Auth::guard($guard)->check()) {
// return redirect()->route('dashboard.index');
// }
break;
default:
if (Auth::guard($guard)->check()) {
return redirect('/');
}
Auth::logout();
// if (Auth::guard($guard)->check()) {
// return redirect('/');
// }
break;
}

View File

@ -27,6 +27,7 @@ class SetDb
*/
public function handle($request, Closure $next)
{
$error = [
'message' => 'Invalid Token',
'errors' => new stdClass,

View File

@ -34,13 +34,58 @@ class SetDomainNameDb
/*
* Use the host name to set the active DB
**/
if ($request->getSchemeAndHttpHost() && config('ninja.db.multi_db_enabled') && ! MultiDB::findAndSetDbByDomain($request->getSchemeAndHttpHost())) {
if (request()->json) {
return response()->json($error, 403);
} else {
abort(404);
if(!config('ninja.db.multi_db_enabled'))
return $next($request);
if (strpos($request->getHost(), 'invoicing.co') !== false)
{
$subdomain = explode('.', $request->getHost())[0];
$query = [
'subdomain' => $subdomain,
'portal_mode' => 'subdomain',
];
if($company = MultiDB::findAndSetDbByDomain($query)){
$request->attributes->add(['account_id' => $company->account_id]);
}
else
{
if ($request->json) {
return response()->json($error, 403);
} else {
MultiDB::setDb('db-ninja-01');
nlog("I could not set the DB - defaulting to DB1");
//abort(400, 'Domain not found');
}
}
}
else {
$query = [
'portal_domain' => $request->getSchemeAndHttpHost(),
'portal_mode' => 'domain',
];
if($company = MultiDB::findAndSetDbByDomain($query)){
$request->attributes->add(['account_id' => $company->account_id]);
}
else
{
if ($request->json) {
return response()->json($error, 403);
} else {
MultiDB::setDb('db-ninja-01');
nlog("I could not set the DB - defaulting to DB1");
//abort(400, 'Domain not found');
}
}
}
return $next($request);
}

View File

@ -34,15 +34,10 @@ class SetEmailDb
if ($request->input('email') && config('ninja.db.multi_db_enabled')) {
if (! MultiDB::userFindAndSetDb($request->input('email')))
return response()->json($error, 400);
}
// else {
// return response()->json($error, 403);
// }
return $next($request);
}

View File

@ -28,7 +28,7 @@ class SetInviteDb
public function handle($request, Closure $next)
{
$error = [
'message' => 'Invalid URL',
'message' => 'I could not find the database for this object.',
'errors' => new stdClass,
];
/*
@ -46,7 +46,7 @@ class SetInviteDb
if (request()->json) {
return response()->json($error, 403);
} else {
abort(404);
abort(404,'I could not find the database for this object.');
}
}

View File

@ -49,7 +49,6 @@ class TokenAuth
| us to decouple a $user and their attached companies completely.
|
*/
$user->setCompany($company_token->company);
app('queue')->createPayloadUsing(function () use ($company_token) {
return ['db' => $company_token->company->db];
@ -67,6 +66,7 @@ class TokenAuth
//stateless, don't remember the user.
auth()->login($user, false);
auth()->user()->setCompany($company_token->company);
} else {
$error = [

View File

@ -40,13 +40,13 @@ class CreateAccountRequest extends Request
'password' => 'required|string|min:6',
'email' => 'bail|required|email:rfc,dns',
'email' => new NewUniqueUserRule(),
'privacy_policy' => 'required',
'terms_of_service' => 'required',
'privacy_policy' => 'required|boolean',
'terms_of_service' => 'required|boolean',
];
}
protected function prepareForValidation()
{
{nlog($this->all());
$input = $this->all();
$input['user_agent'] = request()->server('HTTP_USER_AGENT');

View File

@ -0,0 +1,55 @@
<?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\Requests\Client;
use App\Http\Requests\Request;
use App\Utils\Traits\MakesHash;
use Illuminate\Validation\Rule;
class AdjustClientLedgerRequest extends Request
{
use MakesHash;
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('edit', $this->client);
}
public function rules()
{
/* Ensure we have a client name, and that all emails are unique*/
$rules = [];
return $rules;
}
public function messages()
{
return [
];
}
protected function prepareForValidation()
{
$input = $this->all();
$this->replace($input);
}
}

View File

@ -53,6 +53,6 @@ class RegisterRequest extends FormRequest
return $company;
}
abort(404);
abort(404, 'Register request not found.');
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Http\Requests\ClientPortal\Tasks;
use Illuminate\Foundation\Http\FormRequest;
class ShowTasksRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return (bool)auth()->user('contact')->client->getSetting('enable_client_portal_tasks');
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
//
];
}
}

View File

@ -14,8 +14,10 @@ namespace App\Http\Requests\Company;
use App\DataMapper\CompanySettings;
use App\Http\Requests\Request;
use App\Http\ValidationRules\Company\ValidCompanyQuantity;
use App\Http\ValidationRules\Company\ValidSubdomain;
use App\Http\ValidationRules\ValidSettingsRule;
use App\Models\Company;
use App\Utils\Ninja;
use App\Utils\Traits\MakesHash;
class StoreCompanyRequest extends Request
@ -45,7 +47,13 @@ class StoreCompanyRequest extends Request
if (isset($input['portal_mode']) && ($input['portal_mode'] == 'domain' || $input['portal_mode'] == 'iframe')) {
$rules['portal_domain'] = 'sometimes|url';
} else {
$rules['subdomain'] = 'nullable|alpha_num';
if(Ninja::isHosted()){
$rules['subdomain'] = ['nullable', 'alpha_num', new ValidSubdomain($this->all())];
}
else
$rules['subdomain'] = 'nullable|alpha_num';
}
return $rules;
@ -55,8 +63,9 @@ class StoreCompanyRequest extends Request
{
$input = $this->all();
if(array_key_exists('portal_domain', $input) && strlen($input['portal_domain']) > 1)
$input['portal_domain'] = str_replace("http:", "https:", $input['portal_domain']);
//https not sure i should be forcing this.
// if(array_key_exists('portal_domain', $input) && strlen($input['portal_domain']) > 1)
// $input['portal_domain'] = str_replace("http:", "https:", $input['portal_domain']);
if (array_key_exists('google_analytics_url', $input)) {
$input['google_analytics_key'] = $input['google_analytics_url'];

View File

@ -13,7 +13,9 @@ namespace App\Http\Requests\Company;
use App\DataMapper\CompanySettings;
use App\Http\Requests\Request;
use App\Http\ValidationRules\Company\ValidSubdomain;
use App\Http\ValidationRules\ValidSettingsRule;
use App\Utils\Ninja;
use App\Utils\Traits\MakesHash;
class UpdateCompanyRequest extends Request
@ -46,7 +48,12 @@ class UpdateCompanyRequest extends Request
if (isset($input['portal_mode']) && ($input['portal_mode'] == 'domain' || $input['portal_mode'] == 'iframe')) {
$rules['portal_domain'] = 'sometimes|url';
} else {
$rules['subdomain'] = 'nullable|alpha_num';
if(Ninja::isHosted()){
$rules['subdomain'] = ['nullable', 'alpha_num', new ValidSubdomain($this->all())];
}
else
$rules['subdomain'] = 'nullable|alpha_num';
}
// if($this->company->account->isPaidHostedClient()) {
@ -60,8 +67,8 @@ class UpdateCompanyRequest extends Request
{
$input = $this->all();
if(array_key_exists('portal_domain', $input) && strlen($input['portal_domain']) > 1)
$input['portal_domain'] = str_replace("http:", "https:", $input['portal_domain']);
// if(array_key_exists('portal_domain', $input) && strlen($input['portal_domain']) > 1)
// $input['portal_domain'] = str_replace("http:", "https:", $input['portal_domain']);
if (array_key_exists('settings', $input)) {
$input['settings'] = $this->filterSaveableSettings($input['settings']);

View File

@ -0,0 +1,39 @@
<?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\Requests\Export;
use App\Http\Requests\Request;
class StoreExportRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return auth()->user()->isAdmin();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [];
}
}

View File

@ -0,0 +1,39 @@
<?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\Requests\Import;
use App\Http\Requests\Request;
class ImportJsonRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->isAdmin();
}
public function rules()
{
return [
// 'import_type' => 'required',
// 'files' => 'required_without:hash|array|min:1|max:6',
// 'hash' => 'nullable|string',
// 'column_map' => 'required_with:hash|array',
// 'skip_header' => 'required_with:hash|boolean',
// 'files.*' => 'file|mimes:csv,txt',
];
}
}

View File

@ -27,7 +27,7 @@ class PaymentWebhookRequest extends Request
public function authorize()
{
MultiDB::findAndSetDbByCompanyKey($this->getCompany()->company_key);
MultiDB::findAndSetDbByCompanyKey($this->company_key);
return true;
}
@ -45,7 +45,7 @@ class PaymentWebhookRequest extends Request
* @param mixed $id
* @return null|\App\Models\CompanyGateway
*/
public function getCompanyGateway(): ?CompanyGateway
public function getCompanyGateway()
{
return CompanyGateway::findOrFail($this->decodePrimaryKey($this->company_gateway_id));
}
@ -56,13 +56,13 @@ class PaymentWebhookRequest extends Request
* @param string $hash
* @return null|\App\Models\PaymentHash
*/
public function getPaymentHash(): ?PaymentHash
public function getPaymentHash()
{
if ($this->query('hash')) {
return PaymentHash::where('hash', $this->query('hash'))->firstOrFail();
}
return null;
return false;
}
/**
@ -94,7 +94,7 @@ class PaymentWebhookRequest extends Request
// If none of previously done logics is correct, we'll just display
// not found page.
abort(404);
return false;
}
/**
@ -102,11 +102,14 @@ class PaymentWebhookRequest extends Request
*
* @return null|\App\Models\Client
*/
public function getClient(): ?Client
public function getClient()
{
$hash = $this->getPaymentHash();
return Client::find($hash->data->client_id)->firstOrFail();
if($hash)
return Client::find($hash->data->client_id)->firstOrFail();
return false;
}
/**

View File

@ -49,6 +49,9 @@ class InitializeStripeConnectRequest extends FormRequest
*/
public function getTokenContent()
{
if($this->state)
$this->token = $this->state;
$data = Cache::get($this->token);
return $data;

View File

@ -45,9 +45,6 @@ class UpdateUserRequest extends Request
{
$input = $this->all();
// if (isset($input['company_user']) && ! auth()->user()->isAdmin()) {
// unset($input['company_user']);
// }
$this->replace($input);
}

View File

@ -15,6 +15,7 @@ use App\Http\Requests\Request;
use App\Http\ValidationRules\ValidVendorGroupSettingsRule;
use App\Models\Vendor;
use App\Utils\Traits\MakesHash;
use Illuminate\Validation\Rule;
class StoreVendorRequest extends Request
{
@ -39,6 +40,9 @@ class StoreVendorRequest extends Request
//$rules['settings'] = new ValidVendorGroupSettingsRule();
$rules['contacts.*.email'] = 'nullable|distinct';
if (isset($this->number)) {
$rules['number'] = Rule::unique('vendors')->where('company_id', auth()->user()->company()->id);
}
return $rules;
}

View File

@ -0,0 +1,50 @@
<?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\ValidationRules\Company;
use App\Libraries\MultiDB;
use Illuminate\Contracts\Validation\Rule;
/**
* Class ValidCompanyQuantity.
*/
class ValidSubdomain implements Rule
{
/**
* @param string $attribute
* @param mixed $value
* @return bool
*/
private $input;
public function __construct($input)
{
$this->input = $input;
}
public function passes($attribute, $value)
{
if(empty($input['subdomain']))
return true;
return MultiDB::checkDomainAvailable($input['subdomain']);
}
/**
* @return string
*/
public function message()
{
return ctrans('texts.subdomain_taken');
}
}

View File

@ -57,13 +57,13 @@ class InvoiceBalanceSanity implements Rule
private function checkIfInvoiceBalanceIsSane() : bool
{
$this->invoice->line_items = $this->input['line_items'];
DB::connection(config('database.default'))->beginTransaction();
DB::beginTransaction();
$this->invoice = Invoice::on(config('database.default'))->find($this->invoice->id);
$this->invoice->line_items = $this->input['line_items'];
$temp_invoice = $this->invoice->calc()->getTempEntity();
$temp_invoice = $this->invoice->calc()->getTempEntity();
DB::rollBack();
DB::connection(config('database.default'))->rollBack();
if($temp_invoice->balance < 0){
$this->message = 'Invoice balance cannot go negative';
@ -71,7 +71,7 @@ class InvoiceBalanceSanity implements Rule
}
return true;
return true;
}
}

View File

@ -20,6 +20,7 @@ use Illuminate\Contracts\Validation\Rule;
*/
class AttachableUser implements Rule
{
public $message;
public function __construct()
{
@ -39,7 +40,7 @@ class AttachableUser implements Rule
*/
public function message()
{
return "Cannot add the same user to the same company";
return $this->message;
}
/**
@ -57,15 +58,22 @@ class AttachableUser implements Rule
if(!$user)
return true;
$user_already_attached = CompanyUser::query()
$user_already_attached = CompanyUser::query()
->where('user_id', $user->id)
->where('account_id',$user->account_id)
->where('company_id', auth()->user()->company()->id)
->exists();
if($user_already_attached)
//If the user is already attached or isn't link to this account - return false
if($user_already_attached) {
$this->message = ctrans('texts.user_duplicate_error');
return false;
}
if($user->account_id != auth()->user()->account_id){
$this->message = ctrans('texts.user_cross_linked_error');
return false;
}
return true;
}

View File

@ -34,7 +34,9 @@ class PortalComposer
$view->with($this->portalData());
if (auth()->user()) {
Lang::replace(Ninja::transformTranslations(auth()->user()->client->getMergedSettings()));
App::forgetInstance('translator');
$t = app('translator');
$t->replace(Ninja::transformTranslations(auth()->user()->client->getMergedSettings()));
}
}
@ -82,9 +84,7 @@ class PortalComposer
$data[] = ['title' => ctrans('texts.subscriptions'), 'url' => 'client.subscriptions.index', 'icon' => 'calendar'];
if (auth()->user('contact')->client->getSetting('enable_client_portal_tasks')) {
$data[] = ['title' => ctrans('texts.tasks'), 'url' => 'client.dashboard', 'icon' => 'clock'];
// TODO: Update when 'tasks' module is available in client portal.
$data[] = ['title' => ctrans('texts.tasks'), 'url' => 'client.tasks.index', 'icon' => 'clock'];
}
return $data;

View File

@ -25,11 +25,13 @@ use App\Jobs\Util\VersionCheck;
use App\Mail\Admin\AccountCreatedObject;
use App\Mail\Admin\VerifyUserObject;
use App\Models\Account;
use App\Models\Timezone;
use App\Notifications\Ninja\NewAccountCreated;
use App\Utils\Ninja;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;
use Turbo124\Beacon\Facades\LightLogs;
@ -40,9 +42,12 @@ class CreateAccount
protected $request;
public function __construct(array $sp660339)
protected $client_ip;
public function __construct(array $sp660339, $client_ip)
{
$this->request = $sp660339;
$this->client_ip = $client_ip;
}
public function handle()
@ -74,6 +79,7 @@ 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();
@ -107,4 +113,53 @@ class CreateAccount
return $sp794f3f;
}
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)))
{
$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;
})->first();
if ($currency) {
$settings->currency_id = (string)$currency->id;
}
$country = Cache::get('countries')->filter(function ($item) use ($country_code) {
return strtolower($item->iso_3166_2) == $country_code || strtolower($item->iso_3166_3) == $country_code;
})->first();
if ($country) {
$settings->country_id = (string)$country->id;
}
$language = Cache::get('languages')->filter(function ($item) use ($currency_code) {
return strtolower($item->locale) == $currency_code;
})->first();
if ($language) {
$settings->language_id = (string)$language->id;
}
$timezone = Timezone::where('name', $data['geoplugin_timezone'])->first();
if($timezone) {
$settings->timezone_id = (string)$timezone->id;
}
return $settings;
}
return $settings;
}
}

View File

@ -0,0 +1,509 @@
<?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\Company;
use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject;
use App\Jobs\Util\UnlinkFile;
use App\Libraries\MultiDB;
use App\Mail\DownloadBackup;
use App\Mail\DownloadInvoices;
use App\Models\Company;
use App\Models\CreditInvitation;
use App\Models\InvoiceInvitation;
use App\Models\QuoteInvitation;
use App\Models\RecurringInvoice;
use App\Models\RecurringInvoiceInvitation;
use App\Models\User;
use App\Models\VendorContact;
use App\Utils\Ninja;
use App\Utils\Traits\MakesHash;
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;
use ZipStream\Option\Archive;
use ZipStream\ZipStream;
class CompanyExport implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, MakesHash;
public $company;
private $export_format;
private $export_data = [];
public $user;
/**
* Create a new job instance.
*
* @param Company $company
* @param User $user
* @param string $custom_token_name
*/
public function __construct(Company $company, User $user, $export_format = 'json')
{
$this->company = $company;
$this->user = $user;
$this->export_format = $export_format;
}
/**
* Execute the job.
*
* @return CompanyToken|null
*/
public function handle()
{
MultiDB::setDb($this->company->db);
$this->company = Company::where('company_key', $this->company->company_key)->first();
set_time_limit(0);
$this->export_data['app_version'] = config('ninja.app_version');
$this->export_data['activities'] = $this->company->all_activities->map(function ($activity){
$activity = $this->transformArrayOfKeys($activity, [
'user_id',
'company_id',
'client_id',
'client_contact_id',
'account_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'
]);
return $activity;
})->makeHidden(['id'])->all();
$this->export_data['backups'] = $this->company->all_activities()->with('backup')->cursor()->map(function ($activity){
$backup = $activity->backup;
if(!$backup)
return;
$backup->activity_id = $this->encodePrimaryKey($backup->activity_id);
return $backup;
})->all();
$this->export_data['users'] = $this->company->users()->withTrashed()->cursor()->map(function ($user){
$user->account_id = $this->encodePrimaryKey($user->account_id);
// $user->id = $this->encodePrimaryKey($user->id);
return $user->makeVisible(['id']);
})->all();
$this->export_data['client_contacts'] = $this->company->client_contacts->map(function ($client_contact){
$client_contact = $this->transformArrayOfKeys($client_contact, ['company_id', 'user_id', 'client_id']);
return $client_contact->makeVisible([
'password',
'remember_token',
'user_id',
'company_id',
'client_id',
'google_2fa_secret',
'id',
'oauth_provider_id',
'oauth_user_id',
'token',
'hashed_id',
]);
})->all();
$this->export_data['client_gateway_tokens'] = $this->company->client_gateway_tokens->map(function ($client_gateway_token){
$client_gateway_token = $this->transformArrayOfKeys($client_gateway_token, ['company_id', 'client_id']);
return $client_gateway_token->makeVisible(['id']);
})->all();
$this->export_data['clients'] = $this->company->clients->map(function ($client){
$client = $this->transformArrayOfKeys($client, ['company_id', 'user_id', 'assigned_user_id', 'group_settings_id']);
return $client->makeVisible(['id','private_notes','user_id','company_id','last_login','hashed_id']);
})->all();
$this->export_data['company'] = $this->company->toArray();
$this->export_data['company_gateways'] = $this->company->company_gateways->map(function ($company_gateway){
$company_gateway = $this->transformArrayOfKeys($company_gateway, ['company_id', 'user_id']);
$company_gateway->config = decrypt($company_gateway->config);
return $company_gateway->makeVisible(['id']);
})->all();
$this->export_data['company_tokens'] = $this->company->tokens->map(function ($token){
$token = $this->transformArrayOfKeys($token, ['company_id', 'account_id', 'user_id']);
return $token;
})->all();
$this->export_data['company_ledger'] = $this->company->ledger->map(function ($ledger){
$ledger = $this->transformArrayOfKeys($ledger, ['activity_id', 'client_id', 'company_id', 'account_id', 'user_id','company_ledgerable_id']);
return $ledger;
})->all();
$this->export_data['company_users'] = $this->company->company_users->map(function ($company_user){
$company_user = $this->transformArrayOfKeys($company_user, ['company_id', 'account_id', 'user_id']);
return $company_user;
})->all();
$this->export_data['credits'] = $this->company->credits->map(function ($credit){
$credit = $this->transformBasicEntities($credit);
$credit = $this->transformArrayOfKeys($credit, ['recurring_id','client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id','invoice_id']);
return $credit->makeVisible(['id']);
})->all();
$this->export_data['credit_invitations'] = CreditInvitation::where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($credit){
$credit = $this->transformArrayOfKeys($credit, ['company_id', 'user_id', 'client_contact_id', 'credit_id']);
return $credit->makeVisible(['id']);
})->all();
$this->export_data['designs'] = $this->company->user_designs->makeHidden(['id'])->all();
$this->export_data['documents'] = $this->company->all_documents->map(function ($document){
$document = $this->transformArrayOfKeys($document, ['user_id', 'assigned_user_id', 'company_id', 'project_id', 'vendor_id','documentable_id']);
$document->hashed_id = $this->encodePrimaryKey($document->id);
return $document->makeVisible(['id']);
})->all();
$this->export_data['expense_categories'] = $this->company->expense_categories->map(function ($expense_category){
$expense_category = $this->transformArrayOfKeys($expense_category, ['user_id', 'company_id']);
return $expense_category->makeVisible(['id']);
})->all();
$this->export_data['expenses'] = $this->company->expenses->map(function ($expense){
$expense = $this->transformBasicEntities($expense);
$expense = $this->transformArrayOfKeys($expense, ['vendor_id', 'invoice_id', 'client_id', 'category_id', 'recurring_expense_id','project_id']);
return $expense->makeVisible(['id']);
})->all();
$this->export_data['group_settings'] = $this->company->group_settings->map(function ($gs){
$gs = $this->transformArrayOfKeys($gs, ['user_id', 'company_id']);
return $gs->makeVisible(['id']);
})->all();
$this->export_data['invoices'] = $this->company->invoices->map(function ($invoice){
$invoice = $this->transformBasicEntities($invoice);
$invoice = $this->transformArrayOfKeys($invoice, ['recurring_id','client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id']);
return $invoice->makeVisible(['id',
'private_notes',
'user_id',
'client_id',
'company_id',]);
})->all();
$this->export_data['invoice_invitations'] = InvoiceInvitation::where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($invoice){
$invoice = $this->transformArrayOfKeys($invoice, ['company_id', 'user_id', 'client_contact_id', 'invoice_id']);
return $invoice->makeVisible(['id']);
})->all();
$this->export_data['payment_terms'] = $this->company->user_payment_terms->map(function ($term){
$term = $this->transformArrayOfKeys($term, ['user_id', 'company_id']);
return $term;
})->makeHidden(['id'])->all();
$this->export_data['payments'] = $this->company->payments->map(function ($payment){
$payment = $this->transformBasicEntities($payment);
$payment = $this->transformArrayOfKeys($payment, ['client_id','project_id', 'vendor_id', 'client_contact_id', 'invitation_id', 'company_gateway_id']);
$payment->paymentables = $this->transformPaymentable($payment);
return $payment->makeVisible(['id']);
})->all();
$this->export_data['products'] = $this->company->products->map(function ($product){
$product = $this->transformBasicEntities($product);
$product = $this->transformArrayOfKeys($product, ['vendor_id','project_id']);
return $product->makeVisible(['id']);
})->all();
$this->export_data['projects'] = $this->company->projects->map(function ($project){
$project = $this->transformBasicEntities($project);
$project = $this->transformArrayOfKeys($project, ['client_id']);
return $project->makeVisible(['id']);
})->all();
$this->export_data['quotes'] = $this->company->quotes->map(function ($quote){
$quote = $this->transformBasicEntities($quote);
$quote = $this->transformArrayOfKeys($quote, ['invoice_id','recurring_id','client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id']);
return $quote->makeVisible(['id']);
})->all();
$this->export_data['quote_invitations'] = QuoteInvitation::where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($quote){
$quote = $this->transformArrayOfKeys($quote, ['company_id', 'user_id', 'client_contact_id', 'quote_id']);
return $quote->makeVisible(['id']);
})->all();
$this->export_data['recurring_invoices'] = $this->company->recurring_invoices->makeVisible(['id'])->map(function ($ri){
$ri = $this->transformBasicEntities($ri);
$ri = $this->transformArrayOfKeys($ri, ['client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id']);
return $ri->makeVisible(['id']);
})->all();
$this->export_data['recurring_invoice_invitations'] = RecurringInvoiceInvitation::where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($ri){
$ri = $this->transformArrayOfKeys($ri, ['company_id', 'user_id', 'client_contact_id', 'recurring_invoice_id']);
return $ri;
})->all();
$this->export_data['subscriptions'] = $this->company->subscriptions->map(function ($subscription){
$subscription = $this->transformBasicEntities($subscription);
$subscription->group_id = $this->encodePrimaryKey($subscription->group_id);
return $subscription->makeVisible([ 'id',
'user_id',
'assigned_user_id',
'company_id',
'product_ids',
'recurring_product_ids',
'group_id']);
})->all();
$this->export_data['system_logs'] = $this->company->system_logs->map(function ($log){
$log->client_id = $this->encodePrimaryKey($log->client_id);
$log->company_id = $this->encodePrimaryKey($log->company_id);
return $log;
})->makeHidden(['id'])->all();
$this->export_data['tasks'] = $this->company->tasks->map(function ($task){
$task = $this->transformBasicEntities($task);
$task = $this->transformArrayOfKeys($task, ['client_id', 'invoice_id', 'project_id', 'status_id']);
return $task->makeVisible(['id']);
})->all();
$this->export_data['task_statuses'] = $this->company->task_statuses->map(function ($status){
$status->id = $this->encodePrimaryKey($status->id);
$status->user_id = $this->encodePrimaryKey($status->user_id);
$status->company_id = $this->encodePrimaryKey($status->company_id);
return $status;
})->all();
$this->export_data['tax_rates'] = $this->company->tax_rates->map(function ($rate){
$rate->company_id = $this->encodePrimaryKey($rate->company_id);
$rate->user_id = $this->encodePrimaryKey($rate->user_id);
return $rate;
})->makeHidden(['id'])->all();
$this->export_data['vendors'] = $this->company->vendors->map(function ($vendor){
return $this->transformBasicEntities($vendor)->makeVisible(['id']);
})->all();
$this->export_data['vendor_contacts'] = VendorContact::where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($vendor){
$vendor = $this->transformBasicEntities($vendor);
$vendor->vendor_id = $this->encodePrimaryKey($vendor->vendor_id);
return $vendor->makeVisible(['id']);
})->all();
$this->export_data['webhooks'] = $this->company->webhooks->map(function ($hook){
$hook->user_id = $this->encodePrimaryKey($hook->user_id);
$hook->company_id = $this->encodePrimaryKey($hook->company_id);
return $hook;
})->makeHidden(['id'])->all();
//write to tmp and email to owner();
$this->zipAndSend();
return true;
}
private function transformBasicEntities($model)
{
return $this->transformArrayOfKeys($model, ['user_id', 'assigned_user_id', 'company_id']);
}
private function transformArrayOfKeys($model, $keys)
{
foreach($keys as $key){
$model->{$key} = $this->encodePrimaryKey($model->{$key});
}
return $model;
}
private function transformPaymentable($payment)
{
$new_arr = [];
foreach($payment->paymentables as $paymentable)
{
$paymentable->payment_id = $this->encodePrimaryKey($paymentable->payment_id);
$paymentable->paymentable_id = $this->encodePrimaryKey($paymentable->paymentable_id);
$new_arr[] = $paymentable;
}
return $new_arr;
}
private function zipAndSend()
{
$file_name = date('Y-m-d').'_'.str_replace(' ', '_', $this->company->present()->name() . '_' . $this->company->company_key .'.zip');
$zip_path = public_path('storage/backups/'.$file_name);
$zip = new \ZipArchive();
if ($zip->open($zip_path, \ZipArchive::CREATE)!==TRUE) {
nlog("cannot open {$zip_path}");
}
$zip->addFromString("backup.json", json_encode($this->export_data));
$zip->close();
if(Ninja::isHosted()) {
Storage::disk(config('filesystems.default'))->put('backups/'.$file_name, file_get_contents($zip_path));
}
$nmo = new NinjaMailerObject;
$nmo->mailable = new DownloadBackup(Storage::disk(config('filesystems.default'))->url('backups/'.$file_name), $this->company);
$nmo->to_user = $this->user;
$nmo->company = $this->company;
$nmo->settings = $this->company->settings;
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));
}
}

File diff suppressed because it is too large Load Diff

View File

@ -12,7 +12,9 @@
namespace App\Jobs\Company;
use App\DataMapper\CompanySettings;
use App\Libraries\MultiDB;
use App\Models\Company;
use App\Utils\Ninja;
use App\Utils\Traits\MakesHash;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Http\Request;
@ -60,6 +62,12 @@ class CreateCompany
$company->subdomain = isset($this->request['subdomain']) ? $this->request['subdomain'] : '';
$company->custom_fields = new \stdClass;
$company->default_password_timeout = 1800000;
if(Ninja::isHosted())
$company->subdomain = MultiDB::randomSubdomainGenerator();
else
$company->subdomain = '';
$company->save();
return $company;

View File

@ -0,0 +1,101 @@
<?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\Cron;
use App\Jobs\RecurringInvoice\SendRecurring;
use App\Libraries\MultiDB;
use App\Models\Invoice;
use App\Models\RecurringInvoice;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Support\Carbon;
class AutoBillCron
{
use Dispatchable;
public $tries = 1;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct()
{
}
/**
* Execute the job.
*
* @return void
*/
public function handle() : void
{
set_time_limit(0);
/* Get all invoices where the send date is less than NOW + 30 minutes() */
nlog("Performing Autobilling ".Carbon::now()->format('Y-m-d h:i:s'));
if (! config('ninja.db.multi_db_enabled')) {
$auto_bill_partial_invoices = Invoice::whereDate('partial_due_date', '<=', now())
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
->where('auto_bill_enabled', true)
->where('balance', '>', 0)
->with('company')
->cursor()->each(function ($invoice){
$this->runAutoBiller($invoice);
});
$auto_bill_invoices = Invoice::whereDate('due_date', '<=', now())
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
->where('auto_bill_enabled', true)
->where('balance', '>', 0)
->with('company')
->cursor()->each(function ($invoice){
$this->runAutoBiller($invoice);
});
} else {
//multiDB environment, need to
foreach (MultiDB::$dbs as $db) {
MultiDB::setDB($db);
$auto_bill_partial_invoices = Invoice::whereDate('partial_due_date', '<=', now())
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
->where('auto_bill_enabled', true)
->where('balance', '>', 0)
->with('company')
->cursor()->each(function ($invoice){
$this->runAutoBiller($invoice);
});
$auto_bill_invoices = Invoice::whereDate('due_date', '<=', now())
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
->where('auto_bill_enabled', true)
->where('balance', '>', 0)
->with('company')
->cursor()->each(function ($invoice){
$this->runAutoBiller($invoice);
});
}
}
}
private function runAutoBiller(Invoice $invoice)
{
nlog("Firing autobill for {$invoice->company_id} - {$invoice->number}");
$invoice->service()->autoBill()->save();
}
}

View File

@ -64,7 +64,7 @@ class CreateEntityPdf implements ShouldQueue
*
* @param $invitation
*/
public function __construct($invitation)
public function __construct($invitation, $disk = 'public')
{
$this->invitation = $invitation;
@ -86,22 +86,26 @@ class CreateEntityPdf implements ShouldQueue
$this->contact = $invitation->contact;
$this->disk = $disk ?? config('filesystems.default');
$this->disk = $disk;
// $this->disk = $disk ?? config('filesystems.default');
}
public function handle()
{
/* Set the locale*/
App::setLocale($this->contact->preferredLocale());
/* Forget the singleton*/
App::forgetInstance('translator');
/* Init a new copy of the translator*/
$t = app('translator');
/* Set the locale*/
App::setLocale($this->contact->preferredLocale());
// nlog($this->entity->client->getMergedSettings());
/* Set customized translations _NOW_ */
Lang::replace(Ninja::transformTranslations($this->entity->client->getMergedSettings()));
$t->replace(Ninja::transformTranslations($this->entity->client->getMergedSettings()));
$this->entity->service()->deletePdf();
@ -192,12 +196,12 @@ class CreateEntityPdf implements ShouldQueue
try{
Storage::disk($this->disk)->put($file_path, $pdf);
}
catch(\Exception $e)
{
throw new FilePermissionsFailure('Could not write the PDF, permission issues!');
throw new FilePermissionsFailure($e->getMessage());
}
}
@ -209,8 +213,5 @@ class CreateEntityPdf implements ShouldQueue
{
}
// public function failed(\Exception $exception)
// {
// nlog("help!");
// }
}

View File

@ -96,8 +96,8 @@ class CSVImport implements ShouldQueue {
MultiDB::setDb( $this->company->db );
Auth::login( $this->company->owner(), true );
$this->company->owner()->setCompany( $this->company );
auth()->user()->setCompany($this->company);
$this->buildMaps();
@ -293,10 +293,14 @@ class CSVImport implements ShouldQueue {
],
];
$payment_repository->save(
$payment_data,
PaymentFactory::create( $this->company->id, $invoice->user_id, $invoice->client_id )
);
/* Make sure we don't apply any payments to invoices with a Zero Amount*/
if($invoice->amount > 0)
{
$payment_repository->save(
$payment_data,
PaymentFactory::create( $this->company->id, $invoice->user_id, $invoice->client_id )
);
}
}
}
}

View File

@ -83,23 +83,24 @@ class ZipInvoices implements ShouldQueue
$zip = new ZipStream($file_name, $options);
foreach ($this->invoices as $invoice) {
$zip->addFileFromPath(basename($invoice->pdf_file_path()), TempFile::path($invoice->pdf_file_path()));
//$zip->addFileFromPath(basename($invoice->pdf_file_path()), TempFile::path($invoice->pdf_file_path()));
$zip->addFileFromPath(basename($invoice->pdf_file_path()), $invoice->pdf_file_path());
}
$zip->finish();
Storage::disk(config('filesystems.default'))->put($path.$file_name, $tempStream);
Storage::disk('public')->put($path.$file_name, $tempStream);
fclose($tempStream);
$nmo = new NinjaMailerObject;
$nmo->mailable = new DownloadInvoices(Storage::disk(config('filesystems.default'))->url($path.$file_name), $this->company);
$nmo->mailable = new DownloadInvoices(Storage::disk('public')->url($path.$file_name), $this->company);
$nmo->to_user = $this->user;
$nmo->settings = $this->settings;
$nmo->company = $this->company;
NinjaMailerJob::dispatch($nmo);
UnlinkFile::dispatch(config('filesystems.default'), $path.$file_name)->delay(now()->addHours(1));
UnlinkFile::dispatch('public', $path.$file_name)->delay(now()->addHours(1));
}
}

View File

@ -21,6 +21,7 @@ use App\Libraries\Google\Google;
use App\Libraries\MultiDB;
use App\Mail\TemplateEmail;
use App\Models\ClientContact;
use App\Models\Company;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\SystemLog;
@ -46,30 +47,38 @@ class NinjaMailerJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, MakesHash;
public $tries = 5; //number of retries
public $tries = 3; //number of retries
public $backoff = 5; //seconds to wait until retry
public $backoff = 10; //seconds to wait until retry
public $deleteWhenMissingModels = true;
public $nmo;
public function __construct(NinjaMailerObject $nmo)
public $override;
public $company;
public function __construct(NinjaMailerObject $nmo, bool $override = false)
{
$this->nmo = $nmo;
$this->override = $override;
}
public function handle()
{
/*If we are migrating data we don't want to fire any emails*/
if ($this->nmo->company->is_disabled)
if ($this->nmo->company->is_disabled && !$this->override)
return true;
/*Set the correct database*/
MultiDB::setDb($this->nmo->company->db);
/* Serializing models from other jobs wipes the primary key */
$this->company = Company::where('company_key', $this->nmo->company->company_key)->first();
/* Set the email driver */
$this->setMailDriver();
@ -83,6 +92,10 @@ class NinjaMailerJob implements ShouldQueue
$this->nmo->mailable->replyTo($this->nmo->settings->reply_to_email, $reply_to_name);
}
else {
$this->nmo->mailable->replyTo($this->company->owner()->email, $this->company->owner()->present()->name());
}
if (strlen($this->nmo->settings->bcc_email) > 1) {
nlog('bcc list available');
@ -93,7 +106,7 @@ class NinjaMailerJob implements ShouldQueue
//send email
try {
nlog("trying to send");
nlog("trying to send to {$this->nmo->to_user->email} ". now()->toDateTimeString());
Mail::to($this->nmo->to_user->email)
->send($this->nmo->mailable);
@ -104,10 +117,12 @@ class NinjaMailerJob implements ShouldQueue
} catch (\Exception $e) {
nlog("error failed with {$e->getMessage()}");
// nlog($e);
if($this->nmo->entity)
$this->entityEmailFailed($e->getMessage());
if(Ninja::isHosted())
app('sentry')->captureException($e);
}
}
@ -139,9 +154,9 @@ class NinjaMailerJob implements ShouldQueue
App::forgetInstance('mail.manager'); //singletons must be destroyed!
App::forgetInstance('mailer');
App::forgetInstance('laravelgmail');
$t = app('translator');
/* Inject custom translations if any exist */
Lang::replace(Ninja::transformTranslations($this->nmo->settings));
$t->replace(Ninja::transformTranslations($this->nmo->settings));
switch ($this->nmo->settings->email_sending_method) {
case 'default':
@ -169,7 +184,15 @@ class NinjaMailerJob implements ShouldQueue
nlog("Sending via {$user->name()}");
$google = (new Google())->init();
$google->getClient()->setAccessToken(json_encode($user->oauth_user_token));
try{
$google->getClient()->setAccessToken(json_encode($user->oauth_user_token));
}
catch(\Exception $e) {
$this->logMailError('Gmail Token Invalid', $this->company->clients()->first());
$this->nmo->settings->email_sending_method = 'default';
return $this->setMailDriver();
}
if ($google->getClient()->isAccessTokenExpired()) {
$google->refreshToken($user);
@ -202,7 +225,8 @@ class NinjaMailerJob implements ShouldQueue
SystemLog::CATEGORY_MAIL,
SystemLog::EVENT_MAIL_SEND,
SystemLog::TYPE_FAILURE,
$recipient_object
$recipient_object,
$this->nmo->company
);
}

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