Merge branch 'v5-develop' into v5-2805-tracking-campaign-source

This commit is contained in:
Benjamin Beganović 2021-06-01 13:38:36 +02:00 committed by GitHub
commit bd97b916e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
69 changed files with 102665 additions and 100364 deletions

View File

@ -1 +1 @@
5.1.65 5.1.70

View File

@ -12,12 +12,12 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Jobs\Ninja\SendReminders; use App\Jobs\Ninja\SendReminders;
use App\Jobs\Util\WebHookHandler;
use App\Libraries\MultiDB; use App\Libraries\MultiDB;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\Quote; use App\Models\Quote;
use App\Models\Webhook; use App\Models\Webhook;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use App\Jobs\Util\WebhookHandler;
class SendRemindersCron extends Command class SendRemindersCron extends Command
{ {
@ -54,8 +54,8 @@ class SendRemindersCron extends Command
{ {
SendReminders::dispatchNow(); SendReminders::dispatchNow();
$this->webHookOverdueInvoices(); $this->webHookOverdueInvoices();
$this->webHookExpiredQuotes(); $this->webHookExpiredQuotes();
} }
private function webHookOverdueInvoices() private function webHookOverdueInvoices()
@ -90,6 +90,7 @@ class SendRemindersCron extends Command
$invoices->each(function ($invoice) { $invoices->each(function ($invoice) {
WebHookHandler::dispatch(Webhook::EVENT_LATE_INVOICE, $invoice, $invoice->company); WebHookHandler::dispatch(Webhook::EVENT_LATE_INVOICE, $invoice, $invoice->company);
}); });
$quotes = Quote::where('is_deleted', 0) $quotes = Quote::where('is_deleted', 0)

View File

@ -68,7 +68,7 @@ class CompanySettings extends BaseSettings
public $inclusive_taxes = false; //@implemented public $inclusive_taxes = false; //@implemented
public $quote_footer = ''; //@implmented public $quote_footer = ''; //@implmented
public $translations; //@TODO not used anywhere public $translations;
public $counter_number_applied = 'when_saved'; // when_saved , when_sent //@implemented public $counter_number_applied = 'when_saved'; // when_saved , when_sent //@implemented
public $quote_number_applied = 'when_saved'; // when_saved , when_sent //@implemented public $quote_number_applied = 'when_saved'; // when_saved , when_sent //@implemented
@ -245,8 +245,8 @@ class CompanySettings extends BaseSettings
public $hide_paid_to_date = false; //@TODO where? public $hide_paid_to_date = false; //@TODO where?
public $embed_documents = false; //@TODO where? public $embed_documents = false; //@TODO where?
public $all_pages_header = false; //@implemented public $all_pages_header = false; //@deprecated 31-05-2021
public $all_pages_footer = false; //@implemented public $all_pages_footer = false; //@deprecated 31-05-2021
public $pdf_variables = ''; //@implemented public $pdf_variables = ''; //@implemented
public $portal_custom_head = ''; //@TODO @BEN public $portal_custom_head = ''; //@TODO @BEN

View File

@ -132,8 +132,11 @@ class ForgotPasswordController extends Controller
: $this->sendResetLinkFailedResponse($request, $response); : $this->sendResetLinkFailedResponse($request, $response);
} }
public function showLinkRequestForm() public function showLinkRequestForm(Request $request)
{ {
return $this->render('auth.passwords.request', ['root' => 'themes']); $account_id = $request->get('account_id');
$account = Account::find($account_id);
return $this->render('auth.passwords.request', ['root' => 'themes', 'account' => $account]);
} }
} }

View File

@ -38,6 +38,7 @@ use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Laravel\Socialite\Facades\Socialite;
use PragmaRX\Google2FA\Google2FA; use PragmaRX\Google2FA\Google2FA;
use Turbo124\Beacon\Facades\LightLogs; use Turbo124\Beacon\Facades\LightLogs;
@ -489,49 +490,37 @@ class LoginController extends BaseController
if (request()->has('code')) { if (request()->has('code')) {
return $this->handleProviderCallback($provider); return $this->handleProviderCallback($provider);
} else { } else {
return Socialite::driver($provider)->scopes($scopes)->redirect(); return Socialite::driver($provider)->with(['redirect_uri' => config('ninja.app_url')."/auth/google"])->scopes($scopes)->redirect();
} }
} }
public function handleProviderCallback(string $provider) public function handleProviderCallback(string $provider)
{ {
$socialite_user = Socialite::driver($provider) $socialite_user = Socialite::driver($provider)
->stateless()
->user(); ->user();
// if($user = OAuth::handleAuth($socialite_user, $provider)) if($user = OAuth::handleAuth($socialite_user, $provider))
// { {
// Auth::login($user, true);
// return redirect($this->redirectTo); nlog('found user and updating their user record');
// }
// else if(MultiDB::checkUserEmailExists($socialite_user->getEmail()))
// {
// Session::flash('error', 'User exists in system, but not with this authentication method'); //todo add translations
// return view('auth.login'); $update_user = [
// } 'first_name' => $name[0],
// else { 'last_name' => $name[1],
// //todo 'password' => '',
// $name = OAuth::splitName($socialite_user->getName()); 'email' => $socialite_user->getEmail(),
'oauth_user_id' => $socialite_user->getId(),
'oauth_provider_id' => $provider,
'oauth_user_token' => $socialite_user->refreshToken,
];
// $new_account = [ $user->update($update_user);
// 'first_name' => $name[0],
// 'last_name' => $name[1],
// 'password' => '',
// 'email' => $socialite_user->getEmail(),
// 'oauth_user_id' => $socialite_user->getId(),
// 'oauth_provider_id' => $provider
// ];
// $account = CreateAccount::dispatchNow($new_account); }
else {
// Auth::login($account->default_company->owner(), true); nlog("user not found for oauth");
}
// $cookie = cookie('db', $account->default_company->db);
// return redirect($this->redirectTo)->withCookie($cookie);
// }
return redirect('/#/');
} }
} }

View File

@ -12,6 +12,7 @@
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Account;
use Illuminate\Foundation\Auth\ResetsPasswords; use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
@ -52,7 +53,10 @@ class ResetPasswordController extends Controller
public function showResetForm(Request $request, $token = null) public function showResetForm(Request $request, $token = null)
{ {
return $this->render('auth.passwords.reset', ['root' => 'themes', 'token' => $token]); $account_id = $request->get('account_id');
$account = Account::find($account_id);
return $this->render('auth.passwords.reset', ['root' => 'themes', 'token' => $token, 'account' => $account]);
} }
/** /**

View File

@ -15,6 +15,7 @@ use App\Events\Client\ClientWasCreated;
use App\Events\Client\ClientWasUpdated; use App\Events\Client\ClientWasUpdated;
use App\Factory\ClientFactory; use App\Factory\ClientFactory;
use App\Filters\ClientFilters; use App\Filters\ClientFilters;
use App\Http\Requests\Client\AdjustClientLedgerRequest;
use App\Http\Requests\Client\CreateClientRequest; use App\Http\Requests\Client\CreateClientRequest;
use App\Http\Requests\Client\DestroyClientRequest; use App\Http\Requests\Client\DestroyClientRequest;
use App\Http\Requests\Client\EditClientRequest; use App\Http\Requests\Client\EditClientRequest;
@ -585,4 +586,64 @@ class ClientController extends BaseController
} }
/**
* Update the specified resource in storage.
*
* @param UploadClientRequest $request
* @param Client $client
* @return Response
*
*
*
* @OA\Put(
* path="/api/v1/clients/{id}/adjust_ledger",
* operationId="adjustLedger",
* tags={"clients"},
* summary="Adjust the client ledger to rebalance",
* description="Adjust the client ledger to rebalance",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The Client Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the client object",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-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/Client"),
* ),
* @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"),
* ),
* )
*/
//@deprecated - not available
public function adjustLedger(AdjustClientLedgerRequest $request, Client $client)
{
// $adjustment = $request->input('adjustment');
// $notes = $request->input('notes');
// $client->service()->updateBalance
}
} }

View File

@ -0,0 +1,42 @@
<?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://opensource.org/licenses/AAL
*/
namespace App\Http\Controllers\Gateways;
use App\Http\Controllers\Controller;
use App\Http\Requests\Gateways\Checkout3ds\Checkout3dsRequest;
class Checkout3dsController extends Controller
{
public function index(Checkout3dsRequest $request, string $company_key, string $company_gateway_id, string $hash)
{
if (!$request->getCompany()) {
return response()->json(['message' => 'Company record not found.', 'company_key' => $company_key]);
}
if (!$request->getCompanyGateway()) {
return response()->json(['message' => 'Company gateway record not found.', 'company_gateway_id' => $company_gateway_id]);
}
if (!$request->getPaymentHash()) {
return response()->json(['message' => 'Hash record not found.', 'hash' => $hash]);
}
if (!$request->getClient()) {
return response()->json(['message' => 'Client record not found.']);
}
return $request->getCompanyGateway()
->driver($request->getClient())
->process3dsConfirmation($request);
}
}

View File

