1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-09-28 12:17:09 +02:00

Merge branch 'v5-develop' into v5-stable

This commit is contained in:
David Bomba 2021-06-15 08:21:05 +10:00
commit e91b79a160
52 changed files with 336 additions and 155 deletions

View File

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

View File

@ -1 +1 @@
5.2.1 5.2.2

View File

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

View File

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

View File

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

View File

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

View File

@ -222,14 +222,9 @@ class LoginController extends BaseController
}); });
// $cu->first()->account->companies->each(function ($company) use($cu, $request){ /*On the hosted platform, only owners can login for free/pro accounts*/
if(Ninja::isHosted() && !$cu->first()->is_owner && !$user->account->isEnterpriseClient())
// if($company->tokens()->where('is_system', true)->count() == 0) return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403);
// {
// CreateCompanyToken::dispatchNow($company, $cu->first()->user, $request->server('HTTP_USER_AGENT'));
// }
// });
return $this->timeConstrainedResponse($cu); return $this->timeConstrainedResponse($cu);
@ -318,6 +313,9 @@ class LoginController extends BaseController
if($request->has('current_company') && $request->input('current_company') == 'true') if($request->has('current_company') && $request->input('current_company') == 'true')
$cu->where("company_id", $company_token->company_id); $cu->where("company_id", $company_token->company_id);
if(Ninja::isHosted() && !$cu->first()->is_owner && !$cu->first()->user->account->isEnterpriseClient())
return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403);
return $this->refreshResponse($cu); return $this->refreshResponse($cu);
} }
@ -379,6 +377,9 @@ class LoginController extends BaseController
} }
}); });
if(Ninja::isHosted() && !$cu->first()->is_owner && !$existing_user->account->isEnterpriseClient())
return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403);
return $this->timeConstrainedResponse($cu); return $this->timeConstrainedResponse($cu);
} }
@ -407,6 +408,9 @@ class LoginController extends BaseController
} }
}); });
if(Ninja::isHosted() && !$cu->first()->is_owner && !$existing_login_user->account->isEnterpriseClient())
return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403);
return $this->timeConstrainedResponse($cu); return $this->timeConstrainedResponse($cu);
} }
@ -439,6 +443,9 @@ class LoginController extends BaseController
} }
}); });
if(Ninja::isHosted() && !$cu->first()->is_owner && !$existing_login_user->account->isEnterpriseClient())
return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403);
return $this->timeConstrainedResponse($cu); return $this->timeConstrainedResponse($cu);
} }
@ -478,6 +485,9 @@ class LoginController extends BaseController
} }
}); });
if(Ninja::isHosted() && !$cu->first()->is_owner && !auth()->user()->account->isEnterpriseClient())
return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403);
return $this->timeConstrainedResponse($cu); return $this->timeConstrainedResponse($cu);
} }

View File

@ -164,8 +164,9 @@ class InvoiceController extends Controller
//if only 1 pdf, output to buffer for download //if only 1 pdf, output to buffer for download
if ($invoices->count() == 1) { if ($invoices->count() == 1) {
$invoice = $invoices->first();
$file = $invoices->first()->pdf_file_path(); $invitation = $invoice->invitations->first();
$file = $invoice->pdf_file_path($invitation);
return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);; return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);;
} }

View File

@ -564,7 +564,7 @@ class CreditController extends BaseController
// EmailCredit::dispatch($credit, $credit->company); // EmailCredit::dispatch($credit, $credit->company);
$credit->invitations->load('contact.client.country', 'credit.client.country', 'credit.company')->each(function ($invitation) use ($credit) { $credit->invitations->load('contact.client.country', 'credit.client.country', 'credit.company')->each(function ($invitation) use ($credit) {
EmailEntity::dispatch($invitation, $credit->company, 'credit')->delay(now()->addSeconds(60)); EmailEntity::dispatch($invitation, $credit->company, 'credit');
}); });

View File

@ -132,7 +132,7 @@ class EmailController extends BaseController
$entity_obj->service()->markSent()->save(); $entity_obj->service()->markSent()->save();
EmailEntity::dispatch($invitation->fresh(), $invitation->company, $template, $data) EmailEntity::dispatch($invitation->fresh(), $invitation->company, $template, $data)
->delay(now()->addSeconds(60)); ->delay(now()->addSeconds(30));
} }

View File

