diff --git a/.env.travis b/.env.travis new file mode 100644 index 000000000000..76b578295e0d --- /dev/null +++ b/.env.travis @@ -0,0 +1,23 @@ +APP_ENV=development +APP_DEBUG=true +APP_URL=http://ninja.dev +APP_KEY=SomeRandomStringSomeRandomString +APP_CIPHER=AES-256-CBC +APP_LOCALE=en + +MULTI_DB_ENABLED=true +MULTI_DB_CACHE_ENABLED=true + +DB_TYPE=db-ninja-1 +DB_STRICT=false +DB_HOST=localhost +DB_USERNAME=ninja +DB_PASSWORD=ninja + +DB_DATABASE0=ninja0 +DB_DATABASE1=ninja +DB_DATABASE2=ninja2 + +MAIL_DRIVER=log +TRAVIS=true +API_SECRET=password diff --git a/.travis.yml b/.travis.yml index 067ed7c1530e..d1f2b568c9fe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,21 +44,21 @@ before_script: # prevent MySQL went away error - mysql -u root -e 'SET @@GLOBAL.wait_timeout=28800;' # copy configuration files - - cp .env.example .env + - cp .env.travis .env - cp tests/_bootstrap.php.default tests/_bootstrap.php - php artisan key:generate --no-interaction - - sed -i 's/APP_ENV=production/APP_ENV=development/g' .env - - sed -i 's/APP_DEBUG=false/APP_DEBUG=true/g' .env - - sed -i 's/MAIL_DRIVER=smtp/MAIL_DRIVER=log/g' .env - - sed -i 's/PHANTOMJS_CLOUD_KEY/#PHANTOMJS_CLOUD_KEY/g' .env - sed -i '$a NINJA_DEV=true' .env - - sed -i '$a TRAVIS=true' .env # create the database and user + - mysql -u root -e "create database IF NOT EXISTS ninja0;" - mysql -u root -e "create database IF NOT EXISTS ninja;" + - mysql -u root -e "create database IF NOT EXISTS ninja2;" + - mysql -u root -e "GRANT ALL PRIVILEGES ON ninja0.* To 'ninja'@'localhost' IDENTIFIED BY 'ninja'; FLUSH PRIVILEGES;" - mysql -u root -e "GRANT ALL PRIVILEGES ON ninja.* To 'ninja'@'localhost' IDENTIFIED BY 'ninja'; FLUSH PRIVILEGES;" + - mysql -u root -e "GRANT ALL PRIVILEGES ON ninja2.* To 'ninja'@'localhost' IDENTIFIED BY 'ninja'; FLUSH PRIVILEGES;" # migrate and seed the database - - php artisan migrate --no-interaction - - php artisan db:seed --no-interaction # default seed + - php artisan migrate --database=db-ninja-0 --seed --no-interaction + - php artisan migrate --database=db-ninja-1 --seed --no-interaction + - php artisan migrate --database=db-ninja-2 --seed --no-interaction # Start webserver on ninja.dev:8000 - php artisan serve --host=ninja.dev --port=8000 & # '&' allows to run in background # Start PhantomJS @@ -69,6 +69,7 @@ before_script: - curl -L http://ninja.dev:8000/update - php artisan ninja:create-test-data 4 true - php artisan db:seed --no-interaction --class=UserTableSeeder # development seed + - sed -i 's/DB_TYPE=db-ninja-1/DB_TYPE=db-ninja-2/g' .env script: - php ./vendor/codeception/codeception/codecept run --debug acceptance APICest.php @@ -92,6 +93,10 @@ script: after_script: - php artisan ninja:check-data --no-interaction - cat .env + - mysql -u root -e 'select * from lookup_companies;' ninja0 + - mysql -u root -e 'select * from lookup_accounts;' ninja0 + - mysql -u root -e 'select * from lookup_contacts;' ninja0 + - mysql -u root -e 'select * from lookup_invitations;' ninja0 - mysql -u root -e 'select * from accounts;' ninja - mysql -u root -e 'select * from users;' ninja - mysql -u root -e 'select * from account_gateways;' ninja @@ -103,6 +108,7 @@ after_script: - mysql -u root -e 'select * from payments;' ninja - mysql -u root -e 'select * from credits;' ninja - mysql -u root -e 'select * from expenses;' ninja + - mysql -u root -e 'select * from accounts;' ninja - cat storage/logs/laravel-error.log - cat storage/logs/laravel-info.log - FILES=$(find tests/_output -type f -name '*.png' | sort -nr) diff --git a/app/Console/Commands/ChargeRenewalInvoices.php b/app/Console/Commands/ChargeRenewalInvoices.php index 870406a3af87..105a0e4675b3 100644 --- a/app/Console/Commands/ChargeRenewalInvoices.php +++ b/app/Console/Commands/ChargeRenewalInvoices.php @@ -9,6 +9,7 @@ use App\Ninja\Repositories\AccountRepository; use App\Services\PaymentService; use Illuminate\Console\Command; use Carbon; +use Symfony\Component\Console\Input\InputOption; /** * Class ChargeRenewalInvoices. @@ -60,6 +61,10 @@ class ChargeRenewalInvoices extends Command { $this->info(date('Y-m-d').' ChargeRenewalInvoices...'); + if ($database = $this->option('database')) { + config(['database.default' => $database]); + } + $ninjaAccount = $this->accountRepo->getNinjaAccount(); $invoices = Invoice::whereAccountId($ninjaAccount->id) ->whereDueDate(date('Y-m-d')) @@ -120,6 +125,8 @@ class ChargeRenewalInvoices extends Command */ protected function getOptions() { - return []; + return [ + ['database', null, InputOption::VALUE_OPTIONAL, 'Database', null], + ]; } } diff --git a/app/Console/Commands/CheckData.php b/app/Console/Commands/CheckData.php index 5ed1f51de366..1b33f0713861 100644 --- a/app/Console/Commands/CheckData.php +++ b/app/Console/Commands/CheckData.php @@ -64,6 +64,10 @@ class CheckData extends Command { $this->logMessage(date('Y-m-d') . ' Running CheckData...'); + if ($database = $this->option('database')) { + config(['database.default' => $database]); + } + if (! $this->option('client_id')) { $this->checkBlankInvoiceHistory(); $this->checkPaidToDate(); @@ -72,6 +76,9 @@ class CheckData extends Command $this->checkBalances(); $this->checkContacts(); + // TODO Enable once user_account companies have been merged + //$this->checkUserAccounts(); + if (! $this->option('client_id')) { $this->checkInvitations(); $this->checkFailedJobs(); @@ -83,10 +90,10 @@ class CheckData extends Command $this->info($this->log); if ($errorEmail) { - Mail::raw($this->log, function ($message) use ($errorEmail) { + Mail::raw($this->log, function ($message) use ($errorEmail, $database) { $message->to($errorEmail) ->from(CONTACT_EMAIL) - ->subject('Check-Data: ' . strtoupper($this->isValid ? RESULT_SUCCESS : RESULT_FAILURE)); + ->subject("Check-Data [{$database}]: " . strtoupper($this->isValid ? RESULT_SUCCESS : RESULT_FAILURE)); }); } elseif (! $this->isValid) { throw new Exception('Check data failed!!'); @@ -98,8 +105,86 @@ class CheckData extends Command $this->log .= $str . "\n"; } + private function checkUserAccounts() + { + $userAccounts = DB::table('user_accounts') + ->leftJoin('users as u1', 'u1.id', '=', 'user_accounts.user_id1') + ->leftJoin('accounts as a1', 'a1.id', '=', 'u1.account_id') + ->leftJoin('users as u2', 'u2.id', '=', 'user_accounts.user_id2') + ->leftJoin('accounts as a2', 'a2.id', '=', 'u2.account_id') + ->leftJoin('users as u3', 'u3.id', '=', 'user_accounts.user_id3') + ->leftJoin('accounts as a3', 'a3.id', '=', 'u3.account_id') + ->leftJoin('users as u4', 'u4.id', '=', 'user_accounts.user_id4') + ->leftJoin('accounts as a4', 'a4.id', '=', 'u4.account_id') + ->leftJoin('users as u5', 'u5.id', '=', 'user_accounts.user_id5') + ->leftJoin('accounts as a5', 'a5.id', '=', 'u5.account_id') + ->get([ + 'user_accounts.id', + 'a1.company_id as a1_company_id', + 'a2.company_id as a2_company_id', + 'a3.company_id as a3_company_id', + 'a4.company_id as a4_company_id', + 'a5.company_id as a5_company_id', + ]); + + $countInvalid = 0; + + foreach ($userAccounts as $userAccount) { + $ids = []; + + if ($companyId1 = $userAccount->a1_company_id) { + $ids[$companyId1] = true; + } + if ($companyId2 = $userAccount->a2_company_id) { + $ids[$companyId2] = true; + } + if ($companyId3 = $userAccount->a3_company_id) { + $ids[$companyId3] = true; + } + if ($companyId4 = $userAccount->a4_company_id) { + $ids[$companyId4] = true; + } + if ($companyId5 = $userAccount->a5_company_id) { + $ids[$companyId5] = true; + } + + if (count($ids) > 1) { + $this->info('user_account: ' . $userAccount->id); + $countInvalid++; + } + } + + if ($countInvalid > 0) { + $this->logMessage($countInvalid . ' user accounts with multiple companies'); + $this->isValid = false; + } + } + private function checkContacts() { + // check for contacts with the contact_key value set + $contacts = DB::table('contacts') + ->whereNull('contact_key') + ->orderBy('id') + ->get(['id']); + $this->logMessage(count($contacts) . ' contacts without a contact_key'); + + if (count($contacts) > 0) { + $this->isValid = false; + } + + if ($this->option('fix') == 'true') { + foreach ($contacts as $contact) { + DB::table('contacts') + ->where('id', $contact->id) + ->whereNull('contact_key') + ->update([ + 'contact_key' => strtolower(str_random(RANDOM_KEY_LENGTH)), + ]); + } + } + + // check for missing contacts $clients = DB::table('clients') ->leftJoin('contacts', function($join) { $join->on('contacts.client_id', '=', 'clients.id') @@ -133,6 +218,7 @@ class CheckData extends Command } } + // check for more than one primary contact $clients = DB::table('clients') ->leftJoin('contacts', function($join) { $join->on('contacts.client_id', '=', 'clients.id') @@ -351,7 +437,7 @@ class CheckData extends Command $clients->where('clients.id', '=', $this->option('client_id')); } - $clients = $clients->groupBy('clients.id', 'clients.balance', 'clients.created_at') + $clients = $clients->groupBy('clients.id', 'clients.balance') ->orderBy('accounts.company_id', 'DESC') ->get(['accounts.company_id', 'clients.account_id', 'clients.id', 'clients.balance', 'clients.paid_to_date', DB::raw('sum(invoices.balance) actual_balance')]); $this->logMessage(count($clients) . ' clients with incorrect balance/activities'); @@ -543,6 +629,7 @@ class CheckData extends Command return [ ['fix', null, InputOption::VALUE_OPTIONAL, 'Fix data', null], ['client_id', null, InputOption::VALUE_OPTIONAL, 'Client id', null], + ['database', null, InputOption::VALUE_OPTIONAL, 'Database', null], ]; } } diff --git a/app/Console/Commands/CreateTestData.php b/app/Console/Commands/CreateTestData.php index ac046f3500c9..93b134206378 100644 --- a/app/Console/Commands/CreateTestData.php +++ b/app/Console/Commands/CreateTestData.php @@ -25,7 +25,7 @@ class CreateTestData extends Command /** * @var string */ - protected $signature = 'ninja:create-test-data {count=1} {create_account=false}'; + protected $signature = 'ninja:create-test-data {count=1} {create_account=false} {--database}'; /** * @var @@ -68,12 +68,17 @@ class CreateTestData extends Command public function fire() { if (Utils::isNinjaProd()) { + $this->info('Unable to run in production'); return false; } $this->info(date('Y-m-d').' Running CreateTestData...'); $this->count = $this->argument('count'); + if ($database = $this->option('database')) { + config(['database.default' => $database]); + } + if (filter_var($this->argument('create_account'), FILTER_VALIDATE_BOOLEAN)) { $this->info('Creating new account...'); $account = $this->accountRepo->create( diff --git a/app/Console/Commands/GenerateResources.php b/app/Console/Commands/GenerateResources.php deleted file mode 100644 index 17e139188caf..000000000000 --- a/app/Console/Commands/GenerateResources.php +++ /dev/null @@ -1,63 +0,0 @@ - $value) { - if (is_array($value)) { - echo $key; - } else { - echo "$key => $value\n"; - } - } - } - - /** - * @return array - */ - protected function getArguments() - { - return []; - } - - /** - * @return array - */ - protected function getOptions() - { - return []; - } -} diff --git a/app/Console/Commands/InitLookup.php b/app/Console/Commands/InitLookup.php new file mode 100644 index 000000000000..d639068b035f --- /dev/null +++ b/app/Console/Commands/InitLookup.php @@ -0,0 +1,293 @@ +logMessage('Running InitLookup...'); + + config(['database.default' => DB_NINJA_LOOKUP]); + + $database = $this->option('database'); + $dbServer = DbServer::whereName($database)->first(); + + if ($this->option('truncate')) { + $this->truncateTables(); + $this->logMessage('Truncated'); + } else { + config(['database.default' => $this->option('database')]); + + $count = DB::table('companies') + ->where('id', '>=', $this->option('company_id') ?: 1) + ->count(); + + for ($i=0; $i<$count; $i += (int) $this->option('page_size')) { + $this->initCompanies($dbServer->id, $i); + } + } + + $this->info($this->log); + $this->info('Valid: ' . ($this->isValid ? RESULT_SUCCESS : RESULT_FAILURE)); + + if ($this->option('validate')) { + if ($errorEmail = env('ERROR_EMAIL')) { + Mail::raw($this->log, function ($message) use ($errorEmail, $database) { + $message->to($errorEmail) + ->from(CONTACT_EMAIL) + ->subject("Check-Lookups [{$database}]: " . strtoupper($this->isValid ? RESULT_SUCCESS : RESULT_FAILURE)); + }); + } elseif (! $this->isValid) { + throw new Exception('Check lookups failed!!'); + } + } + } + + private function initCompanies($dbServerId, $offset = 0) + { + $data = []; + + config(['database.default' => $this->option('database')]); + + $companies = DB::table('companies') + ->offset($offset) + ->limit((int) $this->option('page_size')) + ->orderBy('id') + ->where('id', '>=', $this->option('company_id') ?: 1) + ->get(['id']); + foreach ($companies as $company) { + $data[$company->id] = $this->parseCompany($company->id); + } + + config(['database.default' => DB_NINJA_LOOKUP]); + + foreach ($data as $companyId => $company) { + + if ($this->option('validate')) { + $lookupCompany = LookupCompany::whereDbServerId($dbServerId)->whereCompanyId($companyId)->first(); + if (! $lookupCompany) { + $this->logError("LookupCompany - dbServerId: {$dbServerId}, companyId: {$companyId} | Not found!"); + continue; + } + } else { + $lookupCompany = LookupCompany::create([ + 'db_server_id' => $dbServerId, + 'company_id' => $companyId, + ]); + } + + foreach ($company as $accountKey => $account) { + if ($this->option('validate')) { + $lookupAccount = LookupAccount::whereLookupCompanyId($lookupCompany->id)->whereAccountKey($accountKey)->first(); + if (! $lookupAccount) { + $this->logError("LookupAccount - lookupCompanyId: {$lookupCompany->id}, accountKey {$accountKey} | Not found!"); + continue; + } + } else { + $lookupAccount = LookupAccount::create([ + 'lookup_company_id' => $lookupCompany->id, + 'account_key' => $accountKey + ]); + } + + foreach ($account['users'] as $user) { + if ($this->option('validate')) { + $lookupUser = LookupUser::whereLookupAccountId($lookupAccount->id)->whereUserId($user['user_id'])->first(); + if (! $lookupUser) { + $this->logError("LookupUser - lookupAccountId: {$lookupAccount->id}, userId: {$user['user_id']} | Not found!"); + continue; + } + } else { + LookupUser::create([ + 'lookup_account_id' => $lookupAccount->id, + 'email' => $user['email'] ?: null, + 'user_id' => $user['user_id'], + ]); + } + } + + foreach ($account['contacts'] as $contact) { + if ($this->option('validate')) { + $lookupContact = LookupContact::whereLookupAccountId($lookupAccount->id)->whereContactKey($contact['contact_key'])->first(); + if (! $lookupContact) { + $this->logError("LookupContact - lookupAccountId: {$lookupAccount->id}, contactKey: {$contact['contact_key']} | Not found!"); + continue; + } + } else { + LookupContact::create([ + 'lookup_account_id' => $lookupAccount->id, + 'contact_key' => $contact['contact_key'], + ]); + } + } + + foreach ($account['invitations'] as $invitation) { + if ($this->option('validate')) { + $lookupInvitation = LookupInvitation::whereLookupAccountId($lookupAccount->id)->whereInvitationKey($invitation['invitation_key'])->first(); + if (! $lookupInvitation) { + $this->logError("LookupInvitation - lookupAccountId: {$lookupAccount->id}, invitationKey: {$invitation['invitation_key']} | Not found!"); + continue; + } + } else { + LookupInvitation::create([ + 'lookup_account_id' => $lookupAccount->id, + 'invitation_key' => $invitation['invitation_key'], + 'message_id' => $invitation['message_id'] ?: null, + ]); + } + } + + foreach ($account['tokens'] as $token) { + if ($this->option('validate')) { + $lookupToken = LookupAccountToken::whereLookupAccountId($lookupAccount->id)->whereToken($token['token'])->first(); + if (! $lookupToken) { + $this->logError("LookupAccountToken - lookupAccountId: {$lookupAccount->id}, token: {$token['token']} | Not found!"); + continue; + } + } else { + LookupAccountToken::create([ + 'lookup_account_id' => $lookupAccount->id, + 'token' => $token['token'], + ]); + } + } + } + } + } + + private function parseCompany($companyId) + { + $data = []; + + config(['database.default' => $this->option('database')]); + + $accounts = DB::table('accounts')->whereCompanyId($companyId)->orderBy('id')->get(['id', 'account_key']); + foreach ($accounts as $account) { + $data[$account->account_key] = $this->parseAccount($account->id); + } + + return $data; + } + + private function parseAccount($accountId) + { + $data = [ + 'users' => [], + 'contacts' => [], + 'invitations' => [], + 'tokens' => [], + ]; + + $users = DB::table('users')->whereAccountId($accountId)->orderBy('id')->get(['email', 'id']); + foreach ($users as $user) { + $data['users'][] = [ + 'email' => $user->email, + 'user_id' => $user->id, + ]; + } + + $contacts = DB::table('contacts')->whereAccountId($accountId)->orderBy('id')->get(['contact_key']); + foreach ($contacts as $contact) { + $data['contacts'][] = [ + 'contact_key' => $contact->contact_key, + ]; + } + + $invitations = DB::table('invitations')->whereAccountId($accountId)->orderBy('id')->get(['invitation_key', 'message_id']); + foreach ($invitations as $invitation) { + $data['invitations'][] = [ + 'invitation_key' => $invitation->invitation_key, + 'message_id' => $invitation->message_id, + ]; + } + + $tokens = DB::table('account_tokens')->whereAccountId($accountId)->orderBy('id')->get(['token']); + foreach ($tokens as $token) { + $data['tokens'][] = [ + 'token' => $token->token, + ]; + } + + return $data; + } + + private function logMessage($str) + { + $this->log .= date('Y-m-d h:i:s') . ' ' . $str . "\n"; + } + + private function logError($str) + { + $this->isValid = false; + $this->logMessage($str); + } + + private function truncateTables() + { + DB::statement('SET FOREIGN_KEY_CHECKS = 0'); + DB::statement('truncate lookup_companies'); + DB::statement('truncate lookup_accounts'); + DB::statement('truncate lookup_users'); + DB::statement('truncate lookup_contacts'); + DB::statement('truncate lookup_invitations'); + DB::statement('truncate lookup_account_tokens'); + DB::statement('SET FOREIGN_KEY_CHECKS = 1'); + } + + protected function getOptions() + { + return [ + ['truncate', null, InputOption::VALUE_OPTIONAL, 'Truncate', null], + ['company_id', null, InputOption::VALUE_OPTIONAL, 'Company Id', null], + ['page_size', null, InputOption::VALUE_OPTIONAL, 'Page Size', null], + ['database', null, InputOption::VALUE_OPTIONAL, 'Database', null], + ['validate', null, InputOption::VALUE_OPTIONAL, 'Validate', null], + ]; + } + +} diff --git a/app/Console/Commands/Inspire.php b/app/Console/Commands/Inspire.php deleted file mode 100644 index f49e17c9d96e..000000000000 --- a/app/Console/Commands/Inspire.php +++ /dev/null @@ -1,36 +0,0 @@ -comment(PHP_EOL.Inspiring::quote().PHP_EOL); - } -} diff --git a/app/Console/Commands/PruneData.php b/app/Console/Commands/PruneData.php index 33d2bd64c0a9..adc67edc7f18 100644 --- a/app/Console/Commands/PruneData.php +++ b/app/Console/Commands/PruneData.php @@ -4,6 +4,7 @@ namespace App\Console\Commands; use DB; use Illuminate\Console\Command; +use Symfony\Component\Console\Input\InputOption; /** * Class PruneData. @@ -14,7 +15,7 @@ class PruneData extends Command * @var string */ protected $name = 'ninja:prune-data'; - + /** * @var string */ @@ -24,32 +25,42 @@ class PruneData extends Command { $this->info(date('Y-m-d').' Running PruneData...'); + if ($database = $this->option('database')) { + config(['database.default' => $database]); + } + // delete accounts who never registered, didn't create any invoices, // hansn't logged in within the past 6 months and isn't linked to another account - $sql = 'select a.id - from (select id, last_login from accounts) a - left join users u on u.account_id = a.id and u.public_id = 0 - left join invoices i on i.account_id = a.id - left join user_accounts ua1 on ua1.user_id1 = u.id - left join user_accounts ua2 on ua2.user_id2 = u.id - left join user_accounts ua3 on ua3.user_id3 = u.id - left join user_accounts ua4 on ua4.user_id4 = u.id - left join user_accounts ua5 on ua5.user_id5 = u.id - where u.registered = 0 - and a.last_login < DATE_SUB(now(), INTERVAL 6 MONTH) - and (ua1.id is null and ua2.id is null and ua3.id is null and ua4.id is null and ua5.id is null) - group by a.id - having count(i.id) = 0'; + $sql = 'select c.id + from companies c + left join accounts a on a.company_id = c.id + left join clients cl on cl.account_id = a.id + left join tasks t on t.account_id = a.id + left join expenses e on e.account_id = a.id + left join users u on u.account_id = a.id and u.registered = 1 + where c.created_at < DATE_SUB(now(), INTERVAL 6 MONTH) + and c.trial_started is null + and c.plan is null + group by c.id + having count(cl.id) = 0 + and count(t.id) = 0 + and count(e.id) = 0 + and count(u.id) = 0'; $results = DB::select($sql); - + foreach ($results as $result) { - $this->info("Deleting {$result->id}"); - DB::table('accounts') - ->where('id', '=', $result->id) - ->delete(); + $this->info("Deleting company: {$result->id}"); + try { + DB::table('companies') + ->where('id', '=', $result->id) + ->delete(); + } catch (\Illuminate\Database\QueryException $e) { + // most likely because a user_account record exists which doesn't cascade delete + $this->info("Unable to delete companyId: {$result->id}"); + } } - + $this->info('Done'); } @@ -66,6 +77,8 @@ class PruneData extends Command */ protected function getOptions() { - return []; + return [ + ['database', null, InputOption::VALUE_OPTIONAL, 'Database', null], + ]; } } diff --git a/app/Console/Commands/RemoveOrphanedDocuments.php b/app/Console/Commands/RemoveOrphanedDocuments.php index 489d8d94421f..4241e505cd58 100644 --- a/app/Console/Commands/RemoveOrphanedDocuments.php +++ b/app/Console/Commands/RemoveOrphanedDocuments.php @@ -5,6 +5,7 @@ namespace App\Console\Commands; use App\Models\Document; use DateTime; use Illuminate\Console\Command; +use Symfony\Component\Console\Input\InputOption; /** * Class RemoveOrphanedDocuments. @@ -19,14 +20,18 @@ class RemoveOrphanedDocuments extends Command * @var string */ protected $description = 'Removes old documents not associated with an expense or invoice'; - + public function fire() { $this->info(date('Y-m-d').' Running RemoveOrphanedDocuments...'); + if ($database = $this->option('database')) { + config(['database.default' => $database]); + } + $documents = Document::whereRaw('invoice_id IS NULL AND expense_id IS NULL AND updated_at <= ?', [new DateTime('-1 hour')]) ->get(); - + $this->info(count($documents).' orphaned document(s) found'); foreach ($documents as $document) { @@ -49,6 +54,8 @@ class RemoveOrphanedDocuments extends Command */ protected function getOptions() { - return []; + return [ + ['database', null, InputOption::VALUE_OPTIONAL, 'Database', null], + ]; } } diff --git a/app/Console/Commands/ResetData.php b/app/Console/Commands/ResetData.php index 70e9791a27c9..02ba8804c53d 100644 --- a/app/Console/Commands/ResetData.php +++ b/app/Console/Commands/ResetData.php @@ -4,6 +4,7 @@ namespace App\Console\Commands; use Illuminate\Console\Command; use Utils; +use Symfony\Component\Console\Input\InputOption; /** * Class ResetData. @@ -14,7 +15,7 @@ class ResetData extends Command * @var string */ protected $name = 'ninja:reset-data'; - + /** * @var string */ @@ -28,8 +29,24 @@ class ResetData extends Command return; } + if ($database = $this->option('database')) { + config(['database.default' => $database]); + } + Artisan::call('migrate:reset'); Artisan::call('migrate'); Artisan::call('db:seed'); } + + /** + * @return array + */ + protected function getOptions() + { + return [ + ['fix', null, InputOption::VALUE_OPTIONAL, 'Fix data', null], + ['client_id', null, InputOption::VALUE_OPTIONAL, 'Client id', null], + ['database', null, InputOption::VALUE_OPTIONAL, 'Database', null], + ]; + } } diff --git a/app/Console/Commands/ResetInvoiceSchemaCounter.php b/app/Console/Commands/ResetInvoiceSchemaCounter.php deleted file mode 100644 index be221c4d6390..000000000000 --- a/app/Console/Commands/ResetInvoiceSchemaCounter.php +++ /dev/null @@ -1,75 +0,0 @@ -invoice = $invoice; - } - - /** - * Execute the console command. - * - * @return mixed - */ - public function handle() - { - $force = $this->option('force'); - $account = $this->argument('account'); - - $accounts = null; - - if ($account) { - $accounts = Account::find($account)->get(); - } else { - $accounts = Account::all(); - } - - $latestInvoice = $this->invoice->latest()->first(); - $invoiceYear = Carbon::parse($latestInvoice->created_at)->year; - - if (Carbon::now()->year > $invoiceYear || $force) { - $accounts->transform(function ($a) { - /* @var Account $a */ - $a->invoice_number_counter = 1; - $a->update(); - }); - - $this->info('The counter has been resetted successfully for '.$accounts->count().' account(s).'); - } - } -} diff --git a/app/Console/Commands/SendRecurringInvoices.php b/app/Console/Commands/SendRecurringInvoices.php index 0a62ca626fbf..6a13dc3563ad 100644 --- a/app/Console/Commands/SendRecurringInvoices.php +++ b/app/Console/Commands/SendRecurringInvoices.php @@ -9,6 +9,7 @@ use App\Ninja\Repositories\InvoiceRepository; use App\Services\PaymentService; use DateTime; use Illuminate\Console\Command; +use Symfony\Component\Console\Input\InputOption; /** * Class SendRecurringInvoices. @@ -61,6 +62,10 @@ class SendRecurringInvoices extends Command $this->info(date('Y-m-d H:i:s') . ' Running SendRecurringInvoices...'); $today = new DateTime(); + if ($database = $this->option('database')) { + config(['database.default' => $database]); + } + // check for counter resets $accounts = Account::where('reset_counter_frequency_id', '>', 0) ->orderBy('id', 'asc') @@ -130,6 +135,8 @@ class SendRecurringInvoices extends Command */ protected function getOptions() { - return []; + return [ + ['database', null, InputOption::VALUE_OPTIONAL, 'Database', null], + ]; } } diff --git a/app/Console/Commands/SendReminders.php b/app/Console/Commands/SendReminders.php index af5f9bfca2c2..cb76a83ef9f0 100644 --- a/app/Console/Commands/SendReminders.php +++ b/app/Console/Commands/SendReminders.php @@ -7,6 +7,7 @@ use App\Ninja\Mailers\ContactMailer as Mailer; use App\Ninja\Repositories\AccountRepository; use App\Ninja\Repositories\InvoiceRepository; use Illuminate\Console\Command; +use Symfony\Component\Console\Input\InputOption; /** * Class SendReminders. @@ -58,6 +59,10 @@ class SendReminders extends Command { $this->info(date('Y-m-d') . ' Running SendReminders...'); + if ($database = $this->option('database')) { + config(['database.default' => $database]); + } + $accounts = $this->accountRepo->findWithReminders(); $this->info(count($accounts) . ' accounts found'); @@ -82,10 +87,10 @@ class SendReminders extends Command $this->info('Done'); if ($errorEmail = env('ERROR_EMAIL')) { - \Mail::raw('EOM', function ($message) use ($errorEmail) { + \Mail::raw('EOM', function ($message) use ($errorEmail, $database) { $message->to($errorEmail) ->from(CONTACT_EMAIL) - ->subject('SendReminders: Finished successfully'); + ->subject("SendReminders [{$database}]: Finished successfully"); }); } } @@ -103,6 +108,8 @@ class SendReminders extends Command */ protected function getOptions() { - return []; + return [ + ['database', null, InputOption::VALUE_OPTIONAL, 'Database', null], + ]; } } diff --git a/app/Console/Commands/SendRenewalInvoices.php b/app/Console/Commands/SendRenewalInvoices.php index edfc2471b31d..ca0a11568cb7 100644 --- a/app/Console/Commands/SendRenewalInvoices.php +++ b/app/Console/Commands/SendRenewalInvoices.php @@ -7,6 +7,7 @@ use App\Ninja\Mailers\ContactMailer as Mailer; use App\Ninja\Repositories\AccountRepository; use Illuminate\Console\Command; use Utils; +use Symfony\Component\Console\Input\InputOption; /** * Class SendRenewalInvoices. @@ -51,6 +52,10 @@ class SendRenewalInvoices extends Command { $this->info(date('Y-m-d').' Running SendRenewalInvoices...'); + if ($database = $this->option('database')) { + config(['database.default' => $database]); + } + // get all accounts with plans expiring in 10 days $companies = Company::whereRaw("datediff(plan_expires, curdate()) = 10 and (plan = 'pro' or plan = 'enterprise')") ->orderBy('id') @@ -102,10 +107,10 @@ class SendRenewalInvoices extends Command $this->info('Done'); if ($errorEmail = env('ERROR_EMAIL')) { - \Mail::raw('EOM', function ($message) use ($errorEmail) { + \Mail::raw('EOM', function ($message) use ($errorEmail, $database) { $message->to($errorEmail) ->from(CONTACT_EMAIL) - ->subject('SendRenewalInvoices: Finished successfully'); + ->subject("SendRenewalInvoices [{$database}]: Finished successfully"); }); } } @@ -123,6 +128,8 @@ class SendRenewalInvoices extends Command */ protected function getOptions() { - return []; + return [ + ['database', null, InputOption::VALUE_OPTIONAL, 'Database', null], + ]; } } diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 34938ea4c590..c1cf74d0e6d0 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -24,10 +24,10 @@ class Kernel extends ConsoleKernel 'App\Console\Commands\SendRenewalInvoices', 'App\Console\Commands\ChargeRenewalInvoices', 'App\Console\Commands\SendReminders', - 'App\Console\Commands\GenerateResources', 'App\Console\Commands\TestOFX', 'App\Console\Commands\MakeModule', 'App\Console\Commands\MakeClass', + 'App\Console\Commands\InitLookup', ]; /** diff --git a/app/Constants.php b/app/Constants.php index 732731686597..12f112697cc0 100644 --- a/app/Constants.php +++ b/app/Constants.php @@ -229,6 +229,7 @@ if (! defined('APP_NAME')) { define('SESSION_REFERRAL_CODE', 'referralCode'); define('SESSION_LEFT_SIDEBAR', 'showLeftSidebar'); define('SESSION_RIGHT_SIDEBAR', 'showRightSidebar'); + define('SESSION_DB_SERVER', 'dbServer'); define('SESSION_LAST_REQUEST_PAGE', 'SESSION_LAST_REQUEST_PAGE'); define('SESSION_LAST_REQUEST_TIME', 'SESSION_LAST_REQUEST_TIME'); @@ -290,8 +291,8 @@ if (! defined('APP_NAME')) { define('EVENT_DELETE_INVOICE', 9); define('REQUESTED_PRO_PLAN', 'REQUESTED_PRO_PLAN'); - define('DEMO_ACCOUNT_ID', 'DEMO_ACCOUNT_ID'); - define('NINJA_ACCOUNT_KEY', 'zg4ylmzDkdkPOT8yoKQw9LTWaoZJx79h'); + define('NINJA_ACCOUNT_KEY', env('NINJA_ACCOUNT_KEY', 'zg4ylmzDkdkPOT8yoKQw9LTWaoZJx79h')); + define('NINJA_ACCOUNT_EMAIL', env('NINJA_ACCOUNT_EMAIL', 'contact@invoiceninja.com')); define('NINJA_LICENSE_ACCOUNT_KEY', 'AsFmBAeLXF0IKf7tmi0eiyZfmWW9hxMT'); define('NINJA_GATEWAY_ID', GATEWAY_STRIPE); define('NINJA_GATEWAY_CONFIG', 'NINJA_GATEWAY_CONFIG'); @@ -299,7 +300,7 @@ if (! defined('APP_NAME')) { define('NINJA_APP_URL', env('NINJA_APP_URL', 'https://app.invoiceninja.com')); define('NINJA_DOCS_URL', env('NINJA_DOCS_URL', 'http://docs.invoiceninja.com/en/latest')); define('NINJA_DATE', '2000-01-01'); - define('NINJA_VERSION', '3.3.0' . env('NINJA_VERSION_SUFFIX')); + define('NINJA_VERSION', '3.3.1' . env('NINJA_VERSION_SUFFIX')); define('SOCIAL_LINK_FACEBOOK', env('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja')); define('SOCIAL_LINK_TWITTER', env('SOCIAL_LINK_TWITTER', 'https://twitter.com/invoiceninja')); @@ -335,6 +336,10 @@ if (! defined('APP_NAME')) { define('BLANK_IMAGE', 'data:image/png;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='); + define('DB_NINJA_LOOKUP', 'db-ninja-0'); + define('DB_NINJA_1', 'db-ninja-1'); + define('DB_NINJA_2', 'db-ninja-2'); + define('COUNT_FREE_DESIGNS', 4); define('COUNT_FREE_DESIGNS_SELF_HOST', 5); // include the custom design define('PRODUCT_ONE_CLICK_INSTALL', 1); diff --git a/app/Http/Controllers/AccountApiController.php b/app/Http/Controllers/AccountApiController.php index f73a7d2b7cff..f93bf85461bb 100644 --- a/app/Http/Controllers/AccountApiController.php +++ b/app/Http/Controllers/AccountApiController.php @@ -39,6 +39,10 @@ class AccountApiController extends BaseAPIController public function register(RegisterRequest $request) { + if (! \App\Models\LookupUser::validateEmail($request->email)) { + return $this->errorResponse(['message' => trans('texts.email_taken')], 500); + } + $account = $this->accountRepo->create($request->first_name, $request->last_name, $request->email, $request->password); $user = $account->users()->first(); @@ -192,7 +196,7 @@ class AccountApiController extends BaseAPIController $oAuth = new OAuth(); $user = $oAuth->getProvider($provider)->getTokenResponse($token); - if($user) { + if ($user) { Auth::login($user); return $this->processLogin($request); } diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 15236634a71a..58a28f569c1b 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -97,25 +97,6 @@ class AccountController extends BaseController $this->paymentService = $paymentService; } - /** - * @return \Illuminate\Http\RedirectResponse - */ - public function demo() - { - $demoAccountId = Utils::getDemoAccountId(); - - if (! $demoAccountId) { - return Redirect::to('/'); - } - - $account = Account::find($demoAccountId); - $user = $account->users()->first(); - - Auth::login($user, true); - - return Redirect::to('invoices/create'); - } - /** * @return \Illuminate\Http\RedirectResponse */ @@ -975,7 +956,22 @@ class AccountController extends BaseController $account->page_size = Input::get('page_size'); $labels = []; - foreach (['item', 'description', 'unit_cost', 'quantity', 'line_total', 'terms', 'balance_due', 'partial_due', 'subtotal', 'paid_to_date', 'discount', 'tax'] as $field) { + foreach ([ + 'item', + 'description', + 'unit_cost', + 'quantity', + 'line_total', + 'terms', + 'balance_due', + 'partial_due', + 'subtotal', + 'paid_to_date', + 'discount', + 'tax', + 'po_number', + 'due_date', + ] as $field) { $labels[$field] = Input::get("labels_{$field}"); } $account->invoice_labels = json_encode($labels); @@ -1104,6 +1100,14 @@ class AccountController extends BaseController { /** @var \App\Models\User $user */ $user = Auth::user(); + $email = trim(strtolower(Input::get('email'))); + + if (! \App\Models\LookupUser::validateEmail($email, $user)) { + return Redirect::to('settings/' . ACCOUNT_USER_DETAILS) + ->withError(trans('texts.email_taken')) + ->withInput(); + } + $rules = ['email' => 'email|required|unique:users,email,'.$user->id.',id']; $validator = Validator::make(Input::all(), $rules); @@ -1114,8 +1118,8 @@ class AccountController extends BaseController } else { $user->first_name = trim(Input::get('first_name')); $user->last_name = trim(Input::get('last_name')); - $user->username = trim(Input::get('email')); - $user->email = trim(strtolower(Input::get('email'))); + $user->username = $email; + $user->email = $email; $user->phone = trim(Input::get('phone')); if (! Auth::user()->is_admin) { @@ -1212,8 +1216,15 @@ class AccountController extends BaseController */ public function checkEmail() { - $email = User::withTrashed()->where('email', '=', Input::get('email')) - ->where('id', '<>', Auth::user()->registered ? 0 : Auth::user()->id) + $email = trim(strtolower(Input::get('email'))); + $user = Auth::user(); + + if (! \App\Models\LookupUser::validateEmail($email, $user)) { + return 'taken'; + } + + $email = User::withTrashed()->where('email', '=', $email) + ->where('id', '<>', $user->registered ? 0 : $user->id) ->first(); if ($email) { @@ -1253,6 +1264,10 @@ class AccountController extends BaseController $email = trim(strtolower(Input::get('new_email'))); $password = trim(Input::get('new_password')); + if (! \App\Models\LookupUser::validateEmail($email, $user)) { + return ''; + } + if ($user->registered) { $newAccount = $this->accountRepo->create($firstName, $lastName, $email, $password, $account->company); $newUser = $newAccount->users()->first(); diff --git a/app/Http/Controllers/AppController.php b/app/Http/Controllers/AppController.php index e269a7290fc5..add8da162571 100644 --- a/app/Http/Controllers/AppController.php +++ b/app/Http/Controllers/AppController.php @@ -351,9 +351,10 @@ class AppController extends BaseController { try { Artisan::call('ninja:check-data'); + Artisan::call('ninja:init-lookup', ['--validate' => true]); return RESULT_SUCCESS; } catch (Exception $exception) { - return RESULT_FAILURE; + return $exception->getMessage() ?: RESULT_FAILURE; } } diff --git a/app/Http/Controllers/ClientAuth/PasswordController.php b/app/Http/Controllers/ClientAuth/PasswordController.php index bf3f2da9c974..b13f0dffb16e 100644 --- a/app/Http/Controllers/ClientAuth/PasswordController.php +++ b/app/Http/Controllers/ClientAuth/PasswordController.php @@ -51,8 +51,8 @@ class PasswordController extends Controller $data = [ 'clientauth' => true, ]; - $contactKey = session('contact_key'); - if (!$contactKey) { + + if (! session('contact_key')) { return \Redirect::to('/client/sessionexpired'); } @@ -104,7 +104,7 @@ class PasswordController extends Controller * * @return \Illuminate\Http\Response */ - public function showResetForm(Request $request, $key = null, $token = null) + public function showResetForm(Request $request, $token = null) { if (is_null($token)) { return $this->getEmail(); @@ -115,23 +115,8 @@ class PasswordController extends Controller 'clientauth' => true, ); - if ($key) { - $contact = Contact::where('contact_key', '=', $key)->first(); - if ($contact && ! $contact->is_deleted) { - $account = $contact->account; - $data['contact_key'] = $contact->contact_key; - } else { - // Maybe it's an invitation key - $invitation = Invitation::where('invitation_key', '=', $key)->first(); - if ($invitation && ! $invitation->is_deleted) { - $account = $invitation->account; - $data['contact_key'] = $invitation->contact->contact_key; - } - } - - if ( empty($account)) { - return \Redirect::to('/client/sessionexpired'); - } + if (! session('contact_key')) { + return \Redirect::to('/client/sessionexpired'); } return view('clientauth.reset')->with($data); @@ -148,9 +133,9 @@ class PasswordController extends Controller * * @return \Illuminate\Http\Response */ - public function getReset(Request $request, $key = null, $token = null) + public function getReset(Request $request, $token = null) { - return $this->showResetForm($request, $key, $token); + return $this->showResetForm($request, $token); } /** diff --git a/app/Http/Controllers/NinjaController.php b/app/Http/Controllers/NinjaController.php index 3dce07391e98..dd25b4c58dcb 100644 --- a/app/Http/Controllers/NinjaController.php +++ b/app/Http/Controllers/NinjaController.php @@ -7,6 +7,7 @@ use App\Models\Country; use App\Models\License; use App\Ninja\Mailers\ContactMailer; use App\Ninja\Repositories\AccountRepository; +use App\Libraries\CurlUtils; use Auth; use Cache; use CreditCard; @@ -290,4 +291,31 @@ class NinjaController extends BaseController return RESULT_SUCCESS; } + + public function purchaseWhiteLabel() + { + if (Utils::isNinja()) { + return redirect('/'); + } + + $user = Auth::user(); + $url = NINJA_APP_URL . '/buy_now'; + $contactKey = $user->primaryAccount()->account_key; + + $data = [ + 'account_key' => NINJA_LICENSE_ACCOUNT_KEY, + 'contact_key' => $contactKey, + 'product_id' => PRODUCT_WHITE_LABEL, + 'first_name' => Auth::user()->first_name, + 'last_name' => Auth::user()->last_name, + 'email' => Auth::user()->email, + 'return_link' => true, + ]; + + if ($url = CurlUtils::post($url, $data)) { + return redirect($url); + } else { + return redirect()->back()->withError(trans('texts.error_refresh_page')); + } + } } diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index c6429521a875..9e83394cc1d4 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -170,13 +170,22 @@ class UserController extends BaseController $rules['email'] = 'required|email|unique:users,email,'.$user->id.',id'; } else { + $user = false; $rules['email'] = 'required|email|unique:users'; } $validator = Validator::make(Input::all(), $rules); if ($validator->fails()) { - return Redirect::to($userPublicId ? 'users/edit' : 'users/create')->withInput()->withErrors($validator); + return Redirect::to($userPublicId ? 'users/edit' : 'users/create') + ->withErrors($validator) + ->withInput(); + } + + if (! \App\Models\LookupUser::validateEmail($email, $user)) { + return Redirect::to($userPublicId ? 'users/edit' : 'users/create') + ->withError(trans('texts.email_taken')) + ->withInput(); } if ($userPublicId) { diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index e508f8d0c551..49133b82d711 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -29,6 +29,7 @@ class Kernel extends HttpKernel * @var array */ protected $routeMiddleware = [ + 'lookup' => 'App\Http\Middleware\DatabaseLookup', 'auth' => 'App\Http\Middleware\Authenticate', 'auth.basic' => 'Illuminate\Auth\Middleware\AuthenticateWithBasicAuth', 'permissions.required' => 'App\Http\Middleware\PermissionsRequired', diff --git a/app/Http/Middleware/DatabaseLookup.php b/app/Http/Middleware/DatabaseLookup.php new file mode 100644 index 000000000000..b0ccf75d1ab4 --- /dev/null +++ b/app/Http/Middleware/DatabaseLookup.php @@ -0,0 +1,53 @@ + $server]); + } elseif ($email = $request->email) { + LookupUser::setServerByField('email', $email); + } elseif ($code = $request->confirmation_code) { + LookupUser::setServerByField('confirmation_code', $code); + } + } elseif ($guard == 'api') { + if ($token = $request->header('X-Ninja-Token')) { + LookupAccountToken::setServerByField('token', $token); + } elseif ($email = $request->email) { + LookupUser::setServerByField('email', $email); + } + } elseif ($guard == 'contact') { + if ($key = request()->invitation_key) { + LookupInvitation::setServerByField('invitation_key', $key); + } elseif ($key = request()->contact_key ?: session('contact_key')) { + LookupContact::setServerByField('contact_key', $key); + } + } elseif ($guard == 'postmark') { + LookupInvitation::setServerByField('message_id', request()->MessageID); + } elseif ($guard == 'account') { + if ($key = request()->account_key) { + LookupAccount::setServerByField('account_key', $key); + } + } elseif ($guard == 'license') { + config(['database.default' => DB_NINJA_1]); + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php index 2a8b178f5952..dd4e78ebf731 100644 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -21,7 +21,7 @@ class VerifyCsrfToken extends BaseVerifier 'hook/email_bounced', 'reseller_stats', 'payment_hook/*', - 'buy_now/*', + 'buy_now*', 'hook/bot/*', ]; diff --git a/app/Http/Requests/SaveClientPortalSettings.php b/app/Http/Requests/SaveClientPortalSettings.php index 7912c0df45da..cd81bf7d31dd 100644 --- a/app/Http/Requests/SaveClientPortalSettings.php +++ b/app/Http/Requests/SaveClientPortalSettings.php @@ -38,7 +38,7 @@ class SaveClientPortalSettings extends Request $input = $this->all(); if ($this->client_view_css && Utils::isNinja()) { - $input['client_view_css'] = HTMLUtils::sanitize($this->client_view_css); + $input['client_view_css'] = HTMLUtils::sanitizeCSS($this->client_view_css); } if (Utils::isNinja()) { @@ -53,7 +53,7 @@ class SaveClientPortalSettings extends Request $input['subdomain'] = null; } } - + $this->replace($input); return $this->all(); diff --git a/app/Http/routes.php b/app/Http/routes.php index 8506828fee72..02c7aaa539ec 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -25,7 +25,7 @@ Route::get('/keep_alive', 'HomeController@keepAlive'); Route::post('/get_started', 'AccountController@getStarted'); // Client visible pages -Route::group(['middleware' => 'auth:client'], function () { +Route::group(['middleware' => ['lookup:contact', 'auth:client']], function () { Route::get('view/{invitation_key}', 'ClientPortalController@view'); Route::get('download/{invitation_key}', 'ClientPortalController@download'); Route::put('sign/{invitation_key}', 'ClientPortalController@sign'); @@ -62,51 +62,59 @@ Route::group(['middleware' => 'auth:client'], function () { Route::get('api/client.activity', ['as' => 'api.client.activity', 'uses' => 'ClientPortalController@activityDatatable']); }); -Route::get('license', 'NinjaController@show_license_payment'); -Route::post('license', 'NinjaController@do_license_payment'); -Route::get('claim_license', 'NinjaController@claim_license'); - -Route::post('signup/validate', 'AccountController@checkEmail'); -Route::post('signup/submit', 'AccountController@submitSignup'); - -Route::get('/auth/{provider}', 'Auth\AuthController@authLogin'); -Route::get('/auth_unlink', 'Auth\AuthController@authUnlink'); +Route::group(['middleware' => 'lookup:license'], function () { + Route::get('license', 'NinjaController@show_license_payment'); + Route::post('license', 'NinjaController@do_license_payment'); + Route::get('claim_license', 'NinjaController@claim_license'); +}); Route::group(['middleware' => 'cors'], function () { Route::match(['GET', 'POST', 'OPTIONS'], '/buy_now/{gateway_type?}', 'OnlinePaymentController@handleBuyNow'); }); -Route::post('/hook/email_bounced', 'AppController@emailBounced'); -Route::post('/hook/email_opened', 'AppController@emailOpened'); -Route::post('/hook/bot/{platform?}', 'BotController@handleMessage'); -Route::post('/payment_hook/{accountKey}/{gatewayId}', 'OnlinePaymentController@handlePaymentWebhook'); +Route::group(['middleware' => 'lookup:postmark'], function () { + Route::post('/hook/email_bounced', 'AppController@emailBounced'); + Route::post('/hook/email_opened', 'AppController@emailOpened'); +}); + +Route::group(['middleware' => 'lookup:account'], function () { + Route::post('/payment_hook/{account_key}/{gateway_id}', 'OnlinePaymentController@handlePaymentWebhook'); +}); + +//Route::post('/hook/bot/{platform?}', 'BotController@handleMessage'); // Laravel auth routes Route::get('/signup', ['as' => 'signup', 'uses' => 'Auth\AuthController@getRegister']); Route::post('/signup', ['as' => 'signup', 'uses' => 'Auth\AuthController@postRegister']); Route::get('/login', ['as' => 'login', 'uses' => 'Auth\AuthController@getLoginWrapper']); -Route::post('/login', ['as' => 'login', 'uses' => 'Auth\AuthController@postLoginWrapper']); Route::get('/logout', ['as' => 'logout', 'uses' => 'Auth\AuthController@getLogoutWrapper']); Route::get('/recover_password', ['as' => 'forgot', 'uses' => 'Auth\PasswordController@getEmail']); -Route::post('/recover_password', ['as' => 'forgot', 'uses' => 'Auth\PasswordController@postEmail']); Route::get('/password/reset/{token}', ['as' => 'forgot', 'uses' => 'Auth\PasswordController@getReset']); -Route::post('/password/reset', ['as' => 'forgot', 'uses' => 'Auth\PasswordController@postReset']); -Route::get('/user/confirm/{code}', 'UserController@confirm'); +Route::get('/auth/{provider}', 'Auth\AuthController@authLogin'); + +Route::group(['middleware' => ['lookup:user']], function () { + Route::get('/user/confirm/{confirmation_code}', 'UserController@confirm'); + Route::post('/login', ['as' => 'login', 'uses' => 'Auth\AuthController@postLoginWrapper']); + Route::post('/recover_password', ['as' => 'forgot', 'uses' => 'Auth\PasswordController@postEmail']); + Route::post('/password/reset', ['as' => 'forgot', 'uses' => 'Auth\PasswordController@postReset']); +}); // Client auth Route::get('/client/login', ['as' => 'login', 'uses' => 'ClientAuth\AuthController@getLogin']); -Route::post('/client/login', ['as' => 'login', 'uses' => 'ClientAuth\AuthController@postLogin']); Route::get('/client/logout', ['as' => 'logout', 'uses' => 'ClientAuth\AuthController@getLogout']); Route::get('/client/sessionexpired', ['as' => 'logout', 'uses' => 'ClientAuth\AuthController@getSessionExpired']); Route::get('/client/recover_password', ['as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@getEmail']); -Route::post('/client/recover_password', ['as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@postEmail']); -Route::get('/client/password/reset/{invitation_key}/{token}', ['as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@getReset']); -Route::post('/client/password/reset', ['as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@postReset']); +Route::get('/client/password/reset/{token}', ['as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@getReset']); + +Route::group(['middleware' => ['lookup:contact']], function () { + Route::post('/client/login', ['as' => 'login', 'uses' => 'ClientAuth\AuthController@postLogin']); + Route::post('/client/recover_password', ['as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@postEmail']); + Route::post('/client/password/reset', ['as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@postReset']); +}); if (Utils::isNinja()) { Route::post('/signup/register', 'AccountController@doRegister'); Route::get('/news_feed/{user_type}/{version}/', 'HomeController@newsFeed'); - Route::get('/demo', 'AccountController@demo'); } if (Utils::isReseller()) { @@ -117,7 +125,7 @@ if (Utils::isTravis()) { Route::get('/check_data', 'AppController@checkData'); } -Route::group(['middleware' => 'auth:user'], function () { +Route::group(['middleware' => ['lookup:user', 'auth:user']], function () { Route::get('dashboard', 'DashboardController@index'); Route::get('dashboard_chart_data/{group_by}/{start_date}/{end_date}/{currency_id}/{include_expenses}', 'DashboardController@chartData'); Route::get('set_entity_filter/{entity_type}/{filter?}', 'AccountController@setEntityFilter'); @@ -129,6 +137,10 @@ Route::group(['middleware' => 'auth:user'], function () { Route::post('contact_us', 'HomeController@contactUs'); Route::post('handle_command', 'BotController@handleCommand'); + Route::post('signup/validate', 'AccountController@checkEmail'); + Route::post('signup/submit', 'AccountController@submitSignup'); + Route::get('auth_unlink', 'Auth\AuthController@authUnlink'); + Route::get('settings/user_details', 'AccountController@showUserDetails'); Route::post('settings/user_details', 'AccountController@saveUserDetails'); Route::post('settings/payment_gateway_limits', 'AccountGatewayController@savePaymentGatewayLimits'); @@ -223,14 +235,16 @@ Route::group(['middleware' => 'auth:user'], function () { Route::post('bluevine/signup', 'BlueVineController@signup'); Route::get('bluevine/hide_message', 'BlueVineController@hideMessage'); Route::get('bluevine/completed', 'BlueVineController@handleCompleted'); + Route::get('white_label/hide_message', 'NinjaController@hideWhiteLabelMessage'); + Route::get('white_label/purchase', 'NinjaController@purchaseWhiteLabel'); Route::get('reports', 'ReportController@showReports'); Route::post('reports', 'ReportController@showReports'); }); Route::group([ - 'middleware' => ['auth:user', 'permissions.required'], + 'middleware' => ['lookup:user', 'auth:user', 'permissions.required'], 'permissions' => 'admin', ], function () { Route::get('api/users', 'UserController@getDatatable'); @@ -295,12 +309,12 @@ Route::group([ Route::get('self-update/download', 'SelfUpdateController@download'); }); -Route::group(['middleware' => 'auth:user'], function () { +Route::group(['middleware' => ['lookup:user', 'auth:user']], function () { Route::get('settings/{section?}', 'AccountController@showSection'); }); // Route groups for API -Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function () { +Route::group(['middleware' => ['lookup:api', 'api'], 'prefix' => 'api/v1'], function () { Route::get('ping', 'AccountApiController@ping'); Route::post('login', 'AccountApiController@login'); Route::post('oauth_login', 'AccountApiController@oauthLogin'); diff --git a/app/Jobs/ImportData.php b/app/Jobs/ImportData.php index 31271c49e075..b1427775f7ed 100644 --- a/app/Jobs/ImportData.php +++ b/app/Jobs/ImportData.php @@ -34,6 +34,11 @@ class ImportData extends Job implements ShouldQueue */ protected $settings; + /** + * @var string + */ + protected $server; + /** * Create a new job instance. * @@ -45,6 +50,7 @@ class ImportData extends Job implements ShouldQueue $this->user = $user; $this->type = $type; $this->settings = $settings; + $this->server = config('database.default'); } /** diff --git a/app/Jobs/PurgeAccountData.php b/app/Jobs/PurgeAccountData.php index c1f289e2cae3..daad1f1efb97 100644 --- a/app/Jobs/PurgeAccountData.php +++ b/app/Jobs/PurgeAccountData.php @@ -4,6 +4,7 @@ namespace App\Jobs; use App\Jobs\Job; use App\Models\Document; +use App\Models\LookupAccount; use Auth; use DB; use Exception; @@ -55,7 +56,18 @@ class PurgeAccountData extends Job $account->invoice_number_counter = 1; $account->quote_number_counter = 1; - $account->client_number_counter = 1; + $account->client_number_counter = $account->client_number_counter > 0 ? 1 : 0; $account->save(); + + if (env('MULTI_DB_ENABLED')) { + $current = config('database.default'); + config(['database.default' => DB_NINJA_LOOKUP]); + + $lookupAccount = LookupAccount::whereAccountKey($account->account_key)->firstOrFail(); + DB::table('lookup_contacts')->where('lookup_account_id', '=', $lookupAccount->id)->delete(); + DB::table('lookup_invitations')->where('lookup_account_id', '=', $lookupAccount->id)->delete(); + + config(['database.default' => $current]); + } } } diff --git a/app/Jobs/SendInvoiceEmail.php b/app/Jobs/SendInvoiceEmail.php index 04168b609f8e..1046372686ca 100644 --- a/app/Jobs/SendInvoiceEmail.php +++ b/app/Jobs/SendInvoiceEmail.php @@ -38,6 +38,11 @@ class SendInvoiceEmail extends Job implements ShouldQueue */ protected $userId; + /** + * @var string + */ + protected $server; + /** * Create a new job instance. * @@ -52,6 +57,7 @@ class SendInvoiceEmail extends Job implements ShouldQueue $this->userId = $userId; $this->reminder = $reminder; $this->template = $template; + $this->server = config('database.default'); } /** diff --git a/app/Jobs/SendNotificationEmail.php b/app/Jobs/SendNotificationEmail.php index 04fd4fd77c96..e74ace04d10a 100644 --- a/app/Jobs/SendNotificationEmail.php +++ b/app/Jobs/SendNotificationEmail.php @@ -40,6 +40,11 @@ class SendNotificationEmail extends Job implements ShouldQueue */ protected $notes; + /** + * @var string + */ + protected $server; + /** * Create a new job instance. @@ -58,6 +63,7 @@ class SendNotificationEmail extends Job implements ShouldQueue $this->type = $type; $this->payment = $payment; $this->notes = $notes; + $this->server = config('database.default'); } /** diff --git a/app/Jobs/SendPaymentEmail.php b/app/Jobs/SendPaymentEmail.php index e23d291efcec..8e990c00de93 100644 --- a/app/Jobs/SendPaymentEmail.php +++ b/app/Jobs/SendPaymentEmail.php @@ -20,6 +20,11 @@ class SendPaymentEmail extends Job implements ShouldQueue */ protected $payment; + /** + * @var string + */ + protected $server; + /** * Create a new job instance. @@ -28,6 +33,7 @@ class SendPaymentEmail extends Job implements ShouldQueue public function __construct($payment) { $this->payment = $payment; + $this->server = config('database.default'); } /** diff --git a/app/Jobs/SendPushNotification.php b/app/Jobs/SendPushNotification.php index 88a1e59e1789..73acd9299142 100644 --- a/app/Jobs/SendPushNotification.php +++ b/app/Jobs/SendPushNotification.php @@ -25,6 +25,11 @@ class SendPushNotification extends Job implements ShouldQueue */ protected $type; + /** + * @var string + */ + protected $server; + /** * Create a new job instance. @@ -35,6 +40,7 @@ class SendPushNotification extends Job implements ShouldQueue { $this->invoice = $invoice; $this->type = $type; + $this->server = config('database.default'); } /** diff --git a/app/Libraries/HTMLUtils.php b/app/Libraries/HTMLUtils.php index 412252a1eb6d..2afd71cd03d5 100644 --- a/app/Libraries/HTMLUtils.php +++ b/app/Libraries/HTMLUtils.php @@ -7,7 +7,7 @@ use HTMLPurifier_Config; class HTMLUtils { - public static function sanitize($css) + public static function sanitizeCSS($css) { // Allow referencing the body element $css = preg_replace('/(?purify($html); + } } diff --git a/app/Libraries/Utils.php b/app/Libraries/Utils.php index 4d9cd1d70bdc..ff935a32b906 100644 --- a/app/Libraries/Utils.php +++ b/app/Libraries/Utils.php @@ -251,11 +251,6 @@ class Utils } } - public static function getDemoAccountId() - { - return isset($_ENV[DEMO_ACCOUNT_ID]) ? $_ENV[DEMO_ACCOUNT_ID] : false; - } - public static function getNewsFeedResponse($userType = false) { if (! $userType) { @@ -398,6 +393,7 @@ class Utils 'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '', 'ip' => Request::getClientIp(), 'count' => Session::get('error_count', 0), + 'is_console' => App::runningInConsole() ? 'yes' : 'no', ]; if ($info) { diff --git a/app/Listeners/InvoiceListener.php b/app/Listeners/InvoiceListener.php index 3888ccbda063..4883667cd89d 100644 --- a/app/Listeners/InvoiceListener.php +++ b/app/Listeners/InvoiceListener.php @@ -153,6 +153,7 @@ class InvoiceListener public function jobFailed(JobExceptionOccurred $exception) { + /* if ($errorEmail = env('ERROR_EMAIL')) { \Mail::raw(print_r($exception->data, true), function ($message) use ($errorEmail) { $message->to($errorEmail) @@ -160,6 +161,7 @@ class InvoiceListener ->subject('Job failed'); }); } + */ Utils::logError($exception->exception); } diff --git a/app/Models/Account.php b/app/Models/Account.php index 42e427a23936..b5b8b4b2d9c6 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -4,6 +4,7 @@ namespace App\Models; use App; use App\Events\UserSettingsChanged; +use App\Models\LookupAccount; use App\Models\Traits\GeneratesNumbers; use App\Models\Traits\PresentsInvoice; use App\Models\Traits\SendsEmails; @@ -1655,6 +1656,11 @@ class Account extends Eloquent } } +Account::creating(function ($account) +{ + LookupAccount::createAccount($account->account_key, $account->company_id); +}); + Account::updated(function ($account) { // prevent firing event if the invoice/quote counter was changed // TODO: remove once counters are moved to separate table @@ -1665,3 +1671,10 @@ Account::updated(function ($account) { Event::fire(new UserSettingsChanged()); }); + +Account::deleted(function ($account) +{ + LookupAccount::deleteWhere([ + 'account_key' => $account->account_key + ]); +}); diff --git a/app/Models/AccountToken.php b/app/Models/AccountToken.php index dace5f00b91b..0196c64f47f4 100644 --- a/app/Models/AccountToken.php +++ b/app/Models/AccountToken.php @@ -3,6 +3,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\SoftDeletes; +use App\Models\LookupAccountToken; /** * Class AccountToken. @@ -39,3 +40,17 @@ class AccountToken extends EntityModel return $this->belongsTo('App\Models\User')->withTrashed(); } } + +AccountToken::creating(function ($token) +{ + LookupAccountToken::createNew($token->account->account_key, [ + 'token' => $token->token, + ]); +}); + +AccountToken::deleted(function ($token) +{ + LookupAccountToken::deleteWhere([ + 'token' => $token->token + ]); +}); diff --git a/app/Models/Company.php b/app/Models/Company.php index afe117a5dffe..f99c08b87dc1 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -21,6 +21,18 @@ class Company extends Eloquent */ protected $presenter = 'App\Ninja\Presenters\CompanyPresenter'; + /** + * @var array + */ + protected $fillable = [ + 'plan', + 'plan_term', + 'plan_price', + 'plan_paid', + 'plan_started', + 'plan_expires', + ]; + /** * @var array */ @@ -174,3 +186,17 @@ class Company extends Eloquent return false; } } + +Company::deleted(function ($company) +{ + if (! env('MULTI_DB_ENABLED')) { + return; + } + + $server = \App\Models\DbServer::whereName(config('database.default'))->firstOrFail(); + + LookupCompany::deleteWhere([ + 'company_id' => $company->id, + 'db_server_id' => $server->id, + ]); +}); diff --git a/app/Models/Contact.php b/app/Models/Contact.php index ef826e33792e..adc0fa752beb 100644 --- a/app/Models/Contact.php +++ b/app/Models/Contact.php @@ -8,6 +8,7 @@ use Illuminate\Auth\Passwords\CanResetPassword; use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; use Illuminate\Database\Eloquent\SoftDeletes; +use App\Models\LookupContact; /** * Class Contact. @@ -165,3 +166,17 @@ class Contact extends EntityModel implements AuthenticatableContract, CanResetPa return "{$url}/client/dashboard/{$this->contact_key}"; } } + +Contact::creating(function ($contact) +{ + LookupContact::createNew($contact->account->account_key, [ + 'contact_key' => $contact->contact_key, + ]); +}); + +Contact::deleted(function ($contact) +{ + LookupContact::deleteWhere([ + 'contact_key' => $contact->contact_key, + ]); +}); diff --git a/app/Models/DbServer.php b/app/Models/DbServer.php new file mode 100644 index 000000000000..005a19ebb8c8 --- /dev/null +++ b/app/Models/DbServer.php @@ -0,0 +1,24 @@ +
%s: %s', $this->signature_base64, trans('texts.signed'), Utils::fromSqlDateTime($this->signature_date)); } } + +Invitation::creating(function ($invitation) +{ + LookupInvitation::createNew($invitation->account->account_key, [ + 'invitation_key' => $invitation->invitation_key, + ]); +}); + +Invitation::deleted(function ($invitation) +{ + LookupInvitation::deleteWhere([ + 'invitation_key' => $invitation->invitation_key, + ]); +}); diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index edd6cce44d68..086bc0a1f97c 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -1034,7 +1034,7 @@ class Invoice extends EntityModel implements BalanceAffecting $dueDay = $lastDayOfMonth; } - if ($currentDay >= $dueDay) { + if ($currentDay > $dueDay) { // Wait until next month // We don't need to handle the December->January wraparaound, since PHP handles month 13 as January of next year $dueMonth++; @@ -1511,6 +1511,11 @@ class Invoice extends EntityModel implements BalanceAffecting ->orderBy('id', 'desc') ->get(); } + + public function getDueDateLabel() + { + return $this->isQuote() ? 'valid_until' : 'due_date'; + } } Invoice::creating(function ($invoice) { diff --git a/app/Models/LookupAccount.php b/app/Models/LookupAccount.php new file mode 100644 index 000000000000..0d14677339a4 --- /dev/null +++ b/app/Models/LookupAccount.php @@ -0,0 +1,52 @@ +belongsTo('App\Models\LookupCompany'); + } + + public static function createAccount($accountKey, $companyId) + { + if (! env('MULTI_DB_ENABLED')) { + return; + } + + $current = config('database.default'); + config(['database.default' => DB_NINJA_LOOKUP]); + + $server = DbServer::whereName($current)->firstOrFail(); + $lookupCompany = LookupCompany::whereDbServerId($server->id) + ->whereCompanyId($companyId)->first(); + + if (! $lookupCompany) { + $lookupCompany = LookupCompany::create([ + 'db_server_id' => $server->id, + 'company_id' => $companyId, + ]); + } + + LookupAccount::create([ + 'lookup_company_id' => $lookupCompany->id, + 'account_key' => $accountKey, + ]); + + static::setDbServer($current); + } +} diff --git a/app/Models/LookupAccountToken.php b/app/Models/LookupAccountToken.php new file mode 100644 index 000000000000..d72d7613fc34 --- /dev/null +++ b/app/Models/LookupAccountToken.php @@ -0,0 +1,20 @@ +belongsTo('App\Models\DbServer'); + } + +} diff --git a/app/Models/LookupContact.php b/app/Models/LookupContact.php new file mode 100644 index 000000000000..2caf8c6ca6b1 --- /dev/null +++ b/app/Models/LookupContact.php @@ -0,0 +1,20 @@ +belongsTo('App\Models\LookupAccount'); + } + + public static function createNew($accountKey, $data) + { + if (! env('MULTI_DB_ENABLED')) { + return; + } + + $current = config('database.default'); + config(['database.default' => DB_NINJA_LOOKUP]); + + $lookupAccount = LookupAccount::whereAccountKey($accountKey)->first(); + + if ($lookupAccount) { + $data['lookup_account_id'] = $lookupAccount->id; + } else { + abort('Lookup account not found for ' . $accountKey); + } + + static::create($data); + + config(['database.default' => $current]); + } + + public static function deleteWhere($where) + { + if (! env('MULTI_DB_ENABLED')) { + return; + } + + $current = config('database.default'); + config(['database.default' => DB_NINJA_LOOKUP]); + + static::where($where)->delete(); + + config(['database.default' => $current]); + + } + + public static function setServerByField($field, $value) + { + if (! env('MULTI_DB_ENABLED')) { + return; + } + + $className = get_called_class(); + $className = str_replace('Lookup', '', $className); + $key = sprintf('server:%s:%s:%s', $className, $field, $value); + $isUser = $className == 'App\Models\User'; + + // check if we've cached this lookup + if (env('MULTI_DB_CACHE_ENABLED') && $server = Cache::get($key)) { + static::setDbServer($server, $isUser); + return; + } + + $current = config('database.default'); + config(['database.default' => DB_NINJA_LOOKUP]); + + if ($value && $lookupModel = static::where($field, '=', $value)->first()) { + $entity = new $className(); + $server = $lookupModel->getDbServer(); + + static::setDbServer($server, $isUser); + + // check entity is found on the server + if (! $entity::where($field, '=', $value)->first()) { + abort("Looked up {$className} not found: {$field} => {$value}"); + } + + Cache::put($key, $server, 120); + } else { + config(['database.default' => $current]); + } + } + + protected static function setDbServer($server, $isUser = false) + { + if (! env('MULTI_DB_ENABLED')) { + return; + } + + config(['database.default' => $server]); + + if ($isUser) { + session([SESSION_DB_SERVER => $server]); + } + } + + public function getDbServer() + { + return $this->lookupAccount->lookupCompany->dbServer->name; + } +} diff --git a/app/Models/LookupUser.php b/app/Models/LookupUser.php new file mode 100644 index 000000000000..6d829fa48428 --- /dev/null +++ b/app/Models/LookupUser.php @@ -0,0 +1,68 @@ + DB_NINJA_LOOKUP]); + + $lookupAccount = LookupAccount::whereAccountKey($accountKey) + ->firstOrFail(); + + $lookupUser = LookupUser::whereLookupAccountId($lookupAccount->id) + ->whereUserId($userId) + ->firstOrFail(); + + $lookupUser->email = $email; + $lookupUser->confirmation_code = $confirmationCode; + $lookupUser->save(); + + config(['database.default' => $current]); + } + + public static function validateEmail($email, $user = false) + { + if (! env('MULTI_DB_ENABLED')) { + return true; + } + + $current = config('database.default'); + config(['database.default' => DB_NINJA_LOOKUP]); + + $lookupUser = LookupUser::whereEmail($email)->first(); + + if ($user) { + $lookupAccount = LookupAccount::whereAccountKey($user->account->account_key)->firstOrFail(); + $isValid = ! $lookupUser || ($lookupUser->lookup_account_id == $lookupAccount->id && $lookupUser->user_id == $user->id); + } else { + $isValid = ! $lookupUser; + } + + config(['database.default' => $current]); + + return $isValid; + } + +} diff --git a/app/Models/Traits/GeneratesNumbers.php b/app/Models/Traits/GeneratesNumbers.php index c94e50e3edba..2c3bf6880363 100644 --- a/app/Models/Traits/GeneratesNumbers.php +++ b/app/Models/Traits/GeneratesNumbers.php @@ -26,6 +26,7 @@ trait GeneratesNumbers $prefix = $this->getNumberPrefix($entityType); $counterOffset = 0; $check = false; + $lastNumber = false; if ($entityType == ENTITY_CLIENT && ! $this->clientNumbersEnabled()) { return ''; @@ -50,6 +51,13 @@ trait GeneratesNumbers } $counter++; $counterOffset++; + + // prevent getting stuck in a loop + if ($number == $lastNumber) { + return ''; + } + $lastNumber = $number; + } while ($check); // update the counter to be caught up @@ -194,15 +202,17 @@ trait GeneratesNumbers '{$clientCounter}', ]; + $client = $invoice->client; + $clientCounter = ($invoice->isQuote() && ! $this->share_counter) ? $client->quote_number_counter : $client->invoice_number_counter; + $replace = [ - $invoice->client->custom_value1, - $invoice->client->custom_value2, - $invoice->client->id_number, - $invoice->client->custom_value1, // backwards compatibility - $invoice->client->custom_value2, - $invoice->client->id_number, - str_pad($invoice->client->invoice_number_counter, $this->invoice_number_padding, '0', STR_PAD_LEFT), - str_pad($invoice->client->quote_number_counter, $this->invoice_number_padding, '0', STR_PAD_LEFT), + $client->custom_value1, + $client->custom_value2, + $client->id_number, + $client->custom_value1, // backwards compatibility + $client->custom_value2, + $client->id_number, + str_pad($clientCounter, $this->invoice_number_padding, '0', STR_PAD_LEFT), ]; return str_replace($search, $replace, $pattern); diff --git a/app/Models/Traits/PresentsInvoice.php b/app/Models/Traits/PresentsInvoice.php index 073d2479c462..52afd90968e8 100644 --- a/app/Models/Traits/PresentsInvoice.php +++ b/app/Models/Traits/PresentsInvoice.php @@ -162,6 +162,28 @@ trait PresentsInvoice return $fields; } + public function hasCustomLabel($field) + { + $custom = (array) json_decode($this->invoice_labels); + + return isset($custom[$field]) && $custom[$field]; + } + + public function getLabel($field, $override = false) + { + $custom = (array) json_decode($this->invoice_labels); + + if (isset($custom[$field]) && $custom[$field]) { + return $custom[$field]; + } else { + if ($override) { + $field = $override; + } + return $this->isEnglish() ? uctrans("texts.$field") : trans("texts.$field"); + } + + } + /** * @return array */ @@ -239,6 +261,8 @@ trait PresentsInvoice 'work_phone', 'invoice_total', 'outstanding', + 'invoice_due_date', + 'quote_due_date', ]; foreach ($fields as $field) { diff --git a/app/Models/User.php b/app/Models/User.php index 240234d878f5..8137eb42242f 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -10,6 +10,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Foundation\Auth\User as Authenticatable; use Laracasts\Presenter\PresentableTrait; use Session; +use App\Models\LookupUser; /** * Class User. @@ -412,10 +413,34 @@ class User extends Authenticatable } } +User::created(function ($user) +{ + LookupUser::createNew($user->account->account_key, [ + 'email' => $user->email, + 'user_id' => $user->id, + ]); +}); + User::updating(function ($user) { User::onUpdatingUser($user); + + $dirty = $user->getDirty(); + if (isset($dirty['email']) || isset($dirty['confirmation_code'])) { + LookupUser::updateUser($user->account->account_key, $user->id, $user->email, $user->confirmation_code); + } }); User::updated(function ($user) { User::onUpdatedUser($user); }); + +User::deleted(function ($user) +{ + if (! $user->email) { + return; + } + + LookupUser::deleteWhere([ + 'email' => $user->email + ]); +}); diff --git a/app/Ninja/Repositories/AccountRepository.php b/app/Ninja/Repositories/AccountRepository.php index f64cd05566f5..00319b9dee92 100644 --- a/app/Ninja/Repositories/AccountRepository.php +++ b/app/Ninja/Repositories/AccountRepository.php @@ -359,13 +359,12 @@ class AccountRepository $emailSettings = new AccountEmailSettings(); $account->account_email_settings()->save($emailSettings); - $random = strtolower(str_random(RANDOM_KEY_LENGTH)); $user = new User(); $user->registered = true; $user->confirmed = true; - $user->email = 'contact@invoiceninja.com'; - $user->password = $random; - $user->username = $random; + $user->email = NINJA_ACCOUNT_EMAIL; + $user->username = NINJA_ACCOUNT_EMAIL; + $user->password = strtolower(str_random(RANDOM_KEY_LENGTH)); $user->first_name = 'Invoice'; $user->last_name = 'Ninja'; $user->notify_sent = true; @@ -393,7 +392,6 @@ class AccountRepository $client = Client::whereAccountId($ninjaAccount->id) ->wherePublicId($account->id) ->first(); - $clientExists = $client ? true : false; if (! $client) { $client = new Client(); @@ -401,31 +399,22 @@ class AccountRepository $client->account_id = $ninjaAccount->id; $client->user_id = $ninjaUser->id; $client->currency_id = 1; - } - - foreach (['name', 'address1', 'address2', 'city', 'state', 'postal_code', 'country_id', 'work_phone', 'language_id', 'vat_number'] as $field) { - $client->$field = $account->$field; - } - - $client->save(); - - if ($clientExists) { - $contact = $client->getPrimaryContact(); - } else { + foreach (['name', 'address1', 'address2', 'city', 'state', 'postal_code', 'country_id', 'work_phone', 'language_id', 'vat_number'] as $field) { + $client->$field = $account->$field; + } + $client->save(); $contact = new Contact(); $contact->user_id = $ninjaUser->id; $contact->account_id = $ninjaAccount->id; $contact->public_id = $account->id; + $contact->contact_key = strtolower(str_random(RANDOM_KEY_LENGTH)); $contact->is_primary = true; + foreach (['first_name', 'last_name', 'email', 'phone'] as $field) { + $contact->$field = $account->users()->first()->$field; + } + $client->contacts()->save($contact); } - $user = $account->getPrimaryUser(); - foreach (['first_name', 'last_name', 'email', 'phone'] as $field) { - $contact->$field = $user->$field; - } - - $client->contacts()->save($contact); - return $client; } @@ -450,12 +439,16 @@ class AccountRepository if (! $user->registered) { $rules = ['email' => 'email|required|unique:users,email,'.$user->id.',id']; $validator = Validator::make(['email' => $email], $rules); + if ($validator->fails()) { $messages = $validator->messages(); - return $messages->first('email'); } + if (! \App\Models\LookupUser::validateEmail($email, $user)) { + return trans('texts.email_taken'); + } + $user->email = $email; $user->first_name = $firstName; $user->last_name = $lastName; diff --git a/app/Ninja/Repositories/NinjaRepository.php b/app/Ninja/Repositories/NinjaRepository.php index 20921ce07934..968504b69a63 100644 --- a/app/Ninja/Repositories/NinjaRepository.php +++ b/app/Ninja/Repositories/NinjaRepository.php @@ -15,12 +15,7 @@ class NinjaRepository } $company = $account->company; - $company->plan = ! empty($data['plan']) && $data['plan'] != PLAN_FREE ? $data['plan'] : null; - $company->plan_term = ! empty($data['plan_term']) ? $data['plan_term'] : null; - $company->plan_paid = ! empty($data['plan_paid']) ? $data['plan_paid'] : null; - $company->plan_started = ! empty($data['plan_started']) ? $data['plan_started'] : null; - $company->plan_expires = ! empty($data['plan_expires']) ? $data['plan_expires'] : null; - + $company->fill($data); $company->save(); } } diff --git a/app/Ninja/Repositories/TaskRepository.php b/app/Ninja/Repositories/TaskRepository.php index 5bf89158af54..a0b66d5bab46 100644 --- a/app/Ninja/Repositories/TaskRepository.php +++ b/app/Ninja/Repositories/TaskRepository.php @@ -134,6 +134,10 @@ class TaskRepository extends BaseRepository $timeLog = []; } + if(isset($data['client_id'])) { + $task->client_id = Client::getPrivateId($data['client_id']); + } + array_multisort($timeLog); if (isset($data['action'])) { @@ -146,6 +150,8 @@ class TaskRepository extends BaseRepository } elseif ($data['action'] == 'stop' && $task->is_running) { $timeLog[count($timeLog) - 1][1] = time(); $task->is_running = false; + } elseif ($data['action'] == 'offline'){ + $task->is_running = $data['is_running'] ? 1 : 0; } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 518076901af9..b7255fd4420c 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -8,6 +8,8 @@ use Request; use URL; use Utils; use Validator; +use Queue; +use Illuminate\Queue\Events\JobProcessing; /** * Class AppServiceProvider. @@ -21,6 +23,15 @@ class AppServiceProvider extends ServiceProvider */ public function boot() { + // support selecting job database + Queue::before(function (JobProcessing $event) { + $body = $event->job->getRawBody(); + preg_match('/db-ninja-[\d+]/', $body, $matches); + if (count($matches)) { + config(['database.default' => $matches[0]]); + } + }); + Form::macro('image_data', function ($image, $contents = false) { if (! $contents) { $contents = file_get_contents($image); diff --git a/app/Services/AuthService.php b/app/Services/AuthService.php index f9cc9d1ab089..2c395f01eae6 100644 --- a/app/Services/AuthService.php +++ b/app/Services/AuthService.php @@ -4,6 +4,7 @@ namespace App\Services; use App\Events\UserLoggedIn; use App\Ninja\Repositories\AccountRepository; +use App\Models\LookupUser; use Auth; use Input; use Session; @@ -59,13 +60,13 @@ class AuthService $socialiteUser = Socialite::driver($provider)->user(); $providerId = self::getProviderId($provider); + $email = $socialiteUser->email; + $oauthUserId = $socialiteUser->id; + $name = Utils::splitName($socialiteUser->name); + if (Auth::check()) { $user = Auth::user(); $isRegistered = $user->registered; - - $email = $socialiteUser->email; - $oauthUserId = $socialiteUser->id; - $name = Utils::splitName($socialiteUser->name); $result = $this->accountRepo->updateUserFromOauth($user, $name[0], $name[1], $email, $providerId, $oauthUserId); if ($result === true) { @@ -81,6 +82,8 @@ class AuthService Session::flash('error', $result); } } else { + LookupUser::setServerByField('email', $email); + if ($user = $this->accountRepo->findUserByOauth($providerId, $socialiteUser->id)) { Auth::login($user, true); event(new UserLoggedIn()); diff --git a/config/database.php b/config/database.php index e019d61d63cd..e5f0330b2d44 100644 --- a/config/database.php +++ b/config/database.php @@ -46,12 +46,7 @@ return [ 'connections' => [ - 'sqlite' => [ - 'driver' => 'sqlite', - 'database' => storage_path().'/database.sqlite', - 'prefix' => '', - ], - + // single database setup 'mysql' => [ 'driver' => 'mysql', 'host' => env('DB_HOST', 'localhost'), @@ -65,24 +60,44 @@ return [ 'engine' => 'InnoDB', ], - 'pgsql' => [ - 'driver' => 'pgsql', - 'host' => env('DB_HOST', 'localhost'), - 'database' => env('DB_DATABASE', 'forge'), - 'username' => env('DB_USERNAME', 'forge'), - 'password' => env('DB_PASSWORD', ''), - 'charset' => 'utf8', - 'prefix' => '', - 'schema' => 'public', + // multi-database setup + 'db-ninja-0' => [ + 'driver' => 'mysql', + 'host' => env('DB_HOST', env('DB_HOST0', 'localhost')), + 'database' => env('DB_DATABASE0', env('DB_DATABASE', 'forge')), + 'username' => env('DB_USERNAME0', env('DB_USERNAME', 'forge')), + 'password' => env('DB_PASSWORD0', env('DB_PASSWORD', '')), + 'charset' => 'utf8', + 'collation' => 'utf8_unicode_ci', + 'prefix' => '', + 'strict' => env('DB_STRICT', false), + 'engine' => 'InnoDB', ], - 'sqlsrv' => [ - 'driver' => 'sqlsrv', - 'host' => env('DB_HOST', 'localhost'), - 'database' => env('DB_DATABASE', 'forge'), - 'username' => env('DB_USERNAME', 'forge'), - 'password' => env('DB_PASSWORD', ''), - 'prefix' => '', + 'db-ninja-1' => [ + 'driver' => 'mysql', + 'host' => env('DB_HOST', env('DB_HOST1', 'localhost')), + 'database' => env('DB_DATABASE1', env('DB_DATABASE', 'forge')), + 'username' => env('DB_USERNAME1', env('DB_USERNAME', 'forge')), + 'password' => env('DB_PASSWORD1', env('DB_PASSWORD', '')), + 'charset' => 'utf8', + 'collation' => 'utf8_unicode_ci', + 'prefix' => '', + 'strict' => env('DB_STRICT', false), + 'engine' => 'InnoDB', + ], + + 'db-ninja-2' => [ + 'driver' => 'mysql', + 'host' => env('DB_HOST', env('DB_HOST2', 'localhost')), + 'database' => env('DB_DATABASE2', env('DB_DATABASE', 'forge')), + 'username' => env('DB_USERNAME2', env('DB_USERNAME', 'forge')), + 'password' => env('DB_PASSWORD2', env('DB_PASSWORD', '')), + 'charset' => 'utf8', + 'collation' => 'utf8_unicode_ci', + 'prefix' => '', + 'strict' => env('DB_STRICT', false), + 'engine' => 'InnoDB', ], ], diff --git a/config/queue.php b/config/queue.php index 30e8e8b9d10b..6ee3a7d7f16c 100644 --- a/config/queue.php +++ b/config/queue.php @@ -36,6 +36,7 @@ return [ ], 'database' => [ + 'connection' => env('QUEUE_DATABASE', 'mysql'), 'driver' => 'database', 'table' => 'jobs', 'queue' => 'default', @@ -86,7 +87,8 @@ return [ */ 'failed' => [ - 'database' => 'mysql', 'table' => 'failed_jobs', + 'database' => env('QUEUE_DATABASE', 'mysql'), + 'table' => 'failed_jobs', ], ]; diff --git a/database/migrations/2017_04_16_101744_add_custom_contact_fields.php b/database/migrations/2017_04_16_101744_add_custom_contact_fields.php index 45ae7b57a595..ad7545aa556d 100644 --- a/database/migrations/2017_04_16_101744_add_custom_contact_fields.php +++ b/database/migrations/2017_04_16_101744_add_custom_contact_fields.php @@ -22,22 +22,27 @@ class AddCustomContactFields extends Migration $table->string('custom_value2')->nullable(); }); - Schema::table('payment_methods', function ($table) { - $table->unsignedInteger('account_gateway_token_id')->nullable()->change(); - $table->dropForeign('payment_methods_account_gateway_token_id_foreign'); - }); + // This may fail if the foreign key doesn't exist + try { + Schema::table('payment_methods', function ($table) { + $table->unsignedInteger('account_gateway_token_id')->nullable()->change(); + $table->dropForeign('payment_methods_account_gateway_token_id_foreign'); + }); - Schema::table('payment_methods', function ($table) { - $table->foreign('account_gateway_token_id')->references('id')->on('account_gateway_tokens')->onDelete('cascade'); - }); + Schema::table('payment_methods', function ($table) { + $table->foreign('account_gateway_token_id')->references('id')->on('account_gateway_tokens')->onDelete('cascade'); + }); - Schema::table('payments', function ($table) { - $table->dropForeign('payments_payment_method_id_foreign'); - }); + Schema::table('payments', function ($table) { + $table->dropForeign('payments_payment_method_id_foreign'); + }); - Schema::table('payments', function ($table) { - $table->foreign('payment_method_id')->references('id')->on('payment_methods')->onDelete('cascade'); - }); + Schema::table('payments', function ($table) { + $table->foreign('payment_method_id')->references('id')->on('payment_methods')->onDelete('cascade'); + }); + } catch (Exception $e) { + // do nothing + } Schema::table('expenses', function($table) { $table->unsignedInteger('payment_type_id')->nullable(); diff --git a/database/migrations/2017_04_30_174702_add_multiple_database_support.php b/database/migrations/2017_04_30_174702_add_multiple_database_support.php new file mode 100644 index 000000000000..60f2ece903f1 --- /dev/null +++ b/database/migrations/2017_04_30_174702_add_multiple_database_support.php @@ -0,0 +1,71 @@ +unsignedInteger('company_id')->index(); + }); + + Schema::table('lookup_companies', function ($table) { + $table->unique(['db_server_id', 'company_id']); + }); + + Schema::table('lookup_accounts', function ($table) { + $table->string('account_key')->change()->unique(); + }); + + Schema::table('lookup_users', function ($table) { + $table->string('email')->change()->nullable()->unique(); + $table->string('confirmation_code')->nullable()->unique(); + $table->unsignedInteger('user_id')->index(); + }); + + Schema::table('lookup_users', function ($table) { + $table->unique(['lookup_account_id', 'user_id']); + }); + + Schema::table('lookup_contacts', function ($table) { + $table->string('contact_key')->change()->unique(); + }); + + Schema::table('lookup_invitations', function ($table) { + $table->string('invitation_key')->change()->unique(); + $table->string('message_id')->change()->nullable()->unique(); + }); + + Schema::table('lookup_tokens', function ($table) { + $table->string('token')->change()->unique(); + }); + + Schema::rename('lookup_tokens', 'lookup_account_tokens'); + + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('lookup_companies', function ($table) { + $table->dropColumn('company_id'); + }); + + Schema::table('lookup_users', function ($table) { + $table->dropColumn('confirmation_code'); + }); + + Schema::rename('lookup_account_tokens', 'lookup_tokens'); + } +} diff --git a/database/seeds/CurrenciesSeeder.php b/database/seeds/CurrenciesSeeder.php index b6c87bdd3b52..3d40d661dc7f 100644 --- a/database/seeds/CurrenciesSeeder.php +++ b/database/seeds/CurrenciesSeeder.php @@ -73,6 +73,7 @@ class CurrenciesSeeder extends Seeder ['name' => 'Dominican Peso', 'code' => 'DOP', 'symbol' => 'RD$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], ['name' => 'Chilean Peso', 'code' => 'CLP', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','], ['name' => 'Icelandic Króna', 'code' => 'ISK', 'symbol' => 'kr', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ',', 'swap_currency_symbol' => true], + ['name' => 'Papua New Guinean Kina', 'code' => 'PGK', 'symbol' => 'K', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], ]; foreach ($currencies as $currency) { diff --git a/database/seeds/DatabaseSeeder.php b/database/seeds/DatabaseSeeder.php index 0d384ceb6889..11803e0c9873 100644 --- a/database/seeds/DatabaseSeeder.php +++ b/database/seeds/DatabaseSeeder.php @@ -29,5 +29,6 @@ class DatabaseSeeder extends Seeder $this->call('LanguageSeeder'); $this->call('IndustrySeeder'); $this->call('FrequencySeeder'); + $this->call('DbServerSeeder'); } } diff --git a/database/seeds/DbServerSeeder.php b/database/seeds/DbServerSeeder.php new file mode 100644 index 000000000000..91b1e9f7bd0e --- /dev/null +++ b/database/seeds/DbServerSeeder.php @@ -0,0 +1,26 @@ + 'db-ninja-1'], + ['name' => 'db-ninja-2'], + ]; + + foreach ($servers as $server) { + $record = DbServer::where('name', '=', $server['name'])->first(); + + if ($record) { + // do nothing + } else { + DbServer::create($server); + } + } + } +} diff --git a/database/seeds/LanguageSeeder.php b/database/seeds/LanguageSeeder.php index 641445aa314b..1bf2a75ea28b 100644 --- a/database/seeds/LanguageSeeder.php +++ b/database/seeds/LanguageSeeder.php @@ -16,7 +16,7 @@ class LanguageSeeder extends Seeder ['name' => 'Italian', 'locale' => 'it'], ['name' => 'German', 'locale' => 'de'], ['name' => 'French', 'locale' => 'fr'], - ['name' => 'Brazilian Portuguese', 'locale' => 'pt_BR'], + ['name' => 'Portuguese - Brazilian', 'locale' => 'pt_BR'], ['name' => 'Dutch', 'locale' => 'nl'], ['name' => 'Spanish', 'locale' => 'es'], ['name' => 'Norwegian', 'locale' => 'nb_NO'], @@ -32,6 +32,8 @@ class LanguageSeeder extends Seeder ['name' => 'Albanian', 'locale' => 'sq'], ['name' => 'Greek', 'locale' => 'el'], ['name' => 'English - United Kingdom', 'locale' => 'en_UK'], + ['name' => 'Portuguese - Portugal', 'locale' => 'pt_PT'], + ['name' => 'Slovenian', 'locale' => 'sl'], ]; foreach ($languages as $language) { diff --git a/database/seeds/UpdateSeeder.php b/database/seeds/UpdateSeeder.php index 0d494cdfc688..a3326484ed35 100644 --- a/database/seeds/UpdateSeeder.php +++ b/database/seeds/UpdateSeeder.php @@ -25,7 +25,8 @@ class UpdateSeeder extends Seeder $this->call('LanguageSeeder'); $this->call('IndustrySeeder'); $this->call('FrequencySeeder'); - + $this->call('DbServerSeeder'); + Cache::flush(); } } diff --git a/database/seeds/UserTableSeeder.php b/database/seeds/UserTableSeeder.php index d03f0ecccace..4f769b5ec24c 100644 --- a/database/seeds/UserTableSeeder.php +++ b/database/seeds/UserTableSeeder.php @@ -85,6 +85,7 @@ class UserTableSeeder extends Seeder 'email' => env('TEST_EMAIL', TEST_USERNAME), 'is_primary' => true, 'send_invoice' => true, + 'contact_key' => strtolower(str_random(RANDOM_KEY_LENGTH)), ]); Product::create([ diff --git a/database/setup.sql b/database/setup.sql index 9f042032fd5a..53ddcc66011f 100644 --- a/database/setup.sql +++ b/database/setup.sql @@ -835,7 +835,7 @@ CREATE TABLE `currencies` ( `code` varchar(255) COLLATE utf8_unicode_ci NOT NULL, `swap_currency_symbol` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=64 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +) ENGINE=InnoDB AUTO_INCREMENT=65 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -844,7 +844,7 @@ CREATE TABLE `currencies` ( LOCK TABLES `currencies` WRITE; /*!40000 ALTER TABLE `currencies` DISABLE KEYS */; -INSERT INTO `currencies` VALUES (1,'US Dollar','$','2',',','.','USD',0),(2,'British Pound','£','2',',','.','GBP',0),(3,'Euro','€','2','.',',','EUR',0),(4,'South African Rand','R','2','.',',','ZAR',0),(5,'Danish Krone','kr','2','.',',','DKK',1),(6,'Israeli Shekel','NIS ','2',',','.','ILS',0),(7,'Swedish Krona','kr','2','.',',','SEK',1),(8,'Kenyan Shilling','KSh ','2',',','.','KES',0),(9,'Canadian Dollar','C$','2',',','.','CAD',0),(10,'Philippine Peso','P ','2',',','.','PHP',0),(11,'Indian Rupee','Rs. ','2',',','.','INR',0),(12,'Australian Dollar','$','2',',','.','AUD',0),(13,'Singapore Dollar','','2',',','.','SGD',0),(14,'Norske Kroner','kr','2','.',',','NOK',1),(15,'New Zealand Dollar','$','2',',','.','NZD',0),(16,'Vietnamese Dong','','0','.',',','VND',0),(17,'Swiss Franc','','2','\'','.','CHF',0),(18,'Guatemalan Quetzal','Q','2',',','.','GTQ',0),(19,'Malaysian Ringgit','RM','2',',','.','MYR',0),(20,'Brazilian Real','R$','2','.',',','BRL',0),(21,'Thai Baht','','2',',','.','THB',0),(22,'Nigerian Naira','','2',',','.','NGN',0),(23,'Argentine Peso','$','2','.',',','ARS',0),(24,'Bangladeshi Taka','Tk','2',',','.','BDT',0),(25,'United Arab Emirates Dirham','DH ','2',',','.','AED',0),(26,'Hong Kong Dollar','','2',',','.','HKD',0),(27,'Indonesian Rupiah','Rp','2',',','.','IDR',0),(28,'Mexican Peso','$','2',',','.','MXN',0),(29,'Egyptian Pound','E£','2',',','.','EGP',0),(30,'Colombian Peso','$','2','.',',','COP',0),(31,'West African Franc','CFA ','2',',','.','XOF',0),(32,'Chinese Renminbi','RMB ','2',',','.','CNY',0),(33,'Rwandan Franc','RF ','2',',','.','RWF',0),(34,'Tanzanian Shilling','TSh ','2',',','.','TZS',0),(35,'Netherlands Antillean Guilder','','2','.',',','ANG',0),(36,'Trinidad and Tobago Dollar','TT$','2',',','.','TTD',0),(37,'East Caribbean Dollar','EC$','2',',','.','XCD',0),(38,'Ghanaian Cedi','','2',',','.','GHS',0),(39,'Bulgarian Lev','','2',' ','.','BGN',0),(40,'Aruban Florin','Afl. ','2',' ','.','AWG',0),(41,'Turkish Lira','TL ','2','.',',','TRY',0),(42,'Romanian New Leu','','2',',','.','RON',0),(43,'Croatian Kuna','kn','2','.',',','HRK',0),(44,'Saudi Riyal','','2',',','.','SAR',0),(45,'Japanese Yen','¥','0',',','.','JPY',0),(46,'Maldivian Rufiyaa','','2',',','.','MVR',0),(47,'Costa Rican Colón','','2',',','.','CRC',0),(48,'Pakistani Rupee','Rs ','0',',','.','PKR',0),(49,'Polish Zloty','zł','2',' ',',','PLN',1),(50,'Sri Lankan Rupee','LKR','2',',','.','LKR',1),(51,'Czech Koruna','Kč','2',' ',',','CZK',1),(52,'Uruguayan Peso','$','2','.',',','UYU',0),(53,'Namibian Dollar','$','2',',','.','NAD',0),(54,'Tunisian Dinar','','2',',','.','TND',0),(55,'Russian Ruble','','2',',','.','RUB',0),(56,'Mozambican Metical','MT','2','.',',','MZN',1),(57,'Omani Rial','','2',',','.','OMR',0),(58,'Ukrainian Hryvnia','','2',',','.','UAH',0),(59,'Macanese Pataca','MOP$','2',',','.','MOP',0),(60,'Taiwan New Dollar','NT$','2',',','.','TWD',0),(61,'Dominican Peso','RD$','2',',','.','DOP',0),(62,'Chilean Peso','$','2','.',',','CLP',0),(63,'Icelandic Króna','kr','2','.',',','ISK',1); +INSERT INTO `currencies` VALUES (1,'US Dollar','$','2',',','.','USD',0),(2,'British Pound','£','2',',','.','GBP',0),(3,'Euro','€','2','.',',','EUR',0),(4,'South African Rand','R','2','.',',','ZAR',0),(5,'Danish Krone','kr','2','.',',','DKK',1),(6,'Israeli Shekel','NIS ','2',',','.','ILS',0),(7,'Swedish Krona','kr','2','.',',','SEK',1),(8,'Kenyan Shilling','KSh ','2',',','.','KES',0),(9,'Canadian Dollar','C$','2',',','.','CAD',0),(10,'Philippine Peso','P ','2',',','.','PHP',0),(11,'Indian Rupee','Rs. ','2',',','.','INR',0),(12,'Australian Dollar','$','2',',','.','AUD',0),(13,'Singapore Dollar','','2',',','.','SGD',0),(14,'Norske Kroner','kr','2','.',',','NOK',1),(15,'New Zealand Dollar','$','2',',','.','NZD',0),(16,'Vietnamese Dong','','0','.',',','VND',0),(17,'Swiss Franc','','2','\'','.','CHF',0),(18,'Guatemalan Quetzal','Q','2',',','.','GTQ',0),(19,'Malaysian Ringgit','RM','2',',','.','MYR',0),(20,'Brazilian Real','R$','2','.',',','BRL',0),(21,'Thai Baht','','2',',','.','THB',0),(22,'Nigerian Naira','','2',',','.','NGN',0),(23,'Argentine Peso','$','2','.',',','ARS',0),(24,'Bangladeshi Taka','Tk','2',',','.','BDT',0),(25,'United Arab Emirates Dirham','DH ','2',',','.','AED',0),(26,'Hong Kong Dollar','','2',',','.','HKD',0),(27,'Indonesian Rupiah','Rp','2',',','.','IDR',0),(28,'Mexican Peso','$','2',',','.','MXN',0),(29,'Egyptian Pound','E£','2',',','.','EGP',0),(30,'Colombian Peso','$','2','.',',','COP',0),(31,'West African Franc','CFA ','2',',','.','XOF',0),(32,'Chinese Renminbi','RMB ','2',',','.','CNY',0),(33,'Rwandan Franc','RF ','2',',','.','RWF',0),(34,'Tanzanian Shilling','TSh ','2',',','.','TZS',0),(35,'Netherlands Antillean Guilder','','2','.',',','ANG',0),(36,'Trinidad and Tobago Dollar','TT$','2',',','.','TTD',0),(37,'East Caribbean Dollar','EC$','2',',','.','XCD',0),(38,'Ghanaian Cedi','','2',',','.','GHS',0),(39,'Bulgarian Lev','','2',' ','.','BGN',0),(40,'Aruban Florin','Afl. ','2',' ','.','AWG',0),(41,'Turkish Lira','TL ','2','.',',','TRY',0),(42,'Romanian New Leu','','2',',','.','RON',0),(43,'Croatian Kuna','kn','2','.',',','HRK',0),(44,'Saudi Riyal','','2',',','.','SAR',0),(45,'Japanese Yen','¥','0',',','.','JPY',0),(46,'Maldivian Rufiyaa','','2',',','.','MVR',0),(47,'Costa Rican Colón','','2',',','.','CRC',0),(48,'Pakistani Rupee','Rs ','0',',','.','PKR',0),(49,'Polish Zloty','zł','2',' ',',','PLN',1),(50,'Sri Lankan Rupee','LKR','2',',','.','LKR',1),(51,'Czech Koruna','Kč','2',' ',',','CZK',1),(52,'Uruguayan Peso','$','2','.',',','UYU',0),(53,'Namibian Dollar','$','2',',','.','NAD',0),(54,'Tunisian Dinar','','2',',','.','TND',0),(55,'Russian Ruble','','2',',','.','RUB',0),(56,'Mozambican Metical','MT','2','.',',','MZN',1),(57,'Omani Rial','','2',',','.','OMR',0),(58,'Ukrainian Hryvnia','','2',',','.','UAH',0),(59,'Macanese Pataca','MOP$','2',',','.','MOP',0),(60,'Taiwan New Dollar','NT$','2',',','.','TWD',0),(61,'Dominican Peso','RD$','2',',','.','DOP',0),(62,'Chilean Peso','$','2','.',',','CLP',0),(63,'Icelandic Króna','kr','2','.',',','ISK',1),(64,'Papua New Guinean Kina','K','2',',','.','PGK',0); /*!40000 ALTER TABLE `currencies` ENABLE KEYS */; UNLOCK TABLES; @@ -910,7 +910,7 @@ CREATE TABLE `db_servers` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL, PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -919,6 +919,7 @@ CREATE TABLE `db_servers` ( LOCK TABLES `db_servers` WRITE; /*!40000 ALTER TABLE `db_servers` DISABLE KEYS */; +INSERT INTO `db_servers` VALUES (1,'db-ninja-1'),(2,'db-ninja-2'); /*!40000 ALTER TABLE `db_servers` ENABLE KEYS */; UNLOCK TABLES; @@ -1209,7 +1210,7 @@ CREATE TABLE `gateways` ( LOCK TABLES `gateways` WRITE; /*!40000 ALTER TABLE `gateways` DISABLE KEYS */; -INSERT INTO `gateways` VALUES (1,'2017-04-30 10:36:46','2017-04-30 10:36:46','Authorize.Net AIM','AuthorizeNet_AIM',1,1,4,0,NULL,0,0),(2,'2017-04-30 10:36:46','2017-04-30 10:36:46','Authorize.Net SIM','AuthorizeNet_SIM',1,2,10000,0,NULL,0,0),(3,'2017-04-30 10:36:46','2017-04-30 10:36:46','CardSave','CardSave',1,1,10000,0,NULL,0,0),(4,'2017-04-30 10:36:46','2017-04-30 10:36:46','Eway Rapid','Eway_RapidShared',1,1,10000,0,NULL,1,0),(5,'2017-04-30 10:36:46','2017-04-30 10:36:46','FirstData Connect','FirstData_Connect',1,1,10000,0,NULL,0,0),(6,'2017-04-30 10:36:46','2017-04-30 10:36:46','GoCardless','GoCardless',1,1,10000,0,NULL,1,0),(7,'2017-04-30 10:36:46','2017-04-30 10:36:46','Migs ThreeParty','Migs_ThreeParty',1,1,10000,0,NULL,0,0),(8,'2017-04-30 10:36:46','2017-04-30 10:36:46','Migs TwoParty','Migs_TwoParty',1,1,10000,0,NULL,0,0),(9,'2017-04-30 10:36:46','2017-04-30 10:36:46','Mollie','Mollie',1,1,7,0,NULL,1,0),(10,'2017-04-30 10:36:46','2017-04-30 10:36:46','MultiSafepay','MultiSafepay',1,1,10000,0,NULL,0,0),(11,'2017-04-30 10:36:46','2017-04-30 10:36:46','Netaxept','Netaxept',1,1,10000,0,NULL,0,0),(12,'2017-04-30 10:36:46','2017-04-30 10:36:46','NetBanx','NetBanx',1,1,10000,0,NULL,0,0),(13,'2017-04-30 10:36:46','2017-04-30 10:36:46','PayFast','PayFast',1,1,10000,0,NULL,1,0),(14,'2017-04-30 10:36:46','2017-04-30 10:36:46','Payflow Pro','Payflow_Pro',1,1,10000,0,NULL,0,0),(15,'2017-04-30 10:36:46','2017-04-30 10:36:46','PaymentExpress PxPay','PaymentExpress_PxPay',1,1,10000,0,NULL,0,0),(16,'2017-04-30 10:36:46','2017-04-30 10:36:46','PaymentExpress PxPost','PaymentExpress_PxPost',1,1,10000,0,NULL,0,0),(17,'2017-04-30 10:36:46','2017-04-30 10:36:46','PayPal Express','PayPal_Express',1,1,3,0,NULL,1,0),(18,'2017-04-30 10:36:46','2017-04-30 10:36:46','PayPal Pro','PayPal_Pro',1,1,10000,0,NULL,0,0),(19,'2017-04-30 10:36:46','2017-04-30 10:36:46','Pin','Pin',1,1,10000,0,NULL,0,0),(20,'2017-04-30 10:36:46','2017-04-30 10:36:46','SagePay Direct','SagePay_Direct',1,1,10000,0,NULL,0,0),(21,'2017-04-30 10:36:46','2017-04-30 10:36:46','SagePay Server','SagePay_Server',1,1,10000,0,NULL,0,0),(22,'2017-04-30 10:36:46','2017-04-30 10:36:46','SecurePay DirectPost','SecurePay_DirectPost',1,1,10000,0,NULL,0,0),(23,'2017-04-30 10:36:47','2017-04-30 10:36:47','Stripe','Stripe',1,1,1,0,NULL,0,0),(24,'2017-04-30 10:36:47','2017-04-30 10:36:47','TargetPay Direct eBanking','TargetPay_Directebanking',1,1,10000,0,NULL,0,0),(25,'2017-04-30 10:36:47','2017-04-30 10:36:47','TargetPay Ideal','TargetPay_Ideal',1,1,10000,0,NULL,0,0),(26,'2017-04-30 10:36:47','2017-04-30 10:36:47','TargetPay Mr Cash','TargetPay_Mrcash',1,1,10000,0,NULL,0,0),(27,'2017-04-30 10:36:47','2017-04-30 10:36:47','TwoCheckout','TwoCheckout',1,1,10000,0,NULL,1,0),(28,'2017-04-30 10:36:47','2017-04-30 10:36:47','WorldPay','WorldPay',1,1,10000,0,NULL,0,0),(29,'2017-04-30 10:36:47','2017-04-30 10:36:47','BeanStream','BeanStream',1,2,10000,0,NULL,0,0),(30,'2017-04-30 10:36:47','2017-04-30 10:36:47','Psigate','Psigate',1,2,10000,0,NULL,0,0),(31,'2017-04-30 10:36:47','2017-04-30 10:36:47','moolah','AuthorizeNet_AIM',1,1,10000,0,NULL,0,0),(32,'2017-04-30 10:36:47','2017-04-30 10:36:47','Alipay','Alipay_Express',1,1,10000,0,NULL,0,0),(33,'2017-04-30 10:36:47','2017-04-30 10:36:47','Buckaroo','Buckaroo_CreditCard',1,1,10000,0,NULL,0,0),(34,'2017-04-30 10:36:47','2017-04-30 10:36:47','Coinbase','Coinbase',1,1,10000,0,NULL,0,0),(35,'2017-04-30 10:36:47','2017-04-30 10:36:47','DataCash','DataCash',1,1,10000,0,NULL,0,0),(36,'2017-04-30 10:36:47','2017-04-30 10:36:47','Neteller','Neteller',1,2,10000,0,NULL,0,0),(37,'2017-04-30 10:36:47','2017-04-30 10:36:47','Pacnet','Pacnet',1,1,10000,0,NULL,0,0),(38,'2017-04-30 10:36:47','2017-04-30 10:36:47','PaymentSense','PaymentSense',1,2,10000,0,NULL,0,0),(39,'2017-04-30 10:36:47','2017-04-30 10:36:47','Realex','Realex_Remote',1,1,10000,0,NULL,0,0),(40,'2017-04-30 10:36:47','2017-04-30 10:36:47','Sisow','Sisow',1,1,10000,0,NULL,0,0),(41,'2017-04-30 10:36:47','2017-04-30 10:36:47','Skrill','Skrill',1,1,10000,0,NULL,1,0),(42,'2017-04-30 10:36:47','2017-04-30 10:36:47','BitPay','BitPay',1,1,6,0,NULL,1,0),(43,'2017-04-30 10:36:47','2017-04-30 10:36:47','Dwolla','Dwolla',1,1,5,0,NULL,1,0),(44,'2017-04-30 10:36:47','2017-04-30 10:36:47','AGMS','Agms',1,1,10000,0,NULL,0,0),(45,'2017-04-30 10:36:47','2017-04-30 10:36:47','Barclays','BarclaysEpdq\\Essential',1,1,10000,0,NULL,0,0),(46,'2017-04-30 10:36:47','2017-04-30 10:36:47','Cardgate','Cardgate',1,1,10000,0,NULL,0,0),(47,'2017-04-30 10:36:47','2017-04-30 10:36:47','Checkout.com','CheckoutCom',1,1,10000,0,NULL,0,0),(48,'2017-04-30 10:36:47','2017-04-30 10:36:47','Creditcall','Creditcall',1,1,10000,0,NULL,0,0),(49,'2017-04-30 10:36:47','2017-04-30 10:36:47','Cybersource','Cybersource',1,1,10000,0,NULL,0,0),(50,'2017-04-30 10:36:47','2017-04-30 10:36:47','ecoPayz','Ecopayz',1,1,10000,0,NULL,0,0),(51,'2017-04-30 10:36:47','2017-04-30 10:36:47','Fasapay','Fasapay',1,1,10000,0,NULL,0,0),(52,'2017-04-30 10:36:47','2017-04-30 10:36:47','Komoju','Komoju',1,1,10000,0,NULL,0,0),(53,'2017-04-30 10:36:47','2017-04-30 10:36:47','Multicards','Multicards',1,1,10000,0,NULL,0,0),(54,'2017-04-30 10:36:47','2017-04-30 10:36:47','Pagar.Me','Pagarme',1,2,10000,0,NULL,0,0),(55,'2017-04-30 10:36:47','2017-04-30 10:36:47','Paysafecard','Paysafecard',1,1,10000,0,NULL,0,0),(56,'2017-04-30 10:36:47','2017-04-30 10:36:47','Paytrace','Paytrace_CreditCard',1,1,10000,0,NULL,0,0),(57,'2017-04-30 10:36:47','2017-04-30 10:36:47','Secure Trading','SecureTrading',1,1,10000,0,NULL,0,0),(58,'2017-04-30 10:36:47','2017-04-30 10:36:47','SecPay','SecPay',1,1,10000,0,NULL,0,0),(59,'2017-04-30 10:36:47','2017-04-30 10:36:47','WeChat Express','WeChat_Express',1,2,10000,0,NULL,0,0),(60,'2017-04-30 10:36:47','2017-04-30 10:36:47','WePay','WePay',1,1,10000,0,NULL,0,0),(61,'2017-04-30 10:36:47','2017-04-30 10:36:47','Braintree','Braintree',1,1,2,0,NULL,0,0),(62,'2017-04-30 10:36:47','2017-04-30 10:36:47','Custom','Custom',1,1,8,0,NULL,1,0); +INSERT INTO `gateways` VALUES (1,'2017-05-08 08:16:15','2017-05-08 08:16:15','Authorize.Net AIM','AuthorizeNet_AIM',1,1,4,0,NULL,0,0),(2,'2017-05-08 08:16:15','2017-05-08 08:16:15','Authorize.Net SIM','AuthorizeNet_SIM',1,2,10000,0,NULL,0,0),(3,'2017-05-08 08:16:15','2017-05-08 08:16:15','CardSave','CardSave',1,1,10000,0,NULL,0,0),(4,'2017-05-08 08:16:15','2017-05-08 08:16:15','Eway Rapid','Eway_RapidShared',1,1,10000,0,NULL,1,0),(5,'2017-05-08 08:16:15','2017-05-08 08:16:15','FirstData Connect','FirstData_Connect',1,1,10000,0,NULL,0,0),(6,'2017-05-08 08:16:15','2017-05-08 08:16:15','GoCardless','GoCardless',1,1,10000,0,NULL,1,0),(7,'2017-05-08 08:16:15','2017-05-08 08:16:15','Migs ThreeParty','Migs_ThreeParty',1,1,10000,0,NULL,0,0),(8,'2017-05-08 08:16:15','2017-05-08 08:16:15','Migs TwoParty','Migs_TwoParty',1,1,10000,0,NULL,0,0),(9,'2017-05-08 08:16:15','2017-05-08 08:16:15','Mollie','Mollie',1,1,7,0,NULL,1,0),(10,'2017-05-08 08:16:15','2017-05-08 08:16:15','MultiSafepay','MultiSafepay',1,1,10000,0,NULL,0,0),(11,'2017-05-08 08:16:15','2017-05-08 08:16:15','Netaxept','Netaxept',1,1,10000,0,NULL,0,0),(12,'2017-05-08 08:16:15','2017-05-08 08:16:15','NetBanx','NetBanx',1,1,10000,0,NULL,0,0),(13,'2017-05-08 08:16:15','2017-05-08 08:16:15','PayFast','PayFast',1,1,10000,0,NULL,1,0),(14,'2017-05-08 08:16:15','2017-05-08 08:16:15','Payflow Pro','Payflow_Pro',1,1,10000,0,NULL,0,0),(15,'2017-05-08 08:16:15','2017-05-08 08:16:15','PaymentExpress PxPay','PaymentExpress_PxPay',1,1,10000,0,NULL,0,0),(16,'2017-05-08 08:16:15','2017-05-08 08:16:15','PaymentExpress PxPost','PaymentExpress_PxPost',1,1,10000,0,NULL,0,0),(17,'2017-05-08 08:16:15','2017-05-08 08:16:15','PayPal Express','PayPal_Express',1,1,3,0,NULL,1,0),(18,'2017-05-08 08:16:15','2017-05-08 08:16:15','PayPal Pro','PayPal_Pro',1,1,10000,0,NULL,0,0),(19,'2017-05-08 08:16:15','2017-05-08 08:16:15','Pin','Pin',1,1,10000,0,NULL,0,0),(20,'2017-05-08 08:16:15','2017-05-08 08:16:15','SagePay Direct','SagePay_Direct',1,1,10000,0,NULL,0,0),(21,'2017-05-08 08:16:15','2017-05-08 08:16:15','SagePay Server','SagePay_Server',1,1,10000,0,NULL,0,0),(22,'2017-05-08 08:16:15','2017-05-08 08:16:15','SecurePay DirectPost','SecurePay_DirectPost',1,1,10000,0,NULL,0,0),(23,'2017-05-08 08:16:15','2017-05-08 08:16:15','Stripe','Stripe',1,1,1,0,NULL,0,0),(24,'2017-05-08 08:16:15','2017-05-08 08:16:15','TargetPay Direct eBanking','TargetPay_Directebanking',1,1,10000,0,NULL,0,0),(25,'2017-05-08 08:16:15','2017-05-08 08:16:15','TargetPay Ideal','TargetPay_Ideal',1,1,10000,0,NULL,0,0),(26,'2017-05-08 08:16:15','2017-05-08 08:16:15','TargetPay Mr Cash','TargetPay_Mrcash',1,1,10000,0,NULL,0,0),(27,'2017-05-08 08:16:15','2017-05-08 08:16:15','TwoCheckout','TwoCheckout',1,1,10000,0,NULL,1,0),(28,'2017-05-08 08:16:15','2017-05-08 08:16:15','WorldPay','WorldPay',1,1,10000,0,NULL,0,0),(29,'2017-05-08 08:16:15','2017-05-08 08:16:15','BeanStream','BeanStream',1,2,10000,0,NULL,0,0),(30,'2017-05-08 08:16:15','2017-05-08 08:16:15','Psigate','Psigate',1,2,10000,0,NULL,0,0),(31,'2017-05-08 08:16:15','2017-05-08 08:16:15','moolah','AuthorizeNet_AIM',1,1,10000,0,NULL,0,0),(32,'2017-05-08 08:16:15','2017-05-08 08:16:15','Alipay','Alipay_Express',1,1,10000,0,NULL,0,0),(33,'2017-05-08 08:16:15','2017-05-08 08:16:15','Buckaroo','Buckaroo_CreditCard',1,1,10000,0,NULL,0,0),(34,'2017-05-08 08:16:15','2017-05-08 08:16:15','Coinbase','Coinbase',1,1,10000,0,NULL,0,0),(35,'2017-05-08 08:16:15','2017-05-08 08:16:15','DataCash','DataCash',1,1,10000,0,NULL,0,0),(36,'2017-05-08 08:16:15','2017-05-08 08:16:15','Neteller','Neteller',1,2,10000,0,NULL,0,0),(37,'2017-05-08 08:16:15','2017-05-08 08:16:15','Pacnet','Pacnet',1,1,10000,0,NULL,0,0),(38,'2017-05-08 08:16:15','2017-05-08 08:16:15','PaymentSense','PaymentSense',1,2,10000,0,NULL,0,0),(39,'2017-05-08 08:16:15','2017-05-08 08:16:15','Realex','Realex_Remote',1,1,10000,0,NULL,0,0),(40,'2017-05-08 08:16:15','2017-05-08 08:16:15','Sisow','Sisow',1,1,10000,0,NULL,0,0),(41,'2017-05-08 08:16:15','2017-05-08 08:16:15','Skrill','Skrill',1,1,10000,0,NULL,1,0),(42,'2017-05-08 08:16:15','2017-05-08 08:16:15','BitPay','BitPay',1,1,6,0,NULL,1,0),(43,'2017-05-08 08:16:15','2017-05-08 08:16:15','Dwolla','Dwolla',1,1,5,0,NULL,1,0),(44,'2017-05-08 08:16:15','2017-05-08 08:16:15','AGMS','Agms',1,1,10000,0,NULL,0,0),(45,'2017-05-08 08:16:15','2017-05-08 08:16:15','Barclays','BarclaysEpdq\\Essential',1,1,10000,0,NULL,0,0),(46,'2017-05-08 08:16:15','2017-05-08 08:16:15','Cardgate','Cardgate',1,1,10000,0,NULL,0,0),(47,'2017-05-08 08:16:15','2017-05-08 08:16:15','Checkout.com','CheckoutCom',1,1,10000,0,NULL,0,0),(48,'2017-05-08 08:16:15','2017-05-08 08:16:15','Creditcall','Creditcall',1,1,10000,0,NULL,0,0),(49,'2017-05-08 08:16:15','2017-05-08 08:16:15','Cybersource','Cybersource',1,1,10000,0,NULL,0,0),(50,'2017-05-08 08:16:15','2017-05-08 08:16:15','ecoPayz','Ecopayz',1,1,10000,0,NULL,0,0),(51,'2017-05-08 08:16:15','2017-05-08 08:16:15','Fasapay','Fasapay',1,1,10000,0,NULL,0,0),(52,'2017-05-08 08:16:15','2017-05-08 08:16:15','Komoju','Komoju',1,1,10000,0,NULL,0,0),(53,'2017-05-08 08:16:15','2017-05-08 08:16:15','Multicards','Multicards',1,1,10000,0,NULL,0,0),(54,'2017-05-08 08:16:15','2017-05-08 08:16:15','Pagar.Me','Pagarme',1,2,10000,0,NULL,0,0),(55,'2017-05-08 08:16:15','2017-05-08 08:16:15','Paysafecard','Paysafecard',1,1,10000,0,NULL,0,0),(56,'2017-05-08 08:16:15','2017-05-08 08:16:15','Paytrace','Paytrace_CreditCard',1,1,10000,0,NULL,0,0),(57,'2017-05-08 08:16:15','2017-05-08 08:16:15','Secure Trading','SecureTrading',1,1,10000,0,NULL,0,0),(58,'2017-05-08 08:16:15','2017-05-08 08:16:15','SecPay','SecPay',1,1,10000,0,NULL,0,0),(59,'2017-05-08 08:16:15','2017-05-08 08:16:15','WeChat Express','WeChat_Express',1,2,10000,0,NULL,0,0),(60,'2017-05-08 08:16:15','2017-05-08 08:16:15','WePay','WePay',1,1,10000,0,NULL,0,0),(61,'2017-05-08 08:16:15','2017-05-08 08:16:15','Braintree','Braintree',1,1,2,0,NULL,0,0),(62,'2017-05-08 08:16:15','2017-05-08 08:16:15','Custom','Custom',1,1,8,0,NULL,1,0); /*!40000 ALTER TABLE `gateways` ENABLE KEYS */; UNLOCK TABLES; @@ -1508,7 +1509,7 @@ CREATE TABLE `languages` ( `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL, `locale` varchar(255) COLLATE utf8_unicode_ci NOT NULL, PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -1517,7 +1518,7 @@ CREATE TABLE `languages` ( LOCK TABLES `languages` WRITE; /*!40000 ALTER TABLE `languages` DISABLE KEYS */; -INSERT INTO `languages` VALUES (1,'English','en'),(2,'Italian','it'),(3,'German','de'),(4,'French','fr'),(5,'Brazilian Portuguese','pt_BR'),(6,'Dutch','nl'),(7,'Spanish','es'),(8,'Norwegian','nb_NO'),(9,'Danish','da'),(10,'Japanese','ja'),(11,'Swedish','sv'),(12,'Spanish - Spain','es_ES'),(13,'French - Canada','fr_CA'),(14,'Lithuanian','lt'),(15,'Polish','pl'),(16,'Czech','cs'),(17,'Croatian','hr'),(18,'Albanian','sq'),(19,'Greek','el'),(20,'English - United Kingdom','en_UK'); +INSERT INTO `languages` VALUES (1,'English','en'),(2,'Italian','it'),(3,'German','de'),(4,'French','fr'),(5,'Portuguese - Brazilian','pt_BR'),(6,'Dutch','nl'),(7,'Spanish','es'),(8,'Norwegian','nb_NO'),(9,'Danish','da'),(10,'Japanese','ja'),(11,'Swedish','sv'),(12,'Spanish - Spain','es_ES'),(13,'French - Canada','fr_CA'),(14,'Lithuanian','lt'),(15,'Polish','pl'),(16,'Czech','cs'),(17,'Croatian','hr'),(18,'Albanian','sq'),(19,'Greek','el'),(20,'English - United Kingdom','en_UK'),(21,'Portuguese - Portugal','pt_PT'),(22,'Slovenian','sl'); /*!40000 ALTER TABLE `languages` ENABLE KEYS */; UNLOCK TABLES; @@ -1557,6 +1558,33 @@ LOCK TABLES `licenses` WRITE; /*!40000 ALTER TABLE `licenses` ENABLE KEYS */; UNLOCK TABLES; +-- +-- Table structure for table `lookup_account_tokens` +-- + +DROP TABLE IF EXISTS `lookup_account_tokens`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `lookup_account_tokens` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `lookup_account_id` int(10) unsigned NOT NULL, + `token` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `lookup_tokens_token_unique` (`token`), + KEY `lookup_tokens_lookup_account_id_index` (`lookup_account_id`), + CONSTRAINT `lookup_tokens_lookup_account_id_foreign` FOREIGN KEY (`lookup_account_id`) REFERENCES `lookup_accounts` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `lookup_account_tokens` +-- + +LOCK TABLES `lookup_account_tokens` WRITE; +/*!40000 ALTER TABLE `lookup_account_tokens` DISABLE KEYS */; +/*!40000 ALTER TABLE `lookup_account_tokens` ENABLE KEYS */; +UNLOCK TABLES; + -- -- Table structure for table `lookup_accounts` -- @@ -1569,6 +1597,7 @@ CREATE TABLE `lookup_accounts` ( `lookup_company_id` int(10) unsigned NOT NULL, `account_key` varchar(255) COLLATE utf8_unicode_ci NOT NULL, PRIMARY KEY (`id`), + UNIQUE KEY `lookup_accounts_account_key_unique` (`account_key`), KEY `lookup_accounts_lookup_company_id_index` (`lookup_company_id`), CONSTRAINT `lookup_accounts_lookup_company_id_foreign` FOREIGN KEY (`lookup_company_id`) REFERENCES `lookup_companies` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; @@ -1593,8 +1622,10 @@ DROP TABLE IF EXISTS `lookup_companies`; CREATE TABLE `lookup_companies` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `db_server_id` int(10) unsigned NOT NULL, + `company_id` int(10) unsigned NOT NULL, PRIMARY KEY (`id`), - KEY `lookup_companies_db_server_id_foreign` (`db_server_id`), + UNIQUE KEY `lookup_companies_db_server_id_company_id_unique` (`db_server_id`,`company_id`), + KEY `lookup_companies_company_id_index` (`company_id`), CONSTRAINT `lookup_companies_db_server_id_foreign` FOREIGN KEY (`db_server_id`) REFERENCES `db_servers` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; @@ -1620,6 +1651,7 @@ CREATE TABLE `lookup_contacts` ( `lookup_account_id` int(10) unsigned NOT NULL, `contact_key` varchar(255) COLLATE utf8_unicode_ci NOT NULL, PRIMARY KEY (`id`), + UNIQUE KEY `lookup_contacts_contact_key_unique` (`contact_key`), KEY `lookup_contacts_lookup_account_id_index` (`lookup_account_id`), CONSTRAINT `lookup_contacts_lookup_account_id_foreign` FOREIGN KEY (`lookup_account_id`) REFERENCES `lookup_accounts` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; @@ -1645,8 +1677,10 @@ CREATE TABLE `lookup_invitations` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `lookup_account_id` int(10) unsigned NOT NULL, `invitation_key` varchar(255) COLLATE utf8_unicode_ci NOT NULL, - `message_id` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `message_id` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, PRIMARY KEY (`id`), + UNIQUE KEY `lookup_invitations_invitation_key_unique` (`invitation_key`), + UNIQUE KEY `lookup_invitations_message_id_unique` (`message_id`), KEY `lookup_invitations_lookup_account_id_index` (`lookup_account_id`), CONSTRAINT `lookup_invitations_lookup_account_id_foreign` FOREIGN KEY (`lookup_account_id`) REFERENCES `lookup_accounts` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; @@ -1661,32 +1695,6 @@ LOCK TABLES `lookup_invitations` WRITE; /*!40000 ALTER TABLE `lookup_invitations` ENABLE KEYS */; UNLOCK TABLES; --- --- Table structure for table `lookup_tokens` --- - -DROP TABLE IF EXISTS `lookup_tokens`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `lookup_tokens` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `lookup_account_id` int(10) unsigned NOT NULL, - `token` varchar(255) COLLATE utf8_unicode_ci NOT NULL, - PRIMARY KEY (`id`), - KEY `lookup_tokens_lookup_account_id_index` (`lookup_account_id`), - CONSTRAINT `lookup_tokens_lookup_account_id_foreign` FOREIGN KEY (`lookup_account_id`) REFERENCES `lookup_accounts` (`id`) ON DELETE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `lookup_tokens` --- - -LOCK TABLES `lookup_tokens` WRITE; -/*!40000 ALTER TABLE `lookup_tokens` DISABLE KEYS */; -/*!40000 ALTER TABLE `lookup_tokens` ENABLE KEYS */; -UNLOCK TABLES; - -- -- Table structure for table `lookup_users` -- @@ -1697,9 +1705,15 @@ DROP TABLE IF EXISTS `lookup_users`; CREATE TABLE `lookup_users` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `lookup_account_id` int(10) unsigned NOT NULL, - `email` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `email` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, + `confirmation_code` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, + `user_id` int(10) unsigned NOT NULL, PRIMARY KEY (`id`), + UNIQUE KEY `lookup_users_lookup_account_id_user_id_unique` (`lookup_account_id`,`user_id`), + UNIQUE KEY `lookup_users_email_unique` (`email`), + UNIQUE KEY `lookup_users_confirmation_code_unique` (`confirmation_code`), KEY `lookup_users_lookup_account_id_index` (`lookup_account_id`), + KEY `lookup_users_user_id_index` (`user_id`), CONSTRAINT `lookup_users_lookup_account_id_foreign` FOREIGN KEY (`lookup_account_id`) REFERENCES `lookup_accounts` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; @@ -1732,7 +1746,7 @@ CREATE TABLE `migrations` ( LOCK TABLES `migrations` WRITE; /*!40000 ALTER TABLE `migrations` DISABLE KEYS */; -INSERT INTO `migrations` VALUES ('2013_11_05_180133_confide_setup_users_table',1),('2013_11_28_195703_setup_countries_table',1),('2014_02_13_151500_add_cascase_drops',1),('2014_02_19_151817_add_support_for_invoice_designs',1),('2014_03_03_155556_add_phone_to_account',1),('2014_03_19_201454_add_language_support',1),('2014_03_20_200300_create_payment_libraries',1),('2014_03_23_051736_enable_forcing_jspdf',1),('2014_03_25_102200_add_sort_and_recommended_to_gateways',1),('2014_04_03_191105_add_pro_plan',1),('2014_04_17_100523_add_remember_token',1),('2014_04_17_145108_add_custom_fields',1),('2014_04_23_170909_add_products_settings',1),('2014_04_29_174315_add_advanced_settings',1),('2014_05_17_175626_add_quotes',1),('2014_06_17_131940_add_accepted_credit_cards_to_account_gateways',1),('2014_07_13_142654_one_click_install',1),('2014_07_17_205900_support_hiding_quantity',1),('2014_07_24_171214_add_zapier_support',1),('2014_10_01_141248_add_company_vat_number',1),('2014_10_05_141856_track_last_seen_message',1),('2014_10_06_103529_add_timesheets',1),('2014_10_06_195330_add_invoice_design_table',1),('2014_10_13_054100_add_invoice_number_settings',1),('2014_10_14_225227_add_danish_translation',1),('2014_10_22_174452_add_affiliate_price',1),('2014_10_30_184126_add_company_id_number',1),('2014_11_04_200406_allow_null_client_currency',1),('2014_12_03_154119_add_discount_type',1),('2015_02_12_102940_add_email_templates',1),('2015_02_17_131714_support_token_billing',1),('2015_02_27_081836_add_invoice_footer',1),('2015_03_03_140259_add_tokens',1),('2015_03_09_151011_add_ip_to_activity',1),('2015_03_15_174122_add_pdf_email_attachment_option',1),('2015_03_30_100000_create_password_resets_table',1),('2015_04_12_093447_add_sv_language',1),('2015_04_13_100333_add_notify_approved',1),('2015_04_16_122647_add_partial_amount_to_invoices',1),('2015_05_21_184104_add_font_size',1),('2015_05_27_121828_add_tasks',1),('2015_05_27_170808_add_custom_invoice_labels',1),('2015_06_09_134208_add_has_tasks_to_invoices',1),('2015_06_14_093410_enable_resuming_tasks',1),('2015_06_14_173025_multi_company_support',1),('2015_07_07_160257_support_locking_account',1),('2015_07_08_114333_simplify_tasks',1),('2015_07_19_081332_add_custom_design',1),('2015_07_27_183830_add_pdfmake_support',1),('2015_08_13_084041_add_formats_to_datetime_formats_table',1),('2015_09_04_080604_add_swap_postal_code',1),('2015_09_07_135935_add_account_domain',1),('2015_09_10_185135_add_reminder_emails',1),('2015_10_07_135651_add_social_login',1),('2015_10_21_075058_add_default_tax_rates',1),('2015_10_21_185724_add_invoice_number_pattern',1),('2015_10_27_180214_add_is_system_to_activities',1),('2015_10_29_133747_add_default_quote_terms',1),('2015_11_01_080417_encrypt_tokens',1),('2015_11_03_181318_improve_currency_localization',1),('2015_11_30_133206_add_email_designs',1),('2015_12_27_154513_add_reminder_settings',1),('2015_12_30_042035_add_client_view_css',1),('2016_01_04_175228_create_vendors_table',1),('2016_01_06_153144_add_invoice_font_support',1),('2016_01_17_155725_add_quote_to_invoice_option',1),('2016_01_18_195351_add_bank_accounts',1),('2016_01_24_112646_add_bank_subaccounts',1),('2016_01_27_173015_add_header_footer_option',1),('2016_02_01_135956_add_source_currency_to_expenses',1),('2016_02_25_152948_add_client_password',1),('2016_02_28_081424_add_custom_invoice_fields',1),('2016_03_14_066181_add_user_permissions',1),('2016_03_14_214710_add_support_three_decimal_taxes',1),('2016_03_22_168362_add_documents',1),('2016_03_23_215049_support_multiple_tax_rates',1),('2016_04_16_103943_enterprise_plan',1),('2016_04_18_174135_add_page_size',1),('2016_04_23_182223_payments_changes',1),('2016_05_16_102925_add_swap_currency_symbol_to_currency',1),('2016_05_18_085739_add_invoice_type_support',1),('2016_05_24_164847_wepay_ach',1),('2016_07_08_083802_support_new_pricing',1),('2016_07_13_083821_add_buy_now_buttons',1),('2016_08_10_184027_add_support_for_bots',1),('2016_09_05_150625_create_gateway_types',1),('2016_10_20_191150_add_expense_to_activities',1),('2016_11_03_113316_add_invoice_signature',1),('2016_11_03_161149_add_bluevine_fields',1),('2016_11_28_092904_add_task_projects',1),('2016_12_13_113955_add_pro_plan_discount',1),('2017_01_01_214241_add_inclusive_taxes',1),('2017_02_23_095934_add_custom_product_fields',1),('2017_03_16_085702_add_gateway_fee_location',1),('2017_04_16_101744_add_custom_contact_fields',1); +INSERT INTO `migrations` VALUES ('2013_11_05_180133_confide_setup_users_table',1),('2013_11_28_195703_setup_countries_table',1),('2014_02_13_151500_add_cascase_drops',1),('2014_02_19_151817_add_support_for_invoice_designs',1),('2014_03_03_155556_add_phone_to_account',1),('2014_03_19_201454_add_language_support',1),('2014_03_20_200300_create_payment_libraries',1),('2014_03_23_051736_enable_forcing_jspdf',1),('2014_03_25_102200_add_sort_and_recommended_to_gateways',1),('2014_04_03_191105_add_pro_plan',1),('2014_04_17_100523_add_remember_token',1),('2014_04_17_145108_add_custom_fields',1),('2014_04_23_170909_add_products_settings',1),('2014_04_29_174315_add_advanced_settings',1),('2014_05_17_175626_add_quotes',1),('2014_06_17_131940_add_accepted_credit_cards_to_account_gateways',1),('2014_07_13_142654_one_click_install',1),('2014_07_17_205900_support_hiding_quantity',1),('2014_07_24_171214_add_zapier_support',1),('2014_10_01_141248_add_company_vat_number',1),('2014_10_05_141856_track_last_seen_message',1),('2014_10_06_103529_add_timesheets',1),('2014_10_06_195330_add_invoice_design_table',1),('2014_10_13_054100_add_invoice_number_settings',1),('2014_10_14_225227_add_danish_translation',1),('2014_10_22_174452_add_affiliate_price',1),('2014_10_30_184126_add_company_id_number',1),('2014_11_04_200406_allow_null_client_currency',1),('2014_12_03_154119_add_discount_type',1),('2015_02_12_102940_add_email_templates',1),('2015_02_17_131714_support_token_billing',1),('2015_02_27_081836_add_invoice_footer',1),('2015_03_03_140259_add_tokens',1),('2015_03_09_151011_add_ip_to_activity',1),('2015_03_15_174122_add_pdf_email_attachment_option',1),('2015_03_30_100000_create_password_resets_table',1),('2015_04_12_093447_add_sv_language',1),('2015_04_13_100333_add_notify_approved',1),('2015_04_16_122647_add_partial_amount_to_invoices',1),('2015_05_21_184104_add_font_size',1),('2015_05_27_121828_add_tasks',1),('2015_05_27_170808_add_custom_invoice_labels',1),('2015_06_09_134208_add_has_tasks_to_invoices',1),('2015_06_14_093410_enable_resuming_tasks',1),('2015_06_14_173025_multi_company_support',1),('2015_07_07_160257_support_locking_account',1),('2015_07_08_114333_simplify_tasks',1),('2015_07_19_081332_add_custom_design',1),('2015_07_27_183830_add_pdfmake_support',1),('2015_08_13_084041_add_formats_to_datetime_formats_table',1),('2015_09_04_080604_add_swap_postal_code',1),('2015_09_07_135935_add_account_domain',1),('2015_09_10_185135_add_reminder_emails',1),('2015_10_07_135651_add_social_login',1),('2015_10_21_075058_add_default_tax_rates',1),('2015_10_21_185724_add_invoice_number_pattern',1),('2015_10_27_180214_add_is_system_to_activities',1),('2015_10_29_133747_add_default_quote_terms',1),('2015_11_01_080417_encrypt_tokens',1),('2015_11_03_181318_improve_currency_localization',1),('2015_11_30_133206_add_email_designs',1),('2015_12_27_154513_add_reminder_settings',1),('2015_12_30_042035_add_client_view_css',1),('2016_01_04_175228_create_vendors_table',1),('2016_01_06_153144_add_invoice_font_support',1),('2016_01_17_155725_add_quote_to_invoice_option',1),('2016_01_18_195351_add_bank_accounts',1),('2016_01_24_112646_add_bank_subaccounts',1),('2016_01_27_173015_add_header_footer_option',1),('2016_02_01_135956_add_source_currency_to_expenses',1),('2016_02_25_152948_add_client_password',1),('2016_02_28_081424_add_custom_invoice_fields',1),('2016_03_14_066181_add_user_permissions',1),('2016_03_14_214710_add_support_three_decimal_taxes',1),('2016_03_22_168362_add_documents',1),('2016_03_23_215049_support_multiple_tax_rates',1),('2016_04_16_103943_enterprise_plan',1),('2016_04_18_174135_add_page_size',1),('2016_04_23_182223_payments_changes',1),('2016_05_16_102925_add_swap_currency_symbol_to_currency',1),('2016_05_18_085739_add_invoice_type_support',1),('2016_05_24_164847_wepay_ach',1),('2016_07_08_083802_support_new_pricing',1),('2016_07_13_083821_add_buy_now_buttons',1),('2016_08_10_184027_add_support_for_bots',1),('2016_09_05_150625_create_gateway_types',1),('2016_10_20_191150_add_expense_to_activities',1),('2016_11_03_113316_add_invoice_signature',1),('2016_11_03_161149_add_bluevine_fields',1),('2016_11_28_092904_add_task_projects',1),('2016_12_13_113955_add_pro_plan_discount',1),('2017_01_01_214241_add_inclusive_taxes',1),('2017_02_23_095934_add_custom_product_fields',1),('2017_03_16_085702_add_gateway_fee_location',1),('2017_04_16_101744_add_custom_contact_fields',1),('2017_04_30_174702_add_multiple_database_support',1); /*!40000 ALTER TABLE `migrations` ENABLE KEYS */; UNLOCK TABLES; @@ -1783,7 +1797,7 @@ CREATE TABLE `payment_libraries` ( LOCK TABLES `payment_libraries` WRITE; /*!40000 ALTER TABLE `payment_libraries` DISABLE KEYS */; -INSERT INTO `payment_libraries` VALUES (1,'2017-04-30 10:36:45','2017-04-30 10:36:45','Omnipay',1),(2,'2017-04-30 10:36:45','2017-04-30 10:36:45','PHP-Payments [Deprecated]',1); +INSERT INTO `payment_libraries` VALUES (1,'2017-05-08 08:16:14','2017-05-08 08:16:14','Omnipay',1),(2,'2017-05-08 08:16:14','2017-05-08 08:16:14','PHP-Payments [Deprecated]',1); /*!40000 ALTER TABLE `payment_libraries` ENABLE KEYS */; UNLOCK TABLES; @@ -1893,7 +1907,7 @@ CREATE TABLE `payment_terms` ( LOCK TABLES `payment_terms` WRITE; /*!40000 ALTER TABLE `payment_terms` DISABLE KEYS */; -INSERT INTO `payment_terms` VALUES (1,7,'Net 7','2017-04-30 10:36:45','2017-04-30 10:36:45',NULL,0,0,1),(2,10,'Net 10','2017-04-30 10:36:45','2017-04-30 10:36:45',NULL,0,0,2),(3,14,'Net 14','2017-04-30 10:36:45','2017-04-30 10:36:45',NULL,0,0,3),(4,15,'Net 15','2017-04-30 10:36:45','2017-04-30 10:36:45',NULL,0,0,4),(5,30,'Net 30','2017-04-30 10:36:45','2017-04-30 10:36:45',NULL,0,0,5),(6,60,'Net 60','2017-04-30 10:36:45','2017-04-30 10:36:45',NULL,0,0,6),(7,90,'Net 90','2017-04-30 10:36:45','2017-04-30 10:36:45',NULL,0,0,7),(8,-1,'Net 0','2017-04-30 10:36:48','2017-04-30 10:36:48',NULL,0,0,0); +INSERT INTO `payment_terms` VALUES (1,7,'Net 7','2017-05-08 08:16:14','2017-05-08 08:16:14',NULL,0,0,1),(2,10,'Net 10','2017-05-08 08:16:14','2017-05-08 08:16:14',NULL,0,0,2),(3,14,'Net 14','2017-05-08 08:16:14','2017-05-08 08:16:14',NULL,0,0,3),(4,15,'Net 15','2017-05-08 08:16:14','2017-05-08 08:16:14',NULL,0,0,4),(5,30,'Net 30','2017-05-08 08:16:14','2017-05-08 08:16:14',NULL,0,0,5),(6,60,'Net 60','2017-05-08 08:16:14','2017-05-08 08:16:14',NULL,0,0,6),(7,90,'Net 90','2017-05-08 08:16:14','2017-05-08 08:16:14',NULL,0,0,7),(8,-1,'Net 0','2017-05-08 08:16:17','2017-05-08 08:16:17',NULL,0,0,0); /*!40000 ALTER TABLE `payment_terms` ENABLE KEYS */; UNLOCK TABLES; @@ -2488,4 +2502,4 @@ UNLOCK TABLES; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2017-04-30 16:36:49 +-- Dump completed on 2017-05-08 14:16:18 diff --git a/docs/conf.py b/docs/conf.py index 7ff140451815..9b176bbb3e15 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -59,7 +59,7 @@ author = u'Invoice Ninja' # The short X.Y version. version = u'3.3' # The full version, including alpha/beta/rc tags. -release = u'3.3.0' +release = u'3.3.1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/install.rst b/docs/install.rst index 4af8b0ee81d8..fb6f0e312741 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -29,7 +29,7 @@ Step 1: Download the code You can either download the zip file below or checkout the code from our GitHub repository. The zip includes all third party libraries whereas using GitHub requires you to use Composer to install the dependencies. -https://download.invoiceninja.com/ninja-v3.2.1.zip +https://download.invoiceninja.com/ninja-v3.3.0.zip .. Note:: All Pro and Enterprise features from our hosted app are included in both the zip file and the GitHub repository. We offer a $20 per year white-label license to remove our branding. diff --git a/public/built.js b/public/built.js index 8bff320ec6ea..0c61f0b40a8d 100644 --- a/public/built.js +++ b/public/built.js @@ -1,27 +1,27 @@ -function generatePDF(t,e,n,i){if(t&&e){if(!n)return refreshTimer&&clearTimeout(refreshTimer),void(refreshTimer=setTimeout(function(){generatePDF(t,e,!0,i)},500));refreshTimer=null,t=calculateAmounts(t);var o=GetPdfMake(t,e,i);return i&&o.getDataUrl(i),o}}function copyObject(t){return!!t&&JSON.parse(JSON.stringify(t))}function processVariables(t){if(!t)return"";for(var e=["MONTH","QUARTER","YEAR"],n=0;nt |
t |
t<"F"ip>'),C.renderer?i.isPlainObject(C.renderer)&&!C.renderer.header&&(C.renderer.header="jqueryui"):C.renderer="jqueryui"):i.extend(N,Yt.ext.classes,m.oClasses),i(this).addClass(N.sTable),""===C.oScroll.sX&&""===C.oScroll.sY||(C.oScroll.iBarWidth=wt()),C.oScroll.sX===!0&&(C.oScroll.sX="100%"),C.iInitDisplayStart===n&&(C.iInitDisplayStart=m.iDisplayStart,C._iDisplayStart=m.iDisplayStart),null!==m.iDeferLoading){C.bDeferLoading=!0;var O=i.isArray(m.iDeferLoading);C._iRecordsDisplay=O?m.iDeferLoading[0]:m.iDeferLoading,C._iRecordsTotal=O?m.iDeferLoading[1]:m.iDeferLoading}var S=C.oLanguage;i.extend(!0,S,m.oLanguage),""!==S.sUrl&&(i.ajax({dataType:"json",url:S.sUrl,success:function(t){s(t),a(z.oLanguage,t),i.extend(!0,S,t),rt(C)},error:function(){rt(C)}}),v=!0),null===m.asStripeClasses&&(C.asStripeClasses=[N.sStripeOdd,N.sStripeEven]);var x=C.asStripeClasses,L=i("tbody tr:eq(0)",this);i.inArray(!0,i.map(x,function(t,e){return L.hasClass(t)}))!==-1&&(i("tbody tr",this).removeClass(x.join(" ")),C.asDestroyStripes=x.slice());var D,q=[],W=this.getElementsByTagName("thead");if(0!==W.length&&(R(C.aoHeader,W[0]),q=F(C)),null===m.aoColumns)for(D=[],g=0,p=q.length;g
").appendTo(this)),C.nTHead=P[0];var H=i(this).children("tbody");0===H.length&&(H=i("
").appendTo(this)),C.nTBody=H[0];var j=i(this).children("tfoot");if(0===j.length&&X.length>0&&(""!==C.oScroll.sX||""!==C.oScroll.sY)&&(j=i("").appendTo(this)),0===j.length||0===j.children().length?i(this).addClass(N.sNoFooter):j.length>0&&(C.nTFoot=j[0],R(C.aoFooter,C.nTFoot)),m.aaData)for(g=0;gt<"F"ip>'),C.renderer?i.isPlainObject(C.renderer)&&!C.renderer.header&&(C.renderer.header="jqueryui"):C.renderer="jqueryui"):i.extend(O,Yt.ext.classes,m.oClasses),i(this).addClass(O.sTable),""===C.oScroll.sX&&""===C.oScroll.sY||(C.oScroll.iBarWidth=wt()),C.oScroll.sX===!0&&(C.oScroll.sX="100%"),C.iInitDisplayStart===n&&(C.iInitDisplayStart=m.iDisplayStart,C._iDisplayStart=m.iDisplayStart),null!==m.iDeferLoading){C.bDeferLoading=!0;var N=i.isArray(m.iDeferLoading);C._iRecordsDisplay=N?m.iDeferLoading[0]:m.iDeferLoading,C._iRecordsTotal=N?m.iDeferLoading[1]:m.iDeferLoading}var S=C.oLanguage;i.extend(!0,S,m.oLanguage),""!==S.sUrl&&(i.ajax({dataType:"json",url:S.sUrl,success:function(t){s(t),a(z.oLanguage,t),i.extend(!0,S,t),rt(C)},error:function(){rt(C)}}),v=!0),null===m.asStripeClasses&&(C.asStripeClasses=[O.sStripeOdd,O.sStripeEven]);var x=C.asStripeClasses,L=i("tbody tr:eq(0)",this);i.inArray(!0,i.map(x,function(t,e){return L.hasClass(t)}))!==-1&&(i("tbody tr",this).removeClass(x.join(" ")),C.asDestroyStripes=x.slice());var D,q=[],W=this.getElementsByTagName("thead");if(0!==W.length&&(R(C.aoHeader,W[0]),q=F(C)),null===m.aoColumns)for(D=[],g=0,p=q.length;g
").appendTo(this)),C.nTHead=P[0];var H=i(this).children("tbody");0===H.length&&(H=i("
").appendTo(this)),C.nTBody=H[0];var j=i(this).children("tfoot");if(0===j.length&&X.length>0&&(""!==C.oScroll.sX||""!==C.oScroll.sY)&&(j=i("").appendTo(this)),0===j.length||0===j.children().length?i(this).addClass(O.sNoFooter):j.length>0&&(C.nTFoot=j[0],R(C.aoFooter,C.nTFoot)),m.aaData)for(g=0;g