1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-10 05:02:36 +01:00

Merge pull request #6750 from turbo124/v5-develop

PDF Performance improvements.
This commit is contained in:
David Bomba 2021-09-30 13:51:13 +10:00 committed by GitHub
commit 1bb88a702a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 347 additions and 72 deletions

View File

@ -0,0 +1,101 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper;
class ClientRegistrationFields
{
public static function generate()
{
$data =
[
[
'key' => 'first_name',
'required' => true
],
[
'key' => 'last_name',
'required' => true
],
[
'key' => 'email',
'required' => true
],
[
'key' => 'phone',
'required' => true
],
[
'key' => 'password',
'required' => true
],
[
'key' => 'name',
'required' => false
],
[
'key' => 'website',
'required' => false
],
[
'key' => 'address1',
'required' => false
],
[
'key' => 'address2',
'required' => false
],
[
'key' => 'city',
'required' => false
],
[
'key' => 'state',
'required' => false
],
[
'key' => 'postal_code',
'required' => false
],
[
'key' => 'country_id',
'required' => false
],
[
'key' => 'custom_value1',
'required' => false
],
[
'key' => 'custom_value2',
'required' => false
],
[
'key' => 'custom_value3',
'required' => false
],
[
'key' => 'custom_value4',
'required' => false
],
[
'key' => 'public_notes',
'required' => false
],
[
'key' => 'vat_number',
'required' => false
],
];
return $data;
}
}

View File

@ -11,6 +11,7 @@
namespace App\Factory;
use App\DataMapper\ClientRegistrationFields;
use App\DataMapper\CompanySettings;
use App\Libraries\MultiDB;
use App\Models\Company;
@ -35,7 +36,8 @@ class CompanyFactory
$company->db = config('database.default');
//$company->custom_fields = (object) ['invoice1' => '1', 'invoice2' => '2', 'client1'=>'3'];
$company->custom_fields = (object) [];
$company->client_registration_fields = ClientRegistrationFields::generate();
if(Ninja::isHosted())
$company->subdomain = MultiDB::randomSubdomainGenerator();
else

View File

@ -746,6 +746,8 @@ class BaseController extends Controller
//pass referral code to front end
$data['rc'] = request()->has('rc') ? request()->input('rc') : '';
$data['build'] = request()->has('build') ? request()->input('build') : '';
$data['login'] = request()->has('login') ? request()->input('login') : "false";
$data['user_agent'] = request()->server('HTTP_USER_AGENT');
$data['path'] = $this->setBuild();

View File