@ -49,7 +49,7 @@ class StoreCompanyRequest extends Request
} else { } else {
if(Ninja::isHosted()){ if(Ninja::isHosted()){
$rules['subdomain'] = ['nullable', 'alpha_num', new ValidSubdomain($this->all())]; $rules['subdomain'] = ['nullable', 'regex:/^[a-zA-Z0-9][a-zA-Z0-9.-]+[a-zA-Z0-9]$/', new ValidSubdomain($this->all())];
} }
else else
$rules['subdomain'] = 'nullable|alpha_num'; $rules['subdomain'] = 'nullable|alpha_num';

View File

@ -50,7 +50,7 @@ class UpdateCompanyRequest extends Request
} else { } else {
if(Ninja::isHosted()){ if(Ninja::isHosted()){
$rules['subdomain'] = ['nullable', 'alpha_num', new ValidSubdomain($this->all())]; $rules['subdomain'] = ['nullable', 'regex:/^[a-zA-Z0-9][a-zA-Z0-9.-]+[a-zA-Z0-9]$/', new ValidSubdomain($this->all())];
} }
else else
$rules['subdomain'] = 'nullable|alpha_num'; $rules['subdomain'] = 'nullable|alpha_num';

View File

@ -104,7 +104,10 @@ class CreateAccount
//todo implement SLACK notifications //todo implement SLACK notifications
//$sp035a66->notification(new NewAccountCreated($spaa9f78, $sp035a66))->ninja(); //$sp035a66->notification(new NewAccountCreated($spaa9f78, $sp035a66))->ninja();
VersionCheck::dispatchNow(); if(Ninja::isHosted())
\Modules\Admin\Jobs\Account\NinjaUser::dispatch([], $sp035a66);
VersionCheck::dispatch();
LightLogs::create(new AnalyticsAccountCreated()) LightLogs::create(new AnalyticsAccountCreated())
->increment() ->increment()
@ -118,10 +121,6 @@ class CreateAccount
if(Ninja::isHosted() && Cache::get('currencies')) 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) { $currency = Cache::get('currencies')->filter(function ($item) use ($currency_code) {
return strtolower($item->code) == $currency_code; return strtolower($item->code) == $currency_code;
})->first(); })->first();
@ -146,8 +145,6 @@ class CreateAccount
$settings->language_id = (string)$language->id; $settings->language_id = (string)$language->id;
} }
//$timezone = Timezone::where('name', $data['geoplugin_timezone'])->first();
if($timezone) { if($timezone) {
$settings->timezone_id = (string)$timezone->id; $settings->timezone_id = (string)$timezone->id;
} }

View File

@ -480,7 +480,11 @@ class CompanyExport implements ShouldQueue
$file_name = date('Y-m-d').'_'.str_replace(' ', '_', $this->company->present()->name() . '_' . $this->company->company_key .'.zip'); $file_name = date('Y-m-d').'_'.str_replace(' ', '_', $this->company->present()->name() . '_' . $this->company->company_key .'.zip');
Storage::makeDirectory(public_path('storage/backups/'), 0775); $path = public_path('storage/backups/');
if(!Storage::exists($path))
Storage::makeDirectory($path, 0775);
$zip_path = public_path('storage/backups/'.$file_name); $zip_path = public_path('storage/backups/'.$file_name);
$zip = new \ZipArchive(); $zip = new \ZipArchive();

View File

