diff --git a/app/DataMapper/EmailTemplateDefaults.php b/app/DataMapper/EmailTemplateDefaults.php index 6302fdfd9fdb..40163be8f58d 100644 --- a/app/DataMapper/EmailTemplateDefaults.php +++ b/app/DataMapper/EmailTemplateDefaults.php @@ -34,7 +34,7 @@ class EmailTemplateDefaults return self::emailPaymentTemplate(); break; case 'email_template_payment_partial': - return self::emailPaymentTemplate(); + return self::emailPaymentPartialTemplate(); break; case 'email_template_statement': return self::emailStatementTemplate(); @@ -73,7 +73,7 @@ class EmailTemplateDefaults return self::emailPaymentSubject(); break; case 'email_subject_payment_partial': - return self::emailPaymentSubject(); + return self::emailPaymentPartialSubject(); break; case 'email_subject_statement': return self::emailStatementSubject(); @@ -140,13 +140,11 @@ class EmailTemplateDefaults ]); return $converter->convertToHtml(self::transformText('quote_message')); - //return Parsedown::instance()->line(self::transformText('quote_message')); } public static function emailPaymentSubject() { return ctrans('texts.payment_subject'); - //return Parsedown::instance()->line(self::transformText('payment_subject')); } public static function emailPaymentTemplate() @@ -158,7 +156,11 @@ class EmailTemplateDefaults return $converter->convertToHtml(self::transformText('payment_message')); - // return Parsedown::instance()->line(self::transformText('payment_message')); + } + + public static function emailPaymentPartialSubject() + { + return ctrans('texts.payment_subject'); } public static function emailReminder1Subject() @@ -201,17 +203,17 @@ class EmailTemplateDefaults public static function emailReminderEndlessTemplate() { - return Parsedown::instance()->line('Endless Email Reminder Text'); + return ctrans('Endless Email Reminder Text'); } public static function emailStatementSubject() { - return Parsedown::instance()->line('Statement Subject needs texts record!'); + return ctrans('Statement Subject needs texts record!'); } public static function emailStatementTemplate() { - return Parsedown::instance()->line('Statement Templates needs texts record!'); + return ctrans('Statement Templates needs texts record!'); } private static function transformText($string) diff --git a/app/Http/Controllers/BaseController.php b/app/Http/Controllers/BaseController.php index 81772a8beed4..a69893e728bf 100644 --- a/app/Http/Controllers/BaseController.php +++ b/app/Http/Controllers/BaseController.php @@ -433,6 +433,8 @@ class BaseController extends Controller $data['hash'] = md5(public_path('main.dart.js')); + $this->buildCache(); + return view('index.index', $data); } diff --git a/app/Http/Controllers/ClientPortal/InvitationController.php b/app/Http/Controllers/ClientPortal/InvitationController.php index e891f57691b7..7cb3c536d8e7 100644 --- a/app/Http/Controllers/ClientPortal/InvitationController.php +++ b/app/Http/Controllers/ClientPortal/InvitationController.php @@ -22,6 +22,7 @@ use App\Utils\Traits\MakesDates; use App\Utils\Traits\MakesHash; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Str; /** * Class InvitationController. @@ -33,9 +34,10 @@ class InvitationController extends Controller public function router(string $entity, string $invitation_key) { + $key = $entity.'_id'; - $entity_obj = 'App\Models\\'.ucfirst($entity).'Invitation'; + $entity_obj = 'App\Models\\'.ucfirst(Str::camel($entity)).'Invitation'; //todo sensitive to the route parameters here $invitation = $entity_obj::whereRaw('BINARY `key`= ?', [$invitation_key]) ->with('contact.client') diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 679f5cef0c7a..b7f4db0c3774 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -55,7 +55,7 @@ class Kernel extends HttpKernel 'throttle:60,1', 'bindings', 'query_logging', - \App\Http\Middleware\StartupCheck::class, + //\App\Http\Middleware\StartupCheck::class, \App\Http\Middleware\Cors::class, ], 'contact' => [ @@ -71,7 +71,7 @@ class Kernel extends HttpKernel \Illuminate\View\Middleware\ShareErrorsFromSession::class, \App\Http\Middleware\VerifyCsrfToken::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, - \App\Http\Middleware\StartupCheck::class, + //\App\Http\Middleware\StartupCheck::class, \App\Http\Middleware\QueryLogging::class, ], 'shop' => [ diff --git a/app/Http/Middleware/StartupCheck.php b/app/Http/Middleware/StartupCheck.php index acb9e53e0486..48f2517fdeb7 100644 --- a/app/Http/Middleware/StartupCheck.php +++ b/app/Http/Middleware/StartupCheck.php @@ -11,6 +11,7 @@ namespace App\Http\Middleware; +use App\DataMapper\EmailTemplateDefaults; use App\Models\Account; use App\Models\Language; use App\Utils\CurlUtils; @@ -30,7 +31,7 @@ class StartupCheck { /** * Handle an incoming request. - * + * @deprecated * @param Request $request * @param Closure $next * @@ -45,6 +46,7 @@ class StartupCheck foreach ($cached_tables as $name => $class) { if ($request->has('clear_cache') || ! Cache::has($name)) { + // check that the table exists in case the migration is pending if (! Schema::hasTable((new $class())->getTable())) { continue; @@ -65,8 +67,56 @@ class StartupCheck } } + + /*Build template cache*/ + if ($request->has('clear_cache') || ! Cache::has('templates')) + $this->buildTemplates(); + $response = $next($request); return $response; } + + + private function buildTemplates($name = 'templates') + { + $data = [ + + 'invoice' => [ + 'subject' => EmailTemplateDefaults::emailInvoiceSubject(), + 'body' => EmailTemplateDefaults::emailInvoiceTemplate(), + ], + + 'quote' => [ + 'subject' => EmailTemplateDefaults::emailQuoteSubject(), + 'body' => EmailTemplateDefaults::emailQuoteTemplate(), + ], + 'payment' => [ + 'subject' => EmailTemplateDefaults::emailPaymentSubject(), + 'body' => EmailTemplateDefaults::emailPaymentTemplate(), + ], + 'reminder1' => [ + 'subject' => EmailTemplateDefaults::emailReminder1Subject(), + 'body' => EmailTemplateDefaults::emailReminder1Template(), + ], + 'reminder2' => [ + 'subject' => EmailTemplateDefaults::emailReminder2Subject(), + 'body' => EmailTemplateDefaults::emailReminder2Template(), + ], + 'reminder3' => [ + 'subject' => EmailTemplateDefaults::emailReminder3Subject(), + 'body' => EmailTemplateDefaults::emailReminder3Template(), + ], + 'reminder_endless' => [ + 'subject' => EmailTemplateDefaults::emailReminderEndlessSubject(), + 'body' => EmailTemplateDefaults::emailReminderEndlessTemplate(), + ], + 'statement' => [ + 'subject' => EmailTemplateDefaults::emailStatementSubject(), + 'body' => EmailTemplateDefaults::emailStatementTemplate(), + ], + ]; + + Cache::forever($name, $data); + } } diff --git a/app/Models/Gateway.php b/app/Models/Gateway.php index 4ac87da2facf..725352c06ab1 100644 --- a/app/Models/Gateway.php +++ b/app/Models/Gateway.php @@ -100,7 +100,7 @@ class Gateway extends StaticModel * Returns an array of methods and the gatewaytypes possible * * @return array - */ + *///todo remove methods replace with gatewaytype:: and then nest refund / token billing public function getMethods() { switch ($this->id) { diff --git a/app/PaymentDrivers/CheckoutComPaymentDriver.php b/app/PaymentDrivers/CheckoutComPaymentDriver.php index 644885a42998..bf6b4aa42a30 100644 --- a/app/PaymentDrivers/CheckoutComPaymentDriver.php +++ b/app/PaymentDrivers/CheckoutComPaymentDriver.php @@ -53,6 +53,8 @@ class CheckoutComPaymentDriver extends BaseDriver /** Instance of \Checkout\CheckoutApi */ public $gateway; + public $payment_method; //the gateway type id + public static $methods = [ GatewayType::CREDIT_CARD => '', ]; @@ -100,6 +102,9 @@ class CheckoutComPaymentDriver extends BaseDriver */ public function viewForType($gateway_type_id) { + + $this->payment_method = $gateway_type_id; + if ($gateway_type_id == GatewayType::CREDIT_CARD) { return 'gateways.checkout.credit_card'; } @@ -355,13 +360,43 @@ class CheckoutComPaymentDriver extends BaseDriver public function saveCard($state) { + //some cards just can't be tokenized.... + if(!$state['payment_response']->source['id']) + return; + + // [id] => src_hck5nsv3fljehbam2cvdm7fioa + // [type] => card + // [expiry_month] => 10 + // [expiry_year] => 2022 + // [scheme] => Visa + // [last4] => 4242 + // [fingerprint] => 688192847DB9AE8A26C53776D036D5B8AD2CEAF1D5A8F5475F542B021041EFA1 + // [bin] => 424242 + // [card_type] => Credit + // [card_category] => Consumer + // [issuer] => JPMORGAN CHASE BANK NA + // [issuer_country] => US + // [product_id] => A + // [product_type] => Visa Traditional + // [avs_check] => S + // [cvv_check] => Y + // [payouts] => 1 + // [fast_funds] => d + + $payment_meta = new \stdClass; + $payment_meta->exp_month = (string)$state['payment_response']->source['expiry_month']; + $payment_meta->exp_year = (string)$state['payment_response']->source['expiry_year']; + $payment_meta->brand = (string)$state['payment_response']->source['scheme']; + $payment_meta->last4 = (string)$state['payment_response']->source['last4']; + $payment_meta->type = $this->payment_method; + $company_gateway_token = new ClientGatewayToken(); $company_gateway_token->company_id = $this->client->company->id; $company_gateway_token->client_id = $this->client->id; $company_gateway_token->token = $state['payment_response']->source['id']; $company_gateway_token->company_gateway_id = $this->company_gateway->id; $company_gateway_token->gateway_type_id = $state['payment_method_id']; - $company_gateway_token->meta = $state['payment_response']->source; + $company_gateway_token->meta = $payment_meta; $company_gateway_token->save(); if ($this->client->gateway_tokens->count() == 1) { diff --git a/app/Services/Invoice/AutoBillInvoice.php b/app/Services/Invoice/AutoBillInvoice.php index 4d3b0781aee4..f94a4d98ef47 100644 --- a/app/Services/Invoice/AutoBillInvoice.php +++ b/app/Services/Invoice/AutoBillInvoice.php @@ -53,7 +53,8 @@ class AutoBillInvoice extends AbstractService if ((int)$this->invoice->balance == 0) return $this->invoice->service()->markPaid()->save(); - $this->applyCreditPayment(); //if the credits cover the payments, we stop here, build the payment with credits and exit early + //if the credits cover the payments, we stop here, build the payment with credits and exit early + $this->applyCreditPayment(); /* Determine $amount */ if ($this->invoice->partial > 0) @@ -70,7 +71,7 @@ class AutoBillInvoice extends AbstractService return $this->invoice; /* $gateway fee */ - $fee = $gateway_token->gateway->calcGatewayFee($this->invoice->partial); + $fee = $gateway_token->gateway->calcGatewayFee($amount); /* Build payment hash */ $payment_hash = PaymentHash::create([ @@ -98,7 +99,7 @@ class AutoBillInvoice extends AbstractService info("finalizing"); info(print_r($this->used_credit,1)); $amount = array_sum(array_column($this->used_credit, 'amount')); - info("amount {$amount}"); + info("amount {$amount}"); $payment = PaymentFactory::create($this->invoice->company_id, $this->invoice->user_id); $payment->amount = $amount; diff --git a/app/Services/Recurring/RecurringService.php b/app/Services/Recurring/RecurringService.php index 7cbfa0e8b163..56b75f5e679b 100644 --- a/app/Services/Recurring/RecurringService.php +++ b/app/Services/Recurring/RecurringService.php @@ -55,12 +55,19 @@ class RecurringService if($this->recurring_entity->remaining_cycles == 0) return $this; - $this->recurring_entity->status_id = RecurringInvoice::STATUS_ACTIVE; + $this->createInvitations()->setStatus(RecurringInvoice::STATUS_ACTIVE); return $this; } + public function setStatus($status) + { + $this->recurring_entity->status_id = $status; + + return $this; + } + /** * Applies the invoice number. * @return $this InvoiceService object diff --git a/app/Transformers/ClientGatewayTokenTransformer.php b/app/Transformers/ClientGatewayTokenTransformer.php index f084768bc2bc..1507d73a6ad8 100644 --- a/app/Transformers/ClientGatewayTokenTransformer.php +++ b/app/Transformers/ClientGatewayTokenTransformer.php @@ -35,11 +35,33 @@ class ClientGatewayTokenTransformer extends EntityTransformer 'gateway_type_id' => (string) $cgt->gateway_type_id ?: '', 'company_gateway_id' => (string) $this->encodePrimaryKey($cgt->company_gateway_id) ?: '', 'is_default' => (bool) $cgt->is_default, - 'meta' => $cgt->meta, + 'meta' => $this->typeCastMeta($cgt->meta), 'created_at' => (int) $cgt->created_at, 'updated_at' => (int) $cgt->updated_at, 'archived_at' => (int) $cgt->deleted_at, 'is_deleted' => (bool) $cgt->is_deleted, ]; } + + private function typeCastMeta($meta) + { + $casted = new \stdClass; + + if(property_exists($meta, 'exp_month')) + $casted->exp_month = (string)$meta->exp_month; + + if(property_exists($meta, 'exp_year')) + $casted->exp_year = (string)$meta->exp_year; + + if(property_exists($meta, 'brand')) + $casted->brand = (string)$meta->brand; + + if(property_exists($meta, 'last4')) + $casted->last4 = (string)$meta->last4; + + if(property_exists($meta, 'type')) + $casted->type = (int)$meta->type; + + return $casted; + } } diff --git a/app/Utils/HtmlEngine.php b/app/Utils/HtmlEngine.php index a1c75a71ef5b..3d3b3e64b9b4 100644 --- a/app/Utils/HtmlEngine.php +++ b/app/Utils/HtmlEngine.php @@ -121,7 +121,7 @@ class HtmlEngine $data['$entity.terms'] = ['value' => $this->entity->terms ?: ' ', 'label' => ctrans('texts.invoice_terms')]; $data['$terms'] = &$data['$entity.terms']; $data['$view_link'] = ['value' => ''.ctrans('texts.view_invoice').'', 'label' => ctrans('texts.view_invoice')]; - // $data['$view_link'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_invoice')]; + $data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_invoice')]; } if ($this->entity_string == 'quote') { diff --git a/app/Utils/Statics.php b/app/Utils/Statics.php index 9f78e42a16be..890d84af849e 100644 --- a/app/Utils/Statics.php +++ b/app/Utils/Statics.php @@ -104,6 +104,9 @@ class Statics })->sortBy(function ($currency) { return $currency->name; })->values(); + + $data['templates'] = Cache::get('templates'); + } return $data; diff --git a/app/Utils/Traits/AppSetup.php b/app/Utils/Traits/AppSetup.php index 358e4bc645f8..a1f5280e66ac 100644 --- a/app/Utils/Traits/AppSetup.php +++ b/app/Utils/Traits/AppSetup.php @@ -11,8 +11,11 @@ namespace App\Utils\Traits; +use App\DataMapper\EmailTemplateDefaults; use App\Utils\Ninja; use App\Utils\SystemHealth; +use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\Schema; trait AppSetup { @@ -26,4 +29,79 @@ trait AppSetup return $check['system_health'] == 'true'; } + + public function buildCache() + { + $cached_tables = config('ninja.cached_tables'); + + foreach ($cached_tables as $name => $class) { + if (request()->has('clear_cache') || ! Cache::has($name)) { + + // check that the table exists in case the migration is pending + if (! Schema::hasTable((new $class())->getTable())) { + continue; + } + if ($name == 'payment_terms') { + $orderBy = 'num_days'; + } elseif ($name == 'fonts') { + $orderBy = 'sort_order'; + } elseif (in_array($name, ['currencies', 'industries', 'languages', 'countries', 'banks'])) { + $orderBy = 'name'; + } else { + $orderBy = 'id'; + } + $tableData = $class::orderBy($orderBy)->get(); + if ($tableData->count()) { + Cache::forever($name, $tableData); + } + } + } + + /*Build template cache*/ + if (request()->has('clear_cache') || ! Cache::has('templates')) + $this->buildTemplates(); + } + + + private function buildTemplates($name = 'templates') + { + $data = [ + + 'invoice' => [ + 'subject' => EmailTemplateDefaults::emailInvoiceSubject(), + 'body' => EmailTemplateDefaults::emailInvoiceTemplate(), + ], + + 'quote' => [ + 'subject' => EmailTemplateDefaults::emailQuoteSubject(), + 'body' => EmailTemplateDefaults::emailQuoteTemplate(), + ], + 'payment' => [ + 'subject' => EmailTemplateDefaults::emailPaymentSubject(), + 'body' => EmailTemplateDefaults::emailPaymentTemplate(), + ], + 'reminder1' => [ + 'subject' => EmailTemplateDefaults::emailReminder1Subject(), + 'body' => EmailTemplateDefaults::emailReminder1Template(), + ], + 'reminder2' => [ + 'subject' => EmailTemplateDefaults::emailReminder2Subject(), + 'body' => EmailTemplateDefaults::emailReminder2Template(), + ], + 'reminder3' => [ + 'subject' => EmailTemplateDefaults::emailReminder3Subject(), + 'body' => EmailTemplateDefaults::emailReminder3Template(), + ], + 'reminder_endless' => [ + 'subject' => EmailTemplateDefaults::emailReminderEndlessSubject(), + 'body' => EmailTemplateDefaults::emailReminderEndlessTemplate(), + ], + 'statement' => [ + 'subject' => EmailTemplateDefaults::emailStatementSubject(), + 'body' => EmailTemplateDefaults::emailStatementTemplate(), + ], + ]; + + Cache::forever($name, $data); + } } diff --git a/composer.json b/composer.json index 7316737155a7..9fc290fc3d51 100644 --- a/composer.json +++ b/composer.json @@ -64,16 +64,17 @@ "laravel/ui": "^3.0" }, "require-dev": { - "wildbit/postmark-php": "^4.0", "anahkiasen/former": "^4.2", "barryvdh/laravel-debugbar": "^3.4", + "brianium/paratest": "^5.0", "darkaonline/l5-swagger": "^8.0", + "facade/ignition": "^2.3.6", "filp/whoops": "^2.7", + "fzaninotto/faker": "^1.9.1", "mockery/mockery": "^1.3.1", "nunomaduro/collision": "^5.0", "phpunit/phpunit": "^9.0", - "fzaninotto/faker": "^1.9.1", - "facade/ignition": "^2.3.6" + "wildbit/postmark-php": "^4.0" }, "autoload": { "psr-4": { diff --git a/cypress.json b/cypress.json index 79a612a766cb..92e52adf8c0c 100644 --- a/cypress.json +++ b/cypress.json @@ -1,5 +1,5 @@ { "video": false, - "baseUrl": "http://invoiceninja.wip/", + "baseUrl": "http://ninja.test:8000/", "chromeWebSecurity": false }