@ -24,6 +24,7 @@ use App\Models\CompanyToken;
use App\Utils\Ninja;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\App;
@ -231,10 +232,23 @@ class MigrationController extends BaseController
* @return \Illuminate\Http\JsonResponse|void
*/
public function startMigration(Request $request)
{
nlog("Starting Migration");
{
$companies = json_decode($request->companies);
// v4 Laravel 6
// $companies = [];
// foreach($request->all() as $input){
// if($input instanceof UploadedFile)
// nlog('is file');
// else
// $companies[] = json_decode($input);
// }
nlog("Starting Migration");
$companies = json_decode($request->companies,1);
if (app()->environment() === 'local') {
nlog($request->all());
@ -250,19 +264,31 @@ class MigrationController extends BaseController
} finally {
// Controller logic here
foreach ($companies as $company) {
$is_valid = $request->file($company->company_index)->isValid();
foreach($companies as $company)
{
if (!$is_valid) {
continue;
}
$company = (array)$company;
// v4 Laravel 6
// $input = $request->all();
// foreach ($input as $company) {
// if($company instanceof UploadedFile)
// continue;
// else
// $company = json_decode($company,1);
// if (!$company || !is_int($company['company_index'] || !$request->file($company['company_index'])->isValid())) {
// continue;
// }
$user = auth()->user();
$company_count = $user->account->companies()->count();
// Look for possible existing company (based on company keys).
$existing_company = Company::whereRaw('BINARY `company_key` = ?', [$company->company_key])->first();
$existing_company = Company::whereRaw('BINARY `company_key` = ?', [$company['company_key']])->first();
App::forgetInstance('translator');
$t = app('translator');
@ -291,7 +317,7 @@ class MigrationController extends BaseController
$checks = [
'existing_company' => $existing_company ? (bool)1 : false,
'force' => property_exists($company, 'force') ? (bool) $company->force : false,
'force' => array_key_exists('force', $company) ? (bool) $company['force'] : false,
];
// If there's existing company and ** no ** force is provided - skip migration.
@ -378,10 +404,10 @@ class MigrationController extends BaseController
]);
}
$migration_file = $request->file($company->company_index)
$migration_file = $request->file($company['company_index'])
->storeAs(
'migrations',
$request->file($company->company_index)->getClientOriginalName(),
$request->file($company['company_index'])->getClientOriginalName(),
'public'
);

View File

@ -12,6 +12,7 @@
namespace App\Http\Controllers;
use App\DataMapper\Analytics\EmailBounce;
use App\DataMapper\Analytics\Mail\EmailBounce;
use App\DataMapper\Analytics\Mail\EmailSpam;
use App\Jobs\Util\SystemLogger;
use App\Libraries\MultiDB;
@ -74,7 +75,6 @@ class PostMarkController extends BaseController
if($request->header('X-API-SECURITY') && $request->header('X-API-SECURITY') == config('postmark.secret'))
{
// nlog($request->all());
MultiDB::findAndSetDbByCompanyKey($request->input('Tag'));
@ -165,13 +165,13 @@ class PostMarkController extends BaseController
$this->invitation->email_status = 'bounced';
$this->invitation->save();
// $bounce = new EmailBounce(
// $request->input('Tag'),
// $request->input('From'),
// $request->input('MessageID')
// );
$bounce = new EmailBounce(
$request->input('Tag'),
$request->input('From'),
$request->input('MessageID')
);
// LightLogs::create($bounce)->batch();
LightLogs::create($bounce)->batch();
SystemLogger::dispatch($request->all(), SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_BOUNCED, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client, $this->invitation->company);
}

View File

@ -212,7 +212,10 @@ class PreviewController extends BaseController
}
$entity_obj = $repo->save($request->all(), $entity_obj);
$entity_obj->service()->fillDefaults()->save();
if(!$request->has('entity_id'))
$entity_obj->service()->fillDefaults()->save();
$entity_obj->load('client');
App::forgetInstance('translator');

View File

@ -76,7 +76,7 @@ class SendingController extends Controller
}
Mail::to(config('ninja.contact.ninja_official_contact'))
->send(new SupportMessageSent($request->input('message'), $send_logs));
->send(new SupportMessageSent($request->all(), $send_logs));
return response()->json([
'success' => true,

View File

@ -44,6 +44,7 @@ class UpdateCompanyRequest extends Request
$rules['size_id'] = 'integer|nullable';
$rules['country_id'] = 'integer|nullable';
$rules['work_email'] = 'email|nullable';
// $rules['client_registration_fields'] = 'array';
if (isset($input['portal_mode']) && ($input['portal_mode'] == 'domain' || $input['portal_mode'] == 'iframe')) {
$rules['portal_domain'] = 'sometimes|url';

View File

@ -794,6 +794,7 @@ class CompanyImport implements ShouldQueue
['clients' => 'client_id'],
['projects' => 'project_id'],
['vendors' => 'vendor_id'],
['invoices' => 'invoice_id'],
],
'expenses',
'number');

View File

@ -11,6 +11,7 @@
namespace App\Jobs\Company;
use App\DataMapper\ClientRegistrationFields;
use App\DataMapper\CompanySettings;
use App\Libraries\MultiDB;
use App\Models\Company;
@ -62,7 +63,8 @@ class CreateCompany
$company->subdomain = isset($this->request['subdomain']) ? $this->request['subdomain'] : '';
$company->custom_fields = new \stdClass;
$company->default_password_timeout = 1800000;
$company->client_registration_fields = ClientRegistrationFields::generate();
if(Ninja::isHosted())
$company->subdomain = MultiDB::randomSubdomainGenerator();
else

View File

@ -132,7 +132,6 @@ class SendRecurring implements ShouldQueue
});
if ($invoice->client->getSetting('auto_bill_date') == 'on_send_date' && $invoice->auto_bill_enabled) {
nlog("attempting to autobill {$invoice->number}");
$invoice->service()->autoBill()->save();

View File

@ -13,13 +13,13 @@ class SupportMessageSent extends Mailable
{
// use Queueable, SerializesModels;
public $support_message;
public $data;
public $send_logs;
public function __construct($support_message, $send_logs)
public function __construct(array $data, $send_logs)
{
$this->support_message = $support_message;
$this->data = $data;
$this->send_logs = $send_logs;
}
@ -63,17 +63,19 @@ class SupportMessageSent extends Mailable
$user = auth()->user();
$db = str_replace("db-ninja-", "", $company->db);
$is_large = $company->is_large ? "L" : "S";
$platform = array_key_exists('platform', $this->data) ? $this->data['platform'] : "U";
$migrated = strlen($company->company_key) == 32 ? "M" : "";
if(Ninja::isHosted())
$subject = "{$priority}Hosted-{$db}-{$is_large} :: {$plan} :: ".date('M jS, g:ia');
$subject = "{$priority}Hosted-{$db}-{$is_large}{$platform}{$migrated} :: {$plan} :: ".date('M jS, g:ia');
else
$subject = "{$priority}Self Hosted :: {$plan} :: ".date('M jS, g:ia');
$subject = "{$priority}Self Hosted :: {$plan}{$platform} :: ".date('M jS, g:ia');
return $this->from(config('mail.from.address'), $user->present()->name())
->replyTo($user->email, $user->present()->name())
->subject($subject)
->view('email.support.message', [
'support_message' => $this->support_message,
'support_message' => $this->data['support_message'],
'system_info' => $system_info,
'laravel_log' => $log_lines,
'logo' => $company->present()->logo(),

View File

@ -96,6 +96,7 @@ class Company extends BaseModel
'show_task_end_date',
'use_comma_as_decimal_place',
'report_include_drafts',
'client_registration_fields',
];
protected $hidden = [
@ -111,6 +112,7 @@ class Company extends BaseModel
'updated_at' => 'timestamp',
'created_at' => 'timestamp',
'deleted_at' => 'timestamp',
'client_registration_fields' => 'array',
];
protected $with = [
@ -485,7 +487,7 @@ class Company extends BaseModel
{
if (Ninja::isHosted()) {
if($this->portal_mode == 'domain')
if($this->portal_mode == 'domain' && strlen($this->portal_domain) > 3)
return $this->portal_domain;
return "https://{$this->subdomain}." . config('ninja.app_domain');

View File

@ -85,7 +85,7 @@ class Gateway extends StaticModel
return [GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => true]];//eWay
break;
case 11:
return [GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => false]];//Payfast
return [GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => true]];//Payfast
break;
case 7:
return [

View File

@ -119,6 +119,15 @@ class BraintreePaymentDriver extends BaseDriver
]);
if ($result->success) {
$address = $this->gateway->address()->create([
'customerId' => $result->customer->id,
'firstName' => $this->client->present()->name,
'streetAddress' => $this->client->address1,
'postalCode' => $this->client->postal_code,
'countryCodeAlpha2' => $this->client->country ? $this->client->country->iso_3166_2 : '',
]);
return $result->customer;
}
}

View File

@ -77,36 +77,47 @@ class Token
$amount = array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total;
$amount = round(($amount * pow(10, $this->payfast->client->currency()->precision)),0);
// $header =[
// 'merchant-id' => $this->payfast->company_gateway->getConfigField('merchantId'),
// 'timestamp' => now()->format('c'),
// 'version' => 'v1',
// ];
$header =[
'merchant-id' => $this->payfast->company_gateway->getConfigField('merchantId'),
'version' => 'v1',
'timestamp' => now()->format('c'),
];
// $body = [
// 'amount' => $amount,
// 'item_name' => 'purchase',
// 'item_description' => ctrans('texts.invoices') . ': ' . collect($payment_hash->invoices())->pluck('invoice_number'),
// // 'm_payment_id' => $payment_hash->hash,
// ];
$body = [
'amount' => $amount,
'item_name' => 'purchase',
'item_description' => ctrans('texts.invoices') . ': ' . collect($payment_hash->invoices())->pluck('invoice_number'),
'm_payment_id' => $payment_hash->hash,
];
nlog(array_merge($body, $header));
// $header['signature'] = md5( $this->generate_parameter_string(array_merge($header, $body), false) );
// $result = $this->send($header, $body, $cgt->token);
$api = new \PayFast\PayFastApi(
[
'merchantId' => $this->payfast->company_gateway->getConfigField('merchantId'),
'passPhrase' => $this->payfast->company_gateway->getConfigField('passPhrase'),
'testMode' => $this->payfast->company_gateway->getConfigField('testMode')
]
);
$header['signature'] = $this->payfast->generateTokenSignature(array_merge($body, $header));
$adhocArray = $api
->subscriptions
->adhoc($cgt->token, ['amount' => $amount, 'item_name' => 'purchase']);
nlog($header['signature']);
$result = $this->send($header, $body, $cgt->token);
nlog($result);
// $api = new \PayFast\PayFastApi(
// [
// 'merchantId' => $this->payfast->company_gateway->getConfigField('merchantId'),
// 'passPhrase' => $this->payfast->company_gateway->getConfigField('passPhrase'),
// 'testMode' => $this->payfast->company_gateway->getConfigField('testMode')
// ]
// );
// $adhocArray = $api
// ->subscriptions
// ->adhoc($cgt->token, ['amount' => $amount, 'item_name' => 'purchase']);
// nlog($adhocArray);
nlog($adhocArray);
// /*Refactor and push to BaseDriver*/
// if ($data['response'] != null && $data['response']->getMessages()->getResultCode() == 'Ok') {
@ -151,8 +162,8 @@ class Token
protected function generate_parameter_string( $api_data, $sort_data_before_merge = true, $skip_empty_values = true ) {
// if sorting is required the passphrase should be added in before sort.
if ( ! empty( $this->payfast->company_gateway->getConfigField('passPhrase') ) && $sort_data_before_merge )
$api_data['passphrase'] = $this->payfast->company_gateway->getConfigField('passPhrase');
if ( ! empty( $this->payfast->company_gateway->getConfigField('passphrase') ) && $sort_data_before_merge )
$api_data['passphrase'] = $this->payfast->company_gateway->getConfigField('passphrase');
if ( $sort_data_before_merge ) {
ksort( $api_data );
@ -175,7 +186,7 @@ class Token
if ( $sort_data_before_merge ) {
$parameter_string = rtrim( $parameter_string, '&' );
} elseif ( ! empty( $this->pass_phrase ) ) {
$parameter_string .= 'passphrase=' . urlencode( $this->payfast->company_gateway->getConfigField('passPhrase') );
$parameter_string .= 'passphrase=' . urlencode( $this->payfast->company_gateway->getConfigField('passphrase') );
} else {
$parameter_string = rtrim( $parameter_string, '&' );
}
@ -199,6 +210,8 @@ class Token
}
}
nlog(http_build_query($fields));
return md5(http_build_query($fields));
}