@ -219,7 +219,7 @@ class CompanyImport implements ShouldQueue
if(count($backup_users) > 1){ if(count($backup_users) > 1){
// $this->message = 'Only one user can be in the import for a Free Account'; // $this->message = 'Only one user can be in the import for a Free Account';
// $this->pre_flight_checks_pass = false; // $this->pre_flight_checks_pass = false;
$this->force_user_coalesce = true; //$this->force_user_coalesce = true;
} }
nlog("backup users email = " . $backup_users[0]->email); nlog("backup users email = " . $backup_users[0]->email);
@ -227,7 +227,7 @@ class CompanyImport implements ShouldQueue
if(count($backup_users) == 1 && $this->company_owner->email != $backup_users[0]->email) { if(count($backup_users) == 1 && $this->company_owner->email != $backup_users[0]->email) {
// $this->message = 'Account emails do not match. Account owner email must match backup user email'; // $this->message = 'Account emails do not match. Account owner email must match backup user email';
// $this->pre_flight_checks_pass = false; // $this->pre_flight_checks_pass = false;
$this->force_user_coalesce = true; // $this->force_user_coalesce = true;
} }
$backup_users_emails = array_column($backup_users, 'email'); $backup_users_emails = array_column($backup_users, 'email');
@ -243,7 +243,7 @@ class CompanyImport implements ShouldQueue
if($this->account->plan == 'pro'){ if($this->account->plan == 'pro'){
// $this->message = 'Pro plan is limited to one user, you have multiple users in the backup file'; // $this->message = 'Pro plan is limited to one user, you have multiple users in the backup file';
// $this->pre_flight_checks_pass = false; // $this->pre_flight_checks_pass = false;
$this->force_user_coalesce = true; // $this->force_user_coalesce = true;
} }
if($this->account->plan == 'enterprise'){ if($this->account->plan == 'enterprise'){

View File

@ -102,12 +102,11 @@ class CreateEntityPdf implements ShouldQueue
/* Set the locale*/ /* Set the locale*/
App::setLocale($this->contact->preferredLocale()); App::setLocale($this->contact->preferredLocale());
// nlog($this->entity->client->getMergedSettings());
/* Set customized translations _NOW_ */ /* Set customized translations _NOW_ */
$t->replace(Ninja::transformTranslations($this->entity->client->getMergedSettings())); $t->replace(Ninja::transformTranslations($this->entity->client->getMergedSettings()));
$this->entity->service()->deletePdf(); /*This line of code hurts... it deletes ALL $entity PDFs... this causes a race condition when trying to send an email*/
// $this->entity->service()->deletePdf();
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
return (new Phantom)->generate($this->invitation); return (new Phantom)->generate($this->invitation);
@ -116,16 +115,16 @@ class CreateEntityPdf implements ShouldQueue
$entity_design_id = ''; $entity_design_id = '';
if ($this->entity instanceof Invoice) { if ($this->entity instanceof Invoice) {
$path = $this->entity->client->invoice_filepath(); $path = $this->entity->client->invoice_filepath($this->invitation);
$entity_design_id = 'invoice_design_id'; $entity_design_id = 'invoice_design_id';
} elseif ($this->entity instanceof Quote) { } elseif ($this->entity instanceof Quote) {
$path = $this->entity->client->quote_filepath(); $path = $this->entity->client->quote_filepath($this->invitation);
$entity_design_id = 'quote_design_id'; $entity_design_id = 'quote_design_id';
} elseif ($this->entity instanceof Credit) { } elseif ($this->entity instanceof Credit) {
$path = $this->entity->client->credit_filepath(); $path = $this->entity->client->credit_filepath($this->invitation);
$entity_design_id = 'credit_design_id'; $entity_design_id = 'credit_design_id';
} elseif ($this->entity instanceof RecurringInvoice) { } elseif ($this->entity instanceof RecurringInvoice) {
$path = $this->entity->client->recurring_invoice_filepath(); $path = $this->entity->client->recurring_invoice_filepath($this->invitation);
$entity_design_id = 'invoice_design_id'; $entity_design_id = 'invoice_design_id';
} }
@ -194,7 +193,12 @@ class CreateEntityPdf implements ShouldQueue
if ($pdf) { if ($pdf) {
try{ try{
if(!Storage::disk($this->disk)->exists($path))
Storage::disk($this->disk)->makeDirectory($path, 0775);
nlog($file_path);
Storage::disk($this->disk)->put($file_path, $pdf); Storage::disk($this->disk)->put($file_path, $pdf);
} }

View File

@ -78,13 +78,16 @@ class ZipInvoices implements ShouldQueue
// create a new zipstream object // create a new zipstream object
$file_name = date('Y-m-d').'_'.str_replace(' ', '_', trans('texts.invoices')).'.zip'; $file_name = date('Y-m-d').'_'.str_replace(' ', '_', trans('texts.invoices')).'.zip';
$path = $this->invoices->first()->client->invoice_filepath(); $invoice = $this->invoices->first();
$invitation = $invoice->invitations->first();
$path = $invoice->client->invoice_filepath($invitation);
$zip = new ZipStream($file_name, $options); $zip = new ZipStream($file_name, $options);
foreach ($this->invoices as $invoice) { 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->addFileFromPath(basename($invoice->pdf_file_path($invitation)), $invoice->pdf_file_path());
} }
$zip->finish(); $zip->finish();

View File

@ -213,7 +213,7 @@ class SendReminders implements ShouldQueue
if ($this->checkSendSetting($invoice, $template) && $invoice->company->account->hasFeature(Account::FEATURE_EMAIL_TEMPLATES_REMINDERS)) { if ($this->checkSendSetting($invoice, $template) && $invoice->company->account->hasFeature(Account::FEATURE_EMAIL_TEMPLATES_REMINDERS)) {
nlog("firing email"); nlog("firing email");
EmailEntity::dispatchNow($invitation, $invitation->company, $template)->delay(now()->addSeconds(60)); EmailEntity::dispatchNow($invitation, $invitation->company, $template);
} }
}); });

View File

