Merge pull request #9242 from turbo124/v5-develop

v5.8.22
This commit is contained in:
David Bomba 2024-02-04 11:17:06 +11:00 committed by GitHub
commit 4018295190
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 84 additions and 75 deletions

View File

@ -1 +1 @@
5.8.21 5.8.22

View File

@ -49,7 +49,7 @@ class StoreSchedulerRequest extends Request
'parameters.entity_id' => ['bail', 'sometimes', 'string'], 'parameters.entity_id' => ['bail', 'sometimes', 'string'],
'parameters.report_name' => ['bail','sometimes', 'string', 'required_if:template,email_report','in:ar_detailed,ar_summary,client_balance,tax_summary,profitloss,client_sales,user_sales,product_sales,activity,client,contact,client_contact,credit,document,expense,invoice,invoice_item,quote,quote_item,recurring_invoice,payment,product,task'], 'parameters.report_name' => ['bail','sometimes', 'string', 'required_if:template,email_report','in:ar_detailed,ar_summary,client_balance,tax_summary,profitloss,client_sales,user_sales,product_sales,activity,client,contact,client_contact,credit,document,expense,invoice,invoice_item,quote,quote_item,recurring_invoice,payment,product,task'],
'parameters.date_key' => ['bail','sometimes', 'string'], 'parameters.date_key' => ['bail','sometimes', 'string'],
'parameters.status' => ['bail','sometimes', 'string'], 'parameters.status' => ['bail','sometimes', 'nullable', 'string'],
]; ];
return $rules; return $rules;

View File

@ -1091,15 +1091,15 @@ class Import implements ShouldQueue
{ {
Invoice::unguard(); Invoice::unguard();
$rules = [ // $rules = [
'*.client_id' => ['required'], // '*.client_id' => ['required'],
]; // ];
$validator = Validator::make($data, $rules); // // $validator = Validator::make($data, $rules);
if ($validator->fails()) { // if ($validator->fails()) {
throw new MigrationValidatorFailed(json_encode($validator->errors())); // throw new MigrationValidatorFailed(json_encode($validator->errors()));
} // }
$invoice_repository = new InvoiceMigrationRepository(); $invoice_repository = new InvoiceMigrationRepository();
@ -1350,11 +1350,11 @@ class Import implements ShouldQueue
'*.client_id' => ['required'], '*.client_id' => ['required'],
]; ];
$validator = Validator::make($data, $rules); // $validator = Validator::make($data, $rules);
if ($validator->fails()) { // if ($validator->fails()) {
throw new MigrationValidatorFailed(json_encode($validator->errors())); // throw new MigrationValidatorFailed(json_encode($validator->errors()));
} // }
$payment_repository = new PaymentMigrationRepository(new CreditRepository()); $payment_repository = new PaymentMigrationRepository(new CreditRepository());
@ -1525,18 +1525,19 @@ class Import implements ShouldQueue
} }
} }
if (!$entity) {
continue;
}
// throw new Exception("Resource invoice/quote document not available."); // throw new Exception("Resource invoice/quote document not available.");
} }
if (array_key_exists('expense_id', $resource) && $resource['expense_id'] && array_key_exists('expenses', $this->ids)) { if (array_key_exists('expense_id', $resource) && $resource['expense_id'] && array_key_exists('expenses', $this->ids)) {
$expense_id = $this->transformId('expenses', $resource['expense_id']); $expense_id = $this->transformId('expenses', $resource['expense_id']);
$entity = Expense::query()->where('id', $expense_id)->withTrashed()->first(); $entity = Expense::query()->where('id', $expense_id)->withTrashed()->first();
} }
if (!$entity) {
continue;
}
$file_url = $resource['url']; $file_url = $resource['url'];
$file_name = $resource['name']; $file_name = $resource['name'];
$file_path = sys_get_temp_dir().'/'.$file_name; $file_path = sys_get_temp_dir().'/'.$file_name;
@ -2056,7 +2057,7 @@ class Import implements ShouldQueue
public function failed($exception = null) public function failed($exception = null)
{ {
info('the job failed'); nlog('the job failed');
config(['queue.failed.driver' => null]); config(['queue.failed.driver' => null]);
@ -2067,11 +2068,11 @@ class Import implements ShouldQueue
LightLogs::create($job_failure) LightLogs::create($job_failure)
->queue(); ->queue();
info(print_r($exception->getMessage(), 1)); nlog(print_r($exception->getMessage(), 1));
if (Ninja::isHosted()) { // if (Ninja::isHosted()) {
app('sentry')->captureException($exception); app('sentry')->captureException($exception);
} // }
} }