@ -0,0 +1,96 @@
<?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://opensource.org/licenses/AAL
*/
namespace App\Http\Controllers;
use App\Http\Requests\Import\ImportJsonRequest;
use App\Jobs\Company\CompanyExport;
use App\Jobs\Company\CompanyImport;
use App\Utils\Traits\MakesHash;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
class ImportJsonController extends BaseController
{
use MakesHash;
public function __construct()
{
parent::__construct();
}
/**
* @OA\Post(
* path="/api/v1/import_json",
* operationId="getImportJson",
* tags={"import"},
* summary="Import data from the system",
* description="Import data from the system",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Response(
* response=200,
* description="success",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-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\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 index(ImportJsonRequest $request)
{
$import_file = $request->file('files');
$contents = $this->unzipFile($import_file->getPathname());
$hash = Str::random(32);
Cache::put( $hash, base64_encode( $contents ), 3600 );
CompanyImport::dispatch(auth()->user()->getCompany(), auth()->user(), $hash, $request->all());
return response()->json(['message' => 'Processing'], 200);
}
private function unzipFile($file_contents)
{
$zip = new ZipArchive();
$archive = $zip->open($file_contents);
$filename = pathinfo($file_contents, PATHINFO_FILENAME);
$zip->extractTo(public_path("storage/backups/{$filename}"));
$zip->close();
$file_location = public_path("storage/backups/$filename/backup.json");
if (! file_exists($file_location))
throw new NonExistingMigrationFile('Backup file does not exist, or is corrupted.');
$data = json_decode(file_get_contents($file_location));
unlink($file_contents);
unlink($file_location);
return $data
}
}

View File

@ -845,13 +845,11 @@ class InvoiceController extends BaseController
*/ */
public function deliveryNote(ShowInvoiceRequest $request, Invoice $invoice) public function deliveryNote(ShowInvoiceRequest $request, Invoice $invoice)
{ {
$file = $invoice->service()->getInvoiceDeliveryNote($invoice, $invoice->invitations->first()->contact); $file = $invoice->service()->getInvoiceDeliveryNote($invoice, $invoice->invitations->first()->contact);
try { return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
} catch (\Exception $e) {
return response(['message' => 'Oops, something went wrong. Make sure you have symlink to storage/ in public/ directory.'], 500);
}
} }
/** /**

View File

@ -99,9 +99,10 @@ class PreviewController extends BaseController
$entity_obj->load('client'); $entity_obj->load('client');
App::setLocale($entity_obj->client->primary_contact()->preferredLocale());
App::forgetInstance('translator'); App::forgetInstance('translator');
Lang::replace(Ninja::transformTranslations($entity_obj->client->getMergedSettings())); $t = app('translator');
App::setLocale($entity_obj->client->primary_contact()->preferredLocale());
$t->replace(Ninja::transformTranslations($entity_obj->client->getMergedSettings()));
$html = new HtmlEngine($entity_obj->invitations()->first()); $html = new HtmlEngine($entity_obj->invitations()->first());
@ -151,7 +152,8 @@ class PreviewController extends BaseController
private function blankEntity() private function blankEntity()
{ {
App::forgetInstance('translator'); App::forgetInstance('translator');
Lang::replace(Ninja::transformTranslations(auth()->user()->company()->settings)); $t = app('translator');
$t->replace(Ninja::transformTranslations(auth()->user()->company()->settings));
DB::beginTransaction(); DB::beginTransaction();

View File

@ -163,7 +163,7 @@ class SetupController extends Controller
/* Create the first account. */ /* Create the first account. */
if (Account::count() == 0) { if (Account::count() == 0) {
CreateAccount::dispatchNow($request->all()); CreateAccount::dispatchNow($request->all(), $request->getClientIp());
} }
VersionCheck::dispatchNow(); VersionCheck::dispatchNow();

View File

@ -112,6 +112,7 @@ class Kernel extends HttpKernel
VerifyCsrfToken::class, VerifyCsrfToken::class,
SubstituteBindings::class, SubstituteBindings::class,
QueryLogging::class, QueryLogging::class,
Cors::class,
], ],
'shop' => [ 'shop' => [
'throttle:120,1', 'throttle:120,1',

View File

@ -16,7 +16,7 @@ class Cors
// ALLOW OPTIONS METHOD // ALLOW OPTIONS METHOD
$headers = [ $headers = [
'Access-Control-Allow-Methods'=> 'POST, GET, OPTIONS, PUT, DELETE', 'Access-Control-Allow-Methods'=> 'POST, GET, OPTIONS, PUT, DELETE',
'Access-Control-Allow-Headers'=> 'X-API-COMPANY-KEY,X-CLIENT-VERSION,X-API-SECRET,X-API-TOKEN,X-API-PASSWORD,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range', 'Access-Control-Allow-Headers'=> 'X-API-COMPANY-KEY,X-CLIENT-VERSION,X-API-SECRET,X-API-TOKEN,X-API-PASSWORD,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,X-CSRF-TOKEN,X-LIVEWIRE',
]; ];
return Response::make('OK', 200, $headers); return Response::make('OK', 200, $headers);
@ -27,7 +27,7 @@ class Cors
$response->headers->set('Access-Control-Allow-Origin', '*'); $response->headers->set('Access-Control-Allow-Origin', '*');
$response->headers->set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); $response->headers->set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
$response->headers->set('Access-Control-Allow-Headers', 'X-API-COMPANY-KEY,X-API-SECRET,X-API-TOKEN,X-API-PASSWORD,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'); $response->headers->set('Access-Control-Allow-Headers', 'X-API-COMPANY-KEY,X-API-SECRET,X-API-TOKEN,X-API-PASSWORD,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range');
$response->headers->set('Access-Control-Expose-Headers', 'X-APP-VERSION,X-MINIMUM-CLIENT-VERSION'); $response->headers->set('Access-Control-Expose-Headers', 'X-APP-VERSION,X-MINIMUM-CLIENT-VERSION,X-CSRF-TOKEN,X-LIVEWIRE');
$response->headers->set('X-APP-VERSION', config('ninja.app_version')); $response->headers->set('X-APP-VERSION', config('ninja.app_version'));
$response->headers->set('X-MINIMUM-CLIENT-VERSION', config('ninja.minimum_client_version')); $response->headers->set('X-MINIMUM-CLIENT-VERSION', config('ninja.minimum_client_version'));

View File

@ -0,0 +1,55 @@
<?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://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\Client;
use App\Http\Requests\Request;
use App\Utils\Traits\MakesHash;
use Illuminate\Validation\Rule;
class AdjustClientLedgerRequest extends Request
{
use MakesHash;
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('edit', $this->client);
}
public function rules()
{
/* Ensure we have a client name, and that all emails are unique*/
$rules = [];
return $rules;
}
public function messages()
{
return [
];
}
protected function prepareForValidation()
{
$input = $this->all();
$this->replace($input);
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace App\Http\Requests\Gateways\Checkout3ds;
use App\Models\Client;
use App\Models\Company;
use App\Models\CompanyGateway;
use App\Models\PaymentHash;
use App\Utils\Traits\MakesHash;
use Illuminate\Foundation\Http\FormRequest;
class Checkout3dsRequest extends FormRequest
{
use MakesHash;
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
//
];
}
public function getCompany()
{
return Company::where('company_key', $this->company_key)->first();
}
public function getCompanyGateway()
{
return CompanyGateway::find($this->decodePrimaryKey($this->company_gateway_id));
}
public function getPaymentHash()
{
return PaymentHash::where('hash', $this->hash)->first();
}
public function getClient()
{
return Client::find($this->getPaymentHash()->data->client_id);
}
}

View File

@ -0,0 +1,39 @@
<?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://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\Import;
use App\Http\Requests\Request;
class ImportJsonRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->isAdmin();
}
public function rules()
{
return [
// 'import_type' => 'required',
// 'files' => 'required_without:hash|array|min:1|max:6',
// 'hash' => 'nullable|string',
// 'column_map' => 'required_with:hash|array',
// 'skip_header' => 'required_with:hash|boolean',
// 'files.*' => 'file|mimes:csv,txt',
];
}
}

View File

@ -100,14 +100,15 @@ class PaymentWebhookRequest extends Request
/** /**
* Resolve client from payment hash. * Resolve client from payment hash.
* *
* @return null|\App\Models\Client * @return null|\App\Models\Client|bool
*/ */
public function getClient() public function getClient()
{ {
$hash = $this->getPaymentHash(); $hash = $this->getPaymentHash();
if($hash) if($hash) {
return Client::find($hash->data->client_id)->firstOrFail(); return Client::find($hash->data->client_id)->firstOrFail();
}
return false; return false;
} }

View File

@ -58,7 +58,7 @@ class AttachableUser implements Rule
if(!$user) if(!$user)
return true; return true;
$user_already_attached = CompanyUser::query() $user_already_attached = CompanyUser::query()
->where('user_id', $user->id) ->where('user_id', $user->id)
->where('account_id',$user->account_id) ->where('account_id',$user->account_id)
->where('company_id', auth()->user()->company()->id) ->where('company_id', auth()->user()->company()->id)

View File

@ -13,6 +13,7 @@ namespace App\Http\ViewComposers;
use App\Utils\Ninja; use App\Utils\Ninja;
use App\Utils\TranslationHelper; use App\Utils\TranslationHelper;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Lang; use Illuminate\Support\Facades\Lang;
use Illuminate\View\View; use Illuminate\View\View;
@ -34,7 +35,9 @@ class PortalComposer
$view->with($this->portalData()); $view->with($this->portalData());
if (auth()->user()) { if (auth()->user()) {
Lang::replace(Ninja::transformTranslations(auth()->user()->client->getMergedSettings())); App::forgetInstance('translator');
$t = app('translator');
$t->replace(Ninja::transformTranslations(auth()->user()->client->getMergedSettings()));
} }
} }

View File

