diff --git a/app/DataMapper/CompanySettings.php b/app/DataMapper/CompanySettings.php index 0d0420db0884..36775506d080 100644 --- a/app/DataMapper/CompanySettings.php +++ b/app/DataMapper/CompanySettings.php @@ -169,6 +169,36 @@ class CompanySettings extends BaseSettings public $email_signature = ''; public $enable_email_markup = true; + + public $email_subject_custom1 = ''; + public $email_subject_custom2 = ''; + public $email_subject_custom3 = ''; + + public $email_template_custom1 = ''; + public $email_template_custom2 = ''; + public $email_template_custom3 = ''; + + public $enable_reminder1 = false; + public $enable_reminder2 = false; + public $enable_reminder3 = false; + + public $num_days_reminder1 = 0; + public $num_days_reminder2 = 0; + public $num_days_reminder3 = 0; + + public $schedule_reminder1 = ''; // (enum: after_invoice_date, before_due_date, after_due_date) + public $schedule_reminder2 = ''; // (enum: after_invoice_date, before_due_date, after_due_date) + public $schedule_reminder3 = ''; // (enum: after_invoice_date, before_due_date, after_due_date) + + public $late_fee_amount1 = ''; + public $late_fee_amount2 = ''; + public $late_fee_amount3 = ''; + + public $endless_reminder_frequency_id = '0'; + + public $client_online_payment_notification = true; + public $client_manual_payment_notification = true; + /* Company Meta data that we can use to build sub companies*/ public $name = ''; @@ -187,8 +217,8 @@ class CompanySettings extends BaseSettings public $page_size = 'A4'; public $font_size = 9; - public $primary_font = 'roboto'; - public $secondary_font = 'roboto'; + public $primary_font = 'Roboto'; + public $secondary_font = 'Roboto'; public $hide_paid_to_date = false; public $embed_documents = false; public $all_pages_header = true; @@ -196,6 +226,27 @@ class CompanySettings extends BaseSettings public static $casts = [ + 'email_subject_custom1' => 'string', + 'email_subject_custom2' => 'string', + 'email_subject_custom3' => 'string', + 'email_template_custom1' => 'string', + 'email_template_custom2' => 'string', + 'email_template_custom3' => 'string', + 'enable_reminder1' => 'bool', + 'enable_reminder2' => 'bool', + 'enable_reminder3' => 'bool', + 'num_days_reminder1' => 'int', + 'num_days_reminder2' => 'int', + 'num_days_reminder3' => 'int', + 'schedule_reminder1' => 'string', // (enum: after_invoice_date, before_due_date, after_due_date) + 'schedule_reminder2' => 'string', // (enum: after_invoice_date, before_due_date, after_due_date) + 'schedule_reminder3' => 'string', // (enum: after_invoice_date, before_due_date, after_due_date) + 'late_fee_amount1' => 'string', + 'late_fee_amount2' => 'string', + 'late_fee_amount3' => 'string', + 'endless_reminder_frequency_id' => 'integer', + 'client_online_payment_notification' => 'bool', + 'client_manual_payment_notification' => 'bool', 'document_email_attachment' => 'bool', 'enable_client_portal_password' => 'bool', 'enable_email_markup' => 'bool', diff --git a/app/Factory/UserFactory.php b/app/Factory/UserFactory.php index f246e85f3347..09ebee1395dd 100644 --- a/app/Factory/UserFactory.php +++ b/app/Factory/UserFactory.php @@ -27,7 +27,7 @@ class UserFactory $user->failed_logins = 0; $user->signature = ''; $user->theme_id = 0; - + return $user; } } \ No newline at end of file diff --git a/app/Http/Controllers/Auth/ContactLoginController.php b/app/Http/Controllers/Auth/ContactLoginController.php index 19db5828a23d..10af2c12bd73 100644 --- a/app/Http/Controllers/Auth/ContactLoginController.php +++ b/app/Http/Controllers/Auth/ContactLoginController.php @@ -63,13 +63,13 @@ class ContactLoginController extends Controller public function authenticated(Request $request, ClientContact $client) { - + Auth::guard('contact')->login($client, true); if(session()->get('url.intended')) return redirect(session()->get('url.intended')); - - return redirect()->intended(); + + return redirect(route('client.dashboard')); } public function logout() diff --git a/app/Http/Controllers/ClientPortal/InvitationController.php b/app/Http/Controllers/ClientPortal/InvitationController.php index e7e9aba211ad..425e5b9663b7 100644 --- a/app/Http/Controllers/ClientPortal/InvitationController.php +++ b/app/Http/Controllers/ClientPortal/InvitationController.php @@ -36,8 +36,6 @@ class InvitationController extends Controller if($invitation){ -\Log::error("bool val = ".boolval($invitation->contact->client->getSetting('enable_client_portal_password'))); - if((bool)$invitation->contact->client->getSetting('enable_client_portal_password') !== false) $this->middleware('auth:contact'); diff --git a/app/Http/Controllers/ClientPortal/RecurringInvoiceController.php b/app/Http/Controllers/ClientPortal/RecurringInvoiceController.php index 399b26f023c3..73b4c6df7204 100644 --- a/app/Http/Controllers/ClientPortal/RecurringInvoiceController.php +++ b/app/Http/Controllers/ClientPortal/RecurringInvoiceController.php @@ -14,10 +14,13 @@ namespace App\Http\Controllers\ClientPortal; use App\Http\Controllers\Controller; use App\Http\Requests\ClientPortal\ShowRecurringInvoiceRequest; use App\Models\RecurringInvoice; +use App\Notifications\ClientContactRequestCancellation; +use App\Notifications\ClientContactResetPassword; use App\Utils\Traits\MakesHash; use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Mail; +use Illuminate\Support\Facades\Notification; use Yajra\DataTables\Facades\DataTables; use Yajra\DataTables\Html\Builder; @@ -93,9 +96,8 @@ class RecurringInvoiceController extends Controller ]; //todo double check the user is able to request a cancellation - - Mail::to(config('ninja.contact.ninja_official_contact')) - ->send(new RecurringCancellationRequest($invoice)); + //can add locale specific by chaining ->locale(); + $recurring_invoice->user->notify(new ClientContactRequestCancellation($recurring_invoice, auth()->user())); return view('portal.default.recurring_invoices.request_cancellation', $data); diff --git a/app/Http/Controllers/OpenAPI/CompanySettingsSchema.php b/app/Http/Controllers/OpenAPI/CompanySettingsSchema.php index bf1a36291320..aca9287e16e1 100644 --- a/app/Http/Controllers/OpenAPI/CompanySettingsSchema.php +++ b/app/Http/Controllers/OpenAPI/CompanySettingsSchema.php @@ -139,11 +139,31 @@ * @OA\Property(property="enable_email_markup", type="boolean", example=false, description="____________"), * @OA\Property(property="enable_client_portal_dashboard", type="boolean", example=false, description="____________"), * @OA\Property(property="enable_client_portal", type="boolean", example=false, description="____________"), - * @OA\Property(property="email_template_statement", type="string", example='template matter', description="____________"), - * @OA\Property(property="email_subject_statement", type="string", example='subject matter', description="____________"), + * @OA\Property(property="email_template_statement", type="string", example="template matter", description="____________"), + * @OA\Property(property="email_subject_statement", type="string", example="subject matter", description="____________"), * @OA\Property(property="signature_on_pdf", type="boolean", example=false, description="____________"), * @OA\Property(property="send_portal_password", type="boolean", example=false, description="____________"), - * @OA\Property(property="quote_footer", type="string", example='the quote footer', description="____________"), + * @OA\Property(property="quote_footer", type="string", example="the quote footer", description="____________"), + * @OA\Property(property="email_subject_custom1", type="string", example="Custom Subject 1", description="____________"), + * @OA\Property(property="email_subject_custom2", type="string", example="Custom Subject 2", description="____________"), + * @OA\Property(property="email_subject_custom3", type="string", example="Custom Subject 3", description="____________"), + * @OA\Property(property="email_template_custom1", type="string", example="", description="____________"), + * @OA\Property(property="email_template_custom2", type="string", example="", description="____________"), + * @OA\Property(property="email_template_custom3", type="string", example="", description="____________"), + * @OA\Property(property="enable_reminder1", type="boolean", example=false, description="____________"), + * @OA\Property(property="enable_reminder2", type="boolean", example=false, description="____________"), + * @OA\Property(property="enable_reminder3", type="boolean", example=false, description="____________"), + * @OA\Property(property="num_days_reminder1", type="number", example="9", description="The Reminder interval"), + * @OA\Property(property="num_days_reminder2", type="number", example="9", description="The Reminder interval"), + * @OA\Property(property="num_days_reminder3", type="number", example="9", description="The Reminder interval"), + * @OA\Property(property="schedule_reminder1", type="string", example="after_invoice_date", description="(enum: after_invoice_date, before_due_date, after_due_date)"), + * @OA\Property(property="schedule_reminder2", type="string", example="after_invoice_date", description="(enum: after_invoice_date, before_due_date, after_due_date)"), + * @OA\Property(property="schedule_reminder3", type="string", example="after_invoice_date", description="(enum: after_invoice_date, before_due_date, after_due_date)"), + * @OA\Property(property="late_fee_amount1", type="string", example="10.00", description="____________"), + * @OA\Property(property="late_fee_amount2", type="string", example="20.00", description="____________"), + * @OA\Property(property="late_fee_amount3", type="string", example="100.00", description="____________"), + * @OA\Property(property="endless_reminder_frequency_id", type="string", example="1", description="____________"), + * @OA\Property(property="client_online_payment_notification", type="boolean", example=false, description="____________"), + * @OA\Property(property="client_manual_payment_notification", type="boolean", example=false, description="____________"), * ) - */ - + */ \ No newline at end of file diff --git a/app/Http/Controllers/OpenAPI/TemplateSchema.php b/app/Http/Controllers/OpenAPI/TemplateSchema.php new file mode 100644 index 000000000000..4ff9ae1ea57f --- /dev/null +++ b/app/Http/Controllers/OpenAPI/TemplateSchema.php @@ -0,0 +1,8 @@ +", description="The template HTML"), + * ) + */ diff --git a/app/Http/Controllers/TemplateController.php b/app/Http/Controllers/TemplateController.php new file mode 100644 index 000000000000..ddd4ef52b401 --- /dev/null +++ b/app/Http/Controllers/TemplateController.php @@ -0,0 +1,135 @@ +json(request()->all(), 200); + } + + /** + * Returns a template filled with entity variables + * + * @return \Illuminate\Http\Response + * + * @OA\Get( + * path="/api/v1/templates/{entity}/{entity_id}", + * operationId="getShowTemplate", + * tags={"templates"}, + * summary="Returns a entity template with the template variables replaced with the Entities", + * description="Returns a blank HTML entity temlpate", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter( + * name="entity", + * in="path", + * description="The Entity (invoice,quote,recurring_invoice)", + * example="invoice", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Parameter( + * name="entity_id", + * in="path", + * description="The Entity ID", + * example="X9f87dkf", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="The template response", + * @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/Template"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ + public function show($entity, $entity_id) + { + return response()->json(request()->all(), 200); + } + +} \ No newline at end of file diff --git a/app/Mail/RecurringCancellationRequest.php b/app/Mail/RecurringCancellationRequest.php deleted file mode 100644 index 5af596937821..000000000000 --- a/app/Mail/RecurringCancellationRequest.php +++ /dev/null @@ -1,35 +0,0 @@ -recurring_invoice = $recurring_invoice - } - - /** - * Build the message. - * - * @return $this - */ - public function build() - { - return $this->view('view.name'); - } -} diff --git a/app/Models/User.php b/app/Models/User.php index f446fe411d55..4afd002eed2c 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -27,10 +27,11 @@ use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Illuminate\Support\Carbon; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Log; use Laracasts\Presenter\PresentableTrait; -class User extends Authenticatable implements MustVerifyEmail,HasLocalePreference +class User extends Authenticatable implements MustVerifyEmail { use Notifiable; use SoftDeletes; @@ -140,6 +141,7 @@ class User extends Authenticatable implements MustVerifyEmail,HasLocalePreferenc */ public function setCompany($company) { + \Log::error('setting company'); $this->company = $company; } @@ -161,6 +163,14 @@ class User extends Authenticatable implements MustVerifyEmail,HasLocalePreferenc return $this->getCompany(); } + private function setCompanyByGuard() + { + + if(Auth::guard('contact')->check()) + $this->setCompany(auth()->user()->client->company); + + } + /** * Returns the pivot tables for Company / User * @@ -319,6 +329,7 @@ class User extends Authenticatable implements MustVerifyEmail,HasLocalePreferenc public function getEmailVerifiedAt() { + if($this->email_verified_at) return Carbon::parse($this->email_verified_at)->timestamp; else @@ -335,9 +346,16 @@ class User extends Authenticatable implements MustVerifyEmail,HasLocalePreferenc public function preferredLocale() { + \Log::error(print_r($this->company(),1)); + $lang = Language::find($this->company()->settings->language_id); return $lang->locale; } + + public function routeNotificationForMail($notification) + { + return $this->email; + } } diff --git a/app/Notifications/ClientContactRequestCancellation.php b/app/Notifications/ClientContactRequestCancellation.php new file mode 100644 index 000000000000..6bf6549efc76 --- /dev/null +++ b/app/Notifications/ClientContactRequestCancellation.php @@ -0,0 +1,117 @@ +recurring_invoice = $recurring_invoice; + $this->client_contact = $client_contact; + } + + /** + * Get the notification's delivery channels. + * + * @param mixed $notifiable + * @return array + */ + public function via($notifiable) + { + return ['mail']; + } + + /** + * Get the mail representation of the notification. + * + * @param mixed $notifiable + * @return \Illuminate\Notifications\Messages\MailMessage + */ + public function toMail($notifiable) + { + if (static::$toMailCallback) { + return call_user_func(static::$toMailCallback, $notifiable, $this->client_contact); + } + + + $client_contact_name = $this->client_contact->present()->name(); + $client_name = $this->client_contact->client->present()->name(); + $recurring_invoice_number = $this->recurring_invoice->invoice_number; + + + return (new MailMessage) + ->subject('Request for recurring invoice cancellation from '.$client_contact_name) + ->markdown('email.support.cancellation', [ + 'message' => "Contact {$client_contact_name} from client {$client_name} requested to cancel Recurring Invoice #{$recurring_invoice_number}", + ]); + + } + + /** + * Get the array representation of the notification. + * + * @param mixed $notifiable + * @return array + */ + public function toArray($notifiable) + { + return [ + // + ]; + } + + public function toSlack($notifiable) + { + + $name = $this->client_contact->present()->name(); + $client_name = $this->client_contact->client->present()->name(); + $recurring_invoice_number = $this->recurring_invoice->invoice_number; + + return (new SlackMessage) + ->success() + ->to("#devv2") + ->from("System") + ->image('https://app.invoiceninja.com/favicon.png') + ->content("Contact {$name} from client {$client_name} requested to cancel Recurring Invoice #{$recurring_invoice_number}"); + } + + + /** + * Set a callback that should be used when building the notification mail message. + * + * @param \Closure $callback + * @return void + */ + public static function toMailUsing($callback) + { + static::$toMailCallback = $callback; + } +} diff --git a/app/Notifications/ClientContactResetPassword.php b/app/Notifications/ClientContactResetPassword.php index 62eb1b2c7f60..9a005116aab1 100644 --- a/app/Notifications/ClientContactResetPassword.php +++ b/app/Notifications/ClientContactResetPassword.php @@ -60,11 +60,11 @@ class ClientContactResetPassword extends Notification } return (new MailMessage) - ->subject(Lang::getFromJson('Reset Password Notification')) - ->line(Lang::getFromJson('You are receiving this email because we received a password reset request for your account.')) - ->action(Lang::getFromJson('Reset Password'), url(config('app.url').route('client.password.reset', ['token' => $this->token, 'email' => $notifiable->getEmailForPasswordReset()], false))) - ->line(Lang::getFromJson('This password reset link will expire in :count minutes.', ['count' => config('auth.passwords.users.expire')])) - ->line(Lang::getFromJson('If you did not request a password reset, no further action is required.')); + ->subject('Reset Password Notification') + ->line('You are receiving this email because we received a password reset request for your account.') + ->action('Reset Password', url(config('app.url').route('client.password.reset', ['token' => $this->token, 'email' => $notifiable->getEmailForPasswordReset()], false))) + ->line('This password reset link will expire in :count minutes.', ['count' => config('auth.passwords.users.expire')]) + ->line('If you did not request a password reset, no further action is required.'); } /** diff --git a/resources/views/email/support/cancellation.blade.php b/resources/views/email/support/cancellation.blade.php new file mode 100644 index 000000000000..507584f0c18e --- /dev/null +++ b/resources/views/email/support/cancellation.blade.php @@ -0,0 +1,30 @@ +@component('mail::layout') + +{{-- Header --}} +@slot('header') +@component('mail::header', ['url' => config('app.url')]) +Header Title +@endcomponent +@endslot + +{{-- Body --}} +{{ $message }} + +{{-- Subcopy --}} +@isset($subcopy) +@slot('subcopy') +@component('mail::subcopy') +{{ $subcopy }} +@endcomponent +@endslot +@endisset + + +{{-- Footer --}} +@slot('footer') +@component('mail::footer') +© {{ date('Y') }} {{ config('ninja.app_name') }}. +@endcomponent +@endslot + +@endcomponent diff --git a/routes/api.php b/routes/api.php index 28dc956d22c7..0d5201714657 100644 --- a/routes/api.php +++ b/routes/api.php @@ -82,6 +82,10 @@ Route::group(['middleware' => ['api_db','api_secret_check','token_auth'], 'prefi Route::resource('tax_rates', 'TaxRateController'); // name = (tasks. index / create / show / update / destroy / edit Route::post('refresh', 'Auth\LoginController@refresh'); + + Route::get('templates/{entity}/create', 'TemplateController@create')->name('templates.create'); + Route::get('templates/{entity}/{entity_id}', 'TemplateController@show')->name('templates.show'); + /* Route::resource('tasks', 'TaskController'); // name = (tasks. index / create / show / update / destroy / edit