View File

@ -63,7 +63,6 @@ class UpdateUserLastLogin implements ShouldQueue
$ip = array_key_exists('ip', $event->event_vars) ? $event->event_vars['ip'] : 'IP address not resolved'; $ip = array_key_exists('ip', $event->event_vars) ? $event->event_vars['ip'] : 'IP address not resolved';
$key = "user_logged_in_{$user->id}{$event->company->db}"; $key = "user_logged_in_{$user->id}{$event->company->db}";
if ($user->ip != $ip && is_null(Cache::get($key)) && $user->user_logged_in_notification) { if ($user->ip != $ip && is_null(Cache::get($key)) && $user->user_logged_in_notification) {
$nmo = new NinjaMailerObject(); $nmo = new NinjaMailerObject();
$nmo->mailable = new UserLoggedIn($user, $user->account->companies->first(), $ip); $nmo->mailable = new UserLoggedIn($user, $user->account->companies->first(), $ip);

View File

@ -73,7 +73,8 @@ class EntityCreatedObject
); );
$mail_obj->markdown = 'email.admin.generic'; $mail_obj->markdown = 'email.admin.generic';
$mail_obj->text_view = 'email.template.text';
$content = ctrans( $content = ctrans(
$this->template_body, $this->template_body,
[ [
@ -92,7 +93,7 @@ class EntityCreatedObject
'logo' => $this->company->present()->logo(), 'logo' => $this->company->present()->logo(),
'settings' => $this->company->settings, 'settings' => $this->company->settings,
'whitelabel' => $this->company->account->isPaid() ? true : false, 'whitelabel' => $this->company->account->isPaid() ? true : false,
'text_body' => $content, 'text_body' => str_replace(['$view_button','$viewButton','$viewLink','$view_url'], '$view_url', $content),
]; ];
} else { } else {
$this->entity->load('client.country', 'client.company'); $this->entity->load('client.country', 'client.company');
@ -179,7 +180,7 @@ class EntityCreatedObject
'logo' => $this->company->present()->logo(), 'logo' => $this->company->present()->logo(),
'settings' => $settings, 'settings' => $settings,
'whitelabel' => $this->company->account->isPaid() ? true : false, 'whitelabel' => $this->company->account->isPaid() ? true : false,
'text_body' => $content, 'text_body' => str_replace(['$view_button','$viewButton','$view_link','$view_button'], '$view_url', $content),
]; ];
} }
} }

View File

@ -123,6 +123,15 @@ class BaseEmailEngine implements EngineInterface
public function setTextBody($text) public function setTextBody($text)
{ {
if (! empty($this->variables)) {
$text = str_replace(['$paymentLink', '$viewButton', '$view_button', '$viewLink', '$view_link'], '$view_url', $text);
$text = str_replace(array_keys($this->variables), array_values($this->variables), $text);
$text = str_replace(array_keys($this->variables), array_values($this->variables), $text);
}
$this->text_body = $text; $this->text_body = $text;
return $this; return $this;
@ -189,22 +198,4 @@ class BaseEmailEngine implements EngineInterface
return $this->text_body; return $this->text_body;
} }
private function replaceEntities($content)
{
$find = [
'<p>',
'</p>',
'<div class="center">',
'<\div>',
];
$replace = [
'',
'\n\n',
'',
'\n\n',
];
return str_replace($find, $replace, $content);
}
} }

View File

