[], '5.7.35' => [ Payment::class => [ 'refund_meta', 'category_id', ], User::class => [ 'user_logged_in_notification', ], Design::class => [ 'is_template', ] ], ]; /** * Create a new job instance. * * @param Company $company * @param User $user * @param string $file_location - the cache hash of the import data. * @param array $request_array */ public function __construct(Company $company, User $user, string $file_location, array $request_array) { $this->company = $company; $this->user = $user; $this->file_location = $file_location; $this->request_array = $request_array; $this->current_app_version = config('ninja.app_version'); } private function getObject($key, $force_array = false) { set_time_limit(0); $json = JsonMachine::fromFile($this->file_path, '/'.$key, new ExtJsonDecoder); if ($force_array) { return iterator_to_array($json); } return $json; } public function handle() { MultiDB::setDb($this->company->db); $this->company = Company::query()->where('company_key', $this->company->company_key)->firstOrFail(); $this->account = $this->company->account; $this->company_owner = $this->company->owner(); nlog("Company ID = {$this->company->id}"); nlog("file_location ID = {$this->file_location}"); // $this->backup_file = Cache::get($this->hash); if (empty($this->file_location)) { throw new \Exception('No import data found, has the cache expired?'); } // $this->backup_file = json_decode(file_get_contents($this->file_location)); $tmp_file = $this->unzipFile(); $this->file_path = $tmp_file; $this->checkUserCount(); if (array_key_exists('import_settings', $this->request_array) && $this->request_array['import_settings'] == 'true') { $this->preFlightChecks()->importSettings(); } if (array_key_exists('import_data', $this->request_array) && $this->request_array['import_data'] == 'true') { try { $this->preFlightChecks() ->purgeCompanyData() ->importCompany() ->importData() ->postImportCleanup(); $data = [ 'errors' => [] ]; $_company = Company::query()->find($this->company->id); $nmo = new NinjaMailerObject; $nmo->mailable = new ImportCompleted($_company, $data); $nmo->company = $_company; $nmo->settings = $_company->settings; $nmo->to_user = $_company->owner(); NinjaMailerJob::dispatch($nmo); } catch(\Exception $e) { info($e->getMessage()); } } unlink($tmp_file); } // private function postImportCleanup() { //ensure all clients have a contact $this->company ->clients() ->whereDoesntHave('contacts') ->cursor() ->each(function ($client) { $new_contact = ClientContactFactory::create($client->company_id, $client->user_id); $new_contact->client_id = $client->id; $new_contact->contact_key = Str::random(40); $new_contact->is_primary = true; $new_contact->confirmed = true; $new_contact->email = ' '; $new_contact->save(); }); } private function unzipFile() { $path = TempFile::filePath(Storage::disk(config('filesystems.default'))->get($this->file_location), basename($this->file_location)); $zip = new ZipArchive(); $archive = $zip->open($path); $file_path = sys_get_temp_dir().'/'.sha1(microtime()); $zip->extractTo($file_path); $zip->close(); $file_location = "{$file_path}/backup.json"; if (! file_exists($file_path)) { throw new NonExistingMigrationFile('Backup file does not exist, or is corrupted.'); } return $file_location; } /** * On the hosted platform we cannot allow the * import to start if there are users > plan number * due to entity user_id dependencies * * @return bool */ private function checkUserCount() { if (Ninja::isSelfHost()) { $this->pre_flight_checks_pass = true; } // $backup_users = $this->backup_file->users; $backup_users = $this->getObject('users', true); $company_users = $this->company->users; nlog("Backup user count = ".count($backup_users)); if (count($backup_users) > 1) { } nlog("backup users email = " . $backup_users[0]->email); if (count($backup_users) == 1 && $this->company_owner->email != $backup_users[0]->email) { } $backup_users_emails = array_column($backup_users, 'email'); $company_users_emails = $company_users->pluck('email')->toArray(); $existing_user_count = count(array_intersect($backup_users_emails, $company_users_emails)); nlog("existing user count = {$existing_user_count}"); if ($existing_user_count > 1) { if ($this->account->plan == 'pro') { } if ($this->account->plan == 'enterprise') { } } if ($this->company->account->isFreeHostedClient() && (count($this->getObject('clients', true)) > config('ninja.quotas.free.clients'))) { nlog("client quota busted"); $client_limit = config('ninja.quotas.free.clients'); $client_count = count($this->getObject('clients', true)); $this->message = "You are attempting to import ({$client_count}) clients, your current plan allows a total of ({$client_limit})"; $this->pre_flight_checks_pass = false; } return $this; } //check if this is a complete company import OR if it is selective /* Company and settings only Data */ private function preFlightChecks() { //check the file version and perform any necessary adjustments to the file in order to proceed - needed when we change schema $data = (object)$this->getObject('app_version', true); $this->import_version = $data->app_version; if ($this->pre_flight_checks_pass === false) { $this->sendImportMail($this->message); throw new \Exception($this->message); } return $this; } private function importSettings() { $co = (object)$this->getObject("company", true); $settings = $co->settings; $settings->invoice_number_counter = 1; $settings->recurring_invoice_number_counter = 1; $settings->quote_number_counter = 1; $settings->credit_number_counter = 1; $settings->task_number_counter = 1; $settings->expense_number_counter = 1; $settings->recurring_expense_number_counter = 1; $settings->recurring_quote_number_counter = 1; $settings->vendor_number_counter = 1; $settings->ticket_number_counter = 1; $settings->payment_number_counter = 1; $settings->project_number_counter = 1; $settings->purchase_order_number_counter = 1; $this->company->settings = $co->settings; $this->company->saveSettings($co->settings, $this->company); $this->company->save(); return $this; } private function purgeCompanyData() { $this->company->clients()->forceDelete(); $this->company->all_activities()->forceDelete(); $this->company->products()->forceDelete(); $this->company->projects()->forceDelete(); $this->company->tasks()->forceDelete(); $this->company->vendors()->forceDelete(); $this->company->expenses()->forceDelete(); $this->company->subscriptions()->forceDelete(); $this->company->purchase_orders()->forceDelete(); $this->company->bank_integrations()->forceDelete(); $this->company->bank_transactions()->forceDelete(); $this->company->schedulers()->forceDelete(); $this->company->system_log_relation()->forceDelete(); $this->company->save(); return $this; } private function importCompany() { //$tmp_company = $this->backup_file->company; $tmp_company = (object)$this->getObject("company", true); $tmp_company->company_key = $this->createHash(); $tmp_company->db = config('database.default'); $tmp_company->account_id = $this->account->id; if (Ninja::isHosted()) { $tmp_company->subdomain = MultiDB::randomSubdomainGenerator(); } else { $tmp_company->subdomain = ''; } foreach ($this->company_properties as $value) { if (property_exists($tmp_company, $value)) { $this->company->{$value} = $tmp_company->{$value}; } } if (Ninja::isHosted()) { $this->company->portal_mode = 'subdomain'; $this->company->portal_domain = ''; } $this->company->save(); return $this; } private function importData() { foreach ($this->importables as $import) { $method = "import_{$import}"; nlog($method); $this->{$method}(); } nlog("finished importing company data"); return $this; } private function import_schedulers() { $this->genericNewClassImport( \App\Models\Scheduler::class, ['company_id', 'id', 'hashed_id'], [ ['users' => 'user_id'], ], 'schedulers' ); return $this; } private function import_bank_integrations() { $this->genericImport( BankIntegration::class, ['assigned_user_id','account_id', 'company_id', 'id', 'hashed_id'], [ ['users' => 'user_id'], ], 'bank_integrations', 'description' ); return $this; } private function import_bank_transactions() { $this->genericImport( BankTransaction::class, ['assigned_user_id','company_id', 'id', 'hashed_id', 'user_id'], [ ['users' => 'user_id'], ['expenses' => 'expense_id'], ['vendors' => 'vendor_id'], ['expense_categories' => 'ninja_category_id'], ['expense_categories' => 'category_id'], ['bank_integrations' => 'bank_integration_id'] ], 'bank_transactions', null ); return $this; } private function import_recurring_expenses() { //unset / transforms / object_property / match_key $this->genericImport( RecurringExpense::class, ['assigned_user_id', 'user_id', 'client_id', 'company_id', 'id', 'hashed_id', 'project_id', 'vendor_id','recurring_expense_id'], [ ['users' => 'user_id'], ['users' => 'assigned_user_id'], ['clients' => 'client_id'], ['projects' => 'project_id'], ['vendors' => 'vendor_id'], ['invoices' => 'invoice_id'], ['expense_categories' => 'category_id'], ], 'recurring_expenses', 'number' ); return $this; } private function import_payment_terms() { $this->genericImport( PaymentTerm::class, ['user_id', 'assigned_user_id', 'company_id', 'id', 'hashed_id'], [['users' => 'user_id']], 'payment_terms', 'num_days' ); return $this; } /* Cannot use generic as we are matching on two columns for existing data */ private function import_tax_rates() { // foreach($this->backup_file->tax_rates as $obj) foreach ((object)$this->getObject("tax_rates") as $obj) { $user_id = $this->transformId('users', $obj->user_id); $obj_array = (array)$obj; unset($obj_array['user_id']); unset($obj_array['company_id']); unset($obj_array['hashed_id']); unset($obj_array['id']); unset($obj_array['tax_rate_id']); /** @var \App\Models\TaxRate $new_obj */ $new_obj = TaxRate::firstOrNew( ['name' => $obj->name, 'company_id' => $this->company->id, 'rate' => $obj->rate], $obj_array, ); $new_obj->company_id = $this->company->id; $new_obj->user_id = $user_id; $new_obj->save(['timestamps' => false]); } return $this; } private function import_expense_categories() { $this->genericImport( ExpenseCategory::class, ['user_id', 'company_id', 'id', 'hashed_id'], [['users' => 'user_id']], 'expense_categories', 'name' ); return $this; } private function import_task_statuses() { $this->genericImport( TaskStatus::class, ['user_id', 'company_id', 'id', 'hashed_id'], [['users' => 'user_id']], 'task_statuses', 'name' ); return $this; } private function import_clients() { $this->genericImport( Client::class, ['user_id', 'assigned_user_id', 'company_id', 'id', 'hashed_id', 'gateway_tokens', 'contacts', 'documents','country'], [['users' => 'user_id'], ['users' => 'assigned_user_id']], 'clients', 'number' ); return $this; } private function import_client_contacts() { $this->genericImport( ClientContact::class, ['user_id', 'company_id', 'id', 'hashed_id','company'], [['users' => 'user_id'], ['clients' => 'client_id']], 'client_contacts', 'email' ); return $this; } private function import_vendors() { $this->genericImport( Vendor::class, ['user_id', 'assigned_user_id', 'company_id', 'id', 'hashed_id'], [['users' => 'user_id'], ['users' =>'assigned_user_id']], 'vendors', 'number' ); return $this; } private function import_vendor_contacts() { $this->genericImport( VendorContact::class, ['user_id', 'company_id', 'id', 'hashed_id','company','assigned_user_id'], [['users' => 'user_id'], ['vendors' => 'vendor_id']], 'vendor_contacts', 'email' ); return $this; } private function import_projects() { $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' ); return $this; } private function import_products() { $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' ); return $this; } private function import_company_gateways() { $this->genericNewClassImport( CompanyGateway::class, ['user_id', 'company_id', 'hashed_id', 'id'], [['users' => 'user_id']], 'company_gateways' ); return $this; } private function import_client_gateway_tokens() { $this->genericNewClassImport( ClientGatewayToken::class, ['company_id', 'id', 'hashed_id','client_id'], [['clients' => 'client_id', 'company_gateways' => 'company_gateway_id']], 'client_gateway_tokens' ); return $this; } private function import_group_settings() { $this->genericImport( GroupSetting::class, ['user_id', 'company_id', 'id', 'hashed_id'], [['users' => 'user_id']], 'group_settings', 'name' ); return $this; } private function import_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' ); return $this; } private function import_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' ); return $this; } private function import_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' ); return $this; } private function import_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' ); return $this; } private function import_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' ); return $this; } private function import_purchase_orders() { $this->genericImport( PurchaseOrder::class, ['user_id', 'company_id', 'id', 'hashed_id', 'recurring_id','status', 'vendor_id', 'subscription_id','client_id'], [ ['users' => 'user_id'], ['users' => 'assigned_user_id'], ['recurring_invoices' => 'recurring_id'], ['projects' => 'project_id'], ['vendors' => 'vendor_id'], ], 'purchase_orders', 'number' ); return $this; } private function import_purchase_order_invitations() { $this->genericImport( PurchaseOrderInvitation::class, ['user_id', 'vendor_contact_id', 'company_id', 'id', 'hashed_id', 'purchase_order_id'], [ ['users' => 'user_id'], ['purchase_orders' => 'purchase_order_id'], ['vendor_contacts' => 'vendor_contact_id'], ], 'purchase_order_invitations', 'key' ); return $this; } private function import_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' ); return $this; } private function import_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' ); return $this; } private function import_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' ); return $this; } private function import_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' ); return $this; } private function import_expenses() { $this->genericImport( Expense::class, ['assigned_user_id', 'user_id', 'client_id', 'company_id', 'id', 'hashed_id', 'project_id','vendor_id','recurring_expense_id'], [ ['users' => 'user_id'], ['users' => 'assigned_user_id'], ['clients' => 'client_id'], ['projects' => 'project_id'], ['vendors' => 'vendor_id'], ['invoices' => 'invoice_id'], // ['recurring_expenses' => 'recurring_expense_id'], ['expense_categories' => 'category_id'], ], 'expenses', 'number' ); return $this; } private function import_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' ); return $this; } private function import_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'], ['bank_transactions' => 'transaction_id'], ], 'payments', 'number' ); $this->paymentablesImport(); return $this; } private function import_activities() { $activities = []; $this->genericNewClassImport( Activity::class, [ 'hashed_id', 'company_id', 'backup', 'invitation_id', 'payment_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'], // ['recurring_expenses' => 'recurring_expense_id'], // ['invitations' => 'invitation_id'], ], 'activities' ); return $this; } private function import_backups() { $this->genericImportWithoutCompany( Backup::class, ['hashed_id','id'], [ ['activities' => 'activity_id'], ], 'backups', 'created_at' ); return $this; } private function import_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' ); return $this; } private function import_designs() { $this->genericImport( Design::class, ['company_id', 'user_id', 'hashed_id'], [ ['users' => 'user_id'], ], 'designs', 'name' ); return $this; } private function import_documents() { foreach ((object)$this->getObject("documents") as $document) { //todo enable this for v5.5.51 if (!$this->transformDocumentId($document->documentable_id, $document->documentable_type)) { continue; } $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]); $storage_url = (object)$this->getObject('storage_url', true); if (!Storage::exists($new_document->url) && is_string($storage_url)) { $url = $storage_url . $new_document->url; $file = @file_get_contents($url); if ($file) { try { Storage::disk(config('filesystems.default'))->put($new_document->url, $file); $new_document->disk = config('filesystems.default'); $new_document->save(); } catch(\Exception $e) { nlog($e->getMessage()); nlog("I could not upload {$new_document->url}"); $new_document->forceDelete(); } } } } return $this; } private function import_webhooks() { $this->genericImport( Webhook::class, ['company_id', 'user_id'], [ ['users' => 'user_id'], ], 'webhooks', 'created_at' ); return $this; } private function import_system_logs() { return $this; } private function import_users() { User::unguard(); //foreach ($this->backup_file->users as $user) foreach ((object)$this->getObject("users") as $user) { if (User::withTrashed()->where('email', $user->email)->where('account_id', '!=', $this->account->id)->exists()) { throw new ImportCompanyFailed("{$user->email} is already in the system attached to a different account"); } $user_array = (array)$user; unset($user_array['laravel_through_key']); unset($user_array['hashed_id']); unset($user_array['id']); /*Make sure we are searching for archived users also and restore if we find them.*/ $new_user = User::withTrashed()->firstOrNew( ['email' => $user->email], $user_array, ); $new_user->account_id = $this->account->id; $new_user->save(['timestamps' => false]); $this->ids['users']["{$user->hashed_id}"] = $new_user->id; } User::reguard(); } private function import_company_users() { CompanyUser::unguard(); // foreach($this->backup_file->company_users as $cu) foreach ((object)$this->getObject("company_users") as $cu) { $user_id = $this->transformId('users', $cu->user_id); $cu_array = (array)$cu; unset($cu_array['id']); unset($cu_array['company_id']); unset($cu_array['user_id']); unset($cu_array['user']); unset($cu_array['account']); // $cu_array['settings'] = json_encode($cu_array['settings']); // $cu_array['notifications'] = json_encode($cu_array['notifications']); // $cu_array['permissions'] = json_encode($cu_array['permissions']); $new_cu = CompanyUser::withTrashed()->firstOrNew( ['user_id' => $user_id, 'company_id' => $this->company->id], $cu_array, ); $new_cu->account_id = $this->account->id; $new_cu->save(['timestamps' => false]); } CompanyUser::reguard(); } 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 Project::class: return $this->transformId('projects', $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: return false; break; } } private function paymentablesImport() { // foreach($this->backup_file->payments as $payment) foreach ((object)$this->getObject("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]); } } return $this; } 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 filterVersionProps($class, array $obj_array): array { if($this->current_app_version == $this->import_version) return $obj_array; $version_index = 0; $index = 0; $filters = collect($this->version_keys) ->map(function ($value, $key) use (&$version_index, &$index) { if($this->import_version == $key) { $version_index = $index; } $index++; return $value; }) ->when($version_index == 0, function ($collection) { return collect([]); }) ->when($version_index > 0, function ($collection) use (&$version_index, $class) { return $collection->slice($version_index)->pluck($class)->filter(); }); return collect($obj_array)->diffKeys($filters->flatten()->flip())->toArray(); } private function genericNewClassImport($class, $unset, $transforms, $object_property) { $class::unguard(); foreach ((object)$this->getObject($object_property) as $obj) { /* Remove unwanted keys*/ $obj_array = (array)$obj; foreach ($unset as $un) { unset($obj_array[$un]); } if ($class == 'App\Models\CompanyGateway') { if (Ninja::isHosted() && $obj_array['gateway_key'] == 'd14dd26a37cecc30fdd65700bfb55b23') { $obj_array['gateway_key'] = 'd14dd26a47cecc30fdd65700bfb67b34'; } if (Ninja::isSelfHost() && $obj_array['gateway_key'] == 'd14dd26a47cecc30fdd65700bfb67b34') { $obj_array['gateway_key'] = 'd14dd26a37cecc30fdd65700bfb55b23'; } if(!isset($obj_array['fees_and_limits'])){ $obj_array['fees_and_limits'] = \json_encode([]); } } if (array_key_exists('deleted_at', $obj_array) && $obj_array['deleted_at'] > 1) { $obj_array['deleted_at'] = now(); } $activity_invitation_key = false; if ($class == 'App\Models\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'; } } $obj_array['account_id'] = $this->account->id; } /* Transform old keys to new keys */ foreach ($transforms as $transform) { foreach ($transform as $key => $value) { if ($class == 'App\Models\Activity' && $activity_invitation_key && $key == 'invitations') { $key = $activity_invitation_key; } $obj_array["{$value}"] = $this->transformId($key, $obj->{$value}); } } if ($class == 'App\Models\CompanyGateway') { $obj_array['config'] = encrypt($obj_array['config']); } if($class == 'App\Models\Scheduler') { $parameters = $obj_array['parameters']; if(isset($parameters->clients)){ $parameters->clients = collect($parameters->clients)->map(function ($client_hash){ return $this->encodePrimaryKey($this->transformId('clients', $client_hash)); })->toArray(); } if(isset($parameters->entity_id)){ $parameters->entity_id = $this->encodePrimaryKey($this->transformId($parameters->entity."s", $parameters->entity_id)); } $obj_array['parameters'] = $parameters; } $new_obj = new $class(); $new_obj->company_id = $this->company->id; $obj_array = $this->filterVersionProps($class,$obj_array); $new_obj->fill($obj_array); $new_obj->save(['timestamps' => false]); $this->ids["{$object_property}"]["{$obj->hashed_id}"] = $new_obj->id; } $class::reguard(); } private function genericImportWithoutCompany($class, $unset, $transforms, $object_property, $match_key) { $class::unguard(); //foreach($this->backup_file->{$object_property} as $obj) foreach ((object)$this->getObject($object_property) as $obj) { if (is_null($obj)) { continue; } /* 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}); } } if (array_key_exists('deleted_at', $obj_array) && $obj_array['deleted_at'] > 1) { $obj_array['deleted_at'] = now(); } /* New to convert product ids from old hashes to new hashes*/ if ($class == 'App\Models\Subscription') { $obj_array['product_ids'] = $this->recordProductIds($obj_array['product_ids']); $obj_array['recurring_product_ids'] = $this->recordProductIds($obj_array['recurring_product_ids']); $obj_array['webhook_configuration'] = \json_encode($obj_array['webhook_configuration']); } $obj_array = $this->filterVersionProps($class, $obj_array); $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(); } /* Ensure if no number is set, we don't overwrite a record with an existing number */ private function genericImport($class, $unset, $transforms, $object_property, $match_key) { $class::unguard(); $x = 0; foreach ((object)$this->getObject($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}); } } if (array_key_exists('deleted_at', $obj_array) && $obj_array['deleted_at'] > 1) { $obj_array['deleted_at'] = now(); } /* New to convert product ids from old hashes to new hashes*/ if ($class == 'App\Models\Subscription') { if (array_key_exists('company', $obj_array)) { unset($obj_array['company']); } $obj_array['webhook_configuration'] = (array)$obj_array['webhook_configuration']; $obj_array['recurring_product_ids'] = ''; $obj_array['product_ids'] = ''; } $obj_array = $this->filterVersionProps($class, $obj_array); /* Expenses that don't have a number will not be inserted - so need to override here*/ if ($class == 'App\Models\Expense' && is_null($obj->{$match_key})) { $new_obj = new Expense(); $new_obj->company_id = $this->company->id; $new_obj->fill($obj_array); $new_obj->save(['timestamps' => false]); $new_obj->number = $this->getNextExpenseNumber($new_obj); } elseif ($class == 'App\Models\Invoice' && is_null($obj->{$match_key})) { $new_obj = new Invoice(); $new_obj->company_id = $this->company->id; $new_obj->fill($obj_array); $new_obj->save(['timestamps' => false]); $new_obj->number = $this->getNextInvoiceNumber($client = Client::withTrashed()->find($obj_array['client_id']), $new_obj); } elseif ($class == 'App\Models\PurchaseOrder' && is_null($obj->{$match_key})) { $new_obj = new PurchaseOrder(); $new_obj->company_id = $this->company->id; $new_obj->fill($obj_array); $new_obj->save(['timestamps' => false]); $new_obj->number = $this->getNextPurchaseOrderNumber($new_obj); } elseif ($class == 'App\Models\Payment' && is_null($obj->{$match_key})) { $new_obj = new Payment(); $new_obj->company_id = $this->company->id; $new_obj->fill($obj_array); $new_obj->save(['timestamps' => false]); $new_obj->number = $this->getNextPaymentNumber($client = Client::withTrashed()->find($obj_array['client_id']), $new_obj); } elseif ($class == 'App\Models\Quote' && is_null($obj->{$match_key})) { $new_obj = new Quote(); $new_obj->company_id = $this->company->id; $new_obj->fill($obj_array); $new_obj->save(['timestamps' => false]); $new_obj->number = $this->getNextQuoteNumber($client = Client::withTrashed()->find($obj_array['client_id']), $new_obj); } elseif ($class == 'App\Models\ClientContact') { $new_obj = new ClientContact(); $new_obj->company_id = $this->company->id; $new_obj->fill($obj_array); $new_obj->save(['timestamps' => false]); } elseif ($class == 'App\Models\VendorContact') { $new_obj = new VendorContact(); $new_obj->company_id = $this->company->id; $new_obj->fill($obj_array); $new_obj->save(['timestamps' => false]); } elseif ($class == 'App\Models\RecurringExpense' && is_null($obj->{$match_key})) { $new_obj = new RecurringExpense(); $new_obj->company_id = $this->company->id; $new_obj->fill($obj_array); $new_obj->save(['timestamps' => false]); $new_obj->number = $this->getNextRecurringExpenseNumber($new_obj); } elseif ($class == 'App\Models\Project' && is_null($obj->{$match_key})) { $new_obj = new Project(); $new_obj->company_id = $this->company->id; $new_obj->fill($obj_array); $new_obj->save(['timestamps' => false]); $new_obj->number = $this->getNextProjectNumber($new_obj); } elseif ($class == 'App\Models\Task' && is_null($obj->{$match_key})) { $new_obj = new Task(); $new_obj->company_id = $this->company->id; $new_obj->fill($obj_array); $new_obj->save(['timestamps' => false]); $new_obj->number = $this->getNextTaskNumber($new_obj); } elseif ($class == 'App\Models\Vendor' && is_null($obj->{$match_key})) { $new_obj = new Vendor(); $new_obj->company_id = $this->company->id; $new_obj->fill($obj_array); $new_obj->save(['timestamps' => false]); $new_obj->number = $this->getNextVendorNumber($new_obj); } elseif ($class == 'App\Models\CompanyLedger') { $new_obj = $class::firstOrNew( [$match_key => $obj->{$match_key}, 'company_id' => $this->company->id], $obj_array, ); } elseif ($class == 'App\Models\BankIntegration') { $new_obj = new BankIntegration(); $new_obj->company_id = $this->company->id; $new_obj->account_id = $this->account->id; $new_obj->fill($obj_array); $new_obj->save(['timestamps' => false]); } elseif ($class == 'App\Models\BankTransaction') { $new_obj = new BankTransaction(); $new_obj->company_id = $this->company->id; $obj_array['invoice_ids'] = collect(explode(",", $obj_array['invoice_ids']))->map(function ($id) { return $this->transformId('invoices', $id); })->map(function ($encodeable) { return $this->encodePrimaryKey($encodeable); })->implode(","); $obj_array['expense_id'] = collect(explode(",", $obj_array['expense_id']))->map(function ($id) { return $this->transformId('expenses', $id); })->map(function ($encodeable) { return $this->encodePrimaryKey($encodeable); })->implode(","); $new_obj->fill($obj_array); $new_obj->save(['timestamps' => false]); } else { $new_obj = $class::withTrashed()->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) { if (!$id) { continue; } $id = $this->decodePrimaryKey($id); nlog($id); $tmp_arr[] = $this->encodePrimaryKey($this->transformId('products', $id)); } return implode(",", $tmp_arr); } /* Transform all IDs from old to new * * In the case of users - we need to check if the system * is attempting to migrate resources above their quota, * * ie. > 50 clients or more than 1 user */ private function transformId(string $resource, ?string $old): ?int { if (empty($old)) { return null; } if ($resource == 'users' && $this->force_user_coalesce) { return $this->company_owner->id; } if (! array_key_exists($resource, $this->ids)) { $this->sendImportMail("The Import failed due to missing data in the import file. Resource {$resource} not available."); throw new \Exception("Resource {$resource} not available."); } if (! array_key_exists("{$old}", $this->ids[$resource])) { // nlog($this->ids[$resource]); nlog("searching for {$old} in {$resource}"); nlog("If we are missing a user - default to the company owner"); if ($resource == 'users') { return $this->company_owner->id; } $this->sendImportMail("The Import failed due to missing data in the import file. Resource {$resource} not available."); nlog($this->ids[$resource]); throw new \Exception("Missing {$resource} key: {$old}"); } return $this->ids[$resource]["{$old}"]; } private function sendImportMail($message) { App::forgetInstance('translator'); $t = app('translator'); $t->replace(Ninja::transformTranslations($this->company->settings)); $_company = Company::find($this->company->id); $nmo = new NinjaMailerObject; $nmo->mailable = new CompanyImportFailure($_company, $message); $nmo->company = $this->company; $nmo->settings = $this->company->settings; $nmo->to_user = $this->company->owner(); NinjaMailerJob::dispatch($nmo); } }