diff --git a/LICENSE b/LICENSE index e92123e414..8427bcbcea 100644 --- a/LICENSE +++ b/LICENSE @@ -45,3 +45,5 @@ AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +For more information regarding the interpretation of this license please see here: https://invoiceninja.github.io/docs/legal/license/ \ No newline at end of file diff --git a/VERSION.txt b/VERSION.txt index 804440660c..fb467b1573 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.2.1 \ No newline at end of file +5.2.2 \ No newline at end of file diff --git a/app/Console/Commands/HostedUsers.php b/app/Console/Commands/HostedUsers.php new file mode 100644 index 0000000000..621102382e --- /dev/null +++ b/app/Console/Commands/HostedUsers.php @@ -0,0 +1,61 @@ +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); + + }); + + } + +} diff --git a/app/Console/Commands/S3Cleanup.php b/app/Console/Commands/S3Cleanup.php new file mode 100644 index 0000000000..f7409764b3 --- /dev/null +++ b/app/Console/Commands/S3Cleanup.php @@ -0,0 +1,71 @@ +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"; + } +} diff --git a/app/Helpers/Mail/GmailTransport.php b/app/Helpers/Mail/GmailTransport.php index 648bb043fd..9cc4147908 100644 --- a/app/Helpers/Mail/GmailTransport.php +++ b/app/Helpers/Mail/GmailTransport.php @@ -74,14 +74,12 @@ class GmailTransport extends Transport } - } $this->gmail->send(); $this->sendPerformed($message); - return $this->numberOfRecipients($message); } } diff --git a/app/Helpers/Mail/GmailTransportConfig.php b/app/Helpers/Mail/GmailTransportConfig.php deleted file mode 100644 index 68a5a52527..0000000000 --- a/app/Helpers/Mail/GmailTransportConfig.php +++ /dev/null @@ -1,47 +0,0 @@ - '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')); - } -} diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 293a0eb665..97abc500b8 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -222,14 +222,9 @@ class LoginController extends BaseController }); - // $cu->first()->account->companies->each(function ($company) use($cu, $request){ - - // if($company->tokens()->where('is_system', true)->count() == 0) - // { - // CreateCompanyToken::dispatchNow($company, $cu->first()->user, $request->server('HTTP_USER_AGENT')); - // } - - // }); + /*On the hosted platform, only owners can login for free/pro accounts*/ + if(Ninja::isHosted() && !$cu->first()->is_owner && !$user->account->isEnterpriseClient()) + return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403); return $this->timeConstrainedResponse($cu); @@ -318,6 +313,9 @@ class LoginController extends BaseController if($request->has('current_company') && $request->input('current_company') == 'true') $cu->where("company_id", $company_token->company_id); + if(Ninja::isHosted() && !$cu->first()->is_owner && !$cu->first()->user->account->isEnterpriseClient()) + return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403); + return $this->refreshResponse($cu); } @@ -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); } @@ -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); } @@ -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); } @@ -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); } diff --git a/app/Http/Controllers/ClientPortal/InvoiceController.php b/app/Http/Controllers/ClientPortal/InvoiceController.php index 0472578597..0fec89cc29 100644 --- a/app/Http/Controllers/ClientPortal/InvoiceController.php +++ b/app/Http/Controllers/ClientPortal/InvoiceController.php @@ -164,8 +164,9 @@ class InvoiceController extends Controller //if only 1 pdf, output to buffer for download if ($invoices->count() == 1) { - - $file = $invoices->first()->pdf_file_path(); + $invoice = $invoices->first(); + $invitation = $invoice->invitations->first(); + $file = $invoice->pdf_file_path($invitation); return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);; } diff --git a/app/Http/Controllers/CreditController.php b/app/Http/Controllers/CreditController.php index 968298e8a5..9feb62c7e4 100644 --- a/app/Http/Controllers/CreditController.php +++ b/app/Http/Controllers/CreditController.php @@ -564,7 +564,7 @@ class CreditController extends BaseController // EmailCredit::dispatch($credit, $credit->company); $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'); }); diff --git a/app/Http/Controllers/EmailController.php b/app/Http/Controllers/EmailController.php index 2689e3aadd..dd884a9f83 100644 --- a/app/Http/Controllers/EmailController.php +++ b/app/Http/Controllers/EmailController.php @@ -132,7 +132,7 @@ class EmailController extends BaseController $entity_obj->service()->markSent()->save(); EmailEntity::dispatch($invitation->fresh(), $invitation->company, $template, $data) - ->delay(now()->addSeconds(60)); + ->delay(now()->addSeconds(30)); } diff --git a/app/Http/Requests/Company/StoreCompanyRequest.php b/app/Http/Requests/Company/StoreCompanyRequest.php index 12b3c9a26e..3fb397608d 100644 --- a/app/Http/Requests/Company/StoreCompanyRequest.php +++ b/app/Http/Requests/Company/StoreCompanyRequest.php @@ -49,7 +49,7 @@ class StoreCompanyRequest extends Request } else { 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 $rules['subdomain'] = 'nullable|alpha_num'; diff --git a/app/Http/Requests/Company/UpdateCompanyRequest.php b/app/Http/Requests/Company/UpdateCompanyRequest.php index 42fb295e7f..01b94b28de 100644 --- a/app/Http/Requests/Company/UpdateCompanyRequest.php +++ b/app/Http/Requests/Company/UpdateCompanyRequest.php @@ -50,7 +50,7 @@ class UpdateCompanyRequest extends Request } else { 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 $rules['subdomain'] = 'nullable|alpha_num'; diff --git a/app/Jobs/Account/CreateAccount.php b/app/Jobs/Account/CreateAccount.php index 32490cf2e0..6be9106205 100644 --- a/app/Jobs/Account/CreateAccount.php +++ b/app/Jobs/Account/CreateAccount.php @@ -104,7 +104,10 @@ class CreateAccount //todo implement SLACK notifications //$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()) ->increment() @@ -118,10 +121,6 @@ class CreateAccount 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(); @@ -146,8 +145,6 @@ class CreateAccount $settings->language_id = (string)$language->id; } - //$timezone = Timezone::where('name', $data['geoplugin_timezone'])->first(); - if($timezone) { $settings->timezone_id = (string)$timezone->id; } diff --git a/app/Jobs/Company/CompanyExport.php b/app/Jobs/Company/CompanyExport.php index aca3306f57..f9447a1357 100644 --- a/app/Jobs/Company/CompanyExport.php +++ b/app/Jobs/Company/CompanyExport.php @@ -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'); - 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 = new \ZipArchive(); diff --git a/app/Jobs/Company/CompanyImport.php b/app/Jobs/Company/CompanyImport.php index 3c010945c3..977f7d95dc 100644 --- a/app/Jobs/Company/CompanyImport.php +++ b/app/Jobs/Company/CompanyImport.php @@ -219,7 +219,7 @@ class CompanyImport implements ShouldQueue if(count($backup_users) > 1){ // $this->message = 'Only one user can be in the import for a Free Account'; // $this->pre_flight_checks_pass = false; - $this->force_user_coalesce = true; + //$this->force_user_coalesce = true; } 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) { // $this->message = 'Account emails do not match. Account owner email must match backup user email'; // $this->pre_flight_checks_pass = false; - $this->force_user_coalesce = true; + // $this->force_user_coalesce = true; } $backup_users_emails = array_column($backup_users, 'email'); @@ -243,7 +243,7 @@ class CompanyImport implements ShouldQueue if($this->account->plan == 'pro'){ // $this->message = 'Pro plan is limited to one user, you have multiple users in the backup file'; // $this->pre_flight_checks_pass = false; - $this->force_user_coalesce = true; + // $this->force_user_coalesce = true; } if($this->account->plan == 'enterprise'){ diff --git a/app/Jobs/Entity/CreateEntityPdf.php b/app/Jobs/Entity/CreateEntityPdf.php index 62b3e938d1..d86ee6b041 100644 --- a/app/Jobs/Entity/CreateEntityPdf.php +++ b/app/Jobs/Entity/CreateEntityPdf.php @@ -102,12 +102,11 @@ class CreateEntityPdf implements ShouldQueue /* Set the locale*/ App::setLocale($this->contact->preferredLocale()); - // nlog($this->entity->client->getMergedSettings()); - /* Set customized translations _NOW_ */ $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') { return (new Phantom)->generate($this->invitation); @@ -116,16 +115,16 @@ class CreateEntityPdf implements ShouldQueue $entity_design_id = ''; 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'; } 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'; } 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'; } 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'; } @@ -194,7 +193,12 @@ class CreateEntityPdf implements ShouldQueue if ($pdf) { 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); } diff --git a/app/Jobs/Invoice/ZipInvoices.php b/app/Jobs/Invoice/ZipInvoices.php index 07a0544b63..fc66984ffc 100644 --- a/app/Jobs/Invoice/ZipInvoices.php +++ b/app/Jobs/Invoice/ZipInvoices.php @@ -78,13 +78,16 @@ class ZipInvoices implements ShouldQueue // create a new zipstream object $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); 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()), $invoice->pdf_file_path()); + $zip->addFileFromPath(basename($invoice->pdf_file_path($invitation)), $invoice->pdf_file_path()); } $zip->finish(); diff --git a/app/Jobs/Ninja/SendReminders.php b/app/Jobs/Ninja/SendReminders.php index 45d6db5c92..61dc6bdd03 100644 --- a/app/Jobs/Ninja/SendReminders.php +++ b/app/Jobs/Ninja/SendReminders.php @@ -213,7 +213,7 @@ class SendReminders implements ShouldQueue if ($this->checkSendSetting($invoice, $template) && $invoice->company->account->hasFeature(Account::FEATURE_EMAIL_TEMPLATES_REMINDERS)) { nlog("firing email"); - EmailEntity::dispatchNow($invitation, $invitation->company, $template)->delay(now()->addSeconds(60)); + EmailEntity::dispatchNow($invitation, $invitation->company, $template); } }); diff --git a/app/Jobs/RecurringInvoice/SendRecurring.php b/app/Jobs/RecurringInvoice/SendRecurring.php index 0fc68bbea7..ba25d51e88 100644 --- a/app/Jobs/RecurringInvoice/SendRecurring.php +++ b/app/Jobs/RecurringInvoice/SendRecurring.php @@ -96,7 +96,7 @@ class SendRecurring implements ShouldQueue if ($invitation->contact && strlen($invitation->contact->email) >=1) { try{ - EmailEntity::dispatch($invitation, $invoice->company)->delay(now()->addSeconds(60)); + EmailEntity::dispatch($invitation, $invoice->company); } catch(\Exception $e) { nlog($e->getMessage()); diff --git a/app/Jobs/Util/ReminderJob.php b/app/Jobs/Util/ReminderJob.php index 8a53bd0a50..71300863de 100644 --- a/app/Jobs/Util/ReminderJob.php +++ b/app/Jobs/Util/ReminderJob.php @@ -53,14 +53,18 @@ class ReminderJob implements ShouldQueue 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()) { $reminder_template = $invoice->calculateTemplate('invoice'); $invoice->service()->touchReminder($reminder_template)->save(); $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}"); }); diff --git a/app/Jobs/Util/SendFailedEmails.php b/app/Jobs/Util/SendFailedEmails.php index d8c9fc2513..33d8f0e0e0 100644 --- a/app/Jobs/Util/SendFailedEmails.php +++ b/app/Jobs/Util/SendFailedEmails.php @@ -64,7 +64,7 @@ class SendFailedEmails implements ShouldQueue if ($invitation->invoice) { 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']); } } }); diff --git a/app/Mail/Engine/CreditEmailEngine.php b/app/Mail/Engine/CreditEmailEngine.php index 07d5b60d3e..fae73019de 100644 --- a/app/Mail/Engine/CreditEmailEngine.php +++ b/app/Mail/Engine/CreditEmailEngine.php @@ -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(Ninja::isHosted()) - $this->setAttachments([$this->credit->pdf_file_path(null, 'url', true)]); + $this->setAttachments([$this->credit->pdf_file_path($this->invitation, 'url', true)]); else - $this->setAttachments([$this->credit->pdf_file_path()]); + $this->setAttachments([$this->credit->pdf_file_path($this->invitation)]); } diff --git a/app/Mail/Engine/InvoiceEmailEngine.php b/app/Mail/Engine/InvoiceEmailEngine.php index 09fe9b9c3e..59e1a4c0e2 100644 --- a/app/Mail/Engine/InvoiceEmailEngine.php +++ b/app/Mail/Engine/InvoiceEmailEngine.php @@ -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(Ninja::isHosted()) - $this->setAttachments([$this->invoice->pdf_file_path(null, 'url', true)]); + $this->setAttachments([$this->invoice->pdf_file_path($this->invitation, 'url', true)]); 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())]); diff --git a/app/Mail/Engine/PaymentEmailEngine.php b/app/Mail/Engine/PaymentEmailEngine.php index cc32c7e26f..010e047958 100644 --- a/app/Mail/Engine/PaymentEmailEngine.php +++ b/app/Mail/Engine/PaymentEmailEngine.php @@ -77,7 +77,7 @@ class PaymentEmailEngine extends BaseEmailEngine $this->payment->invoices->each(function ($invoice){ - $this->setAttachments([$invoice->pdf_file_path()]); + $this->setAttachments([$invoice->pdf_file_path($invoice->invitations->first())]); }); diff --git a/app/Mail/Engine/QuoteEmailEngine.php b/app/Mail/Engine/QuoteEmailEngine.php index 901a0c38db..978cff6cdc 100644 --- a/app/Mail/Engine/QuoteEmailEngine.php +++ b/app/Mail/Engine/QuoteEmailEngine.php @@ -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(Ninja::isHosted()) - $this->setAttachments([$this->quote->pdf_file_path(null, 'url', true)]); + $this->setAttachments([$this->quote->pdf_file_path($this->invitation, 'url', true)]); else - $this->setAttachments([$this->quote->pdf_file_path()]); + $this->setAttachments([$this->quote->pdf_file_path($this->invitation)]); } diff --git a/app/Mail/MigrationCompleted.php b/app/Mail/MigrationCompleted.php index 18d3d3636e..9181c1f268 100644 --- a/app/Mail/MigrationCompleted.php +++ b/app/Mail/MigrationCompleted.php @@ -41,9 +41,6 @@ class MigrationCompleted extends Mailable $result = $this->from(config('mail.from.address'), config('mail.from.name')) ->view('email.import.completed', $data); - // if($this->company->invoices->count() >=1) - // $result->attach($this->company->invoices->first()->pdf_file_path()); - return $result; } } diff --git a/app/Models/Client.php b/app/Models/Client.php index 732a70123c..e163e79066 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -638,24 +638,28 @@ class Client extends BaseModel implements HasLocalePreference })->first()->locale; } - public function invoice_filepath() - { - return $this->company->company_key.'/'.$this->client_hash.'/invoices/'; + public function invoice_filepath($invitation) + { + $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() diff --git a/app/Models/Credit.php b/app/Models/Credit.php index 242dabd24d..3cd4028c6a 100644 --- a/app/Models/Credit.php +++ b/app/Models/Credit.php @@ -267,7 +267,7 @@ class Credit extends BaseModel if(!$invitation) 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)){ return Storage::disk(config('filesystems.default'))->{$type}($file_path); diff --git a/app/Models/CreditInvitation.php b/app/Models/CreditInvitation.php index 2db1c13167..ffabc6b265 100644 --- a/app/Models/CreditInvitation.php +++ b/app/Models/CreditInvitation.php @@ -126,9 +126,9 @@ class CreditInvitation extends BaseModel 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))); CreateEntityPdf::dispatchNow($this); } diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 6febb5876f..5715f4823b 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -409,13 +409,13 @@ class Invoice extends BaseModel if(!$invitation) 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)){ return Storage::disk(config('filesystems.default'))->{$type}($file_path); } 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); } diff --git a/app/Models/InvoiceInvitation.php b/app/Models/InvoiceInvitation.php index 3fa1f4918c..1e55b60751 100644 --- a/app/Models/InvoiceInvitation.php +++ b/app/Models/InvoiceInvitation.php @@ -142,7 +142,7 @@ class InvoiceInvitation extends BaseModel { $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))); CreateEntityPdf::dispatchNow($this); } diff --git a/app/Models/Quote.php b/app/Models/Quote.php index c7d3118dc7..dfe32b9e90 100644 --- a/app/Models/Quote.php +++ b/app/Models/Quote.php @@ -219,7 +219,7 @@ class Quote extends BaseModel if(!$invitation) 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)){ return Storage::disk(config('filesystems.default'))->{$type}($file_path); diff --git a/app/Models/QuoteInvitation.php b/app/Models/QuoteInvitation.php index 901d0bc54e..c5159cfa28 100644 --- a/app/Models/QuoteInvitation.php +++ b/app/Models/QuoteInvitation.php @@ -130,9 +130,9 @@ class QuoteInvitation extends BaseModel 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))); CreateEntityPdf::dispatchNow($this); } diff --git a/app/Observers/InvoiceObserver.php b/app/Observers/InvoiceObserver.php index b91436d2ed..fddcb6624b 100644 --- a/app/Observers/InvoiceObserver.php +++ b/app/Observers/InvoiceObserver.php @@ -51,11 +51,6 @@ class InvoiceObserver if ($subscriptions) { 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'); } diff --git a/app/PaymentDrivers/Stripe/UpdatePaymentMethods.php b/app/PaymentDrivers/Stripe/UpdatePaymentMethods.php index 8d4e975796..3e83847a97 100644 --- a/app/PaymentDrivers/Stripe/UpdatePaymentMethods.php +++ b/app/PaymentDrivers/Stripe/UpdatePaymentMethods.php @@ -58,7 +58,7 @@ class UpdatePaymentMethods // } - private function updateMethods(Customer $customer, Client $client) + public function updateMethods(Customer $customer, Client $client) { $card_methods = PaymentMethod::all([ '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) { diff --git a/app/Providers/MailServiceProvider.php b/app/Providers/MailServiceProvider.php index 3ccdc93a46..6f1da83db1 100644 --- a/app/Providers/MailServiceProvider.php +++ b/app/Providers/MailServiceProvider.php @@ -24,7 +24,7 @@ class MailServiceProvider extends MailProvider protected function registerIlluminateMailer() { $this->app->singleton('mail.manager', function($app) { - return new GmailTransportManager($app); + return new GmailTransportManager($app); }); // $this->app->bind('mail.manager', function($app) { diff --git a/app/Services/Credit/CreditService.php b/app/Services/Credit/CreditService.php index a07e572eb2..48073b5772 100644 --- a/app/Services/Credit/CreditService.php +++ b/app/Services/Credit/CreditService.php @@ -140,7 +140,11 @@ class CreditService 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; } diff --git a/app/Services/Credit/GetCreditPdf.php b/app/Services/Credit/GetCreditPdf.php index 9114ec5fdb..11acb7dfcb 100644 --- a/app/Services/Credit/GetCreditPdf.php +++ b/app/Services/Credit/GetCreditPdf.php @@ -37,7 +37,7 @@ class GetCreditPdf extends AbstractService $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'; diff --git a/app/Services/Invoice/GenerateDeliveryNote.php b/app/Services/Invoice/GenerateDeliveryNote.php index 4c8d681a73..9f324680f6 100644 --- a/app/Services/Invoice/GenerateDeliveryNote.php +++ b/app/Services/Invoice/GenerateDeliveryNote.php @@ -60,14 +60,15 @@ class GenerateDeliveryNote ? $this->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') { return (new Phantom)->generate($this->invoice->invitations->first()); } $design = Design::find($design_id); - $html = new HtmlEngine($this->invoice->invitations->first()); + $html = new HtmlEngine($invitation); if ($design->is_custom) { $options = ['custom_partials' => json_decode(json_encode($design->design), true)]; @@ -105,6 +106,9 @@ class GenerateDeliveryNote 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); return Storage::disk($this->disk)->path($file_path); diff --git a/app/Services/Invoice/GetInvoicePdf.php b/app/Services/Invoice/GetInvoicePdf.php index 8d42aabfb6..535d70ddb7 100644 --- a/app/Services/Invoice/GetInvoicePdf.php +++ b/app/Services/Invoice/GetInvoicePdf.php @@ -35,7 +35,7 @@ class GetInvoicePdf extends AbstractService $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'; diff --git a/app/Services/Invoice/InvoiceService.php b/app/Services/Invoice/InvoiceService.php index 54049fa002..304d83233e 100644 --- a/app/Services/Invoice/InvoiceService.php +++ b/app/Services/Invoice/InvoiceService.php @@ -307,12 +307,15 @@ class InvoiceService public function deletePdf() { - //UnlinkFile::dispatchNow(config('filesystems.default'), $this->invoice->client->invoice_filepath() . $this->invoice->numberFormatter().'.pdf'); - Storage::disk(config('filesystems.default'))->delete($this->invoice->client->invoice_filepath() . $this->invoice->numberFormatter().'.pdf'); - - if(Ninja::isHosted()) { - Storage::disk('public')->delete($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($invitation) . $this->invoice->numberFormatter().'.pdf'); + + if(Ninja::isHosted()) { + Storage::disk('public')->delete($this->invoice->client->invoice_filepath($invitation) . $this->invoice->numberFormatter().'.pdf'); + } + + }); return $this; } @@ -351,8 +354,17 @@ class InvoiceService * PDF when it is updated etc. * @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) { CreateEntityPdf::dispatch($invitation); }); @@ -380,7 +392,8 @@ class InvoiceService $this->invoice->reminder_last_sent = now()->format('Y-m-d'); break; default: - // code... + $this->invoice->reminder1_sent = now()->format('Y-m-d'); + $this->invoice->reminder_last_sent = now()->format('Y-m-d'); break; } diff --git a/app/Services/Invoice/TriggeredActions.php b/app/Services/Invoice/TriggeredActions.php index d62bc2cece..74dc9dcbbe 100644 --- a/app/Services/Invoice/TriggeredActions.php +++ b/app/Services/Invoice/TriggeredActions.php @@ -62,7 +62,7 @@ class TriggeredActions extends AbstractService $reminder_template = 'payment'; $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) { diff --git a/app/Services/Invoice/UpdateReminder.php b/app/Services/Invoice/UpdateReminder.php index 5f2090a4cd..f08d873b9d 100644 --- a/app/Services/Invoice/UpdateReminder.php +++ b/app/Services/Invoice/UpdateReminder.php @@ -126,8 +126,11 @@ class UpdateReminder extends AbstractService $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; } } \ No newline at end of file diff --git a/app/Services/Quote/GetQuotePdf.php b/app/Services/Quote/GetQuotePdf.php index 7990c81a98..a28a09a6b8 100644 --- a/app/Services/Quote/GetQuotePdf.php +++ b/app/Services/Quote/GetQuotePdf.php @@ -35,7 +35,7 @@ class GetQuotePdf extends AbstractService $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'; diff --git a/app/Services/Quote/QuoteService.php b/app/Services/Quote/QuoteService.php index b768462082..76b007896b 100644 --- a/app/Services/Quote/QuoteService.php +++ b/app/Services/Quote/QuoteService.php @@ -178,7 +178,11 @@ class QuoteService 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; } diff --git a/app/Services/Recurring/GetInvoicePdf.php b/app/Services/Recurring/GetInvoicePdf.php index 6c4b6dee22..9a68b5bf56 100644 --- a/app/Services/Recurring/GetInvoicePdf.php +++ b/app/Services/Recurring/GetInvoicePdf.php @@ -37,7 +37,7 @@ class GetInvoicePdf extends AbstractService $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'; diff --git a/app/Services/Recurring/RecurringService.php b/app/Services/Recurring/RecurringService.php index 65c80791ff..e8a2761b48 100644 --- a/app/Services/Recurring/RecurringService.php +++ b/app/Services/Recurring/RecurringService.php @@ -87,7 +87,13 @@ class RecurringService 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; } diff --git a/app/Utils/HtmlEngine.php b/app/Utils/HtmlEngine.php index 4db72206fd..c1a8e791c9 100644 --- a/app/Utils/HtmlEngine.php +++ b/app/Utils/HtmlEngine.php @@ -181,6 +181,7 @@ class HtmlEngine $data['$amount_due'] = ['value' => &$data['$total']['value'], 'label' => ctrans('texts.amount_due')]; $data['$quote.total'] = &$data['$total']; $data['$invoice.total'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: ' ', '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['$quote.amount'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: ' ', 'label' => ctrans('texts.quote_total')]; $data['$credit.total'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: ' ', 'label' => ctrans('texts.credit_total')]; diff --git a/app/Utils/PhantomJS/Phantom.php b/app/Utils/PhantomJS/Phantom.php index 3692099794..2b53673042 100644 --- a/app/Utils/PhantomJS/Phantom.php +++ b/app/Utils/PhantomJS/Phantom.php @@ -62,19 +62,19 @@ class Phantom $entity_obj = $invitation->{$entity}; if ($entity == 'invoice') { - $path = $entity_obj->client->invoice_filepath(); + $path = $entity_obj->client->invoice_filepath($invitation); } if ($entity == 'quote') { - $path = $entity_obj->client->quote_filepath(); + $path = $entity_obj->client->quote_filepath($invitation); } if ($entity == 'credit') { - $path = $entity_obj->client->credit_filepath(); + $path = $entity_obj->client->credit_filepath($invitation); } 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'; @@ -90,6 +90,9 @@ class Phantom $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); return $file_path; @@ -118,8 +121,6 @@ class Phantom $finfo = new \finfo(FILEINFO_MIME); -nlog($pdf); - if($finfo->buffer($pdf) != 'application/pdf; charset=binary') { SystemLogger::dispatch( diff --git a/config/ninja.php b/config/ninja.php index 2d9866d9a9..dae4a2fdcc 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -14,8 +14,8 @@ return [ 'require_https' => env('REQUIRE_HTTPS', true), 'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'), - 'app_version' => '5.2.1', - 'app_tag' => '5.2.1-release', + 'app_version' => '5.2.2', + 'app_tag' => '5.2.2-release', 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', ''), diff --git a/tests/Integration/MultiDBUserTest.php b/tests/Integration/MultiDBUserTest.php index 311493f1e4..8f3abf239a 100644 --- a/tests/Integration/MultiDBUserTest.php +++ b/tests/Integration/MultiDBUserTest.php @@ -194,6 +194,8 @@ class MultiDBUserTest extends TestCase ], ]; + $response = false; + try { $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), @@ -203,7 +205,7 @@ class MultiDBUserTest extends TestCase } catch (ValidationException $e) { $message = json_decode($e->validator->getMessageBag(), 1); $this->assertNotNull($message); - + nlog($message); } if ($response) { diff --git a/tests/Unit/S3CleanupTest.php b/tests/Unit/S3CleanupTest.php new file mode 100644 index 0000000000..b4056ff4f5 --- /dev/null +++ b/tests/Unit/S3CleanupTest.php @@ -0,0 +1,39 @@ +merge($c2)->toArray(); + + $this->assertTrue(in_array("1", $merged)); + $this->assertFalse(in_array("10", $merged)); + + } +}