@ -31,6 +31,7 @@ class PaymentEmailEngine extends BaseEmailEngine
public $client; public $client;
/** @var \App\Models\Payment $payment */
public $payment; public $payment;
public $template_data; public $template_data;
@ -91,6 +92,7 @@ class PaymentEmailEngine extends BaseEmailEngine
->setVariables($this->makeValues()) ->setVariables($this->makeValues())
->setSubject($subject_template) ->setSubject($subject_template)
->setBody($body_template) ->setBody($body_template)
->setTextBody($body_template)
->setFooter('') ->setFooter('')
->setViewLink('') ->setViewLink('')
->setViewText(''); ->setViewText('');
@ -206,7 +208,7 @@ class PaymentEmailEngine extends BaseEmailEngine
$data['$payment.date'] = ['value' => $this->translateDate($this->payment->date, $this->client->date_format(), $this->client->locale()), 'label' => ctrans('texts.payment_date')]; $data['$payment.date'] = ['value' => $this->translateDate($this->payment->date, $this->client->date_format(), $this->client->locale()), 'label' => ctrans('texts.payment_date')];
$data['$transaction_reference'] = ['value' => $this->payment->transaction_reference, 'label' => ctrans('texts.transaction_reference')]; $data['$transaction_reference'] = ['value' => $this->payment->transaction_reference, 'label' => ctrans('texts.transaction_reference')];
$data['$reference'] = ['value' => '', 'label' => ctrans('texts.reference')]; $data['$reference'] = ['value' => '', 'label' => ctrans('texts.reference')];
$data['$public_notes'] = ['value' => $this->payment->public_notes, 'label' => ctrans('texts.notes')]; $data['$public_notes'] = ['value' => '', 'label' => ctrans('texts.notes')];
$data['$payment1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'payment1', $this->payment->custom_value1, $this->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'payment1')]; $data['$payment1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'payment1', $this->payment->custom_value1, $this->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'payment1')];
$data['$payment2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'payment2', $this->payment->custom_value2, $this->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'payment2')]; $data['$payment2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'payment2', $this->payment->custom_value2, $this->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'payment2')];

View File

