diff --git a/.env.example b/.env.example index c40ef51eb943..abc9da3a5254 100644 --- a/.env.example +++ b/.env.example @@ -19,3 +19,5 @@ MAIL_USERNAME MAIL_FROM_ADDRESS MAIL_FROM_NAME MAIL_PASSWORD + +ALLOW_NEW_ACCOUNTS \ No newline at end of file diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 084f74fcc498..e0db4f0a89f5 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -1,8 +1,10 @@ get_class($e), diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index e7b08bf612f9..8503d0b76494 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -77,10 +77,12 @@ class AccountController extends BaseController { if (Auth::check()) { return Redirect::to('invoices/create'); - } elseif (!Utils::isNinja() && Account::count() > 0) { - return Redirect::to('/login'); } + if (!Utils::isNinja() && !Utils::allowNewAccounts() && Account::count() > 0) { + return Redirect::to('/login'); + } + $user = false; $guestKey = Input::get('guest_key'); @@ -728,7 +730,7 @@ class AccountController extends BaseController $email = trim(Input::get('email')); if (!$email || $email == 'user@example.com') { - return RESULT_SUCCESS; + return ''; } $license = new License(); @@ -742,7 +744,7 @@ class AccountController extends BaseController $license->is_claimed = 1; $license->save(); - return RESULT_SUCCESS; + return ''; } public function cancelAccount() @@ -762,6 +764,7 @@ class AccountController extends BaseController $account->forceDelete(); Auth::logout(); + Session::flush(); return Redirect::to('/')->with('clearGuestKey', true); } diff --git a/app/Http/Controllers/Auth/AuthController.php b/app/Http/Controllers/Auth/AuthController.php index 797c54ac4a92..8cde7ad8190c 100644 --- a/app/Http/Controllers/Auth/AuthController.php +++ b/app/Http/Controllers/Auth/AuthController.php @@ -3,10 +3,12 @@ use Auth; use Event; use Utils; +use Session; use Illuminate\Http\Request; use App\Models\User; use App\Events\UserLoggedIn; use App\Http\Controllers\Controller; +use App\Ninja\Repositories\AccountRepository; use Illuminate\Contracts\Auth\Guard; use Illuminate\Contracts\Auth\Registrar; use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers; @@ -28,6 +30,7 @@ class AuthController extends Controller { protected $loginPath = '/login'; protected $redirectTo = '/dashboard'; + protected $accountRepo; /** * Create a new authentication controller instance. @@ -36,12 +39,13 @@ class AuthController extends Controller { * @param \Illuminate\Contracts\Auth\Registrar $registrar * @return void */ - public function __construct(Guard $auth, Registrar $registrar) + public function __construct(Guard $auth, Registrar $registrar, AccountRepository $repo) { $this->auth = $auth; $this->registrar = $registrar; + $this->accountRepo = $repo; - $this->middleware('guest', ['except' => 'getLogout']); + //$this->middleware('guest', ['except' => 'getLogout']); } public function getLoginWrapper() @@ -55,13 +59,36 @@ class AuthController extends Controller { public function postLoginWrapper(Request $request) { + $userId = Auth::check() ? Auth::user()->id : null; $response = self::postLogin($request); if (Auth::check()) { Event::fire(new UserLoggedIn()); + + if (Utils::isPro()) { + $users = false; + // we're linking a new account + if ($userId && Auth::user()->id != $userId) { + $users = $this->accountRepo->associateAccounts($userId, Auth::user()->id); + Session::flash('message', trans('texts.associated_accounts')); + // check if other accounts are linked + } else { + $users = $this->accountRepo->loadAccounts(Auth::user()->id); + } + + Session::put(SESSION_USER_ACCOUNTS, $users); + } } return $response; } + public function getLogoutWrapper() + { + $response = self::getLogout(); + + Session::flush(); + + return $response; + } } diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index adf28e133cec..1db0b1698224 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -45,6 +45,11 @@ class HomeController extends BaseController public function invoiceNow() { + if (Auth::check() && Input::get('logout')) { + Auth::user()->clearSession(); + Auth::logout(); + } + if (Auth::check()) { return Redirect::to('invoices/create')->with('sign_up', Input::get('sign_up')); } else { diff --git a/app/Http/Controllers/PaymentController.php b/app/Http/Controllers/PaymentController.php index a3103b8b780a..4196e1d1661f 100644 --- a/app/Http/Controllers/PaymentController.php +++ b/app/Http/Controllers/PaymentController.php @@ -630,8 +630,9 @@ class PaymentController extends BaseController $invoice = $invitation->invoice; $accountGateway = $invoice->client->account->getGatewayByType(Session::get('payment_type')); - if ($invoice->account->account_key == NINJA_ACCOUNT_KEY) { - $account = Account::find($invoice->client->public_id); + if ($invoice->account->account_key == NINJA_ACCOUNT_KEY + && $invoice->amount == PRO_PLAN_PRICE) { + $account = Account::with('users')->find($invoice->client->public_id); if ($account->pro_plan_paid && $account->pro_plan_paid != '0000-00-00') { $date = DateTime::createFromFormat('Y-m-d', $account->pro_plan_paid); $account->pro_plan_paid = $date->modify('+1 year')->format('Y-m-d'); @@ -639,6 +640,9 @@ class PaymentController extends BaseController $account->pro_plan_paid = date_create()->format('Y-m-d'); } $account->save(); + + $user = $account->users()->first(); + $this->accountRepo->syncAccounts($user->id, $account->pro_plan_paid); } $payment = Payment::createNew($invitation); diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 1a1afd3f1df9..6a1b425ce4a1 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -7,6 +7,7 @@ use DB; use Event; use Input; use View; +use Request; use Redirect; use Session; use URL; @@ -309,10 +310,8 @@ class UserController extends BaseController } } - Session::forget('news_feed_id'); - Session::forget('news_feed_message'); - Auth::logout(); + Session::flush(); return Redirect::to('/')->with('clearGuestKey', true); } @@ -342,4 +341,32 @@ class UserController extends BaseController return RESULT_SUCCESS; } + + public function switchAccount($newUserId) + { + $oldUserId = Auth::user()->id; + $referer = Request::header('referer'); + $account = $this->accountRepo->findUserAccounts($newUserId, $oldUserId); + + if ($account) { + if ($account->hasUserId($newUserId) && $account->hasUserId($oldUserId)) { + Auth::loginUsingId($newUserId); + Auth::user()->account->loadLocalizationSettings(); + } + } + + return Redirect::to($referer); + } + + public function unlinkAccount($userAccountId, $userId) + { + $this->accountRepo->unlinkAccount($userAccountId, $userId); + $referer = Request::header('referer'); + + $users = $this->accountRepo->loadAccounts(Auth::user()->id); + Session::put(SESSION_USER_ACCOUNTS, $users); + + Session::flash('message', trans('texts.unlinked_account')); + return Redirect::to($referer); + } } diff --git a/app/Http/routes.php b/app/Http/routes.php index 391e2df44670..8cf91232690a 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -67,8 +67,7 @@ get('/signup', array('as' => 'signup', 'uses' => 'Auth\AuthController@getRegiste post('/signup', array('as' => 'signup', 'uses' => 'Auth\AuthController@postRegister')); get('/login', array('as' => 'login', 'uses' => 'Auth\AuthController@getLoginWrapper')); post('/login', array('as' => 'login', 'uses' => 'Auth\AuthController@postLoginWrapper')); -//get('/logout', array('as' => 'logout', 'uses' => 'Auth\AuthController@getLogoutWrapper')); -get('/logout', array('as' => 'logout', 'uses' => 'Auth\AuthController@getLogout')); +get('/logout', array('as' => 'logout', 'uses' => 'Auth\AuthController@getLogoutWrapper')); get('/forgot', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getEmail')); post('/forgot', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postEmail')); get('/password/reset/{token}', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getReset')); @@ -95,6 +94,7 @@ Route::group(['middleware' => 'auth'], function() { Route::get('restore_user/{user_id}', 'UserController@restoreUser'); Route::post('users/change_password', 'UserController@changePassword'); Route::get('/switch_account/{user_id}', 'UserController@switchAccount'); + Route::get('/unlink_account/{user_account_id}/{user_id}', 'UserController@unlinkAccount'); Route::get('api/tokens', array('as'=>'api.tokens', 'uses'=>'TokenController@getDatatable')); Route::resource('tokens', 'TokenController'); diff --git a/app/Libraries/Utils.php b/app/Libraries/Utils.php index d09e60cfab4f..94025347fcda 100644 --- a/app/Libraries/Utils.php +++ b/app/Libraries/Utils.php @@ -51,12 +51,17 @@ class Utils public static function isNinjaProd() { - return isset($_ENV['NINJA_PROD']) && $_ENV['NINJA_PROD']; + return isset($_ENV['NINJA_PROD']) && $_ENV['NINJA_PROD'] == 'true'; } public static function isNinjaDev() { - return isset($_ENV['NINJA_DEV']) && $_ENV['NINJA_DEV']; + return isset($_ENV['NINJA_DEV']) && $_ENV['NINJA_DEV'] == 'true'; + } + + public static function allowNewAccounts() + { + return isset($_ENV['ALLOW_NEW_ACCOUNTS']) && $_ENV['ALLOW_NEW_ACCOUNTS'] == 'true'; } public static function isPro() diff --git a/app/Listeners/HandleUserSettingsChanged.php b/app/Listeners/HandleUserSettingsChanged.php index e01a010c9dd1..f15966db9764 100644 --- a/app/Listeners/HandleUserSettingsChanged.php +++ b/app/Listeners/HandleUserSettingsChanged.php @@ -1,9 +1,9 @@ accountRepo = $accountRepo; } /** @@ -29,6 +29,9 @@ class HandleUserSettingsChanged { { $account = Auth::user()->account; $account->loadLocalizationSettings(); + + $users = $this->accountRepo->loadAccounts(Auth::user()->id); + Session::put(SESSION_USER_ACCOUNTS, $users); } } diff --git a/app/Models/User.php b/app/Models/User.php index 593d19e1486e..66d33492db2a 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -128,6 +128,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon return Session::get(SESSION_COUNTER, 0); } + /* public function getPopOverText() { if (!Utils::isNinja() || !Auth::check() || Session::has('error')) { @@ -146,7 +147,8 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon return false; } - + */ + public function afterSave($success = true, $forced = false) { if ($this->email) { @@ -176,4 +178,22 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon return 'remember_token'; } + public function clearSession() + { + $keys = [ + RECENTLY_VIEWED, + SESSION_USER_ACCOUNTS, + SESSION_TIMEZONE, + SESSION_DATE_FORMAT, + SESSION_DATE_PICKER_FORMAT, + SESSION_DATETIME_FORMAT, + SESSION_CURRENCY, + SESSION_LOCALE, + ]; + + foreach ($keys as $key) { + Session::forget($key); + } + } + } diff --git a/app/Models/UserAccount.php b/app/Models/UserAccount.php new file mode 100644 index 000000000000..a1cd15f89b2d --- /dev/null +++ b/app/Models/UserAccount.php @@ -0,0 +1,52 @@ +$field && $this->$field == $userId) { + return true; + } + } + return false; + } + + public function setUserId($userId) + { + if (self::hasUserId($userId)) { + return; + } + + for ($i=1; $i<=5; $i++) { + $field = "user_id{$i}"; + if (!$this->$field) { + $this->$field = $userId; + break; + } + } + } + + public function removeUserId($userId) + { + if (!$userId || !self::hasUserId($userId)) { + return; + } + + for ($i=1; $i<=5; $i++) { + $field = "user_id{$i}"; + if ($this->$field && $this->$field == $userId) { + $this->$field = null; + } + } + } +} diff --git a/app/Ninja/Repositories/AccountRepository.php b/app/Ninja/Repositories/AccountRepository.php index 2b7f66595e42..cf9dd153f856 100644 --- a/app/Ninja/Repositories/AccountRepository.php +++ b/app/Ninja/Repositories/AccountRepository.php @@ -4,6 +4,8 @@ use Auth; use Request; use Session; use Utils; +use DB; +use stdClass; use App\Models\AccountGateway; use App\Models\Invitation; @@ -14,6 +16,7 @@ use App\Models\Language; use App\Models\Contact; use App\Models\Account; use App\Models\User; +use App\Models\UserAccount; class AccountRepository { @@ -244,4 +247,129 @@ class AccountRepository curl_exec($ch); curl_close($ch); } + + public function findUserAccounts($userId1, $userId2 = false) + { + $query = UserAccount::where('user_id1', '=', $userId1) + ->orWhere('user_id2', '=', $userId1) + ->orWhere('user_id3', '=', $userId1) + ->orWhere('user_id4', '=', $userId1) + ->orWhere('user_id5', '=', $userId1); + + if ($userId2) { + $query->orWhere('user_id1', '=', $userId2) + ->orWhere('user_id2', '=', $userId2) + ->orWhere('user_id3', '=', $userId2) + ->orWhere('user_id4', '=', $userId2) + ->orWhere('user_id5', '=', $userId2); + } + + return $query->first(['id', 'user_id1', 'user_id2', 'user_id3', 'user_id4', 'user_id5']); + } + + public function prepareUsersData($record) { + + if (!$record) { + return false; + } + + $userIds = []; + for ($i=1; $i<=5; $i++) { + $field = "user_id$i"; + if ($record->$field) { + $userIds[] = $record->$field; + } + } + + $users = User::with('account') + ->whereIn('id', $userIds) + ->get(); + + $data = []; + foreach ($users as $user) { + $item = new stdClass(); + $item->id = $record->id; + $item->user_id = $user->id; + $item->user_name = $user->getDisplayName(); + $item->account_id = $user->account->id; + $item->account_name = $user->account->getDisplayName(); + $item->pro_plan_paid = $user->account->pro_plan_paid; + $data[] = $item; + } + + return $data; + } + + public function loadAccounts($userId) { + $record = self::findUserAccounts($userId); + return self::prepareUsersData($record); + } + + public function syncAccounts($userId, $proPlanPaid) { + $users = self::loadAccounts($userId); + self::syncUserAccounts($users, $proPlanPaid); + } + + public function syncUserAccounts($users, $proPlanPaid = false) { + + if (!$proPlanPaid) { + foreach ($users as $user) { + if ($user->pro_plan_paid && $user->pro_plan_paid != '0000-00-00') { + $proPlanPaid = $user->pro_plan_paid; + break; + } + } + } + + if (!$proPlanPaid) { + return; + } + + $accountIds = []; + foreach ($users as $user) { + if ($user->pro_plan_paid != $proPlanPaid) { + $accountIds[] = $user->account_id; + } + } + + if (count($accountIds)) { + DB::table('accounts') + ->whereIn('id', $accountIds) + ->update(['pro_plan_paid' => $proPlanPaid]); + } + } + + public function associateAccounts($userId1, $userId2) { + + $record = self::findUserAccounts($userId1, $userId2); + + if ($record) { + foreach ([$userId1, $userId2] as $userId) { + if (!$record->hasUserId($userId)) { + $record->setUserId($userId); + } + } + } else { + $record = new UserAccount(); + $record->user_id1 = $userId1; + $record->user_id2 = $userId2; + } + + $record->save(); + + $users = self::prepareUsersData($record); + self::syncUserAccounts($users); + + return $users; + } + + public function unlinkAccount($userAccountId, $userId) { + + $userAccount = UserAccount::whereId($userAccountId)->first(); + + if ($userAccount->hasUserId(Auth::user()->id)) { + $userAccount->removeUserId($userId); + $userAccount->save(); + } + } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index a55ecfd5b476..9efba90b91af 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -44,6 +44,10 @@ class AppServiceProvider extends ServiceProvider { $str .= '
{!! Button::success(trans('texts.lets_go'))->large()->submit()->block() !!}
+{!! Button::success(trans(Utils::allowNewAccounts() ? 'texts.login' : 'texts.lets_go'))->large()->submit()->block() !!}
+ + @if (Utils::allowNewAccounts()) +- {{ trans('texts.or') }} -
{!! Button::primary(trans('texts.new_account'))->asLinkTo(URL::to('/invoice_now?logout=true'))->large()->submit()->block() !!}
+ @endif +{!! link_to('/forgot', trans('texts.forgot_password')) !!} diff --git a/resources/views/header.blade.php b/resources/views/header.blade.php index 004296f63468..80d6446420eb 100644 --- a/resources/views/header.blade.php +++ b/resources/views/header.blade.php @@ -186,6 +186,13 @@ }); } + function unlinkAccount(userAccountId, userId) { + if (confirm('{!! trans("texts.are_you_sure") !!}')) { + window.location = '{{ URL::to('/unlink_account') }}' + '/' + userAccountId + '/' + userId; + } + return false; + } + function wordWrapText(value, width) { @if (Auth::user()->account->auto_wrap) @@ -321,7 +328,6 @@ {!! HTML::menu_link('task') !!} {!! HTML::menu_link('invoice') !!} {!! HTML::menu_link('payment') !!} - {!! HTML::menu_link('credit') !!}
+ + - @if (Auth::user()->getPopOverText() && Utils::isRegistered()) - - @endif - - - - - - + + + + diff --git a/resources/views/list.blade.php b/resources/views/list.blade.php index 65a4a417f969..cc9d3e35ce99 100644 --- a/resources/views/list.blade.php +++ b/resources/views/list.blade.php @@ -27,6 +27,8 @@ @if (Auth::user()->isPro() && $entityType == ENTITY_INVOICE) {!! Button::normal(trans('texts.quotes'))->asLinkTo(URL::to('/quotes'))->appendIcon(Icon::create('list')) !!} + @elseif ($entityType == ENTITY_CLIENT) + {!! Button::normal(trans('texts.credits'))->asLinkTo(URL::to('/credits'))->appendIcon(Icon::create('list')) !!} @endif {!! Button::primary(trans("texts.new_$entityType"))->asLinkTo(URL::to("/{$entityType}s/create"))->appendIcon(Icon::create('plus-sign')) !!}