@ -96,7 +96,7 @@ class SendRecurring implements ShouldQueue
if ($invitation->contact && strlen($invitation->contact->email) >=1) { if ($invitation->contact && strlen($invitation->contact->email) >=1) {
try{ try{
EmailEntity::dispatch($invitation, $invoice->company)->delay(now()->addSeconds(60)); EmailEntity::dispatch($invitation, $invoice->company);
} }
catch(\Exception $e) { catch(\Exception $e) {
nlog($e->getMessage()); nlog($e->getMessage());

View File

@ -53,14 +53,18 @@ class ReminderJob implements ShouldQueue
private function processReminders() private function processReminders()
{ {
Invoice::whereDate('next_send_date', '<=', now())->with('invitations')->cursor()->each(function ($invoice) { Invoice::whereDate('next_send_date', '<=', now())
->where('is_deleted', 0)
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
->where('balance', '>', 0)
->with('invitations')->cursor()->each(function ($invoice) {
if ($invoice->isPayable()) { if ($invoice->isPayable()) {
$reminder_template = $invoice->calculateTemplate('invoice'); $reminder_template = $invoice->calculateTemplate('invoice');
$invoice->service()->touchReminder($reminder_template)->save(); $invoice->service()->touchReminder($reminder_template)->save();
$invoice->invitations->each(function ($invitation) use ($invoice, $reminder_template) { $invoice->invitations->each(function ($invitation) use ($invoice, $reminder_template) {
EmailEntity::dispatch($invitation, $invitation->company, $reminder_template)->delay(now()->addSeconds(60)); EmailEntity::dispatch($invitation, $invitation->company, $reminder_template);
nlog("Firing reminder email for invoice {$invoice->number}"); nlog("Firing reminder email for invoice {$invoice->number}");
}); });

View File

@ -64,7 +64,7 @@ class SendFailedEmails implements ShouldQueue
if ($invitation->invoice) { if ($invitation->invoice) {
if ($invitation->contact->send_email && $invitation->contact->email) { if ($invitation->contact->send_email && $invitation->contact->email) {
EmailEntity::dispatch($invitation, $invitation->company, $job_meta_array['reminder_template'])->delay(now()->addSeconds(60)); EmailEntity::dispatch($invitation, $invitation->company, $job_meta_array['reminder_template']);
} }
} }
}); });

View File

@ -101,9 +101,9 @@ class CreditEmailEngine extends BaseEmailEngine
if ($this->client->getSetting('pdf_email_attachment') !== false && $this->credit->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) { if ($this->client->getSetting('pdf_email_attachment') !== false && $this->credit->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) {
if(Ninja::isHosted()) if(Ninja::isHosted())
$this->setAttachments([$this->credit->pdf_file_path(null, 'url', true)]); $this->setAttachments([$this->credit->pdf_file_path($this->invitation, 'url', true)]);
else else
$this->setAttachments([$this->credit->pdf_file_path()]); $this->setAttachments([$this->credit->pdf_file_path($this->invitation)]);
} }

View File

@ -112,9 +112,9 @@ class InvoiceEmailEngine extends BaseEmailEngine
if ($this->client->getSetting('pdf_email_attachment') !== false && $this->invoice->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) { if ($this->client->getSetting('pdf_email_attachment') !== false && $this->invoice->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) {
if(Ninja::isHosted()) if(Ninja::isHosted())
$this->setAttachments([$this->invoice->pdf_file_path(null, 'url', true)]); $this->setAttachments([$this->invoice->pdf_file_path($this->invitation, 'url', true)]);
else else
$this->setAttachments([$this->invoice->pdf_file_path()]); $this->setAttachments([$this->invoice->pdf_file_path($this->invitation)]);
// $this->setAttachments(['path' => $this->invoice->pdf_file_path(), 'name' => basename($this->invoice->pdf_file_path())]); // $this->setAttachments(['path' => $this->invoice->pdf_file_path(), 'name' => basename($this->invoice->pdf_file_path())]);

View File

@ -77,7 +77,7 @@ class PaymentEmailEngine extends BaseEmailEngine
$this->payment->invoices->each(function ($invoice){ $this->payment->invoices->each(function ($invoice){
$this->setAttachments([$invoice->pdf_file_path()]); $this->setAttachments([$invoice->pdf_file_path($invoice->invitations->first())]);
}); });

View File

@ -103,9 +103,9 @@ class QuoteEmailEngine extends BaseEmailEngine
if ($this->client->getSetting('pdf_email_attachment') !== false && $this->quote->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) { if ($this->client->getSetting('pdf_email_attachment') !== false && $this->quote->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) {
if(Ninja::isHosted()) if(Ninja::isHosted())
$this->setAttachments([$this->quote->pdf_file_path(null, 'url', true)]); $this->setAttachments([$this->quote->pdf_file_path($this->invitation, 'url', true)]);
else else
$this->setAttachments([$this->quote->pdf_file_path()]); $this->setAttachments([$this->quote->pdf_file_path($this->invitation)]);
} }

View File

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

View File

@ -638,24 +638,28 @@ class Client extends BaseModel implements HasLocalePreference
})->first()->locale; })->first()->locale;
} }
public function invoice_filepath() public function invoice_filepath($invitation)
{ {
return $this->company->company_key.'/'.$this->client_hash.'/invoices/'; $contact_key = $invitation->contact->contact_key;
return $this->company->company_key.'/'.$this->client_hash.'/'.$contact_key.'/invoices/';
} }
public function quote_filepath() public function quote_filepath($invitation)
{ {
return $this->company->company_key.'/'.$this->client_hash.'/quotes/'; $contact_key = $invitation->contact->contact_key;
return $this->company->company_key.'/'.$this->client_hash.'/'.$contact_key.'/quotes/';
} }
public function credit_filepath() public function credit_filepath($invitation)
{ {
return $this->company->company_key.'/'.$this->client_hash.'/credits/'; $contact_key = $invitation->contact->contact_key;
return $this->company->company_key.'/'.$this->client_hash.'/'.$contact_key.'/credits/';
} }
public function recurring_invoice_filepath() public function recurring_invoice_filepath($invitation)
{ {
return $this->company->company_key.'/'.$this->client_hash.'/recurring_invoices/'; $contact_key = $invitation->contact->contact_key;
return $this->company->company_key.'/'.$this->client_hash.'/'.$contact_key.'/recurring_invoices/';
} }
public function company_filepath() public function company_filepath()

View File

@ -267,7 +267,7 @@ class Credit extends BaseModel
if(!$invitation) if(!$invitation)
throw new \Exception('Hard fail, could not create an invitation - is there a valid contact?'); throw new \Exception('Hard fail, could not create an invitation - is there a valid contact?');
$file_path = $this->client->credit_filepath().$this->numberFormatter().'.pdf'; $file_path = $this->client->credit_filepath($invitation).$this->numberFormatter().'.pdf';
if(Ninja::isHosted() && $portal && Storage::disk(config('filesystems.default'))->exists($file_path)){ if(Ninja::isHosted() && $portal && Storage::disk(config('filesystems.default'))->exists($file_path)){
return Storage::disk(config('filesystems.default'))->{$type}($file_path); return Storage::disk(config('filesystems.default'))->{$type}($file_path);

View File

@ -126,9 +126,9 @@ class CreditInvitation extends BaseModel
public function pdf_file_path() public function pdf_file_path()
{ {
$storage_path = Storage::url($this->credit->client->quote_filepath().$this->credit->numberFormatter().'.pdf'); $storage_path = Storage::url($this->credit->client->quote_filepath($this).$this->credit->numberFormatter().'.pdf');
if (! Storage::exists($this->credit->client->credit_filepath().$this->credit->numberFormatter().'.pdf')) { if (! Storage::exists($this->credit->client->credit_filepath($this).$this->credit->numberFormatter().'.pdf')) {
event(new CreditWasUpdated($this, $this->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); event(new CreditWasUpdated($this, $this->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
CreateEntityPdf::dispatchNow($this); CreateEntityPdf::dispatchNow($this);
} }

View File

@ -409,13 +409,13 @@ class Invoice extends BaseModel
if(!$invitation) if(!$invitation)
throw new \Exception('Hard fail, could not create an invitation - is there a valid contact?'); throw new \Exception('Hard fail, could not create an invitation - is there a valid contact?');
$file_path = $this->client->invoice_filepath().$this->numberFormatter().'.pdf'; $file_path = $this->client->invoice_filepath($invitation).$this->numberFormatter().'.pdf';
if(Ninja::isHosted() && $portal && Storage::disk(config('filesystems.default'))->exists($file_path)){ if(Ninja::isHosted() && $portal && Storage::disk(config('filesystems.default'))->exists($file_path)){
return Storage::disk(config('filesystems.default'))->{$type}($file_path); return Storage::disk(config('filesystems.default'))->{$type}($file_path);
} }
elseif(Ninja::isHosted() && $portal){ elseif(Ninja::isHosted() && $portal){
$file_path = CreateEntityPdf::dispatchNow($invitation,config('filesystems.default')); $file_path = CreateEntityPdf::dispatchNow($invitation, config('filesystems.default'));
return Storage::disk(config('filesystems.default'))->{$type}($file_path); return Storage::disk(config('filesystems.default'))->{$type}($file_path);
} }

View File

@ -142,7 +142,7 @@ class InvoiceInvitation extends BaseModel
{ {
$storage_path = Storage::url($this->invoice->client->invoice_filepath().$this->invoice->numberFormatter().'.pdf'); $storage_path = Storage::url($this->invoice->client->invoice_filepath().$this->invoice->numberFormatter().'.pdf');
if (! Storage::exists($this->invoice->client->invoice_filepath().$this->invoice->numberFormatter().'.pdf')) { if (! Storage::exists($this->invoice->client->invoice_filepath($this).$this->invoice->numberFormatter().'.pdf')) {
event(new InvoiceWasUpdated($this->invoice, $this->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); event(new InvoiceWasUpdated($this->invoice, $this->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
CreateEntityPdf::dispatchNow($this); CreateEntityPdf::dispatchNow($this);
} }

View File

@ -219,7 +219,7 @@ class Quote extends BaseModel
if(!$invitation) if(!$invitation)
throw new \Exception('Hard fail, could not create an invitation - is there a valid contact?'); throw new \Exception('Hard fail, could not create an invitation - is there a valid contact?');
$file_path = $this->client->quote_filepath().$this->numberFormatter().'.pdf'; $file_path = $this->client->quote_filepath($invitation).$this->numberFormatter().'.pdf';
if(Ninja::isHosted() && $portal && Storage::disk(config('filesystems.default'))->exists($file_path)){ if(Ninja::isHosted() && $portal && Storage::disk(config('filesystems.default'))->exists($file_path)){
return Storage::disk(config('filesystems.default'))->{$type}($file_path); return Storage::disk(config('filesystems.default'))->{$type}($file_path);

View File

@ -130,9 +130,9 @@ class QuoteInvitation extends BaseModel
public function pdf_file_path() public function pdf_file_path()
{ {
$storage_path = Storage::url($this->quote->client->quote_filepath().$this->quote->numberFormatter().'.pdf'); $storage_path = Storage::url($this->quote->client->quote_filepath($this).$this->quote->numberFormatter().'.pdf');
if (! Storage::exists($this->quote->client->quote_filepath().$this->quote->numberFormatter().'.pdf')) { if (! Storage::exists($this->quote->client->quote_filepath($this).$this->quote->numberFormatter().'.pdf')) {
event(new QuoteWasUpdated($this->quote, $this->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); event(new QuoteWasUpdated($this->quote, $this->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
CreateEntityPdf::dispatchNow($this); CreateEntityPdf::dispatchNow($this);
} }

View File

@ -51,11 +51,6 @@ class InvoiceObserver
if ($subscriptions) { if ($subscriptions) {
WebhookHandler::dispatch(Webhook::EVENT_UPDATE_INVOICE, $invoice, $invoice->company); WebhookHandler::dispatch(Webhook::EVENT_UPDATE_INVOICE, $invoice, $invoice->company);
} }
// if($invoice->isDirty('date') || $invoice->isDirty('due_date'))
// $invoice->service()->setReminder()->save();
// UnlinkFile::dispatchNow(config('filesystems.default'), $invoice->client->invoice_filepath() . $invoice->numberFormatter().'.pdf');
} }

View File

@ -58,7 +58,7 @@ class UpdatePaymentMethods
// } // }
private function updateMethods(Customer $customer, Client $client) public function updateMethods(Customer $customer, Client $client)
{ {
$card_methods = PaymentMethod::all([ $card_methods = PaymentMethod::all([
'customer' => $customer->id, 'customer' => $customer->id,
@ -145,7 +145,7 @@ class UpdatePaymentMethods
} }
private function buildPaymentMethodMeta(PaymentMethod $method, GatewayType $type_id) private function buildPaymentMethodMeta(PaymentMethod $method, $type_id)
{ {
switch ($type_id) { switch ($type_id) {

View File

@ -24,7 +24,7 @@ class MailServiceProvider extends MailProvider
protected function registerIlluminateMailer() protected function registerIlluminateMailer()
{ {
$this->app->singleton('mail.manager', function($app) { $this->app->singleton('mail.manager', function($app) {
return new GmailTransportManager($app); return new GmailTransportManager($app);
}); });
// $this->app->bind('mail.manager', function($app) { // $this->app->bind('mail.manager', function($app) {

View File

@ -140,7 +140,11 @@ class CreditService
public function deletePdf() public function deletePdf()
{ {
UnlinkFile::dispatchNow(config('filesystems.default'), $this->credit->client->credit_filepath() . $this->credit->numberFormatter().'.pdf'); $this->credit->invitations->each(function ($invitation){
UnlinkFile::dispatchNow(config('filesystems.default'), $this->credit->client->credit_filepath($invitation) . $this->credit->numberFormatter().'.pdf');
});
return $this; return $this;
} }

View File

@ -37,7 +37,7 @@ class GetCreditPdf extends AbstractService
$this->contact = $this->credit->client->primary_contact()->first(); $this->contact = $this->credit->client->primary_contact()->first();
} }
$path = $this->credit->client->credit_filepath(); $path = $this->credit->client->credit_filepath($this->invitation);
$file_path = $path.$this->credit->numberFormatter().'.pdf'; $file_path = $path.$this->credit->numberFormatter().'.pdf';

View File

@ -60,14 +60,15 @@ class GenerateDeliveryNote
? $this->invoice->design_id ? $this->invoice->design_id
: $this->decodePrimaryKey($this->invoice->client->getSetting('invoice_design_id')); : $this->decodePrimaryKey($this->invoice->client->getSetting('invoice_design_id'));
$file_path = sprintf('%s%s_delivery_note.pdf', $this->invoice->client->invoice_filepath(), $this->invoice->number); $invitation = $this->invoice->invitations->first();
$file_path = sprintf('%s%s_delivery_note.pdf', $this->invoice->client->invoice_filepath($invitation), $this->invoice->number);
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
return (new Phantom)->generate($this->invoice->invitations->first()); return (new Phantom)->generate($this->invoice->invitations->first());
} }
$design = Design::find($design_id); $design = Design::find($design_id);
$html = new HtmlEngine($this->invoice->invitations->first()); $html = new HtmlEngine($invitation);
if ($design->is_custom) { if ($design->is_custom) {
$options = ['custom_partials' => json_decode(json_encode($design->design), true)]; $options = ['custom_partials' => json_decode(json_encode($design->design), true)];
@ -105,6 +106,9 @@ class GenerateDeliveryNote
info($maker->getCompiledHTML()); info($maker->getCompiledHTML());
} }
if(!Storage::disk($this->disk)->exists($this->invoice->client->invoice_filepath($invitation)))
Storage::disk($this->disk)->makeDirectory($this->invoice->client->invoice_filepath($invitation), 0775);
Storage::disk($this->disk)->put($file_path, $pdf); Storage::disk($this->disk)->put($file_path, $pdf);
return Storage::disk($this->disk)->path($file_path); return Storage::disk($this->disk)->path($file_path);

View File

@ -35,7 +35,7 @@ class GetInvoicePdf extends AbstractService
$invitation = $this->invoice->invitations->where('client_contact_id', $this->contact->id)->first(); $invitation = $this->invoice->invitations->where('client_contact_id', $this->contact->id)->first();
$path = $this->invoice->client->invoice_filepath(); $path = $this->invoice->client->invoice_filepath($invitation);
$file_path = $path.$this->invoice->numberFormatter().'.pdf'; $file_path = $path.$this->invoice->numberFormatter().'.pdf';

View File

@ -307,12 +307,15 @@ class InvoiceService
public function deletePdf() public function deletePdf()
{ {
//UnlinkFile::dispatchNow(config('filesystems.default'), $this->invoice->client->invoice_filepath() . $this->invoice->numberFormatter().'.pdf'); $this->invoice->invitations->each(function ($invitation){
Storage::disk(config('filesystems.default'))->delete($this->invoice->client->invoice_filepath() . $this->invoice->numberFormatter().'.pdf');
Storage::disk(config('filesystems.default'))->delete($this->invoice->client->invoice_filepath($invitation) . $this->invoice->numberFormatter().'.pdf');
if(Ninja::isHosted()) {
Storage::disk('public')->delete($this->invoice->client->invoice_filepath() . $this->invoice->numberFormatter().'.pdf'); if(Ninja::isHosted()) {
} Storage::disk('public')->delete($this->invoice->client->invoice_filepath($invitation) . $this->invoice->numberFormatter().'.pdf');
}
});
return $this; return $this;
} }
@ -351,8 +354,17 @@ class InvoiceService
* PDF when it is updated etc. * PDF when it is updated etc.
* @return InvoiceService * @return InvoiceService
*/ */
public function touchPdf() public function touchPdf($force = false)
{ {
if($force){
$this->invoice->invitations->each(function ($invitation) {
CreateEntityPdf::dispatchNow($invitation);
});
return $this;
}
$this->invoice->invitations->each(function ($invitation) { $this->invoice->invitations->each(function ($invitation) {
CreateEntityPdf::dispatch($invitation); CreateEntityPdf::dispatch($invitation);
}); });
@ -380,7 +392,8 @@ class InvoiceService
$this->invoice->reminder_last_sent = now()->format('Y-m-d'); $this->invoice->reminder_last_sent = now()->format('Y-m-d');
break; break;
default: default:
// code... $this->invoice->reminder1_sent = now()->format('Y-m-d');
$this->invoice->reminder_last_sent = now()->format('Y-m-d');
break; break;
} }

View File

@ -62,7 +62,7 @@ class TriggeredActions extends AbstractService
$reminder_template = 'payment'; $reminder_template = 'payment';
$this->invoice->invitations->load('contact.client.country', 'invoice.client.country', 'invoice.company')->each(function ($invitation) use ($reminder_template) { $this->invoice->invitations->load('contact.client.country', 'invoice.client.country', 'invoice.company')->each(function ($invitation) use ($reminder_template) {
EmailEntity::dispatch($invitation, $this->invoice->company, $reminder_template)->delay(now()->addSeconds(60)); EmailEntity::dispatch($invitation, $this->invoice->company, $reminder_template);
}); });
if ($this->invoice->invitations->count() > 0) { if ($this->invoice->invitations->count() > 0) {

View File

@ -126,8 +126,11 @@ class UpdateReminder extends AbstractService
$date_collection->push($reminder_date); $date_collection->push($reminder_date);
} }
$this->invoice->next_send_date = $date_collection->sort()->first(); if($date_collection->count() >=1)
$this->invoice->next_send_date = $date_collection->sort()->first();
else
$this->invoice->next_send_date = null;
return $this->invoice; return $this->invoice;
} }
} }

View File

@ -35,7 +35,7 @@ class GetQuotePdf extends AbstractService
$invitation = $this->quote->invitations->where('client_contact_id', $this->contact->id)->first(); $invitation = $this->quote->invitations->where('client_contact_id', $this->contact->id)->first();
$path = $this->quote->client->quote_filepath(); $path = $this->quote->client->quote_filepath($invitation);
$file_path = $path.$this->quote->numberFormatter().'.pdf'; $file_path = $path.$this->quote->numberFormatter().'.pdf';

View File

@ -178,7 +178,11 @@ class QuoteService
public function deletePdf() public function deletePdf()
{ {
UnlinkFile::dispatchNow(config('filesystems.default'), $this->quote->client->quote_filepath() . $this->quote->numberFormatter().'.pdf'); $this->quote->invitations->each(function ($invitation){
UnlinkFile::dispatchNow(config('filesystems.default'), $this->quote->client->quote_filepath($invitation) . $this->quote->numberFormatter().'.pdf');
});
return $this; return $this;
} }

View File

@ -37,7 +37,7 @@ class GetInvoicePdf extends AbstractService
$invitation = $this->entity->invitations->where('client_contact_id', $this->contact->id)->first(); $invitation = $this->entity->invitations->where('client_contact_id', $this->contact->id)->first();
$path = $this->entity->client->recurring_invoice_filepath(); $path = $this->entity->client->recurring_invoice_filepath($invitation);
$file_path = $path.$this->entity->hashed_id.'.pdf'; $file_path = $path.$this->entity->hashed_id.'.pdf';

View File

@ -87,7 +87,13 @@ class RecurringService
public function deletePdf() public function deletePdf()
{ {
UnlinkFile::dispatchNow(config('filesystems.default'), $this->recurring_entity->client->recurring_invoice_filepath() . $this->recurring_entity->numberFormatter().'.pdf');
$this->recurring_entity->invitations->each(function ($invitation){
UnlinkFile::dispatchNow(config('filesystems.default'), $this->recurring_entity->client->recurring_invoice_filepath($invitation) . $this->recurring_entity->numberFormatter().'.pdf');
});
return $this; return $this;
} }

View File

@ -181,6 +181,7 @@ class HtmlEngine
$data['$amount_due'] = ['value' => &$data['$total']['value'], 'label' => ctrans('texts.amount_due')]; $data['$amount_due'] = ['value' => &$data['$total']['value'], 'label' => ctrans('texts.amount_due')];
$data['$quote.total'] = &$data['$total']; $data['$quote.total'] = &$data['$total'];
$data['$invoice.total'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: '&nbsp;', 'label' => ctrans('texts.invoice_total')]; $data['$invoice.total'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: '&nbsp;', 'label' => ctrans('texts.invoice_total')];
$data['$invoice_total_raw'] = ['value' => $this->entity_calc->getTotal(), 'label' => ctrans('texts.invoice_total')];
$data['$invoice.amount'] = &$data['$total']; $data['$invoice.amount'] = &$data['$total'];
$data['$quote.amount'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: '&nbsp;', 'label' => ctrans('texts.quote_total')]; $data['$quote.amount'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: '&nbsp;', 'label' => ctrans('texts.quote_total')];
$data['$credit.total'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: '&nbsp;', 'label' => ctrans('texts.credit_total')]; $data['$credit.total'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: '&nbsp;', 'label' => ctrans('texts.credit_total')];

View File

@ -62,19 +62,19 @@ class Phantom
$entity_obj = $invitation->{$entity}; $entity_obj = $invitation->{$entity};
if ($entity == 'invoice') { if ($entity == 'invoice') {
$path = $entity_obj->client->invoice_filepath(); $path = $entity_obj->client->invoice_filepath($invitation);
} }
if ($entity == 'quote') { if ($entity == 'quote') {
$path = $entity_obj->client->quote_filepath(); $path = $entity_obj->client->quote_filepath($invitation);
} }
if ($entity == 'credit') { if ($entity == 'credit') {
$path = $entity_obj->client->credit_filepath(); $path = $entity_obj->client->credit_filepath($invitation);
} }
if ($entity == 'recurring_invoice') { if ($entity == 'recurring_invoice') {
$path = $entity_obj->client->recurring_invoice_filepath(); $path = $entity_obj->client->recurring_invoice_filepath($invitation);
} }
$file_path = $path.$entity_obj->numberFormatter().'.pdf'; $file_path = $path.$entity_obj->numberFormatter().'.pdf';
@ -90,6 +90,9 @@ class Phantom
$this->checkMime($pdf, $invitation, $entity); $this->checkMime($pdf, $invitation, $entity);
if(!Storage::disk(config('filesystems.default'))->exists($path))
Storage::disk(config('filesystems.default'))->makeDirectory($path, 0775);
$instance = Storage::disk(config('filesystems.default'))->put($file_path, $pdf); $instance = Storage::disk(config('filesystems.default'))->put($file_path, $pdf);
return $file_path; return $file_path;
@ -118,8 +121,6 @@ class Phantom
$finfo = new \finfo(FILEINFO_MIME); $finfo = new \finfo(FILEINFO_MIME);
nlog($pdf);
if($finfo->buffer($pdf) != 'application/pdf; charset=binary') if($finfo->buffer($pdf) != 'application/pdf; charset=binary')
{ {
SystemLogger::dispatch( SystemLogger::dispatch(

View File

@ -14,8 +14,8 @@ return [
'require_https' => env('REQUIRE_HTTPS', true), 'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => '5.2.1', 'app_version' => '5.2.2',
'app_tag' => '5.2.1-release', 'app_tag' => '5.2.2-release',
'minimum_client_version' => '5.0.16', 'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1', 'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', ''), 'api_secret' => env('API_SECRET', ''),

View File

@ -194,6 +194,8 @@ class MultiDBUserTest extends TestCase
], ],
]; ];
$response = false;
try { try {
$response = $this->withHeaders([ $response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'), 'X-API-SECRET' => config('ninja.api_secret'),
@ -203,7 +205,7 @@ class MultiDBUserTest extends TestCase
} catch (ValidationException $e) { } catch (ValidationException $e) {
$message = json_decode($e->validator->getMessageBag(), 1); $message = json_decode($e->validator->getMessageBag(), 1);
$this->assertNotNull($message); $this->assertNotNull($message);
nlog($message);
} }
if ($response) { if ($response) {

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 Tests\Unit;
use App\DataMapper\ClientSettings;
use Tests\TestCase;
/**
* @test
*/
class S3CleanupTest extends TestCase
{
public function setUp() :void
{
parent::setUp();
}
public function testMergeCollections()
{
$c1 = collect(["1","2","3","4"]);
$c2 = collect(["5","6","7","8"]);
$c3 = collect(["1","2","10"]);
$merged = $c1->merge($c2)->toArray();
$this->assertTrue(in_array("1", $merged));
$this->assertFalse(in_array("10", $merged));
}
}