diff --git a/LICENSE b/LICENSE index e92123e41..8427bcbce 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 804440660..fb467b157 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 000000000..621102382 --- /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 000000000..f7409764b --- /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 648bb043f..9cc414790 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 68a5a5252..000000000 --- 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 293a0eb66..97abc500b 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 047257859..0fec89cc2 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 968298e8a..9feb62c7e 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 2689e3aad..dd884a9f8 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 12b3c9a26..3fb397608 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 42fb295e7..01b94b28d 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 32490cf2e..6be910620 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 aca3306f5..f9447a135 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 3c010945c..977f7d95d 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 62b3e938d..d86ee6b04 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 07a0544b6..fc66984ff 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 45d6db5c9..61dc6bdd0 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 0fc68bbea..ba25d51e8 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 8a53bd0a5..71300863d 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 d8c9fc251..33d8f0e0e 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 07d5b60d3..fae73019d 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 09fe9b9c3..59e1a4c0e 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 cc32c7e26..010e04795 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 901a0c38d..978cff6cd 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 18d3d3636..9181c1f26 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 732a70123..e163e7906 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 242dabd24..3cd4028c6 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 2db1c1316..ffabc6b26 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 6febb5876..5715f4823 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 3fa1f4918..1e55b6075 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 c7d3118dc..dfe32b9e9 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 901d0bc54..c5159cfa2 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 b91436d2e..fddcb6624 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 8d4e97579..3e83847a9 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 3ccdc93a4..6f1da83db 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 a07e572eb..48073b577 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 9114ec5fd..11acb7dfc 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 4c8d681a7..9f324680f 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 8d42aabfb..535d70ddb 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 54049fa00..304d83233 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 d62bc2cec..74dc9dcbb 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 5f2090a4c..f08d873b9 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 7990c81a9..a28a09a6b 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 b76846208..76b007896 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 6c4b6dee2..9a68b5bf5 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 65c80791f..e8a2761b4 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 4db72206f..c1a8e791c 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 369209979..2b5367304 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 2d9866d9a..dae4a2fdc 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 311493f1e..8f3abf239 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 000000000..b4056ff4f --- /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)); + + } +}