@ -139,7 +139,7 @@ class TemplateEmail extends Mailable
'whitelabel' => $this->client->user->account->isPaid() ? true : false, 'whitelabel' => $this->client->user->account->isPaid() ? true : false,
'logo' => $this->company->present()->logo($settings), 'logo' => $this->company->present()->logo($settings),
'links' => $this->build_email->getAttachmentLinks(), 'links' => $this->build_email->getAttachmentLinks(),
'email_preferences' => (Ninja::isHosted() && in_array($settings->email_sending_method, ['default', 'mailgun'])) ? URL::signedRoute('client.email_preferences', ['entity' => $this->invitation->getEntityString(), 'invitation_key' => $this->invitation->key]) : false, 'email_preferences' => (Ninja::isHosted() && in_array($settings->email_sending_method, ['default', 'mailgun'])) ? $this->company->domain() . URL::signedRoute('client.email_preferences', ['entity' => $this->invitation->getEntityString(), 'invitation_key' => $this->invitation->key], absolute: false) : false,
]); ]);
foreach ($this->build_email->getAttachments() as $file) { foreach ($this->build_email->getAttachments() as $file) {

View File

@ -12,31 +12,21 @@
namespace App\Mail\User; namespace App\Mail\User;
use Illuminate\Bus\Queueable; use App\Models\User;
use App\Models\Company;
use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\App;
class UserLoggedIn extends Mailable class UserLoggedIn extends Mailable
{ {
// use Queueable, SerializesModels;
public $company;
public $user;
public $ip;
/** /**
* Create a new message instance. * Create a new message instance.
* *
* @return void * @return void
*/ */
public function __construct($user, $company, $ip) public function __construct(public User $user, public Company $company, public string $ip)
{ {
$this->company = $company;
$this->user = $user;
$this->ip = $ip;
} }
/** /**
@ -48,11 +38,13 @@ class UserLoggedIn extends Mailable
{ {
App::setLocale($this->company->getLocale()); App::setLocale($this->company->getLocale());
$text = ctrans('texts.new_login_description', ['email' => $this->user->email, 'ip' => $this->ip, 'time' => now()]);
return $this->from(config('mail.from.address'), config('mail.from.name')) return $this->from(config('mail.from.address'), config('mail.from.name'))
->subject(ctrans('texts.new_login_detected')) ->subject(ctrans('texts.new_login_detected'))
->text('email.admin.generic_text', [ ->text('email.admin.generic_text', [
'title' => ctrans('texts.new_login_detected'), 'title' => ctrans('texts.new_login_detected'),
'body' => strip_tags(ctrans('texts.new_login_description', ['email' => $this->user->email, 'ip' => $this->ip, 'time' => now()])), 'body' => $text,
]) ])
->view('email.admin.notification') ->view('email.admin.notification')
->with([ ->with([

View File

@ -78,7 +78,6 @@ class AppServiceProvider extends ServiceProvider
->middleware('client'); ->middleware('client');
}); });
/* Ensure we don't have stale state in jobs */ /* Ensure we don't have stale state in jobs */
Queue::before(function (JobProcessing $event) { Queue::before(function (JobProcessing $event) {
App::forgetInstance('truthsource'); App::forgetInstance('truthsource');
@ -107,7 +106,6 @@ class AppServiceProvider extends ServiceProvider
return $this; return $this;
}); });
Mailer::macro('mailgun_config', function (string $secret, string $domain, string $endpoint = 'api.mailgun.net') { Mailer::macro('mailgun_config', function (string $secret, string $domain, string $endpoint = 'api.mailgun.net') {
// @phpstan-ignore /** @phpstan-ignore-next-line **/ // @phpstan-ignore /** @phpstan-ignore-next-line **/
Mailer::setSymfonyTransport(app('mail.manager')->createSymfonyTransport([ Mailer::setSymfonyTransport(app('mail.manager')->createSymfonyTransport([

View File

@ -82,10 +82,17 @@ class ClientContactRepository extends BaseRepository
$update_contact->fill($contact); $update_contact->fill($contact);
if (array_key_exists('password', $contact) && strlen($contact['password']) > 1) { if (array_key_exists('password', $contact) && strlen($contact['password']) > 1 && strlen($update_contact->email) > 3) { //updating on a blank contact email will cause large table scanning
$update_contact->password = Hash::make($contact['password']); $update_contact->password = Hash::make($contact['password']);
$client->company->client_contacts()->where('email', $update_contact->email)->update(['password' => $update_contact->password]); ClientContact::withTrashed()
->where('company_id', $client->id)
->where('client_id', $client->company_id)
->where('email', $update_contact->email)->cursor()
->each(function ($saveable_contact) use ($update_contact){
$saveable_contact->password = $update_contact->password;
$saveable_contact->save();
});
} }
if (array_key_exists('email', $contact)) { if (array_key_exists('email', $contact)) {

View File

@ -177,7 +177,7 @@ class EmailDefaults
$breaks = ["<br />","<br>","<br/>"]; $breaks = ["<br />","<br>","<br/>"];
$this->email->email_object->text_body = str_ireplace($breaks, "\r\n", $this->email->email_object->body); $this->email->email_object->text_body = str_ireplace($breaks, "\r\n", $this->email->email_object->body);
$this->email->email_object->text_body = strip_tags($this->email->email_object->text_body); $this->email->email_object->text_body = strip_tags($this->email->email_object->text_body);
$this->email->email_object->text_body = str_replace('$view_button', '$view_url', $this->email->email_object->text_body); $this->email->email_object->text_body = str_replace(['$view_button','$viewButton'], '$view_url', $this->email->email_object->text_body);
if ($this->template == 'email.template.custom') { if ($this->template == 'email.template.custom') {
$this->email->email_object->body = (str_replace('$body', $this->email->email_object->body, str_replace(["\r","\n"], "", $this->email->email_object->settings->email_style_custom))); $this->email->email_object->body = (str_replace('$body', $this->email->email_object->body, str_replace(["\r","\n"], "", $this->email->email_object->settings->email_style_custom)));

View File

@ -49,7 +49,9 @@ trait Inviteable
public function getPaymentLink() public function getPaymentLink()
{ {
if (Ninja::isHosted()) { if (Ninja::isHosted()) {
/**@var \App\Models\Company $company */
$domain = $this->company->domain(); $domain = $this->company->domain();
} else { } else {
$domain = config('ninja.app_url'); $domain = config('ninja.app_url');
@ -105,7 +107,11 @@ trait Inviteable
switch ($this->company->portal_mode) { switch ($this->company->portal_mode) {
case 'subdomain': case 'subdomain':
return $domain.'/client/'.$entity_type.'/'.$this->key;
if(Ninja::isHosted())
return 'https://router.invoiceninja.com/route/'.encrypt($domain.'/client/'.$entity_type.'/'.$this->key);
else
return $domain.'/client/'.$entity_type.'/'.$this->key;
break; break;
case 'iframe': case 'iframe':
return $domain.'/client/'.$entity_type.'/'.$this->key; return $domain.'/client/'.$entity_type.'/'.$this->key;

View File

@ -17,8 +17,8 @@ return [
'require_https' => env('REQUIRE_HTTPS', true), 'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => env('APP_VERSION', '5.8.21'), 'app_version' => env('APP_VERSION', '5.8.22'),
'app_tag' => env('APP_TAG', '5.8.21'), 'app_tag' => env('APP_TAG', '5.8.22'),
'minimum_client_version' => '5.0.16', 'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1', 'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', false), 'api_secret' => env('API_SECRET', false),

View File

@ -1,9 +1,15 @@
{!! $title !!} {{ $title }}
@isset($body) @isset($body)
{!! $body !!} {{ strip_tags(str_replace("<br>", "\r\n", $body)) }}
@endisset @endisset
@isset($content) @isset($content)
{!! $content !!} {{ strip_tags(str_replace("<br>", "\r\n", $content)) }}
@endisset
@isset($whitelabel)
@if(!$whitelabel)
{{ ctrans('texts.ninja_email_footer', ['site' => 'https://invoiceninja.com']) }}
@endif
@endisset @endisset

View File

@ -233,20 +233,20 @@
</td> </td>
</tr> </tr>
@isset($email_preferences) @if(isset($email_preferences) && $email_preferences)
<tr> <tr>
<td bgcolor="#242424" cellpadding="20"> <td bgcolor="#242424" cellpadding="20">
<div class="dark-bg-base" <div class="dark-bg-base"
style="padding-top: 10px;padding-bottom: 10px; background-color: #242424; border: 1px solid #c2c2c2; border-top-color: #242424; border-bottom-color: #242424;"> style="padding-top: 10px;padding-bottom: 10px; background-color: #242424; border: 1px solid #c2c2c2; border-top-color: #242424; border-bottom-color: #242424;">
<a href="{{ $email_preferences }}"> <a href="{{ $email_preferences }}">
<p style="text-align: center; color: #ffffff; font-size: 10px; font-family: Verdana, Geneva, Tahoma, sans-serif;"> <p style="text-align: center; color: #ffffff; font-size: 10px; font-family: Verdana, Geneva, Tahoma, sans-serif;">
{{ ctrans('texts.unsubscribe') }} {{ ctrans('texts.email_preferences') }}
</p> </p>
</a> </a>
</div> </div>
</td> </td>
</tr> </tr>
@endisset @endif
</table> </table>
</td> </td>
</tr> </tr>

View File

@ -1,7 +1,7 @@
{!! $text_body !!} {{ strip_tags(str_replace("<br>", "\r\n", $text_body)) }}
@isset($whitelabel) @isset($whitelabel)
@if(!$whitelabel) @if(!$whitelabel)
{{ ctrans('texts.ninja_email_footer', ['site' => 'https://invoiceninja.com']) }} {{ ctrans('texts.ninja_email_footer', ['site' => 'https://invoiceninja.com']) }}
@endif @endif
@endisset @endisset

View File

@ -141,6 +141,12 @@ Route::group(['middleware' => ['invite_db'], 'prefix' => 'client', 'as' => 'clie
Route::get('unsubscribe/{entity}/{invitation_key}', [App\Http\Controllers\ClientPortal\InvitationController::class, 'unsubscribe'])->name('unsubscribe'); Route::get('unsubscribe/{entity}/{invitation_key}', [App\Http\Controllers\ClientPortal\InvitationController::class, 'unsubscribe'])->name('unsubscribe');
}); });
Route::get('route/{hash}', function ($hash) {
return redirect(decrypt($hash));
});
Route::get('phantom/{entity}/{invitation_key}', [Phantom::class, 'displayInvitation'])->middleware(['invite_db', 'phantom_secret'])->name('phantom_view'); Route::get('phantom/{entity}/{invitation_key}', [Phantom::class, 'displayInvitation'])->middleware(['invite_db', 'phantom_secret'])->name('phantom_view');
Route::get('blade/', [Phantom::class, 'blade'])->name('blade'); Route::get('blade/', [Phantom::class, 'blade'])->name('blade');