diff --git a/LICENSE b/LICENSE index e92123e414f8..8427bcbceaae 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 804440660c71..fb467b15735a 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 000000000000..621102382e2f --- /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 000000000000..f7409764b3a5 --- /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 648bb043fdcc..9cc4147908b8 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 68a5a525274c..000000000000 --- 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 293a0eb66508..97abc500b825 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 047257859720..0fec89cc29ef 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 968298e8a505..9feb62c7e452 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 2689e3aadd93..dd884a9f8366 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 12b3c9a26e8e..3fb397608d0f 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 42fb295e7f70..01b94b28de38 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 32490cf2e01c..6be9106205a4 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 aca3306f5788..f9447a1357e2 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 3c010945c3e0..977f7d95dc51 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 62b3e938d130..d86ee6b041e9 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 07a0544b634f..fc66984ffc42 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 45d6db5c92c0..61dc6bdd03af 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 0fc68bbea707..ba25d51e8871 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 8a53bd0a50ae..71300863de23 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 d8c9fc2513b5..33d8f0e0e075 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 07d5b60d3ed7..fae73019de3c 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 09fe9b9c3e53..59e1a4c0e21d 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 cc32c7e26f80..010e0479583d 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 901a0c38dbae..978cff6cdc48 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 18d3d3636e38..9181c1f26838 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 732a70123c7e..e163e79066a1 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 242dabd24dbc..3cd4028c6a33 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 2db1c1316702..ffabc6b265b6 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 6febb5876f43..5715f4823b83 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 3fa1f4918cf3..1e55b607517e 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 c7d3118dc76a..dfe32b9e9037 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 901d0bc54e12..c5159cfa28bf 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 b91436d2edcf..fddcb6624bf4 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 8d4e97579660..3e83847a97d9 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 3ccdc93a46e6..6f1da83db18e 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 a07e572eb295..48073b577283 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 9114ec5fdbc4..11acb7dfcb08 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 4c8d681a7329..9f324680f6ec 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 8d42aabfb6d0..535d70ddb7e8 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 54049fa00269..304d83233ea6 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 d62bc2ceceeb..74dc9dcbbe7e 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 5f2090a4cdc4..f08d873b9dee 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 7990c81a9877..a28a09a6b854 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 b7684620827e..76b007896b06 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 6c4b6dee2236..9a68b5bf5660 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 65c80791ff0b..e8a2761b4807 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 4db72206fde1..c1a8e791c960 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 369209979456..2b5367304262 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 2d9866d9a908..dae4a2fdccae 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 311493f1e496..8f3abf239a0e 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 000000000000..b4056ff4f5e0 --- /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)); + + } +}