View File

@ -28,7 +28,7 @@ class PayFastPaymentDriver extends BaseDriver
public $refundable = false; //does this gateway support refunds?
public $token_billing = false; //does this gateway support token billing?
public $token_billing = true; //does this gateway support token billing?
public $can_authorise_credit_card = true; //does this gateway support authorizations?
@ -72,7 +72,7 @@ class PayFastPaymentDriver extends BaseDriver
[
'merchantId' => $this->company_gateway->getConfigField('merchantId'),
'merchantKey' => $this->company_gateway->getConfigField('merchantKey'),
'passPhrase' => $this->company_gateway->getConfigField('passPhrase'),
'passPhrase' => $this->company_gateway->getConfigField('passphrase'),
'testMode' => $this->company_gateway->getConfigField('testMode')
]
);
@ -123,7 +123,39 @@ class PayFastPaymentDriver extends BaseDriver
return (new Token($this))->tokenBilling($cgt, $payment_hash);
}
public function generateSignature($data)
public function generateTokenSignature($data)
{
$fields = [];
$keys = [
'merchant-id',
'version',
'timestamp',
'amount',
'item_name',
'item_description',
'itn',
'm_payment_id',
'cc_css',
'split_payment'
];
foreach($keys as $key)
{
if (!empty($data[$key])) {
$fields[$key] = $data[$key];
}
}
if($this->company_gateway->getConfigField('passphrase'))
$fields['passphrase'] = $this->company_gateway->getConfigField('passphrase');
nlog(http_build_query($fields));
return md5(http_build_query($fields));
}
public function generateSignature($data)
{
$fields = array();

View File

@ -80,10 +80,10 @@ class ClientContactRepository extends BaseRepository
});
//need to reload here to shake off stale contacts
$client->load('contacts');
$client->fresh();
//always made sure we have one blank contact to maintain state
if ($client->contacts->count() == 0) {
if ($client->contacts()->count() == 0) {
$new_contact = ClientContactFactory::create($client->company_id, $client->user_id);
$new_contact->client_id = $client->id;
$new_contact->contact_key = Str::random(40);

View File

@ -29,14 +29,25 @@ class InvoiceHistoryTransformer extends EntityTransformer
public function transform(?Backup $backup)
{
if(!$backup)
return [];
if(!$backup){
return [
'id' => '',
'activity_id' => '',
'json_backup' => (string) '',
'html_backup' => (string) '',
'amount' => (float) 0,
'created_at' => (int) 0,
'updated_at' => (int) 0,
];
}
return [
'id' => $this->encodePrimaryKey($backup->id),
'activity_id' => $this->encodePrimaryKey($backup->activity_id),
'json_backup' => (string) $backup->json_backup ?: '',
'html_backup' => (string) $backup->html_backup ?: '',
'json_backup' => (string) '',
'html_backup' => (string) '',
'amount' => (float) $backup->amount,
'created_at' => (int) $backup->created_at,
'updated_at' => (int) $backup->updated_at,

View File

@ -101,6 +101,7 @@ class RecurringExpenseTransformer extends EntityTransformer
'remaining_cycles' => (int) $recurring_expense->remaining_cycles,
'last_sent_date' => $recurring_expense->last_sent_date ?: '',
'next_send_date' => $recurring_expense->next_send_date ?: '',
'recurring_dates' => (array) [],
];
if(request()->has('show_dates') && request()->query('show_dates') == 'true')

View File

@ -127,7 +127,7 @@ class RecurringInvoiceTransformer extends EntityTransformer
'due_date_days' => (string) $invoice->due_date_days ?: '',
'paid_to_date' => (float) $invoice->paid_to_date,
'subscription_id' => (string)$this->encodePrimaryKey($invoice->subscription_id),
'recurring_dates' => (array) [],
];

View File

@ -158,6 +158,7 @@ class HtmlEngine
$data['$view_link'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_quote').'</a>', 'label' => ctrans('texts.view_quote')];
$data['$viewLink'] = &$data['$view_link'];
$data['$viewButton'] = &$data['$view_link'];
$data['$view_button'] = &$data['$view_link'];
$data['$approveButton'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_quote').'</a>', 'label' => ctrans('texts.approve')];
$data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_quote')];
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: '&nbsp;', 'label' => ctrans('texts.quote_date')];
@ -171,6 +172,7 @@ class HtmlEngine
$data['$terms'] = &$data['$entity.terms'];
$data['$view_link'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_credit').'</a>', 'label' => ctrans('texts.view_credit')];
$data['$viewButton'] = &$data['$view_link'];
$data['$view_button'] = &$data['$view_link'];
$data['$viewLink'] = &$data['$view_link'];
$data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_credit')];
// $data['$view_link'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_credit')];

View File

@ -48,7 +48,7 @@ trait Inviteable
$entity_type = Str::snake(class_basename($this->entityType()));
if(Ninja::isHosted()){
$domain = isset($this->company->portal_domain) ? $this->company->portal_domain : $this->company->domain();
$domain = $this->company->domain();
}
else
$domain = config('ninja.app_url');
@ -75,7 +75,7 @@ trait Inviteable
{
if(Ninja::isHosted())
$domain = isset($this->company->portal_domain) ? $this->company->portal_domain : $this->company->domain();
$domain = $this->company->domain();
else
$domain = config('ninja.app_url');

View File

@ -0,0 +1,66 @@
<?php
use App\DataMapper\ClientRegistrationFields;
use App\Models\Company;
use App\Models\Currency;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddRequiredClientRegistrationFields extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('companies', function (Blueprint $table) {
$table->mediumText('client_registration_fields')->nullable();
});
Company::all()->each(function ($company){
$company->update(['client_registration_fields' => ClientRegistrationFields::generate()]);
});
Model::unguard();
$currencies = [
['id' => 111, 'name' => 'Cuban Peso','code' => 'CUP', 'symbol' => '₱', 'precision' => '2','thousand_separator' => ',','decimal_separator' => '.'],
];
foreach ($currencies as $currency) {
$record = Currency::whereCode($currency['code'])->first();
if ($record) {
$record->name = $currency['name'];
$record->symbol = $currency['symbol'];
$record->precision = $currency['precision'];
$record->thousand_separator = $currency['thousand_separator'];
$record->decimal_separator = $currency['decimal_separator'];
if (isset($currency['swap_currency_symbol'])) {
$record->swap_currency_symbol = $currency['swap_currency_symbol'];
}
$record->save();
} else {
Currency::create($currency);
}
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html data-report-errors="{{ $report_errors }}" data-rc="{{ $rc }}" data-user-agent="{{ $user_agent }}">
<html data-report-errors="{{ $report_errors }}" data-rc="{{ $rc }}" data-user-agent="{{ $user_agent }}" data-login="{{ $login }}">
<head>
<!-- Source: https://github.com/invoiceninja/invoiceninja -->
<!-- Version: {{ config('ninja.app_version') }} -->