@ -25,6 +25,7 @@ use App\Models\RecurringInvoice;
use App\Models\RecurringInvoiceInvitation; use App\Models\RecurringInvoiceInvitation;
use App\Models\User; use App\Models\User;
use App\Models\VendorContact; use App\Models\VendorContact;
use App\Utils\Ninja;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
@ -121,7 +122,7 @@ class CompanyExport implements ShouldQueue
$user->account_id = $this->encodePrimaryKey($user->account_id); $user->account_id = $this->encodePrimaryKey($user->account_id);
// $user->id = $this->encodePrimaryKey($user->id); // $user->id = $this->encodePrimaryKey($user->id);
return $user; return $user->makeVisible(['id']);
})->all(); })->all();
@ -205,26 +206,27 @@ class CompanyExport implements ShouldQueue
$credit = $this->transformBasicEntities($credit); $credit = $this->transformBasicEntities($credit);
$credit = $this->transformArrayOfKeys($credit, ['recurring_id','client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id','invoice_id']); $credit = $this->transformArrayOfKeys($credit, ['recurring_id','client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id','invoice_id']);
return $credit; return $credit->makeVisible(['id']);
})->all(); })->all();
$this->export_data['credit_invitations'] = CreditInvitation::where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($credit){ $this->export_data['credit_invitations'] = CreditInvitation::where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($credit){
$credit = $this->transformArrayOfKeys($credit, ['company_id', 'user_id', 'client_contact_id', 'recurring_invoice_id']); $credit = $this->transformArrayOfKeys($credit, ['company_id', 'user_id', 'client_contact_id', 'credit_id']);
return $credit; return $credit->makeVisible(['id']);
})->all(); })->all();
$this->export_data['designs'] = $this->company->user_designs->makeHidden(['id'])->all(); $this->export_data['designs'] = $this->company->user_designs->makeHidden(['id'])->all();
$this->export_data['documents'] = $this->company->documents->map(function ($document){ $this->export_data['documents'] = $this->company->all_documents->map(function ($document){
$document = $this->transformArrayOfKeys($document, ['user_id', 'assigned_user_id', 'company_id', 'project_id', 'vendor_id']); $document = $this->transformArrayOfKeys($document, ['user_id', 'assigned_user_id', 'company_id', 'project_id', 'vendor_id','documentable_id']);
$document->hashed_id = $this->encodePrimaryKey($document->id);
return $document; return $document->makeVisible(['id']);
})->all(); })->all();
@ -232,7 +234,7 @@ class CompanyExport implements ShouldQueue
$expense_category = $this->transformArrayOfKeys($expense_category, ['user_id', 'company_id']); $expense_category = $this->transformArrayOfKeys($expense_category, ['user_id', 'company_id']);
return $expense_category; return $expense_category->makeVisible(['id']);
})->all(); })->all();
@ -242,7 +244,7 @@ class CompanyExport implements ShouldQueue
$expense = $this->transformBasicEntities($expense); $expense = $this->transformBasicEntities($expense);
$expense = $this->transformArrayOfKeys($expense, ['vendor_id', 'invoice_id', 'client_id', 'category_id', 'recurring_expense_id','project_id']); $expense = $this->transformArrayOfKeys($expense, ['vendor_id', 'invoice_id', 'client_id', 'category_id', 'recurring_expense_id','project_id']);
return $expense; return $expense->makeVisible(['id']);
})->all(); })->all();
@ -250,7 +252,7 @@ class CompanyExport implements ShouldQueue
$gs = $this->transformArrayOfKeys($gs, ['user_id', 'company_id']); $gs = $this->transformArrayOfKeys($gs, ['user_id', 'company_id']);
return $gs; return $gs->makeVisible(['id']);
})->all(); })->all();
@ -260,16 +262,20 @@ class CompanyExport implements ShouldQueue
$invoice = $this->transformBasicEntities($invoice); $invoice = $this->transformBasicEntities($invoice);
$invoice = $this->transformArrayOfKeys($invoice, ['recurring_id','client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id']); $invoice = $this->transformArrayOfKeys($invoice, ['recurring_id','client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id']);
return $invoice; return $invoice->makeVisible(['id',
'private_notes',
'user_id',
'client_id',
'company_id',]);
})->all(); })->all();
$this->export_data['invoice_invitations'] = InvoiceInvitation::where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($invoice){ $this->export_data['invoice_invitations'] = InvoiceInvitation::where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($invoice){
$invoice = $this->transformArrayOfKeys($invoice, ['company_id', 'user_id', 'client_contact_id', 'recurring_invoice_id']); $invoice = $this->transformArrayOfKeys($invoice, ['company_id', 'user_id', 'client_contact_id', 'invoice_id']);
return $invoice; return $invoice->makeVisible(['id']);
})->all(); })->all();
@ -281,19 +287,14 @@ class CompanyExport implements ShouldQueue
})->makeHidden(['id'])->all(); })->makeHidden(['id'])->all();
$this->export_data['paymentables'] = $this->company->payments()->with('paymentables')->cursor()->map(function ($paymentable){
$paymentable = $this->transformArrayOfKeys($paymentable, ['payment_id','paymentable_id']);
return $paymentable;
})->all();
$this->export_data['payments'] = $this->company->payments->map(function ($payment){ $this->export_data['payments'] = $this->company->payments->map(function ($payment){
$payment = $this->transformBasicEntities($payment); $payment = $this->transformBasicEntities($payment);
$payment = $this->transformArrayOfKeys($payment, ['client_id','project_id', 'vendor_id', 'client_contact_id', 'invitation_id', 'company_gateway_id']); $payment = $this->transformArrayOfKeys($payment, ['client_id','project_id', 'vendor_id', 'client_contact_id', 'invitation_id', 'company_gateway_id']);
$payment->paymentables = $this->transformPaymentable($payment);
return $payment->makeVisible(['id']); return $payment->makeVisible(['id']);
})->all(); })->all();
@ -312,7 +313,7 @@ class CompanyExport implements ShouldQueue
$project = $this->transformBasicEntities($project); $project = $this->transformBasicEntities($project);
$project = $this->transformArrayOfKeys($project, ['client_id']); $project = $this->transformArrayOfKeys($project, ['client_id']);
return $project; return $project->makeVisible(['id']);
})->all(); })->all();
@ -321,25 +322,26 @@ class CompanyExport implements ShouldQueue
$quote = $this->transformBasicEntities($quote); $quote = $this->transformBasicEntities($quote);
$quote = $this->transformArrayOfKeys($quote, ['invoice_id','recurring_id','client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id']); $quote = $this->transformArrayOfKeys($quote, ['invoice_id','recurring_id','client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id']);
return $quote; return $quote->makeVisible(['id']);
})->all(); })->all();
$this->export_data['quote_invitations'] = QuoteInvitation::where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($quote){ $this->export_data['quote_invitations'] = QuoteInvitation::where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($quote){
$quote = $this->transformArrayOfKeys($quote, ['company_id', 'user_id', 'client_contact_id', 'recurring_invoice_id']); $quote = $this->transformArrayOfKeys($quote, ['company_id', 'user_id', 'client_contact_id', 'quote_id']);
return $quote; return $quote->makeVisible(['id']);
})->all(); })->all();
$this->export_data['recurring_invoices'] = $this->company->recurring_invoices->map(function ($ri){ $this->export_data['recurring_invoices'] = $this->company->recurring_invoices->makeVisible(['id'])->map(function ($ri){
$ri = $this->transformBasicEntities($ri); $ri = $this->transformBasicEntities($ri);
$ri = $this->transformArrayOfKeys($ri, ['client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id']); $ri = $this->transformArrayOfKeys($ri, ['client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id']);
return $ri;
return $ri->makeVisible(['id']);
})->all(); })->all();
@ -357,7 +359,13 @@ class CompanyExport implements ShouldQueue
$subscription = $this->transformBasicEntities($subscription); $subscription = $this->transformBasicEntities($subscription);
$subscription->group_id = $this->encodePrimaryKey($subscription->group_id); $subscription->group_id = $this->encodePrimaryKey($subscription->group_id);
return $subscription; return $subscription->makeVisible([ 'id',
'user_id',
'assigned_user_id',
'company_id',
'product_ids',
'recurring_product_ids',
'group_id']);
})->all(); })->all();
@ -376,7 +384,7 @@ class CompanyExport implements ShouldQueue
$task = $this->transformBasicEntities($task); $task = $this->transformBasicEntities($task);
$task = $this->transformArrayOfKeys($task, ['client_id', 'invoice_id', 'project_id', 'status_id']); $task = $this->transformArrayOfKeys($task, ['client_id', 'invoice_id', 'project_id', 'status_id']);
return $task; return $task->makeVisible(['id']);
})->all(); })->all();
@ -401,7 +409,7 @@ class CompanyExport implements ShouldQueue
$this->export_data['vendors'] = $this->company->vendors->map(function ($vendor){ $this->export_data['vendors'] = $this->company->vendors->map(function ($vendor){
return $this->transformBasicEntities($vendor); return $this->transformBasicEntities($vendor)->makeVisible(['id']);
})->all(); })->all();
@ -411,7 +419,7 @@ class CompanyExport implements ShouldQueue
$vendor = $this->transformBasicEntities($vendor); $vendor = $this->transformBasicEntities($vendor);
$vendor->vendor_id = $this->encodePrimaryKey($vendor->vendor_id); $vendor->vendor_id = $this->encodePrimaryKey($vendor->vendor_id);
return $vendor; return $vendor->makeVisible(['id']);
})->all(); })->all();
@ -425,7 +433,7 @@ class CompanyExport implements ShouldQueue
})->makeHidden(['id'])->all(); })->makeHidden(['id'])->all();
//write to tmp and email to owner(); //write to tmp and email to owner();
$this->zipAndSend(); $this->zipAndSend();
return true; return true;
@ -434,7 +442,7 @@ class CompanyExport implements ShouldQueue
private function transformBasicEntities($model) private function transformBasicEntities($model)
{ {
return $this->transformArrayOfKeys($model, ['id', 'user_id', 'assigned_user_id', 'company_id']); return $this->transformArrayOfKeys($model, ['user_id', 'assigned_user_id', 'company_id']);
} }
@ -449,6 +457,24 @@ class CompanyExport implements ShouldQueue
} }
private function transformPaymentable($payment)
{
$new_arr = [];
foreach($payment->paymentables as $paymentable)
{
$paymentable->payment_id = $this->encodePrimaryKey($paymentable->payment_id);
$paymentable->paymentable_id = $this->encodePrimaryKey($paymentable->paymentable_id);
$new_arr[] = $paymentable;
}
return $new_arr;
}
private function zipAndSend() private function zipAndSend()
{ {
@ -464,6 +490,10 @@ class CompanyExport implements ShouldQueue
$zip->addFromString("backup.json", json_encode($this->export_data)); $zip->addFromString("backup.json", json_encode($this->export_data));
$zip->close(); $zip->close();
if(Ninja::isHosted()) {
Storage::disk(config('filesystems.default'))->put('backups/'.$file_name, file_get_contents($zip_path));
}
$nmo = new NinjaMailerObject; $nmo = new NinjaMailerObject;
$nmo->mailable = new DownloadBackup(Storage::disk(config('filesystems.default'))->url('backups/'.$file_name), $this->company); $nmo->mailable = new DownloadBackup(Storage::disk(config('filesystems.default'))->url('backups/'.$file_name), $this->company);
$nmo->to_user = $this->user; $nmo->to_user = $this->user;
@ -473,6 +503,7 @@ class CompanyExport implements ShouldQueue
NinjaMailerJob::dispatch($nmo); NinjaMailerJob::dispatch($nmo);
UnlinkFile::dispatch(config('filesystems.default'), 'backups/'.$file_name)->delay(now()->addHours(1)); UnlinkFile::dispatch(config('filesystems.default'), 'backups/'.$file_name)->delay(now()->addHours(1));
UnlinkFile::dispatch('public', 'backups/'.$file_name)->delay(now()->addHours(1));
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -93,17 +93,19 @@ class CreateEntityPdf implements ShouldQueue
public function handle() public function handle()
{ {
/* Set the locale*/
App::setLocale($this->contact->preferredLocale());
/* Forget the singleton*/ /* Forget the singleton*/
App::forgetInstance('translator'); App::forgetInstance('translator');
/* Init a new copy of the translator*/ /* Init a new copy of the translator*/
$t = app('translator'); $t = app('translator');
/* Set the locale*/
App::setLocale($this->contact->preferredLocale());
// nlog($this->entity->client->getMergedSettings());
/* Set customized translations _NOW_ */ /* Set customized translations _NOW_ */
Lang::replace(Ninja::transformTranslations($this->entity->client->getMergedSettings())); $t->replace(Ninja::transformTranslations($this->entity->client->getMergedSettings()));
$this->entity->service()->deletePdf(); $this->entity->service()->deletePdf();

View File

@ -154,9 +154,9 @@ class NinjaMailerJob implements ShouldQueue
App::forgetInstance('mail.manager'); //singletons must be destroyed! App::forgetInstance('mail.manager'); //singletons must be destroyed!
App::forgetInstance('mailer'); App::forgetInstance('mailer');
App::forgetInstance('laravelgmail'); App::forgetInstance('laravelgmail');
$t = app('translator');
/* Inject custom translations if any exist */ /* Inject custom translations if any exist */
Lang::replace(Ninja::transformTranslations($this->nmo->settings)); $t->replace(Ninja::transformTranslations($this->nmo->settings));
switch ($this->nmo->settings->email_sending_method) { switch ($this->nmo->settings->email_sending_method) {
case 'default': case 'default':

View File

@ -35,9 +35,9 @@ class WebhookHandler implements ShouldQueue
private $company; private $company;
public $tries = 5; //number of retries public $tries = 3; //number of retries
public $backoff = 5; //seconds to wait until retry public $backoff = 10; //seconds to wait until retry
public $deleteWhenMissingModels = true; public $deleteWhenMissingModels = true;

View File

@ -44,7 +44,8 @@ class CreditEmailEngine extends BaseEmailEngine
public function build() public function build()
{ {
App::forgetInstance('translator'); App::forgetInstance('translator');
Lang::replace(Ninja::transformTranslations($this->client->getMergedSettings())); $t = app('translator');
$t->replace(Ninja::transformTranslations($this->client->getMergedSettings()));
if (is_array($this->template_data) && array_key_exists('body', $this->template_data) && strlen($this->template_data['body']) > 0) { if (is_array($this->template_data) && array_key_exists('body', $this->template_data) && strlen($this->template_data['body']) > 0) {
$body_template = $this->template_data['body']; $body_template = $this->template_data['body'];

View File

@ -47,7 +47,8 @@ class InvoiceEmailEngine extends BaseEmailEngine
{ {
App::forgetInstance('translator'); App::forgetInstance('translator');
Lang::replace(Ninja::transformTranslations($this->client->getMergedSettings())); $t = app('translator');
$t->replace(Ninja::transformTranslations($this->client->getMergedSettings()));
if (is_array($this->template_data) && array_key_exists('body', $this->template_data) && strlen($this->template_data['body']) > 0) { if (is_array($this->template_data) && array_key_exists('body', $this->template_data) && strlen($this->template_data['body']) > 0) {
$body_template = $this->template_data['body']; $body_template = $this->template_data['body'];

View File

@ -45,7 +45,8 @@ class QuoteEmailEngine extends BaseEmailEngine
public function build() public function build()
{ {
App::forgetInstance('translator'); App::forgetInstance('translator');
Lang::replace(Ninja::transformTranslations($this->client->getMergedSettings())); $t = app('translator');
$t->replace(Ninja::transformTranslations($this->client->getMergedSettings()));
if (is_array($this->template_data) && array_key_exists('body', $this->template_data) && strlen($this->template_data['body']) > 0) { if (is_array($this->template_data) && array_key_exists('body', $this->template_data) && strlen($this->template_data['body']) > 0) {
$body_template = $this->template_data['body']; $body_template = $this->template_data['body'];

View File

@ -41,8 +41,8 @@ class MigrationCompleted extends Mailable
$result = $this->from(config('mail.from.address'), config('mail.from.name')) $result = $this->from(config('mail.from.address'), config('mail.from.name'))
->view('email.import.completed', $data); ->view('email.import.completed', $data);
if($this->company->invoices->count() >=1) // if($this->company->invoices->count() >=1)
$result->attach($this->company->invoices->first()->pdf_file_path()); // $result->attach($this->company->invoices->first()->pdf_file_path());
return $result; return $result;
} }

View File

@ -103,6 +103,15 @@ class Activity extends StaticModel
'deleted_at' => 'timestamp', 'deleted_at' => 'timestamp',
]; ];
protected $appends = [
'hashed_id',
];
public function getHashedIdAttribute()
{
return $this->encodePrimaryKey($this->id);
}
public function getEntityType() public function getEntityType()
{ {
return self::class; return self::class;

View File

@ -131,6 +131,11 @@ class Company extends BaseModel
return $this->morphMany(Document::class, 'documentable'); return $this->morphMany(Document::class, 'documentable');
} }
public function all_documents()
{
return $this->HasMany(Document::class);
}
public function getEntityType() public function getEntityType()
{ {
return self::class; return self::class;

View File

@ -71,10 +71,6 @@ class Quote extends BaseModel
'custom_surcharge2', 'custom_surcharge2',
'custom_surcharge3', 'custom_surcharge3',
'custom_surcharge4', 'custom_surcharge4',
// 'custom_surcharge_tax1',
// 'custom_surcharge_tax2',
// 'custom_surcharge_tax3',
// 'custom_surcharge_tax4',
'design_id', 'design_id',
'assigned_user_id', 'assigned_user_id',
'exchange_rate', 'exchange_rate',

View File

@ -144,7 +144,7 @@ class CreditCard
if ($this->checkout->client->currency()->code == 'EUR' || $this->checkout->company_gateway->getConfigField('threeds')) { if ($this->checkout->client->currency()->code == 'EUR' || $this->checkout->company_gateway->getConfigField('threeds')) {
$payment->{'3ds'} = ['enabled' => true]; $payment->{'3ds'} = ['enabled' => true];
$payment->{'success_url'} = route('payment_webhook', [ $payment->{'success_url'} = route('checkout.3ds_redirect', [
'company_key' => $this->checkout->client->company->company_key, 'company_key' => $this->checkout->client->company->company_key,
'company_gateway_id' => $this->checkout->company_gateway->hashed_id, 'company_gateway_id' => $this->checkout->company_gateway->hashed_id,
'hash' => $this->checkout->payment_hash->hash, 'hash' => $this->checkout->payment_hash->hash,

View File

@ -13,6 +13,7 @@
namespace App\PaymentDrivers; namespace App\PaymentDrivers;
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
use App\Http\Requests\Gateways\Checkout3ds\Checkout3dsRequest;
use App\Http\Requests\Payments\PaymentWebhookRequest; use App\Http\Requests\Payments\PaymentWebhookRequest;
use App\Jobs\Mail\PaymentFailureMailer; use App\Jobs\Mail\PaymentFailureMailer;
use App\Jobs\Util\SystemLogger; use App\Jobs\Util\SystemLogger;
@ -287,6 +288,11 @@ class CheckoutComPaymentDriver extends BaseDriver
} }
public function processWebhookRequest(PaymentWebhookRequest $request, Payment $payment = null) public function processWebhookRequest(PaymentWebhookRequest $request, Payment $payment = null)
{
return true;
}
public function process3dsConfirmation(Checkout3dsRequest $request)
{ {
$this->init(); $this->init();
$this->setPaymentHash($request->getPaymentHash()); $this->setPaymentHash($request->getPaymentHash());

View File

@ -193,7 +193,8 @@ class ACH
SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_SUCCESS, SystemLog::EVENT_GATEWAY_SUCCESS,
SystemLog::TYPE_STRIPE, SystemLog::TYPE_STRIPE,
$this->stripe->client $this->stripe->client,
$this->stripe->client->company,
); );
return redirect()->route('client.payments.show', ['payment' => $this->stripe->encodePrimaryKey($payment->id)]); return redirect()->route('client.payments.show', ['payment' => $this->stripe->encodePrimaryKey($payment->id)]);

View File

@ -390,6 +390,13 @@ class StripePaymentDriver extends BaseDriver
$payment->save(); $payment->save();
} }
if ($request->type == 'charge.succeeded') {
$payment->status_id = Payment::STATUS_COMPLETED;
$payment->save();
}
// charge.failed, charge.refunded
return response([], 200); return response([], 200);
} }

View File

@ -188,7 +188,7 @@ class PaymentMethod
$fee_label = $gateway->calcGatewayFeeLabel($this->amount, $this->client, $gateway_type_id); $fee_label = $gateway->calcGatewayFeeLabel($this->amount, $this->client, $gateway_type_id);
if(!$gateway_type_id){ if(!$gateway_type_id || (GatewayType::CUSTOM == $gateway_type_id)){
$this->payment_urls[] = [ $this->payment_urls[] = [
'label' => $gateway->getConfigField('name') . $fee_label, 'label' => $gateway->getConfigField('name') . $fee_label,

View File

@ -346,6 +346,8 @@ class Design extends BaseDesign
$items = $this->transformLineItems($this->entity->line_items, $type); $items = $this->transformLineItems($this->entity->line_items, $type);
$this->processMarkdownOnLineItems($items);
if (count($items) == 0) { if (count($items) == 0) {
return []; return [];
} }

View File

@ -321,10 +321,27 @@ document.addEventListener('DOMContentLoaded', function() {
public static function parseMarkdownToHtml(string $markdown): ?string public static function parseMarkdownToHtml(string $markdown): ?string
{ {
// Use setting to determinate if parsing should be done.
// 'parse_markdown_on_pdfs'
$converter = new CommonMarkConverter([ $converter = new CommonMarkConverter([
'allow_unsafe_links' => false, 'allow_unsafe_links' => false,
]); ]);
return $converter->convertToHtml($markdown); return $converter->convertToHtml($markdown);
} }
public function processMarkdownOnLineItems(array &$items)
{
// Use setting to determinate if parsing should be done.
// 'parse_markdown_on_pdfs'
foreach ($items as $key => $item) {
foreach ($item as $variable => $value) {
$item[$variable] = DesignHelpers::parseMarkdownToHtml($value ?? '');
}
$items[$key] = $item;
}
}
} }

View File

@ -92,7 +92,10 @@ trait PdfMakerUtilities
$contains_html = false; $contains_html = false;
if (isset($child['content'])) { if (isset($child['content'])) {
$child['content'] = nl2br($child['content']); // Commented cause it keeps adding <br> at the end, if markdown parsing is turned on.
// Should update with 'parse_markdown_on_pdfs' setting.
// $child['content'] = nl2br($child['content']);
} }
// "/\/[a-z]*>/i" -> checks for HTML-like tags: // "/\/[a-z]*>/i" -> checks for HTML-like tags:

View File

@ -485,8 +485,9 @@ class SubscriptionService
/** /**
* Handle a plan change where no payment is required * Handle a plan change where no payment is required
* *
* @param array $data * @param array $data
* @deprecated - no usage found
*/ */
public function handlePlanChangeNoPayment($data) public function handlePlanChangeNoPayment($data)
{ {
@ -654,7 +655,7 @@ class SubscriptionService
$status = $response->getStatusCode(); $status = $response->getStatusCode();
//$response_body = $response->getReasonPhrase(); //$response_body = $response->getReasonPhrase();
$body = array_merge($body, ['status' => $status, 'response_body' => $response_body]); //$body = array_merge($body, ['status' => $status, 'response_body' => $response_body]);
} }

View File

@ -17,6 +17,7 @@ use App\Models\CreditInvitation;
use App\Models\InvoiceInvitation; use App\Models\InvoiceInvitation;
use App\Models\QuoteInvitation; use App\Models\QuoteInvitation;
use App\Models\RecurringInvoiceInvitation; use App\Models\RecurringInvoiceInvitation;
use App\Services\PdfMaker\Designs\Utilities\DesignHelpers;
use App\Utils\Traits\MakesDates; use App\Utils\Traits\MakesDates;
use Exception; use Exception;
use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\App;
@ -121,7 +122,7 @@ class HtmlEngine
if ($this->entity_string == 'invoice' || $this->entity_string == 'recurring_invoice') { if ($this->entity_string == 'invoice' || $this->entity_string == 'recurring_invoice') {
$data['$entity'] = ['value' => '', 'label' => ctrans('texts.invoice')]; $data['$entity'] = ['value' => '', 'label' => ctrans('texts.invoice')];
$data['$number'] = ['value' => $this->entity->number ?: '&nbsp;', 'label' => ctrans('texts.invoice_number')]; $data['$number'] = ['value' => $this->entity->number ?: '&nbsp;', 'label' => ctrans('texts.invoice_number')];
$data['$entity.terms'] = ['value' => $this->entity->terms ?: '', 'label' => ctrans('texts.invoice_terms')]; $data['$entity.terms'] = ['value' => DesignHelpers::parseMarkdownToHtml($this->entity->terms ?: '') ?: '', 'label' => ctrans('texts.invoice_terms')];
$data['$terms'] = &$data['$entity.terms']; $data['$terms'] = &$data['$entity.terms'];
$data['$view_link'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_invoice').'</a>', 'label' => ctrans('texts.view_invoice')]; $data['$view_link'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_invoice').'</a>', 'label' => ctrans('texts.view_invoice')];
$data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_invoice')]; $data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_invoice')];
@ -200,7 +201,7 @@ class HtmlEngine
$data['$invoice.custom2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice2', $this->entity->custom_value2, $this->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice2')]; $data['$invoice.custom2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice2', $this->entity->custom_value2, $this->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice2')];
$data['$invoice.custom3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice3', $this->entity->custom_value3, $this->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice3')]; $data['$invoice.custom3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice3', $this->entity->custom_value3, $this->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice3')];
$data['$invoice.custom4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice4', $this->entity->custom_value4, $this->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice4')]; $data['$invoice.custom4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice4', $this->entity->custom_value4, $this->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice4')];
$data['$invoice.public_notes'] = ['value' => $this->entity->public_notes ?: '', 'label' => ctrans('texts.public_notes')]; $data['$invoice.public_notes'] = ['value' => DesignHelpers::parseMarkdownToHtml($this->entity->public_notes ?: '') ?: '', 'label' => ctrans('texts.public_notes')];
$data['$entity.public_notes'] = &$data['$invoice.public_notes']; $data['$entity.public_notes'] = &$data['$invoice.public_notes'];
$data['$public_notes'] = &$data['$invoice.public_notes']; $data['$public_notes'] = &$data['$invoice.public_notes'];
@ -374,7 +375,7 @@ class HtmlEngine
$data['$description'] = ['value' => '', 'label' => ctrans('texts.description')]; $data['$description'] = ['value' => '', 'label' => ctrans('texts.description')];
//$data['$entity_footer'] = ['value' => $this->client->getSetting("{$this->entity_string}_footer"), 'label' => '']; //$data['$entity_footer'] = ['value' => $this->client->getSetting("{$this->entity_string}_footer"), 'label' => ''];
$data['$entity_footer'] = ['value' => $this->entity->footer, 'label' => '']; $data['$entity_footer'] = ['value' => DesignHelpers::parseMarkdownToHtml($this->entity->footer ?: ''), 'label' => ''];
$data['$page_size'] = ['value' => $this->settings->page_size, 'label' => '']; $data['$page_size'] = ['value' => $this->settings->page_size, 'label' => ''];
$data['$page_layout'] = ['value' => property_exists($this->settings, 'page_layout') ? $this->settings->page_layout : 'Portrait', 'label' => '']; $data['$page_layout'] = ['value' => property_exists($this->settings, 'page_layout') ? $this->settings->page_layout : 'Portrait', 'label' => ''];

View File

@ -25,7 +25,7 @@ use Illuminate\Support\Facades\Queue;
class SystemHealth class SystemHealth
{ {
private static $extensions = [ private static $extensions = [
'mysqli', // 'mysqli',
'gd', 'gd',
'curl', 'curl',
'zip', 'zip',
@ -34,7 +34,7 @@ class SystemHealth
'mbstring', 'mbstring',
'xml', 'xml',
'bcmath', 'bcmath',
'mysqlnd', // 'mysqlnd',
//'intl', //todo double check whether we need this for email dns validation //'intl', //todo double check whether we need this for email dns validation
]; ];

View File

@ -106,7 +106,8 @@ class TemplateEngine
} }
App::forgetInstance('translator'); App::forgetInstance('translator');
Lang::replace(Ninja::transformTranslations($this->settings)); $t = app('translator');
$t->replace(Ninja::transformTranslations($this->settings));
return $this; return $this;
} }

View File

@ -14,8 +14,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' => '5.1.65', 'app_version' => '5.1.70',
'app_tag' => '5.1.65-release', 'app_tag' => '5.1.70-release',
'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', ''), 'api_secret' => env('API_SECRET', ''),

View File

@ -0,0 +1,43 @@
<?php
use App\Libraries\MultiDB;
use App\Models\Document;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class MakeDocumentsAssignedUserNullable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('documents', function (Blueprint $table){
$table->unsignedInteger('assigned_user_id')->nullable()->change();
});
Document::where('assigned_user_id', 0)->update(['assigned_user_id' => null]);
if(config('ninja.db.multi_db_enabled')){
foreach (MultiDB::$dbs as $db) {
Document::on($db)->where('assigned_user_id', 0)->update(['assigned_user_id' => null]);
}
}
else{
Document::where('assigned_user_id', 0)->update(['assigned_user_id' => null]);
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

2
public/css/app.css vendored

File diff suppressed because one or more lines are too long

View File

@ -5,7 +5,7 @@ const CACHE_NAME = 'flutter-app-cache';
const RESOURCES = { const RESOURCES = {
"version.json": "9fe5b22a16f39b766c8fdc35a24b3efa", "version.json": "9fe5b22a16f39b766c8fdc35a24b3efa",
"favicon.ico": "51636d3a390451561744c42188ccd628", "favicon.ico": "51636d3a390451561744c42188ccd628",
"main.dart.js": "99c1cdf29ca54a505582ef971ac4f97f", "main.dart.js": "4ed39edfd85c4ac1e202b7c83715f20d",
"/": "23224b5e03519aaa87594403d54412cf", "/": "23224b5e03519aaa87594403d54412cf",
"assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "174c02fc4609e8fc4389f5d21f16a296", "assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "174c02fc4609e8fc4389f5d21f16a296",
"assets/AssetManifest.json": "659dcf9d1baf3aed3ab1b9c42112bf8f", "assets/AssetManifest.json": "659dcf9d1baf3aed3ab1b9c42112bf8f",

Binary file not shown.

After

Width:  |  Height:  |  Size: 622 KiB

100701
public/main.dart.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

99471
public/main.foss.dart.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{ {
"/js/app.js": "/js/app.js?id=696e8203d5e8e7cf5ff5", "/js/app.js": "/js/app.js?id=696e8203d5e8e7cf5ff5",
"/css/app.css": "/css/app.css?id=8bf94fe817c8702670c9", "/css/app.css": "/css/app.css?id=14a824656f32eec8c2b1",
"/js/clients/invoices/action-selectors.js": "/js/clients/invoices/action-selectors.js?id=a09bb529b8e1826f13b4", "/js/clients/invoices/action-selectors.js": "/js/clients/invoices/action-selectors.js?id=a09bb529b8e1826f13b4",
"/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js?id=8ce8955ba775ea5f47d1", "/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js?id=8ce8955ba775ea5f47d1",
"/js/clients/linkify-urls.js": "/js/clients/linkify-urls.js?id=0dc8c34010d09195d2f7", "/js/clients/linkify-urls.js": "/js/clients/linkify-urls.js?id=0dc8c34010d09195d2f7",
@ -19,7 +19,5 @@
"/js/clients/shared/multiple-downloads.js": "/js/clients/shared/multiple-downloads.js?id=5c35d28cf0a3286e7c45", "/js/clients/shared/multiple-downloads.js": "/js/clients/shared/multiple-downloads.js?id=5c35d28cf0a3286e7c45",
"/js/clients/shared/pdf.js": "/js/clients/shared/pdf.js?id=fc3055d6a099f523ea98", "/js/clients/shared/pdf.js": "/js/clients/shared/pdf.js?id=fc3055d6a099f523ea98",
"/js/setup/setup.js": "/js/setup/setup.js?id=8d454e7090f119552a6c", "/js/setup/setup.js": "/js/setup/setup.js?id=8d454e7090f119552a6c",
"/css/card-js.min.css": "/css/card-js.min.css?id=62afeb675235451543ad", "/css/card-js.min.css": "/css/card-js.min.css?id=62afeb675235451543ad"
"/js/admin.js": "/js/admin.js?id=003930085af69b13a86a",
"/css/admin.css": "/css/admin.css?id=0f5a9ba733ae0e1f9cbf"
} }

View File

@ -159,6 +159,10 @@
#footer { #footer {
margin-top: 30px; margin-top: 30px;
} }
[data-ref="totals_table-outstanding"] {
color: var(--primary-color)
}
</style> </style>
<div id="header"></div> <div id="header"></div>

View File

@ -10,52 +10,53 @@
<div class="grid grid-cols-6 gap-6"> <div class="grid grid-cols-6 gap-6">
<div class="col-span-6 sm:col-span-4"> <div class="col-span-6 sm:col-span-4">
<label for="address1" class="input-label">{{ ctrans('texts.address1') }}</label> <label for="address1" class="input-label">{{ ctrans('texts.address1') }}</label>
<input id="address1" class="input w-full" name="address1" /> <input id="address1" class="input w-full" name="address1"/>
@error('address1') @error('address1')
<div class="validation validation-fail"> <div class="validation validation-fail">
{{ $message }} {{ $message }}
</div> </div>
@enderror @enderror
</div> </div>
<div class="col-span-6 sm:col-span-3"> <div class="col-span-6 sm:col-span-3">
<label for="address2" class="input-label">{{ ctrans('texts.address2') }}</label> <label for="address2" class="input-label">{{ ctrans('texts.address2') }}</label>
<input id="address2" class="input w-full" name="address2" /> <input id="address2" class="input w-full" name="address2"/>
@error('address2') @error('address2')
<div class="validation validation-fail"> <div class="validation validation-fail">
{{ $message }} {{ $message }}
</div> </div>
@enderror @enderror
</div> </div>
<div class="col-span-6 sm:col-span-3"> <div class="col-span-6 sm:col-span-3">
<label for="city" class="input-label">{{ ctrans('texts.city') }}</label> <label for="city" class="input-label">{{ ctrans('texts.city') }}</label>
<input id="city" class="input w-full" name="city" /> <input id="city" class="input w-full" name="city"/>
@error('city') @error('city')
<div class="validation validation-fail"> <div class="validation validation-fail">
{{ $message }} {{ $message }}
</div> </div>
@enderror @enderror
</div> </div>
<div class="col-span-6 sm:col-span-2"> <div class="col-span-6 sm:col-span-2">
<label for="state" class="input-label">{{ ctrans('texts.state') }}</label> <label for="state" class="input-label">{{ ctrans('texts.state') }}</label>
<input id="state" class="input w-full" name="state" /> <input id="state" class="input w-full" name="state"/>
@error('state') @error('state')
<div class="validation validation-fail"> <div class="validation validation-fail">
{{ $message }} {{ $message }}
</div> </div>
@enderror @enderror
</div> </div>
<div class="col-span-6 sm:col-span-2"> <div class="col-span-6 sm:col-span-2">
<label for="postal_code" class="input-label">{{ ctrans('texts.postal_code') }}</label> <label for="postal_code" class="input-label">{{ ctrans('texts.postal_code') }}</label>
<input id="postal_code" class="input w-full" name="postal_code" /> <input id="postal_code" class="input w-full" name="postal_code"/>
@error('postal_code') @error('postal_code')
<div class="validation validation-fail"> <div class="validation validation-fail">
{{ $message }} {{ $message }}
</div> </div>
@enderror @enderror
</div> </div>
<div class="col-span-6 sm:col-span-2"> <div class="col-span-6 sm:col-span-2">
<label for="country" class="input-label">{{ ctrans('texts.country') }}</label> <label for="country" class="input-label">{{ ctrans('texts.country') }}</label>
<select id="country" class="input w-full form-select" name="country"> <select id="country" class="input w-full form-select" name="country">
<option value="none"></option>
@foreach(App\Utils\TranslationHelper::getCountries() as $country) @foreach(App\Utils\TranslationHelper::getCountries() as $country)
<option value="{{ $country->id }}"> <option value="{{ $country->id }}">
{{ $country->iso_3166_2 }} ({{ $country->name }}) {{ $country->iso_3166_2 }} ({{ $country->name }})
@ -63,11 +64,11 @@
@endforeach @endforeach
</select> </select>
@error('country') @error('country')
<div class="validation validation-fail"> <div class="validation validation-fail">
{{ $message }} {{ $message }}
</div> </div>
@enderror @enderror
</div> </div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,74 +1,88 @@
<!-- Client shipping address --> <!-- Client shipping address -->
<h3 class="text-lg font-medium leading-6 text-gray-900 mt-8">{{ ctrans('texts.shipping_address') }}</h3> <h3 class="text-lg font-medium leading-6 text-gray-900 mt-8">{{ ctrans('texts.shipping_address') }}</h3>
<p class="mt-1 text-sm leading-5 text-gray-500"> <p class="mt-1 text-sm leading-5 text-gray-500">
{{ ctrans('texts.enter_your_shipping_address') }} {{ ctrans('texts.enter_your_shipping_address') }}
</p> </p>
<div class="shadow overflow-hidden rounded mt-4"> <div class="shadow overflow-hidden rounded mt-4">
<div class="px-4 py-5 bg-white sm:p-6"> <div class="px-4 py-5 bg-white sm:p-6">
<div class="grid grid-cols-6 gap-6"> <div class="grid grid-cols-6 gap-6">
<div class="col-span-6 sm:col-span-4"> <div class="col-span-6 sm:col-span-4">
<label for="shipping_address1" class="input-label">{{ ctrans('texts.shipping_address1') }}</label> <label for="shipping_address1" class="input-label">{{ ctrans('texts.shipping_address1') }}</label>
<input id="shipping_address1" class="input w-full {{ in_array('shipping_address1', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}" name="shipping_address1" /> <input id="shipping_address1"
@error('shipping_address1') class="input w-full {{ in_array('shipping_address1', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}"
<div class="validation validation-fail"> name="shipping_address1"/>
{{ $message }} @error('shipping_address1')
</div> <div class="validation validation-fail">
@enderror {{ $message }}
</div> </div>
<div class="col-span-6 sm:col-span-3"> @enderror
<label for="shipping_address2" class="input-label">{{ ctrans('texts.shipping_address2') }}</label> </div>
<input id="shipping_address2 {{ in_array('shipping_address2', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}" class="input w-full" name="shipping_address2" /> <div class="col-span-6 sm:col-span-3">
@error('shipping_address2') <label for="shipping_address2" class="input-label">{{ ctrans('texts.shipping_address2') }}</label>
<div class="validation validation-fail"> <input
{{ $message }} id="shipping_address2 {{ in_array('shipping_address2', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}"
</div> class="input w-full" name="shipping_address2"/>
@enderror @error('shipping_address2')
</div> <div class="validation validation-fail">
<div class="col-span-6 sm:col-span-3"> {{ $message }}
<label for="shipping_city" class="input-label">{{ ctrans('texts.shipping_city') }}</label> </div>
<input id="shipping_city" class="input w-full {{ in_array('shipping_city', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}" name="shipping_city" /> @enderror
@error('shipping_city') </div>
<div class="validation validation-fail"> <div class="col-span-6 sm:col-span-3">
{{ $message }} <label for="shipping_city" class="input-label">{{ ctrans('texts.shipping_city') }}</label>
</div> <input id="shipping_city"
@enderror class="input w-full {{ in_array('shipping_city', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}"
</div> name="shipping_city"/>
<div class="col-span-6 sm:col-span-2"> @error('shipping_city')
<label for="shipping_state" class="input-label">{{ ctrans('texts.shipping_state') }}</label> <div class="validation validation-fail">
<input id="shipping_state" class="input w-ful {{ in_array('shipping_state', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}l" name="shipping_state" /> {{ $message }}
@error('shipping_state') </div>
<div class="validation validation-fail"> @enderror
{{ $message }} </div>
</div> <div class="col-span-6 sm:col-span-2">
@enderror <label for="shipping_state" class="input-label">{{ ctrans('texts.shipping_state') }}</label>
</div> <input id="shipping_state"
<div class="col-span-6 sm:col-span-2"> class="input w-ful {{ in_array('shipping_state', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}l"
<label for="shipping_postal_code" class="input-label">{{ ctrans('texts.shipping_postal_code') }}</label> name="shipping_state"/>
<input id="shipping_postal_code" class="input w-full {{ in_array('shipping_postal_code', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}" name="shipping_postal_code" /> @error('shipping_state')
@error('shipping_postal_code') <div class="validation validation-fail">
<div class="validation validation-fail"> {{ $message }}
{{ $message }} </div>
</div> @enderror
@enderror </div>
</div> <div class="col-span-6 sm:col-span-2">
<div class="col-span-4 sm:col-span-2"> <label for="shipping_postal_code" class="input-label">{{ ctrans('texts.shipping_postal_code') }}</label>
<label for="shipping_country" class="input-label">{{ ctrans('texts.shipping_country') }}</label> <input id="shipping_postal_code"
<select id="shipping_country" class="input w-full form-select {{ in_array('shipping_country', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}" name="shipping_country"> class="input w-full {{ in_array('shipping_postal_code', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}"
@foreach(App\Utils\TranslationHelper::getCountries() as $country) name="shipping_postal_code"/>
<option {{ $country == isset(auth()->user()->client->shipping_country->id) ? 'selected' : null }} value="{{ $country->id }}"> @error('shipping_postal_code')
<div class="validation validation-fail">
{{ $message }}
</div>
@enderror
</div>
<div class="col-span-4 sm:col-span-2">
<label for="shipping_country" class="input-label">{{ ctrans('texts.shipping_country') }}</label>
<select id="shipping_country"
class="input w-full form-select {{ in_array('shipping_country', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}"
name="shipping_country">
@foreach(App\Utils\TranslationHelper::getCountries() as $country)
<option value="none"></option>
<option
{{ $country == isset(auth()->user()->client->shipping_country->id) ? 'selected' : null }} value="{{ $country->id }}">
{{ $country->iso_3166_2 }} {{ $country->iso_3166_2 }}
({{ $country->name }}) ({{ $country->name }})
</option> </option>
@endforeach @endforeach
</select> </select>
@error('country') @error('country')
<div class="validation validation-fail"> <div class="validation validation-fail">
{{ $message }} {{ $message }}
</div> </div>
@enderror @enderror
</div> </div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -10,14 +10,14 @@
<div class="grid lg:grid-cols-3"> <div class="grid lg:grid-cols-3">
@if($account && !$account->isPaid()) @if($account && !$account->isPaid())
<div class="hidden lg:block col-span-1 bg-red-100 h-screen"> <div class="hidden lg:block col-span-1 bg-red-100 h-screen">
<img src="{{ asset('images/bg-home2018b.jpg') }}" <img src="{{ asset('images/client-portal-new-image.jpg') }}"
class="w-full h-screen object-cover" class="w-full h-screen object-cover"
alt="Background image"> alt="Background image">
</div> </div>
@endif @endif
<div class="col-span-2 h-screen flex"> <div class="col-span-2 h-screen flex">
<div class="m-auto md:w-1/2 lg:w-1/4"> <div class="m-auto md:w-1/2 lg:w-1/4">
@if($account && !$account->isPaid()) @if($account && !$account->isPaid())
<div> <div>
<img src="{{ asset('images/invoiceninja-black-logo-2.png') }}" class="border-b border-gray-100 h-18 pb-4" alt="Invoice Ninja logo"> <img src="{{ asset('images/invoiceninja-black-logo-2.png') }}" class="border-b border-gray-100 h-18 pb-4" alt="Invoice Ninja logo">

View File

@ -3,7 +3,7 @@
@section('body') @section('body')
<div class="grid lg:grid-cols-12 py-8"> <div class="grid lg:grid-cols-12 py-8">
<div class="lg:col-span-4 lg:col-start-5 px-6"> <div class="col-span-12 lg:col-span-6 lg:col-start-4 xl:col-span-4 xl:col-start-5 px-6">
<div class="flex justify-center"> <div class="flex justify-center">
<img class="h-32 w-auto" src="{{ $company->present()->logo() }}" alt="{{ ctrans('texts.logo') }}"> <img class="h-32 w-auto" src="{{ $company->present()->logo() }}" alt="{{ ctrans('texts.logo') }}">
</div> </div>

View File

@ -30,7 +30,7 @@
{{ ctrans('texts.credit_card') }} {{ ctrans('texts.credit_card') }}
@endcomponent @endcomponent
@include('portal.ninja2020.gateways.stripe.includes.card_widget') @include('portal.ninja2020.gateways.stripe.includes.card_widget', ['show_save_method' => false])
@component('portal.ninja2020.gateways.includes.pay_now', ['id' => 'authorize-card']) @component('portal.ninja2020.gateways.includes.pay_now', ['id' => 'authorize-card'])
{{ ctrans('texts.add_payment_method') }} {{ ctrans('texts.add_payment_method') }}
@ -40,4 +40,4 @@
@section('gateway_footer') @section('gateway_footer')
<script src="https://js.stripe.com/v3/"></script> <script src="https://js.stripe.com/v3/"></script>
<script src="{{ asset('js/clients/payments/stripe-credit-card.js') }}"></script> <script src="{{ asset('js/clients/payments/stripe-credit-card.js') }}"></script>
@endsection @endsection

View File

@ -12,4 +12,6 @@
@endunless @endunless
</div> </div>
@include('portal.ninja2020.gateways.includes.save_card') @unless(isset($show_save_method) && $show_save_method == false)
@include('portal.ninja2020.gateways.includes.save_card')
@endunless

View File

@ -43,7 +43,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="@yield('meta_description')"/> <meta name="description" content="@yield('meta_description')"/>
<!-- CSRF Token --> <!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}"> <meta name="csrf-token" content="{{ csrf_token() }}">

View File

@ -34,6 +34,7 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a
Route::post('claim_license', 'LicenseController@index')->name('license.index'); Route::post('claim_license', 'LicenseController@index')->name('license.index');
Route::resource('clients', 'ClientController'); // name = (clients. index / create / show / update / destroy / edit Route::resource('clients', 'ClientController'); // name = (clients. index / create / show / update / destroy / edit
Route::put('clients/{client}/adjust_ledger', 'ClientController@adjustLedger')->name('clients.adjust_ledger');
Route::put('clients/{client}/upload', 'ClientController@upload')->name('clients.upload'); Route::put('clients/{client}/upload', 'ClientController@upload')->name('clients.upload');
Route::post('clients/bulk', 'ClientController@bulk')->name('clients.bulk'); Route::post('clients/bulk', 'ClientController@bulk')->name('clients.bulk');
@ -84,6 +85,7 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a
Route::post('group_settings/bulk', 'GroupSettingController@bulk'); Route::post('group_settings/bulk', 'GroupSettingController@bulk');
Route::post('import', 'ImportController@import')->name('import.import'); Route::post('import', 'ImportController@import')->name('import.import');
Route::post('import_json', 'ImportJsonController@import')->name('import.import_json');
Route::post('preimport', 'ImportController@preimport')->name('import.preimport'); Route::post('preimport', 'ImportController@preimport')->name('import.preimport');
Route::resource('invoices', 'InvoiceController'); // name = (invoices. index / create / show / update / destroy / edit Route::resource('invoices', 'InvoiceController'); // name = (invoices. index / create / show / update / destroy / edit

View File

@ -15,9 +15,9 @@ Route::post('setup/check_db', 'SetupController@checkDB')->middleware('guest');
Route::post('setup/check_mail', 'SetupController@checkMail')->middleware('guest'); Route::post('setup/check_mail', 'SetupController@checkMail')->middleware('guest');
Route::post('setup/check_pdf', 'SetupController@checkPdf')->middleware('guest'); Route::post('setup/check_pdf', 'SetupController@checkPdf')->middleware('guest');
Route::get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm')->name('password.request'); Route::get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm')->middleware('domain_db')->name('password.request');
Route::post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.email'); Route::post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.email');
Route::get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm')->middleware('email_db')->name('password.reset'); Route::get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm')->middleware(['domain_db','email_db'])->name('password.reset');
Route::post('password/reset', 'Auth\ResetPasswordController@reset')->middleware('email_db')->name('password.update'); Route::post('password/reset', 'Auth\ResetPasswordController@reset')->middleware('email_db')->name('password.update');
/* /*
@ -36,4 +36,6 @@ Route::group(['middleware' => ['url_db']], function () {
}); });
Route::get('stripe/signup/{token}', 'StripeConnectController@initialize')->name('stripe_connect.initialization'); Route::get('stripe/signup/{token}', 'StripeConnectController@initialize')->name('stripe_connect.initialization');
Route::get('stripe/completed', 'StripeConnectController@completed')->name('stripe_connect.return'); Route::get('stripe/completed', 'StripeConnectController@completed')->name('stripe_connect.return');
Route::get('checkout/3ds_redirect/{company_key}/{company_gateway_id}/{hash}', 'Gateways\Checkout3dsController@index')->name('checkout.3ds_redirect');

View File

@ -12,24 +12,43 @@ namespace Tests\Feature\Import;
use App\Jobs\Import\CSVImport; use App\Jobs\Import\CSVImport;
use App\Models\Account; use App\Models\Account;
use App\Models\Activity;
use App\Models\Backup;
use App\Models\Client; use App\Models\Client;
use App\Models\ClientContact; use App\Models\ClientContact;
use App\Models\ClientGatewayToken;
use App\Models\Company; use App\Models\Company;
use App\Models\CompanyGateway;
use App\Models\CompanyLedger;
use App\Models\CompanyToken; use App\Models\CompanyToken;
use App\Models\CompanyUser; use App\Models\CompanyUser;
use App\Models\Credit;
use App\Models\CreditInvitation;
use App\Models\Design;
use App\Models\Document;
use App\Models\Expense; use App\Models\Expense;
use App\Models\ExpenseCategory; use App\Models\ExpenseCategory;
use App\Models\GroupSetting;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\InvoiceInvitation;
use App\Models\Payment; use App\Models\Payment;
use App\Models\PaymentTerm; use App\Models\PaymentTerm;
use App\Models\Paymentable;
use App\Models\Product; use App\Models\Product;
use App\Models\Vendor; use App\Models\Project;
use App\Models\VendorContact; use App\Models\Quote;
use App\Models\QuoteInvitation;
use App\Models\RecurringInvoice;
use App\Models\RecurringInvoiceInvitation;
use App\Models\Subscription;
use App\Models\Task;
use App\Models\TaskStatus; use App\Models\TaskStatus;
use App\Models\TaxRate; use App\Models\TaxRate;
use App\Models\User; use App\Models\User;
use App\Models\Vendor; use App\Models\Vendor;
use App\Models\VendorContact;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Routing\Middleware\ThrottleRequests; use Illuminate\Routing\Middleware\ThrottleRequests;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@ -45,6 +64,7 @@ use Tests\TestCase;
class ImportCompanyTest extends TestCase class ImportCompanyTest extends TestCase
{ {
use MakesHash; use MakesHash;
use DatabaseTransactions;
public $account; public $account;
public $company; public $company;
@ -81,6 +101,13 @@ class ImportCompanyTest extends TestCase
$this->backup_json_object = json_decode(file_get_contents($backup_json_file)); $this->backup_json_object = json_decode(file_get_contents($backup_json_file));
Credit::all()->each(function($credit){
$credit->forceDelete();
});
CreditInvitation::all()->each(function($credit){
$credit->forceDelete();
});
} }
public function testBackupJsonRead() public function testBackupJsonRead()
@ -256,7 +283,6 @@ class ImportCompanyTest extends TestCase
$obj_array = (array)$obj; $obj_array = (array)$obj;
unset($obj_array['user_id']); unset($obj_array['user_id']);
unset($obj_array['company_id']); unset($obj_array['company_id']);
unset($obj_array['account_id']);
unset($obj_array['hashed_id']); unset($obj_array['hashed_id']);
unset($obj_array['id']); unset($obj_array['id']);
unset($obj_array['tax_rate_id']); unset($obj_array['tax_rate_id']);
@ -288,10 +314,8 @@ class ImportCompanyTest extends TestCase
$obj_array = (array)$obj; $obj_array = (array)$obj;
unset($obj_array['user_id']); unset($obj_array['user_id']);
unset($obj_array['company_id']); unset($obj_array['company_id']);
unset($obj_array['account_id']);
unset($obj_array['hashed_id']); unset($obj_array['hashed_id']);
unset($obj_array['id']); unset($obj_array['id']);
unset($obj_array['tax_rate_id']);
$new_obj = ExpenseCategory::firstOrNew( $new_obj = ExpenseCategory::firstOrNew(
['name' => $obj->name, 'company_id' => $this->company->id], ['name' => $obj->name, 'company_id' => $this->company->id],
@ -321,10 +345,8 @@ class ImportCompanyTest extends TestCase
$obj_array = (array)$obj; $obj_array = (array)$obj;
unset($obj_array['user_id']); unset($obj_array['user_id']);
unset($obj_array['company_id']); unset($obj_array['company_id']);
unset($obj_array['account_id']);
unset($obj_array['hashed_id']); unset($obj_array['hashed_id']);
unset($obj_array['id']); unset($obj_array['id']);
unset($obj_array['tax_rate_id']);
$new_obj = TaskStatus::firstOrNew( $new_obj = TaskStatus::firstOrNew(
['name' => $obj->name, 'company_id' => $this->company->id], ['name' => $obj->name, 'company_id' => $this->company->id],
@ -416,79 +438,605 @@ class ImportCompanyTest extends TestCase
$this->assertEquals(1, ClientContact::count()); $this->assertEquals(1, ClientContact::count());
/***************************** Client Contacts *****************************/ /***************************** Client Contacts *****************************/
/* Generic */
/* Generic */
//vendors! //vendors!
/* Generic */
$this->assertEquals(1, count($this->backup_json_object->vendors));
$this->genericImport(Vendor::class,
['user_id', 'assigned_user_id', 'company_id', 'id', 'hashed_id'],
[['users' => 'user_id'], ['users' =>'assigned_user_id']],
'vendors',
'number');
$this->assertEquals(1, Vendor::count());
/* Generic */
$this->assertEquals(1, count($this->backup_json_object->projects));
//$class, $unset, $transforms, $object_property, $match_key
$this->genericImport(Project::class,
['user_id', 'assigned_user_id', 'company_id', 'id', 'hashed_id','client_id'],
[['users' => 'user_id'], ['users' =>'assigned_user_id'], ['clients' => 'client_id']],
'projects',
'number');
$this->assertEquals(1, Project::count());
//projects! //projects!
/***************************** Products *****************************/ //products!
// Product::unguard();
// $this->assertEquals(1, count($this->backup_json_object->products)); $this->assertEquals(1, count($this->backup_json_object->products));
// foreach($this->backup_json_object->products as $obj) $this->genericNewClassImport(Product::class,
// { ['user_id', 'company_id', 'hashed_id', 'id'],
[['users' => 'user_id'], ['users' =>'assigned_user_id'], ['vendors' => 'vendor_id'], ['projects' => 'project_id']],
'products'
);
$this->assertEquals(1, Product::count());
// $user_id = $this->transformId('users', $obj->user_id); //company gateways
// $assigned_user_id = $this->transformId('users', $obj->assigned_user_id);
// $vendor_id = $this->transformId('vendors', $obj->vendor_id);
// $project_id = $this->transformId('projects', $obj->project_id);
// $obj_array = (array)$obj; $this->assertEquals(1, count($this->backup_json_object->company_gateways));
// unset($obj_array['user_id']);
// unset($obj_array['company_id']);
// unset($obj_array['account_id']);
// unset($obj_array['hashed_id']);
// unset($obj_array['id']);
// $new_obj = new Product(); $this->genericNewClassImport(CompanyGateway::class,
// $new_obj->company_id = $this->company->id; ['user_id', 'company_id', 'hashed_id', 'id'],
// $new_obj->user_id = $user_id; [['users' => 'user_id']],
// $new_obj->assigned_user_id = $assigned_user_id; 'company_gateways'
// $new_obj->vendor_id = $vendor_id; );
// $new_obj->project_id = $project_id;
// $new_obj->fill($obj_array);
// $new_obj->save(['timestamps' => false]); $this->assertEquals(1, CompanyGateway::count());
// $this->ids['products']["{$obj->hashed_id}"] = $new_obj->id;
// } //company gateways
// Product::reguard();
// $this->assertEquals(1, Product::count());
/***************************** Products *****************************/
//client gateway tokens
$this->genericNewClassImport(ClientGatewayToken::class,
['company_id', 'id', 'hashed_id','client_id'],
[['clients' => 'client_id']],
'client_gateway_tokens');
//client gateway tokens
//Group Settings
$this->genericImport(GroupSetting::class,
['user_id', 'company_id', 'id', 'hashed_id',],
[['users' => 'user_id']],
'group_settings',
'name');
//Group Settings
//Subscriptions
$this->assertEquals(1, count($this->backup_json_object->subscriptions));
$this->genericImport(Subscription::class,
['user_id', 'assigned_user_id', 'company_id', 'id', 'hashed_id',],
[['group_settings' => 'group_id'], ['users' => 'user_id'], ['users' => 'assigned_user_id']],
'subscriptions',
'name');
$this->assertEquals(1, Subscription::count());
//Subscriptions
// Recurring Invoices
$this->assertEquals(2, count($this->backup_json_object->recurring_invoices));
$this->genericImport(RecurringInvoice::class,
['user_id', 'assigned_user_id', 'company_id', 'id', 'hashed_id', 'client_id','subscription_id','project_id','vendor_id','status'],
[
['subscriptions' => 'subscription_id'],
['users' => 'user_id'],
['users' => 'assigned_user_id'],
['clients' => 'client_id'],
['projects' => 'project_id'],
['vendors' => 'vendor_id'],
['clients' => 'client_id'],
],
'recurring_invoices',
'number');
$this->assertEquals(2, RecurringInvoice::count());
// Recurring Invoices
// Recurring Invoice Invitations
$this->assertEquals(2, count($this->backup_json_object->recurring_invoice_invitations));
$this->genericImport(RecurringInvoiceInvitation::class,
['user_id', 'client_contact_id', 'company_id', 'id', 'hashed_id', 'recurring_invoice_id'],
[
['users' => 'user_id'],
['recurring_invoices' => 'recurring_invoice_id'],
['client_contacts' => 'client_contact_id'],
],
'recurring_invoice_invitations',
'key');
$this->assertEquals(2, RecurringInvoiceInvitation::count());
// Recurring Invoice Invitations
// Invoices
$this->assertEquals(2, count($this->backup_json_object->invoices));
$this->genericImport(Invoice::class,
['user_id', 'client_id', 'company_id', 'id', 'hashed_id', 'recurring_id','status'],
[
['users' => 'user_id'],
['users' => 'assigned_user_id'],
['recurring_invoices' => 'recurring_id'],
['clients' => 'client_id'],
['subscriptions' => 'subscription_id'],
['projects' => 'project_id'],
['vendors' => 'vendor_id'],
],
'invoices',
'number');
$this->assertEquals(2, Invoice::count());
// Invoices
// Invoice Invitations
$this->assertEquals(2, count($this->backup_json_object->invoice_invitations));
$this->genericImport(InvoiceInvitation::class,
['user_id', 'client_contact_id', 'company_id', 'id', 'hashed_id', 'invoice_id'],
[
['users' => 'user_id'],
['invoices' => 'invoice_id'],
['client_contacts' => 'client_contact_id'],
],
'invoice_invitations',
'key');
$this->assertEquals(2, InvoiceInvitation::count());
// Invoice Invitations
// Quotes
$this->assertEquals(2, count($this->backup_json_object->quotes));
$this->genericImport(Quote::class,
['user_id', 'client_id', 'company_id', 'id', 'hashed_id', 'recurring_id','status'],
[
['users' => 'user_id'],
['users' => 'assigned_user_id'],
['recurring_invoices' => 'recurring_id'],
['clients' => 'client_id'],
['subscriptions' => 'subscription_id'],
['projects' => 'project_id'],
['vendors' => 'vendor_id'],
],
'quotes',
'number');
$this->assertEquals(2, Quote::count());
// Quotes
// Quotes Invitations
$this->assertEquals(2, count($this->backup_json_object->quote_invitations));
$this->genericImport(QuoteInvitation::class,
['user_id', 'client_contact_id', 'company_id', 'id', 'hashed_id', 'quote_id'],
[
['users' => 'user_id'],
['quotes' => 'quote_id'],
['client_contacts' => 'client_contact_id'],
],
'quote_invitations',
'key');
$this->assertEquals(2, QuoteInvitation::count());
// Quotes Invitations
// Credits
$this->assertEquals(2, count($this->backup_json_object->credits));
$this->genericImport(Credit::class,
['user_id', 'client_id', 'company_id', 'id', 'hashed_id', 'recurring_id','status'],
[
['users' => 'user_id'],
['users' => 'assigned_user_id'],
['recurring_invoices' => 'recurring_id'],
['clients' => 'client_id'],
['subscriptions' => 'subscription_id'],
['projects' => 'project_id'],
['vendors' => 'vendor_id'],
],
'credits',
'number');
$this->assertEquals(2, Credit::count());
// Credits
// Credits Invitations
$this->assertEquals(2, count($this->backup_json_object->credit_invitations));
$this->genericImport(CreditInvitation::class,
['user_id', 'client_contact_id', 'company_id', 'id', 'hashed_id', 'credit_id'],
[
['users' => 'user_id'],
['credits' => 'credit_id'],
['client_contacts' => 'client_contact_id'],
],
'credit_invitations',
'key');
$this->assertEquals(2, CreditInvitation::count());
// Credits Invitations
// Expenses
$this->assertEquals(2, count($this->backup_json_object->expenses));
$this->genericImport(Expense::class,
['assigned_user_id', 'user_id', 'client_id', 'company_id', 'id', 'hashed_id', 'project_id','vendor_id'],
[
['users' => 'user_id'],
['users' => 'assigned_user_id'],
['clients' => 'client_id'],
['projects' => 'project_id'],
['vendors' => 'vendor_id'],
],
'expenses',
'number');
$this->assertEquals(2, Expense::count());
// Expenses
// Tasks
$this->assertEquals(3, count($this->backup_json_object->tasks));
$this->genericImport(Task::class,
['assigned_user_id', 'user_id', 'client_id', 'company_id', 'id', 'hashed_id', 'invoice_id','project_id'],
[
['users' => 'user_id'],
['users' => 'assigned_user_id'],
['clients' => 'client_id'],
['projects' => 'project_id'],
['invoices' => 'invoice_id'],
],
'tasks',
'number');
$this->assertEquals(3, Task::count());
// Tasks
// Payments
$this->assertEquals(2, count($this->backup_json_object->payments));
$this->genericImport(Payment::class,
['assigned_user_id', 'user_id', 'client_id', 'company_id', 'id', 'hashed_id', 'client_contact_id','invitation_id','vendor_id','paymentables'],
[
['users' => 'user_id'],
['users' => 'assigned_user_id'],
['clients' => 'client_id'],
['client_contacts' => 'client_contact_id'],
['vendors' => 'vendor_id'],
['invoice_invitations' => 'invitation_id'],
['company_gateways' => 'company_gateway_id'],
],
'payments',
'number');
$this->assertEquals(2, Payment::count());
// Payments
// Paymentables
$this->paymentablesImport();
$this->assertEquals(1, Paymentable::count());
// Paymentables
// Activities
$activities = [];
foreach($this->backup_json_object->activities as $activity)
{
$activity->account_id = $this->company->account_id;
$activities[] = $activity;
}
$this->assertEquals(25, count($this->backup_json_object->activities));
$this->backup_json_object->activities = $activities;
$this->genericImport(Activity::class,
[
'user_id',
'company_id',
'client_id',
'client_contact_id',
'project_id',
'vendor_id',
'payment_id',
'invoice_id',
'credit_id',
'invitation_id',
'task_id',
'expense_id',
'token_id',
'quote_id',
'subscription_id',
'recurring_invoice_id',
'hashed_id',
'company_id',
],
[
['users' => 'user_id'],
['clients' => 'client_id'],
['client_contacts' => 'client_contact_id'],
['projects' => 'project_id'],
['vendors' => 'vendor_id'],
['payments' => 'payment_id'],
['invoices' => 'invoice_id'],
['credits' => 'credit_id'],
['tasks' => 'task_id'],
['expenses' => 'expense_id'],
['quotes' => 'quote_id'],
['subscriptions' => 'subscription_id'],
['recurring_invoices' => 'recurring_invoice_id'],
['invitations' => 'invitation_id'],
],
'activities',
'created_at');
$this->assertEquals(25, Activity::count());
// Activities
// Backup
$this->assertEquals(25, count($this->backup_json_object->backups));
$this->genericImportWithoutCompany(Backup::class,
['activity_id','hashed_id'],
[
['activities' => 'activity_id'],
],
'backups',
'created_at');
$this->assertEquals(25, Backup::count());
// Backup
// Company Ledger
$this->assertEquals(3, count($this->backup_json_object->company_ledger));
$this->genericImport(CompanyLedger::class,
['company_id', 'user_id', 'client_id', 'activity_id', 'id','account_id'],
[
['users' => 'user_id'],
['clients' => 'client_id'],
['activities' => 'activity_id'],
],
'company_ledger',
'created_at');
$this->assertEquals(3, CompanyLedger::count());
// Company Ledger
// Designs
$this->genericImport(Design::class,
['company_id', 'user_id'],
[
['users' => 'user_id'],
],
'designs',
'name');
// Designs
// Documents
$this->assertEquals(2, count($this->backup_json_object->documents));
$this->documentsImport();
$this->assertEquals(2, Document::count());
// Documents
} }
private function genericImport($class, $unset, $transform, $object_property, $match_key) private function documentsImport()
{
foreach($this->backup_json_object->documents as $document)
{
$new_document = new Document();
$new_document->user_id = $this->transformId('users', $document->user_id);
$new_document->assigned_user_id = $this->transformId('users', $document->assigned_user_id);
$new_document->company_id = $this->company->id;
$new_document->project_id = $this->transformId('projects', $document->project_id);
$new_document->vendor_id = $this->transformId('vendors', $document->vendor_id);
$new_document->url = $document->url;
$new_document->preview = $document->preview;
$new_document->name = $document->name;
$new_document->type = $document->type;
$new_document->disk = $document->disk;
$new_document->hash = $document->hash;
$new_document->size = $document->size;
$new_document->width = $document->width;
$new_document->height = $document->height;
$new_document->is_default = $document->is_default;
$new_document->custom_value1 = $document->custom_value1;
$new_document->custom_value2 = $document->custom_value2;
$new_document->custom_value3 = $document->custom_value3;
$new_document->custom_value4 = $document->custom_value4;
$new_document->deleted_at = $document->deleted_at;
$new_document->documentable_id = $this->transformDocumentId($document->documentable_id, $document->documentable_type);
$new_document->documentable_type = $document->documentable_type;
$new_document->save(['timestamps' => false]);
}
}
private function transformDocumentId($id, $type)
{
switch ($type) {
case Company::class:
return $this->company->id;
break;
case Client::class:
return $this->transformId('clients', $id);
break;
case ClientContact::class:
return $this->transformId('client_contacts', $id);
break;
case Credit::class:
return $this->transformId('credits', $id);
break;
case Expense::class:
return $this->transformId('expenses', $id);
break;
case 'invoices':
return $this->transformId('invoices', $id);
break;
case Payment::class:
return $this->transformId('payments', $id);
break;
case Product::class:
return $this->transformId('products', $id);
break;
case Quote::class:
return $this->transformId('quotes', $id);
break;
case RecurringInvoice::class:
return $this->transformId('recurring_invoices', $id);
break;
case Company::class:
return $this->transformId('clients', $id);
break;
default:
# code...
break;
}
}
private function paymentablesImport()
{
foreach($this->backup_json_object->payments as $payment)
{
foreach($payment->paymentables as $paymentable_obj)
{
$paymentable = new Paymentable();
$paymentable->payment_id = $this->transformId('payments', $paymentable_obj->payment_id);
$paymentable->paymentable_type = $paymentable_obj->paymentable_type;
$paymentable->amount = $paymentable_obj->amount;
$paymentable->refunded = $paymentable_obj->refunded;
$paymentable->created_at = $paymentable_obj->created_at;
$paymentable->deleted_at = $paymentable_obj->deleted_at;
$paymentable->updated_at = $paymentable_obj->updated_at;
$paymentable->paymentable_id = $this->convertPaymentableId($paymentable_obj->paymentable_type, $paymentable_obj->paymentable_id);
$paymentable->paymentable_type = $paymentable_obj->paymentable_type;
$paymentable->save(['timestamps' => false]);
}
}
}
private function convertPaymentableId($type, $id)
{
switch ($type) {
case 'invoices':
return $this->transformId('invoices', $id);
break;
case Credit::class:
return $this->transformId('credits', $id);
break;
case Payment::class:
return $this->transformId('payments', $id);
default:
# code...
break;
}
}
private function genericNewClassImport($class, $unset, $transforms, $object_property)
{ {
$class::unguard(); $class::unguard();
foreach($this->backup_json_object->{$object_property} as $obj) foreach($this->backup_json_object->{$object_property} as $obj)
{ {
/* Remove unwanted keys*/
$obj_array = (array)$obj; $obj_array = (array)$obj;
foreach($unset as $un){ foreach($unset as $un){
unset($obj_array[$un]); unset($obj_array[$un]);
} }
foreach($trans as $key => $value) $activity_invitation_key = false;
{
$obj_array["{$value}"] = $this->transformId($object_property, $obj->{$value}); if($class instanceof Activity){
if(isset($obj->invitation_id)){
if(isset($obj->invoice_id))
$activity_invitation_key = 'invoice_invitations';
elseif(isset($obj->quote_id))
$activity_invitation_key = 'quote_invitations';
elseif($isset($obj->credit_id))
$activity_invitation_key = 'credit_invitations';
}
} }
$new_obj = $class::firstOrNew( /* Transform old keys to new keys */
[$match_key => $obj->{$match_key}, 'company_id' => $this->company->id], foreach($transforms as $transform)
$obj_array, {
); foreach($transform as $key => $value)
{
if($class instanceof Activity && $activity_invitation_key)
$key = $activity_invitation_key;
$obj_array["{$value}"] = $this->transformId($key, $obj->{$value});
}
}
if($class instanceof CompanyGateway) {
$obj_array['config'] = encrypt($obj_array['config']);
}
$new_obj = new $class();
$new_obj->company_id = $this->company->id;
$new_obj->fill($obj_array);
$new_obj->save(['timestamps' => false]); $new_obj->save(['timestamps' => false]);
@ -497,10 +1045,119 @@ class ImportCompanyTest extends TestCase
} }
$class::reguard(); $class::reguard();
} }
private function genericImportWithoutCompany($class, $unset, $transforms, $object_property, $match_key)
{
$class::unguard();
foreach($this->backup_json_object->{$object_property} as $obj)
{
/* Remove unwanted keys*/
$obj_array = (array)$obj;
foreach($unset as $un){
unset($obj_array[$un]);
}
/* Transform old keys to new keys */
foreach($transforms as $transform)
{
foreach($transform as $key => $value)
{
$obj_array["{$value}"] = $this->transformId($key, $obj->{$value});
}
}
/* New to convert product ids from old hashes to new hashes*/
if($class instanceof Subscription){
$obj_array['product_ids'] = $this->recordProductIds($obj_array['product_ids']);
$obj_array['recurring_product_ids'] = $this->recordProductIds($obj_array['recurring_product_ids']);
}
$new_obj = $class::firstOrNew(
[$match_key => $obj->{$match_key}],
$obj_array,
);
$new_obj->save(['timestamps' => false]);
if($new_obj instanceof CompanyLedger){
}
else
$this->ids["{$object_property}"]["{$obj->hashed_id}"] = $new_obj->id;
}
$class::reguard();
}
private function genericImport($class, $unset, $transforms, $object_property, $match_key)
{
$class::unguard();
foreach($this->backup_json_object->{$object_property} as $obj)
{
/* Remove unwanted keys*/
$obj_array = (array)$obj;
foreach($unset as $un){
unset($obj_array[$un]);
}
/* Transform old keys to new keys */
foreach($transforms as $transform)
{
foreach($transform as $key => $value)
{
$obj_array["{$value}"] = $this->transformId($key, $obj->{$value});
}
}
/* New to convert product ids from old hashes to new hashes*/
if($class instanceof Subscription){
$obj_array['product_ids'] = $this->recordProductIds($obj_array['product_ids']);
$obj_array['recurring_product_ids'] = $this->recordProductIds($obj_array['recurring_product_ids']);
}
$new_obj = $class::firstOrNew(
[$match_key => $obj->{$match_key}, 'company_id' => $this->company->id],
$obj_array,
);
$new_obj->save(['timestamps' => false]);
if($new_obj instanceof CompanyLedger){
}
else
$this->ids["{$object_property}"]["{$obj->hashed_id}"] = $new_obj->id;
}
$class::reguard();
}
private function recordProductIds($ids)
{
$id_array = explode(",", $ids);
$tmp_arr = [];
foreach($id_array as $id) {
$tmp_arr[] = $this->encodePrimaryKey($this->transformId('products', $id));
}
return implode(",", $tmp_arr);
}
private function transformId(string $resource, ?string $old): ?int private function transformId(string $resource, ?string $old): ?int
{ {
if(empty($old)) if(empty($old))
@ -511,7 +1168,7 @@ class ImportCompanyTest extends TestCase
} }
if (! array_key_exists("{$old}", $this->ids[$resource])) { if (! array_key_exists("{$old}", $this->ids[$resource])) {
throw new \Exception("Missing resource key: {$old}"); throw new \Exception("Missing {$resource} key: {$old}");
} }
return $this->ids[$resource]["{$old}"]; return $this->ids[$resource]["{$old}"];

Binary file not shown.

View File

@ -54,6 +54,10 @@ class UserTest extends TestCase
ThrottleRequests::class, ThrottleRequests::class,
PasswordProtection::class PasswordProtection::class
); );
// if (config('ninja.testvars.travis') !== false) {
// $this->markTestSkipped('Skip test for Travis');
// }
} }
public function testUserList() public function testUserList()
@ -95,54 +99,58 @@ class UserTest extends TestCase
$this->assertNotNull($arr['data']['company_user']); $this->assertNotNull($arr['data']['company_user']);
} }
// public function testUserAttachAndDetach() public function testUserAttachAndDetach()
// { {
// $this->withoutMiddleware(PasswordProtection::class);
// $user = UserFactory::create($this->account->id); $this->withoutMiddleware(PasswordProtection::class);
// $user->first_name = 'Test';
// $user->last_name = 'Palloni';
// $user->email = $this->default_email;
// $user->save();
// $data = $user->toArray(); $data = [
'first_name' => 'Test',
'last_name' => 'Palloni',
'email' => $this->default_email,
];
// $response = false; $response = false;
// try { try {
// $response = $this->withHeaders([ $response = $this->withHeaders([
// 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-SECRET' => config('ninja.api_secret'),
// 'X-API-TOKEN' => $this->token, 'X-API-TOKEN' => $this->token,
// 'X-API-PASSWORD' => 'ALongAndBriliantPassword', 'X-API-PASSWORD' => 'ALongAndBriliantPassword',
// ])->post('/api/v1/users?include=company_user', $data); ])->post('/api/v1/users?include=company_user', $data);
// } catch (ValidationException $e) { } catch (ValidationException $e) {
// $message = json_decode($e->validator->getMessageBag(), 1); $message = json_decode($e->validator->getMessageBag(), 1);
// nlog($message); nlog($message);
// var_dump($message); var_dump($message);
// $this->assertNotNull($message); $this->assertNotNull($message);
// } }
// $response->assertStatus(200); $response->assertStatus(200);
// // $this->assertNotNull($user->company_user); $arr = $response->json();
// // $this->assertEquals($user->company_user->company_id, $this->company->id);
// $response = $this->withHeaders([ // $this->assertNotNull($user->company_user);
// 'X-API-SECRET' => config('ninja.api_secret'), // $this->assertEquals($user->company_user->company_id, $this->company->id);
// 'X-API-TOKEN' => $this->token,
// 'X-API-PASSWORD' => 'ALongAndBriliantPassword',
// ])->delete('/api/v1/users/'.$this->encodePrimaryKey($user->id).'/detach_from_company?include=company_user');
// $response->assertStatus(200); $response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
'X-API-PASSWORD' => 'ALongAndBriliantPassword',
])->delete('/api/v1/users/'.$arr['data']['id'].'/detach_from_company?include=company_user');
// $cu = CompanyUser::whereUserId($user->id)->whereCompanyId($this->company->id)->first(); $response->assertStatus(200);
// $ct = CompanyToken::whereUserId($user->id)->whereCompanyId($this->company->id)->first();
// $this->assertNull($cu); $user_id = $this->decodePrimaryKey($arr['data']['id']);
// $this->assertNull($ct);
// $this->assertNotNull($user); $cu = CompanyUser::whereUserId($user_id)->whereCompanyId($this->company->id)->first();
// } $ct = CompanyToken::whereUserId($user_id)->whereCompanyId($this->company->id)->first();
$user = User::find($user_id);
$this->assertNull($cu);
$this->assertNull($ct);
$this->assertNotNull($user);
}
public function testAttachUserToMultipleCompanies() public function testAttachUserToMultipleCompanies()
{ {
@ -168,14 +176,12 @@ class UserTest extends TestCase
$cu->save(); $cu->save();
/*Create New Blank User and Attach to Company 2*/ /*Create New Blank User and Attach to Company 2*/
$new_user = UserFactory::create($this->account->id); $data = [
$new_user->first_name = 'Test'; 'first_name' => 'Test',
$new_user->last_name = 'Palloni'; 'last_name' => 'Palloni',
$new_user->email = $this->default_email; 'email' => $this->default_email,
$new_user->save(); ];
$data = $new_user->toArray();
$response = $this->withHeaders([ $response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'), 'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $company_token->token, 'X-API-TOKEN' => $company_token->token,

View File

@ -34,9 +34,9 @@ class SystemHealthTest extends TestCase
$this->assertTrue((bool) $results['system_health']); $this->assertTrue((bool) $results['system_health']);
$this->assertTrue($results['extensions'][0]['mysqli']); // $this->assertTrue($results['extensions'][0]['mysqli']);
$this->assertTrue($results['extensions'][1]['gd']); $this->assertTrue($results['extensions'][0]['gd']);
$this->assertTrue($results['extensions'][2]['curl']); $this->assertTrue($results['extensions'][1]['curl']);
$this->assertTrue($results['extensions'][3]['zip']); $this->assertTrue($results['extensions'][2]['zip']);
} }
} }