From 0148d06205905995059671249bf8fa14c09a9702 Mon Sep 17 00:00:00 2001 From: Joshua Dwire Date: Tue, 15 Mar 2016 19:08:00 -0400 Subject: [PATCH 01/13] Add user permission support --- app/Http/Controllers/AccountController.php | 8 +- app/Http/Controllers/BaseController.php | 45 +++++++- app/Http/Controllers/ClientController.php | 69 +++++++++--- app/Http/Controllers/CreditController.php | 12 +- app/Http/Controllers/ExpenseController.php | 12 +- app/Http/Controllers/InvoiceController.php | 14 ++- app/Http/Controllers/PaymentController.php | 13 ++- app/Http/Controllers/QuoteController.php | 7 +- app/Http/Controllers/TaskController.php | 14 ++- app/Http/Controllers/UserController.php | 4 + app/Http/Controllers/VendorController.php | 15 +++ app/Http/Kernel.php | 1 + app/Http/Middleware/PermissionsRequired.php | 57 ++++++++++ app/Http/routes.php | 105 ++++++++++-------- app/Libraries/Utils.php | 15 +++ app/Models/EntityModel.php | 36 ++++++ app/Models/Product.php | 4 + app/Models/TaxRate.php | 4 + app/Models/User.php | 69 +++++++++++- app/Ninja/Repositories/ClientRepository.php | 3 +- app/Ninja/Repositories/CreditRepository.php | 3 +- app/Ninja/Repositories/ExpenseRepository.php | 3 +- app/Ninja/Repositories/InvoiceRepository.php | 3 +- app/Ninja/Repositories/PaymentRepository.php | 1 + app/Ninja/Repositories/TaskRepository.php | 3 +- app/Ninja/Repositories/UserRepository.php | 3 +- app/Ninja/Repositories/VendorRepository.php | 3 +- app/Providers/AppServiceProvider.php | 46 +++++--- app/Services/ClientService.php | 44 +++++++- app/Services/DatatableService.php | 23 ++-- app/Services/ExpenseService.php | 14 ++- app/Services/InvoiceService.php | 29 ++++- app/Services/PaymentService.php | 8 ++ app/Services/RecurringInvoiceService.php | 9 ++ app/Services/TaskService.php | 14 ++- app/Services/UserService.php | 17 ++- app/Services/VendorService.php | 16 ++- ...2016_03_14_066181_add_user_permissions.php | 33 ++++++ resources/lang/en/texts.php | 8 ++ .../views/accounts/user_details.blade.php | 4 +- resources/views/clients/show.blade.php | 15 ++- resources/views/header.blade.php | 24 ++-- resources/views/list.blade.php | 4 +- resources/views/user_account.blade.php | 13 ++- resources/views/users/edit.blade.php | 31 +++++- 45 files changed, 719 insertions(+), 159 deletions(-) create mode 100644 app/Http/Middleware/PermissionsRequired.php create mode 100644 database/migrations/2016_03_14_066181_add_user_permissions.php diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index abefe0b5f745..755932691d18 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -137,8 +137,6 @@ class AccountController extends BaseController if ($section == ACCOUNT_COMPANY_DETAILS) { return self::showCompanyDetails(); - } elseif ($section == ACCOUNT_USER_DETAILS) { - return self::showUserDetails(); } elseif ($section == ACCOUNT_LOCALIZATION) { return self::showLocalization(); } elseif ($section == ACCOUNT_PAYMENTS) { @@ -232,7 +230,7 @@ class AccountController extends BaseController return View::make('accounts.details', $data); } - private function showUserDetails() + public function showUserDetails() { $oauthLoginUrls = []; foreach (AuthService::$providers as $provider) { @@ -467,8 +465,6 @@ class AccountController extends BaseController { if ($section === ACCOUNT_COMPANY_DETAILS) { return AccountController::saveDetails(); - } elseif ($section === ACCOUNT_USER_DETAILS) { - return AccountController::saveUserDetails(); } elseif ($section === ACCOUNT_LOCALIZATION) { return AccountController::saveLocalization(); } elseif ($section === ACCOUNT_NOTIFICATIONS) { @@ -839,7 +835,7 @@ class AccountController extends BaseController return Redirect::to('settings/'.ACCOUNT_COMPANY_DETAILS); } - private function saveUserDetails() + public function saveUserDetails() { $user = Auth::user(); $rules = ['email' => 'email|required|unique:users,email,'.$user->id.',id']; diff --git a/app/Http/Controllers/BaseController.php b/app/Http/Controllers/BaseController.php index ee7f62686fb4..5124097636a9 100644 --- a/app/Http/Controllers/BaseController.php +++ b/app/Http/Controllers/BaseController.php @@ -1,10 +1,14 @@ layout = View::make($this->layout); } } - - /* - public function __construct() - { - $this->beforeFilter('csrf', array('on' => array('post', 'delete', 'put'))); + + protected function checkViewPermission($object, &$response = null){ + if(!$object->canView()){ + $response = response('Unauthorized.', 401); + return false; + } + return true; + } + + protected function checkEditPermission($object, &$response = null){ + if(!$object->canEdit()){ + $response = response('Unauthorized.', 401); + return false; + } + return true; + } + + protected function checkCreatePermission(&$response = null){ + if(!call_user_func(array($this->model, 'canCreate'))){ + $response = response('Unauthorized.', 401); + return false; + } + return true; + } + + protected function checkUpdatePermission($input, &$response = null){ + $creating = empty($input['public_id']) || $input['public_id'] == '-1'; + + if($creating){ + return $this->checkCreatePermission($response); + } + else{ + $object = call_user_func(array($this->model, 'scope'), $input['public_id'])->firstOrFail(); + return $this->checkEditPermission($object, $response); + } } - */ } diff --git a/app/Http/Controllers/ClientController.php b/app/Http/Controllers/ClientController.php index 163d75f622ba..7e1c01de9e6e 100644 --- a/app/Http/Controllers/ClientController.php +++ b/app/Http/Controllers/ClientController.php @@ -20,6 +20,9 @@ use App\Models\Size; use App\Models\PaymentTerm; use App\Models\Industry; use App\Models\Currency; +use App\Models\Payment; +use App\Models\Credit; +use App\Models\Expense; use App\Models\Country; use App\Models\Task; use App\Ninja\Repositories\ClientRepository; @@ -32,6 +35,7 @@ class ClientController extends BaseController { protected $clientService; protected $clientRepo; + protected $model = 'App\Models\Client'; public function __construct(ClientRepository $clientRepo, ClientService $clientService) { @@ -77,7 +81,13 @@ class ClientController extends BaseController */ public function store(CreateClientRequest $request) { - $client = $this->clientService->save($request->input()); + $data = $request->input(); + + if(!$this->checkUpdatePermission($data, $response)){ + return $response; + } + + $client = $this->clientService->save($data); Session::flash('message', trans('texts.created_client')); @@ -93,22 +103,36 @@ class ClientController extends BaseController public function show($publicId) { $client = Client::withTrashed()->scope($publicId)->with('contacts', 'size', 'industry')->firstOrFail(); + + if(!$this->checkViewPermission($client, $response)){ + return $response; + } + Utils::trackViewed($client->getDisplayName(), ENTITY_CLIENT); - $actionLinks = [ - ['label' => trans('texts.new_task'), 'url' => '/tasks/create/'.$client->public_id] - ]; - - if (Utils::isPro()) { - array_push($actionLinks, ['label' => trans('texts.new_quote'), 'url' => '/quotes/create/'.$client->public_id]); + $actionLinks = []; + if(Task::canCreate()){ + $actionLinks[] = ['label' => trans('texts.new_task'), 'url' => '/tasks/create/'.$client->public_id]; + } + if (Utils::isPro() && Invoice::canCreate()) { + $actionLinks[] = ['label' => trans('texts.new_quote'), 'url' => '/quotes/create/'.$client->public_id]; + } + + if(!empty($actionLinks)){ + $actionLinks[] = \DropdownButton::DIVIDER; + } + + if(Payment::canCreate()){ + $actionLinks[] = ['label' => trans('texts.enter_payment'), 'url' => '/payments/create/'.$client->public_id]; + } + + if(Credit::canCreate()){ + $actionLinks[] = ['label' => trans('texts.enter_credit'), 'url' => '/credits/create/'.$client->public_id]; + } + + if(Expense::canCreate()){ + $actionLinks[] = ['label' => trans('texts.enter_expense'), 'url' => '/expenses/create/0/'.$client->public_id]; } - - array_push($actionLinks, - \DropdownButton::DIVIDER, - ['label' => trans('texts.enter_payment'), 'url' => '/payments/create/'.$client->public_id], - ['label' => trans('texts.enter_credit'), 'url' => '/credits/create/'.$client->public_id], - ['label' => trans('texts.enter_expense'), 'url' => '/expenses/create/0/'.$client->public_id] - ); $data = array( 'actionLinks' => $actionLinks, @@ -132,6 +156,10 @@ class ClientController extends BaseController */ public function create() { + if(!$this->checkCreatePermission($response)){ + return $response; + } + if (Client::scope()->withTrashed()->count() > Auth::user()->getMaxNumClients()) { return View::make('error', ['hideHeader' => true, 'error' => "Sorry, you've exceeded the limit of ".Auth::user()->getMaxNumClients()." clients"]); } @@ -157,6 +185,11 @@ class ClientController extends BaseController public function edit($publicId) { $client = Client::scope($publicId)->with('contacts')->firstOrFail(); + + if(!$this->checkEditPermission($client, $response)){ + return $response; + } + $data = [ 'client' => $client, 'method' => 'PUT', @@ -199,7 +232,13 @@ class ClientController extends BaseController */ public function update(UpdateClientRequest $request) { - $client = $this->clientService->save($request->input()); + $data = $request->input(); + + if(!$this->checkUpdatePermission($data, $response)){ + return $response; + } + + $client = $this->clientService->save($data); Session::flash('message', trans('texts.updated_client')); diff --git a/app/Http/Controllers/CreditController.php b/app/Http/Controllers/CreditController.php index 5a4b3011ad50..26085c3d6b32 100644 --- a/app/Http/Controllers/CreditController.php +++ b/app/Http/Controllers/CreditController.php @@ -17,10 +17,11 @@ class CreditController extends BaseController { protected $creditRepo; protected $creditService; + protected $model = 'App\Models\Credit'; public function __construct(CreditRepository $creditRepo, CreditService $creditService) { - //parent::__construct(); + // parent::__construct(); $this->creditRepo = $creditRepo; $this->creditService = $creditService; @@ -56,6 +57,10 @@ class CreditController extends BaseController public function create($clientPublicId = 0) { + if(!$this->checkCreatePermission($response)){ + return $response; + } + $data = array( 'clientPublicId' => Input::old('client') ? Input::old('client') : $clientPublicId, //'invoicePublicId' => Input::old('invoice') ? Input::old('invoice') : $invoicePublicId, @@ -72,6 +77,11 @@ class CreditController extends BaseController public function edit($publicId) { $credit = Credit::scope($publicId)->firstOrFail(); + + if(!$this->checkEditPermission($credit, $response)){ + return $response; + } + $credit->credit_date = Utils::fromSqlDate($credit->credit_date); $data = array( diff --git a/app/Http/Controllers/ExpenseController.php b/app/Http/Controllers/ExpenseController.php index 2d4294d540a1..eb9fa0c437ee 100644 --- a/app/Http/Controllers/ExpenseController.php +++ b/app/Http/Controllers/ExpenseController.php @@ -25,10 +25,11 @@ class ExpenseController extends BaseController // Expenses protected $expenseRepo; protected $expenseService; + protected $model = 'App\Models\Expense'; public function __construct(ExpenseRepository $expenseRepo, ExpenseService $expenseService) { - //parent::__construct(); + // parent::__construct(); $this->expenseRepo = $expenseRepo; $this->expenseService = $expenseService; @@ -70,6 +71,10 @@ class ExpenseController extends BaseController public function create($vendorPublicId = null, $clientPublicId = null) { + if(!$this->checkCreatePermission($response)){ + return $response; + } + if($vendorPublicId != 0) { $vendor = Vendor::scope($vendorPublicId)->with('vendorcontacts')->firstOrFail(); } else { @@ -95,6 +100,11 @@ class ExpenseController extends BaseController public function edit($publicId) { $expense = Expense::scope($publicId)->firstOrFail(); + + if(!$this->checkEditPermission($expense, $response)){ + return $response; + } + $expense->expense_date = Utils::fromSqlDate($expense->expense_date); $actions = []; diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index fbbdc7c08e6f..089953f84c67 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -34,10 +34,11 @@ class InvoiceController extends BaseController protected $clientRepo; protected $invoiceService; protected $recurringInvoiceService; + protected $model = 'App\Models\Invoice'; public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, InvoiceService $invoiceService, RecurringInvoiceService $recurringInvoiceService) { - //parent::__construct(); + // parent::__construct(); $this->mailer = $mailer; $this->invoiceRepo = $invoiceRepo; @@ -90,6 +91,11 @@ class InvoiceController extends BaseController ->with('invitations', 'account.country', 'client.contacts', 'client.country', 'invoice_items') ->withTrashed() ->firstOrFail(); + + if(!$this->checkEditPermission($invoice, $response)){ + return $response; + } + $entityType = $invoice->getEntityType(); $contactIds = DB::table('invitations') @@ -206,7 +212,11 @@ class InvoiceController extends BaseController public function create($clientPublicId = 0, $isRecurring = false) { - $account = Auth::user()->account; + if(!$this->checkCreatePermission($response)){ + return $response; + } + + $account = Auth::user()->account; $entityType = $isRecurring ? ENTITY_RECURRING_INVOICE : ENTITY_INVOICE; $clientId = null; diff --git a/app/Http/Controllers/PaymentController.php b/app/Http/Controllers/PaymentController.php index 24c4005f1d21..7dc13ec6ed56 100644 --- a/app/Http/Controllers/PaymentController.php +++ b/app/Http/Controllers/PaymentController.php @@ -30,9 +30,11 @@ use App\Http\Requests\UpdatePaymentRequest; class PaymentController extends BaseController { + protected $model = 'App\Models\Payment'; + public function __construct(PaymentRepository $paymentRepo, InvoiceRepository $invoiceRepo, AccountRepository $accountRepo, ContactMailer $contactMailer, PaymentService $paymentService) { - //parent::__construct(); + // parent::__construct(); $this->paymentRepo = $paymentRepo; $this->invoiceRepo = $invoiceRepo; @@ -66,6 +68,10 @@ class PaymentController extends BaseController public function create($clientPublicId = 0, $invoicePublicId = 0) { + if(!$this->checkCreatePermission($response)){ + return $response; + } + $invoices = Invoice::scope() ->where('is_recurring', '=', false) ->where('is_quote', '=', false) @@ -92,6 +98,11 @@ class PaymentController extends BaseController public function edit($publicId) { $payment = Payment::scope($publicId)->firstOrFail(); + + if(!$this->checkEditPermission($payment, $response)){ + return $response; + } + $payment->payment_date = Utils::fromSqlDate($payment->payment_date); $data = array( diff --git a/app/Http/Controllers/QuoteController.php b/app/Http/Controllers/QuoteController.php index 882acb039e08..7777c5a9d006 100644 --- a/app/Http/Controllers/QuoteController.php +++ b/app/Http/Controllers/QuoteController.php @@ -33,10 +33,11 @@ class QuoteController extends BaseController protected $invoiceRepo; protected $clientRepo; protected $invoiceService; + protected $model = 'App\Models\Invoice'; public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, InvoiceService $invoiceService) { - //parent::__construct(); + // parent::__construct(); $this->mailer = $mailer; $this->invoiceRepo = $invoiceRepo; @@ -78,6 +79,10 @@ class QuoteController extends BaseController public function create($clientPublicId = 0) { + if(!$this->checkCreatePermission($response)){ + return $response; + } + if (!Utils::isPro()) { return Redirect::to('/invoices/create'); } diff --git a/app/Http/Controllers/TaskController.php b/app/Http/Controllers/TaskController.php index e09ed50b4ba5..ea3001dfc538 100644 --- a/app/Http/Controllers/TaskController.php +++ b/app/Http/Controllers/TaskController.php @@ -22,10 +22,11 @@ class TaskController extends BaseController { protected $taskRepo; protected $taskService; + protected $model = 'App\Models\Task'; public function __construct(TaskRepository $taskRepo, InvoiceRepository $invoiceRepo, TaskService $taskService) { - //parent::__construct(); + // parent::__construct(); $this->taskRepo = $taskRepo; $this->invoiceRepo = $invoiceRepo; @@ -67,6 +68,10 @@ class TaskController extends BaseController */ public function store() { + if(!$this->checkCreatePermission($response)){ + return $response; + } + return $this->save(); } @@ -84,6 +89,9 @@ class TaskController extends BaseController */ public function create($clientPublicId = 0) { + if(!$this->checkCreatePermission($response)){ + return $response; + } $this->checkTimezone(); $data = [ @@ -113,6 +121,10 @@ class TaskController extends BaseController $task = Task::scope($publicId)->with('client', 'invoice')->withTrashed()->firstOrFail(); + if(!$this->checkEditPermission($task, $response)){ + return $response; + } + $actions = []; if ($task->invoice) { $actions[] = ['url' => URL::to("invoices/{$task->invoice->public_id}/edit"), 'label' => trans("texts.view_invoice")]; diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index ee3f9d65540c..0e5a8dd70dd6 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -192,6 +192,8 @@ class UserController extends BaseController $user->last_name = trim(Input::get('last_name')); $user->username = trim(Input::get('email')); $user->email = trim(Input::get('email')); + $user->is_admin = boolval(Input::get('is_admin')); + $user->permissions = Input::get('permissions'); } else { $lastUser = User::withTrashed()->where('account_id', '=', Auth::user()->account_id) ->orderBy('public_id', 'DESC')->first(); @@ -202,10 +204,12 @@ class UserController extends BaseController $user->last_name = trim(Input::get('last_name')); $user->username = trim(Input::get('email')); $user->email = trim(Input::get('email')); + $user->is_admin = boolval(Input::get('is_admin')); $user->registered = true; $user->password = str_random(RANDOM_KEY_LENGTH); $user->confirmation_code = str_random(RANDOM_KEY_LENGTH); $user->public_id = $lastUser->public_id + 1; + $user->permissions = Input::get('permissions'); } $user->save(); diff --git a/app/Http/Controllers/VendorController.php b/app/Http/Controllers/VendorController.php index 5d25f29215d6..bab1479b446e 100644 --- a/app/Http/Controllers/VendorController.php +++ b/app/Http/Controllers/VendorController.php @@ -30,6 +30,7 @@ class VendorController extends BaseController { protected $vendorService; protected $vendorRepo; + protected $model = 'App\Models\Vendor'; public function __construct(VendorRepository $vendorRepo, VendorService $vendorService) { @@ -92,6 +93,11 @@ class VendorController extends BaseController public function show($publicId) { $vendor = Vendor::withTrashed()->scope($publicId)->with('vendorcontacts', 'size', 'industry')->firstOrFail(); + + if(!$this->checkViewPermission($vendor, $response)){ + return $response; + } + Utils::trackViewed($vendor->getDisplayName(), 'vendor'); $actionLinks = [ @@ -119,6 +125,10 @@ class VendorController extends BaseController */ public function create() { + if(!$this->checkCreatePermission($response)){ + return $response; + } + if (Vendor::scope()->count() > Auth::user()->getMaxNumVendors()) { return View::make('error', ['hideHeader' => true, 'error' => "Sorry, you've exceeded the limit of ".Auth::user()->getMaxNumVendors()." vendors"]); } @@ -144,6 +154,11 @@ class VendorController extends BaseController public function edit($publicId) { $vendor = Vendor::scope($publicId)->with('vendorcontacts')->firstOrFail(); + + if(!$this->checkEditPermission($vendor, $response)){ + return $response; + } + $data = [ 'vendor' => $vendor, 'method' => 'PUT', diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 72ffce9c8bf3..1142338b200e 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -28,6 +28,7 @@ class Kernel extends HttpKernel { protected $routeMiddleware = [ 'auth' => 'App\Http\Middleware\Authenticate', 'auth.basic' => 'Illuminate\Auth\Middleware\AuthenticateWithBasicAuth', + 'permissions.required' => 'App\Http\Middleware\PermissionsRequired', 'guest' => 'App\Http\Middleware\RedirectIfAuthenticated', 'api' => 'App\Http\Middleware\ApiCheck', ]; diff --git a/app/Http/Middleware/PermissionsRequired.php b/app/Http/Middleware/PermissionsRequired.php new file mode 100644 index 000000000000..af0e0015a37f --- /dev/null +++ b/app/Http/Middleware/PermissionsRequired.php @@ -0,0 +1,57 @@ + [action => permission] + */ + static protected $actions = []; + + /** + * Handle an incoming request. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + */ + public function handle($request, Closure $next, $guard = 'user') + { + // Get the current route. + $route = $request->route(); + + // Get the current route actions. + $actions = $route->getAction(); + + // Check if we have any permissions to check the user has. + if ($permissions = !empty($actions['permissions']) ? $actions['permissions'] : null) + { + if(!Auth::user($guard)->hasPermission($permissions, !empty($actions['permissions_require_all']))){ + return response('Unauthorized.', 401); + } + } + + // Check controller permissions + $action = explode('@', $request->route()->getActionName()); + if(isset(static::$actions[$action[0]]) && isset(static::$actions[$action[0]][$action[1]])) { + $controller_permissions = static::$actions[$action[0]][$action[1]]; + if(!Auth::user($guard)->hasPermission($controller_permissions)){ + return response('Unauthorized.', 401); + } + } + + return $next($request); + } + + /** + * add a controller's action permission + * + * @param \App\Http\Controllers\Controller $controller + * @param array $permissions + */ + public static function addPermission(\App\Http\Controllers\Controller $controller, $permissions) + { + static::$actions[get_class($controller)] = $permissions; + } +} diff --git a/app/Http/routes.php b/app/Http/routes.php index 0c9aef7c1a6a..75e7ce9ab2ad 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -104,21 +104,9 @@ Route::group(['middleware' => 'auth:user'], function() { Route::get('view_archive/{entity_type}/{visible}', 'AccountController@setTrashVisible'); Route::get('hide_message', 'HomeController@hideMessage'); Route::get('force_inline_pdf', 'UserController@forcePDFJS'); - - Route::get('api/users', array('as'=>'api.users', 'uses'=>'UserController@getDatatable')); - Route::resource('users', 'UserController'); - Route::post('users/bulk', 'UserController@bulk'); - Route::get('send_confirmation/{user_id}', 'UserController@sendConfirmation'); - Route::get('start_trial', 'AccountController@startTrial'); - 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('/manage_companies', 'UserController@manageCompanies'); - - Route::get('api/tokens', array('as'=>'api.tokens', 'uses'=>'TokenController@getDatatable')); - Route::resource('tokens', 'TokenController'); - Route::post('tokens/bulk', 'TokenController@bulk'); + + Route::get('settings/user_details', 'AccountController@showUserDetails'); + Route::post('settings/user_details', 'AccountController@saveUserDetails'); Route::get('api/products', array('as'=>'api.products', 'uses'=>'ProductController@getDatatable')); Route::resource('products', 'ProductController'); @@ -128,39 +116,6 @@ Route::group(['middleware' => 'auth:user'], function() { Route::resource('tax_rates', 'TaxRateController'); Route::post('tax_rates/bulk', 'TaxRateController@bulk'); - Route::get('company/{section}/{subSection?}', 'AccountController@redirectLegacy'); - Route::get('settings/data_visualizations', 'ReportController@d3'); - Route::get('settings/charts_and_reports', 'ReportController@showReports'); - Route::post('settings/charts_and_reports', 'ReportController@showReports'); - - Route::post('settings/cancel_account', 'AccountController@cancelAccount'); - Route::post('settings/company_details', 'AccountController@updateDetails'); - Route::get('settings/{section?}', 'AccountController@showSection'); - Route::post('settings/{section?}', 'AccountController@doSection'); - - //Route::get('api/payment_terms', array('as'=>'api.payment_terms', 'uses'=>'PaymentTermController@getDatatable')); - //Route::resource('payment_terms', 'PaymentTermController'); - //Route::post('payment_terms/bulk', 'PaymentTermController@bulk'); - - Route::get('account/getSearchData', array('as' => 'getSearchData', 'uses' => 'AccountController@getSearchData')); - Route::post('user/setTheme', 'UserController@setTheme'); - Route::post('remove_logo', 'AccountController@removeLogo'); - Route::post('account/go_pro', 'AccountController@enableProPlan'); - - Route::post('/export', 'ExportController@doExport'); - Route::post('/import', 'ImportController@doImport'); - Route::post('/import_csv', 'ImportController@doImportCSV'); - - Route::resource('gateways', 'AccountGatewayController'); - Route::get('api/gateways', array('as'=>'api.gateways', 'uses'=>'AccountGatewayController@getDatatable')); - Route::post('account_gateways/bulk', 'AccountGatewayController@bulk'); - - Route::resource('bank_accounts', 'BankAccountController'); - Route::get('api/bank_accounts', array('as'=>'api.bank_accounts', 'uses'=>'BankAccountController@getDatatable')); - Route::post('bank_accounts/bulk', 'BankAccountController@bulk'); - Route::post('bank_accounts/validate', 'BankAccountController@validateAccount'); - Route::post('bank_accounts/import_expenses/{bank_id}', 'BankAccountController@importExpenses'); - Route::resource('clients', 'ClientController'); Route::get('api/clients', array('as'=>'api.clients', 'uses'=>'ClientController@getDatatable')); Route::get('api/activities/{client_id?}', array('as'=>'api.activities', 'uses'=>'ActivityController@getDatatable')); @@ -222,6 +177,59 @@ Route::group(['middleware' => 'auth:user'], function() { Route::post('expenses/bulk', 'ExpenseController@bulk'); }); +Route::group([ + 'middleware' => ['auth:user', 'permissions.required'], + 'permissions' => 'admin', +], function() { + Route::get('api/users', array('as'=>'api.users', 'uses'=>'UserController@getDatatable')); + Route::resource('users', 'UserController'); + Route::post('users/bulk', 'UserController@bulk'); + Route::get('send_confirmation/{user_id}', 'UserController@sendConfirmation'); + Route::get('start_trial', 'AccountController@startTrial'); + 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('/manage_companies', 'UserController@manageCompanies'); + + Route::get('api/tokens', array('as'=>'api.tokens', 'uses'=>'TokenController@getDatatable')); + Route::resource('tokens', 'TokenController'); + Route::post('tokens/bulk', 'TokenController@bulk'); + + Route::get('company/{section}/{subSection?}', 'AccountController@redirectLegacy'); + Route::get('settings/data_visualizations', 'ReportController@d3'); + Route::get('settings/charts_and_reports', 'ReportController@showReports'); + Route::post('settings/charts_and_reports', 'ReportController@showReports'); + + Route::post('settings/cancel_account', 'AccountController@cancelAccount'); + Route::post('settings/company_details', 'AccountController@updateDetails'); + Route::get('settings/{section?}', 'AccountController@showSection'); + Route::post('settings/{section?}', 'AccountController@doSection'); + + //Route::get('api/payment_terms', array('as'=>'api.payment_terms', 'uses'=>'PaymentTermController@getDatatable')); + //Route::resource('payment_terms', 'PaymentTermController'); + //Route::post('payment_terms/bulk', 'PaymentTermController@bulk'); + + Route::get('account/getSearchData', array('as' => 'getSearchData', 'uses' => 'AccountController@getSearchData')); + Route::post('user/setTheme', 'UserController@setTheme'); + Route::post('remove_logo', 'AccountController@removeLogo'); + Route::post('account/go_pro', 'AccountController@enableProPlan'); + + Route::post('/export', 'ExportController@doExport'); + Route::post('/import', 'ImportController@doImport'); + Route::post('/import_csv', 'ImportController@doImportCSV'); + + Route::resource('gateways', 'AccountGatewayController'); + Route::get('api/gateways', array('as'=>'api.gateways', 'uses'=>'AccountGatewayController@getDatatable')); + Route::post('account_gateways/bulk', 'AccountGatewayController@bulk'); + + Route::resource('bank_accounts', 'BankAccountController'); + Route::get('api/bank_accounts', array('as'=>'api.bank_accounts', 'uses'=>'BankAccountController@getDatatable')); + Route::post('bank_accounts/bulk', 'BankAccountController@bulk'); + Route::post('bank_accounts/validate', 'BankAccountController@validateAccount'); + Route::post('bank_accounts/import_expenses/{bank_id}', 'BankAccountController@importExpenses'); +}); + // Route groups for API Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function() { @@ -604,6 +612,7 @@ if (!defined('CONTACT_EMAIL')) { define('USER_STATE_PENDING', 'pending'); define('USER_STATE_DISABLED', 'disabled'); define('USER_STATE_ADMIN', 'admin'); + define('USER_STATE_OWNER', 'owner'); define('API_SERIALIZER_ARRAY', 'array'); define('API_SERIALIZER_JSON', 'json'); diff --git a/app/Libraries/Utils.php b/app/Libraries/Utils.php index fcb210b0d7ad..cfcc97c00114 100644 --- a/app/Libraries/Utils.php +++ b/app/Libraries/Utils.php @@ -118,6 +118,21 @@ class Utils return Auth::check() && Auth::user()->isPro(); } + public static function isAdmin() + { + return Auth::check() && Auth::user()->is_admin; + } + + public static function hasPermission($permission, $requireAll = false) + { + return Auth::check() && Auth::user()->hasPermission($permission, $requireAll); + } + + public static function hasAllPermissions($permission) + { + return Auth::check() && Auth::user()->hasPermissions($permission); + } + public static function isTrial() { return Auth::check() && Auth::user()->isTrial(); diff --git a/app/Models/EntityModel.php b/app/Models/EntityModel.php index b57eded2fb7f..6006bcbd4071 100644 --- a/app/Models/EntityModel.php +++ b/app/Models/EntityModel.php @@ -113,4 +113,40 @@ class EntityModel extends Eloquent $name = $parts[count($parts)-1]; return strtolower($name) . '_id'; } + + public static function canCreate() { + return Auth::user()->hasPermission('create_all'); + } + + public function canEdit() { + return static::canEditItem($this); + } + + public static function canEditItem($item) { + return Auth::user()->hasPermission('edit_all') || (isset($item->user_id) && Auth::user()->id == $item->user_id); + } + + public static function canEditItemById($item_id) { + if(Auth::user()->hasPermission('edit_all')) { + return true; + } + + return static::whereId($item_id)->first()->user_id == Auth::user()->id; + } + + public function canView() { + return static::canEdit($this); + } + + public static function canViewItem($item) { + return Auth::user()->hasPermission('view_all') || (isset($item->user_id) && Auth::user()->id == $item->user_id); + } + + public static function canViewItemById($item_id) { + if(Auth::user()->hasPermission('view_all')) { + return true; + } + + return static::whereId($item_id)->first()->user_id == Auth::user()->id; + } } diff --git a/app/Models/Product.php b/app/Models/Product.php index 6f03676618e1..e00d0feebec8 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -21,4 +21,8 @@ class Product extends EntityModel { return $this->belongsTo('App\Models\TaxRate'); } + + public function canEdit() { + return Auth::user()->hasPermission('admin'); + } } diff --git a/app/Models/TaxRate.php b/app/Models/TaxRate.php index 7adf6f768b5f..15c39a7572a0 100644 --- a/app/Models/TaxRate.php +++ b/app/Models/TaxRate.php @@ -16,4 +16,8 @@ class TaxRate extends EntityModel { return ENTITY_TAX_RATE; } + + public function canEdit() { + return Auth::user()->hasPermission('admin'); + } } diff --git a/app/Models/User.php b/app/Models/User.php index 9f66f0d2af8f..1b2ae7815a36 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -14,7 +14,12 @@ use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; use Illuminate\Database\Eloquent\SoftDeletes; class User extends Model implements AuthenticatableContract, CanResetPasswordContract { - + public static $all_permissions = array( + 'create_all' => 0b0001, + 'view_all' => 0b0010, + 'edit_all' => 0b0100, + ); + use Authenticatable, CanResetPassword; /** @@ -253,7 +258,69 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon && $this->email != $this->getOriginal('email') && $this->getOriginal('confirmed'); } + + + + /** + * Set the permissions attribute on the model. + * + * @param mixed $value + * @return $this + */ + protected function setPermissionsAttribute($value){ + if(empty($value)) { + $this->attributes['permissions'] = 0; + } else { + $bitmask = 0; + foreach($value as $permission){ + $bitmask = $bitmask | static::$all_permissions[$permission]; + } + $this->attributes['permissions'] = $bitmask; + } + + return $this; + } + + /** + * Expands the value of the permissions attribute + * + * @param mixed $value + * @return mixed + */ + protected function getPermissionsAttribute($value){ + $permissions = array(); + foreach(static::$all_permissions as $permission => $bitmask){ + if(($value & $bitmask) == $bitmask) { + $permissions[$permission] = $permission; + } + } + + return $permissions; + } + + /** + * Checks to see if the user has the required permission + * + * @param mixed $permission Either a single permission or an array of possible permissions + * @param boolean True to require all permissions, false to require only one + * @return boolean + */ + public function hasPermission($permission, $requireAll = false){ + if ($this->is_admin) { + return true; + } else if(is_string($permission)){ + return !empty($this->permissions[$permission]); + } else if(is_array($permission)) { + if($requireAll){ + return count(array_diff($permission, $this->permissions)) == 0; + } else { + return count(array_intersect($permission, $this->permissions)) > 0; + } + } + + return false; + } } User::updating(function ($user) { diff --git a/app/Ninja/Repositories/ClientRepository.php b/app/Ninja/Repositories/ClientRepository.php index 2e8ce31b5c1c..9df81663a5e5 100644 --- a/app/Ninja/Repositories/ClientRepository.php +++ b/app/Ninja/Repositories/ClientRepository.php @@ -46,7 +46,8 @@ class ClientRepository extends BaseRepository 'clients.work_phone', 'contacts.email', 'clients.deleted_at', - 'clients.is_deleted' + 'clients.is_deleted', + 'clients.user_id' ); if (!\Session::get('show_trash:client')) { diff --git a/app/Ninja/Repositories/CreditRepository.php b/app/Ninja/Repositories/CreditRepository.php index 1c33cb19e41d..7252a7ee0e4f 100644 --- a/app/Ninja/Repositories/CreditRepository.php +++ b/app/Ninja/Repositories/CreditRepository.php @@ -37,7 +37,8 @@ class CreditRepository extends BaseRepository 'contacts.email', 'credits.private_notes', 'credits.deleted_at', - 'credits.is_deleted' + 'credits.is_deleted', + 'credits.user_id' ); if ($clientPublicId) { diff --git a/app/Ninja/Repositories/ExpenseRepository.php b/app/Ninja/Repositories/ExpenseRepository.php index cfd312622ad3..4d5f0d590af4 100644 --- a/app/Ninja/Repositories/ExpenseRepository.php +++ b/app/Ninja/Repositories/ExpenseRepository.php @@ -40,7 +40,8 @@ class ExpenseRepository extends BaseRepository 'expenses.public_id', 'expenses.deleted_at', 'expenses.should_be_invoiced', - 'expenses.created_at' + 'expenses.created_at', + 'expenses.user_id' ); return $query; diff --git a/app/Ninja/Repositories/InvoiceRepository.php b/app/Ninja/Repositories/InvoiceRepository.php index f028acdf930a..e577d93ac58a 100644 --- a/app/Ninja/Repositories/InvoiceRepository.php +++ b/app/Ninja/Repositories/InvoiceRepository.php @@ -65,7 +65,8 @@ class InvoiceRepository extends BaseRepository 'invoices.quote_invoice_id', 'invoices.deleted_at', 'invoices.is_deleted', - 'invoices.partial' + 'invoices.partial', + 'invoices.user_id' ); if (!\Session::get('show_trash:'.$entityType)) { diff --git a/app/Ninja/Repositories/PaymentRepository.php b/app/Ninja/Repositories/PaymentRepository.php index 15f5007251c3..838def01d338 100644 --- a/app/Ninja/Repositories/PaymentRepository.php +++ b/app/Ninja/Repositories/PaymentRepository.php @@ -47,6 +47,7 @@ class PaymentRepository extends BaseRepository 'payments.account_gateway_id', 'payments.deleted_at', 'payments.is_deleted', + 'payments.user_id', 'invoices.is_deleted as invoice_is_deleted', 'gateways.name as gateway_name' ); diff --git a/app/Ninja/Repositories/TaskRepository.php b/app/Ninja/Repositories/TaskRepository.php index 47a052378ff1..8f913c705a0d 100644 --- a/app/Ninja/Repositories/TaskRepository.php +++ b/app/Ninja/Repositories/TaskRepository.php @@ -38,7 +38,8 @@ class TaskRepository 'invoices.public_id as invoice_public_id', 'tasks.is_running', 'tasks.time_log', - 'tasks.created_at' + 'tasks.created_at', + 'tasks.user_id' ); if ($clientPublicId) { diff --git a/app/Ninja/Repositories/UserRepository.php b/app/Ninja/Repositories/UserRepository.php index 5675a161a6ff..ca5878b0c188 100644 --- a/app/Ninja/Repositories/UserRepository.php +++ b/app/Ninja/Repositories/UserRepository.php @@ -22,7 +22,7 @@ class UserRepository extends BaseRepository $query->where('users.deleted_at', '=', null); } - $query->select('users.public_id', 'users.first_name', 'users.last_name', 'users.email', 'users.confirmed', 'users.public_id', 'users.deleted_at'); + $query->select('users.public_id', 'users.first_name', 'users.last_name', 'users.email', 'users.confirmed', 'users.public_id', 'users.deleted_at', 'users.is_admin', 'users.permissions'); return $query; } @@ -34,5 +34,4 @@ class UserRepository extends BaseRepository return $user; } - } diff --git a/app/Ninja/Repositories/VendorRepository.php b/app/Ninja/Repositories/VendorRepository.php index 81fc8fc7f47b..df885f62e12e 100644 --- a/app/Ninja/Repositories/VendorRepository.php +++ b/app/Ninja/Repositories/VendorRepository.php @@ -42,7 +42,8 @@ class VendorRepository extends BaseRepository 'vendors.city', 'vendor_contacts.email', 'vendors.deleted_at', - 'vendors.is_deleted' + 'vendors.is_deleted', + 'vendors.user_id' ); if (!\Session::get('show_trash:vendor')) { diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index e5fcf4540f10..3cd691098747 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -8,6 +8,9 @@ use Form; use URL; use Request; use Validator; +use App\Models\Credit; +use App\Models\Invoice; +use App\Models\Vendor; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { @@ -46,31 +49,38 @@ class AppServiceProvider extends ServiceProvider { $class = ( Request::is($types) || Request::is('*'.$type.'*')) && !Request::is('*settings*') ? ' active' : ''; $str = ''; + $str .= ''; return $str; }); diff --git a/app/Services/ClientService.php b/app/Services/ClientService.php index 21242da54a6b..662fc8eab9e8 100644 --- a/app/Services/ClientService.php +++ b/app/Services/ClientService.php @@ -4,6 +4,12 @@ use Utils; use URL; use Auth; use App\Services\BaseService; +use App\Models\Client; +use App\Models\Invoice; +use App\Models\Credit; +use App\Models\Expense; +use App\Models\Payment; +use App\Models\Task; use App\Ninja\Repositories\ClientRepository; use App\Ninja\Repositories\NinjaRepository; @@ -37,6 +43,10 @@ class ClientService extends BaseService { $query = $this->clientRepo->find($search); + if(!Utils::hasPermission('view_all')){ + $query->where('clients.user_id', '=', Auth::user()->id); + } + return $this->createDatatable(ENTITY_CLIENT, $query); } @@ -89,19 +99,33 @@ class ClientService extends BaseService trans('texts.edit_client'), function ($model) { return URL::to("clients/{$model->public_id}/edit"); + }, + function ($model) { + return Client::canEditItem($model); + } + ], + [ + '--divider--', function(){return false;}, + function ($model) { + return Client::canEditItem($model) && (Task::canCreate() || Invoice::canCreate()); } ], - [], [ trans('texts.new_task'), function ($model) { return URL::to("tasks/create/{$model->public_id}"); + }, + function ($model) { + return Task::canCreate(); } ], [ trans('texts.new_invoice'), function ($model) { return URL::to("invoices/create/{$model->public_id}"); + }, + function ($model) { + return Invoice::canCreate(); } ], [ @@ -110,26 +134,40 @@ class ClientService extends BaseService return URL::to("quotes/create/{$model->public_id}"); }, function ($model) { - return Auth::user()->isPro(); + return Auth::user()->isPro() && Invoice::canCreate(); + } + ], + [ + '--divider--', function(){return false;}, + function ($model) { + return (Task::canCreate() || Invoice::canCreate()) && (Payment::canCreate() || Credit::canCreate() || Expense::canCreate()); } ], - [], [ trans('texts.enter_payment'), function ($model) { return URL::to("payments/create/{$model->public_id}"); + }, + function ($model) { + return Payment::canCreate(); } ], [ trans('texts.enter_credit'), function ($model) { return URL::to("credits/create/{$model->public_id}"); + }, + function ($model) { + return Credit::canCreate(); } ], [ trans('texts.enter_expense'), function ($model) { return URL::to("expenses/create/0/{$model->public_id}"); + }, + function ($model) { + return Expense::canCreate(); } ] ]; diff --git a/app/Services/DatatableService.php b/app/Services/DatatableService.php index 285efe717aef..456c619aef04 100644 --- a/app/Services/DatatableService.php +++ b/app/Services/DatatableService.php @@ -3,6 +3,7 @@ use HtmlString; use Utils; use Datatable; +use Auth; class DatatableService { @@ -45,6 +46,8 @@ class DatatableService $hasAction = false; $str = '
'; + $can_edit = Auth::user()->hasPermission('edit_all') || (isset($model->user_id) && Auth::user()->id == $model->user_id); + if (property_exists($model, 'is_deleted') && $model->is_deleted) { $str .= ''; } elseif ($model->deleted_at && $model->deleted_at !== '0000-00-00') { @@ -70,9 +73,15 @@ class DatatableService } list($value, $url, $visible) = $action; if ($visible($model)) { - $str .= "
  • {$value}
  • "; - $lastIsDivider = false; - $hasAction = true; + if($value == '--divider--'){ + $str .= "
  • "; + $lastIsDivider = true; + } + else { + $str .= "
  • {$value}
  • "; + $hasAction = true; + $lastIsDivider = false; + } } } elseif ( ! $lastIsDivider) { $str .= "
  • "; @@ -84,20 +93,20 @@ class DatatableService return ''; } - if ( ! $lastIsDivider) { + if ( $can_edit && ! $lastIsDivider) { $str .= "
  • "; } - if ($entityType != ENTITY_USER || $model->public_id) { + if (($entityType != ENTITY_USER || $model->public_id) && $can_edit) { $str .= "
  • public_id})\">" . trans("texts.archive_{$entityType}") . "
  • "; } - } else { + } else if($can_edit) { $str .= "
  • public_id})\">" . trans("texts.restore_{$entityType}") . "
  • "; } - if (property_exists($model, 'is_deleted') && !$model->is_deleted) { + if (property_exists($model, 'is_deleted') && !$model->is_deleted && $can_edit) { $str .= "
  • public_id})\">" . trans("texts.delete_{$entityType}") . "
  • "; } diff --git a/app/Services/ExpenseService.php b/app/Services/ExpenseService.php index 9f094240d09f..e2150ac96829 100644 --- a/app/Services/ExpenseService.php +++ b/app/Services/ExpenseService.php @@ -1,10 +1,13 @@ expenseRepo->find($search); + if(!Utils::hasPermission('view_all')){ + $query->where('expenses.user_id', '=', Auth::user()->id); + } + return $this->createDatatable(ENTITY_EXPENSE, $query); } @@ -151,6 +158,9 @@ class ExpenseService extends BaseService trans('texts.edit_expense'), function ($model) { return URL::to("expenses/{$model->public_id}/edit") ; + }, + function ($model) { + return Expense::canEditItem($model); } ], [ @@ -159,7 +169,7 @@ class ExpenseService extends BaseService return URL::to("/invoices/{$model->invoice_public_id}/edit"); }, function ($model) { - return $model->invoice_public_id; + return $model->invoice_public_id && Invoice::canEditItemById($model->invoice_public_id); } ], [ @@ -168,7 +178,7 @@ class ExpenseService extends BaseService return "javascript:invoiceEntity({$model->public_id})"; }, function ($model) { - return ! $model->invoice_id && (!$model->deleted_at || $model->deleted_at == '0000-00-00'); + return ! $model->invoice_id && (!$model->deleted_at || $model->deleted_at == '0000-00-00') && Invoice::canCreate(); } ], ]; diff --git a/app/Services/InvoiceService.php b/app/Services/InvoiceService.php index 522c1a5dffc3..aea19ffc0803 100644 --- a/app/Services/InvoiceService.php +++ b/app/Services/InvoiceService.php @@ -8,6 +8,8 @@ use App\Ninja\Repositories\InvoiceRepository; use App\Ninja\Repositories\ClientRepository; use App\Events\QuoteInvitationWasApproved; use App\Models\Invitation; +use App\Models\Invoice; +use App\Models\Payment; class InvoiceService extends BaseService { @@ -109,6 +111,10 @@ class InvoiceService extends BaseService $query = $this->invoiceRepo->getInvoices($accountId, $clientPublicId, $entityType, $search) ->where('invoices.is_quote', '=', $entityType == ENTITY_QUOTE ? true : false); + if(!Utils::hasPermission('view_all')){ + $query->where('invoices.user_id', '=', Auth::user()->id); + } + return $this->createDatatable($entityType, $query, !$clientPublicId); } @@ -174,12 +180,18 @@ class InvoiceService extends BaseService trans("texts.edit_{$entityType}"), function ($model) use ($entityType) { return URL::to("{$entityType}s/{$model->public_id}/edit"); + }, + function ($model) { + return Invoice::canEditItem($model); } ], [ trans("texts.clone_{$entityType}"), function ($model) use ($entityType) { return URL::to("{$entityType}s/{$model->public_id}/clone"); + }, + function ($model) { + return Invoice::canCreate(); } ], [ @@ -188,14 +200,19 @@ class InvoiceService extends BaseService return URL::to("{$entityType}s/{$entityType}_history/{$model->public_id}"); } ], - [], + [ + '--divider--', function(){return false;}, + function ($model) { + return Invoice::canEditItem($model) || Payment::canCreate(); + } + ], [ trans("texts.mark_sent"), function ($model) { return "javascript:markEntity({$model->public_id})"; }, function ($model) { - return $model->invoice_status_id < INVOICE_STATUS_SENT; + return $model->invoice_status_id < INVOICE_STATUS_SENT && Invoice::canEditItem($model); } ], [ @@ -204,7 +221,7 @@ class InvoiceService extends BaseService return URL::to("payments/create/{$model->client_public_id}/{$model->public_id}"); }, function ($model) use ($entityType) { - return $entityType == ENTITY_INVOICE && $model->balance > 0; + return $entityType == ENTITY_INVOICE && $model->balance > 0 && Payment::canCreate(); } ], [ @@ -213,7 +230,7 @@ class InvoiceService extends BaseService return URL::to("quotes/{$model->quote_id}/edit"); }, function ($model) use ($entityType) { - return $entityType == ENTITY_INVOICE && $model->quote_id; + return $entityType == ENTITY_INVOICE && $model->quote_id && Invoice::canEditItem($model); } ], [ @@ -222,7 +239,7 @@ class InvoiceService extends BaseService return URL::to("invoices/{$model->quote_invoice_id}/edit"); }, function ($model) use ($entityType) { - return $entityType == ENTITY_QUOTE && $model->quote_invoice_id; + return $entityType == ENTITY_QUOTE && $model->quote_invoice_id && Invoice::canEditItem($model); } ], [ @@ -231,7 +248,7 @@ class InvoiceService extends BaseService return "javascript:convertEntity({$model->public_id})"; }, function ($model) use ($entityType) { - return $entityType == ENTITY_QUOTE && ! $model->quote_invoice_id; + return $entityType == ENTITY_QUOTE && ! $model->quote_invoice_id && Invoice::canEditItem($model); } ] ]; diff --git a/app/Services/PaymentService.php b/app/Services/PaymentService.php index 83429f5b2068..10db86800628 100644 --- a/app/Services/PaymentService.php +++ b/app/Services/PaymentService.php @@ -1,6 +1,7 @@ paymentRepo->find($clientPublicId, $search); + if(!Utils::hasPermission('view_all')){ + $query->where('payments.user_id', '=', Auth::user()->id); + } + return $this->createDatatable(ENTITY_PAYMENT, $query, !$clientPublicId); } @@ -339,6 +344,9 @@ class PaymentService extends BaseService trans('texts.edit_payment'), function ($model) { return URL::to("payments/{$model->public_id}/edit"); + }, + function ($model) { + return Payment::canEditItem($model); } ] ]; diff --git a/app/Services/RecurringInvoiceService.php b/app/Services/RecurringInvoiceService.php index bd23d2a5e048..2786ccfc6869 100644 --- a/app/Services/RecurringInvoiceService.php +++ b/app/Services/RecurringInvoiceService.php @@ -1,7 +1,9 @@ invoiceRepo->getRecurringInvoices($accountId, $clientPublicId, $search); + if(!Utils::hasPermission('view_all')){ + $query->where('invoices.user_id', '=', Auth::user()->id); + } + return $this->createDatatable(ENTITY_RECURRING_INVOICE, $query, !$clientPublicId); } @@ -66,6 +72,9 @@ class RecurringInvoiceService extends BaseService trans('texts.edit_invoice'), function ($model) { return URL::to("invoices/{$model->public_id}/edit"); + }, + function ($model) { + return Invoice::canEditItem($model); } ] ]; diff --git a/app/Services/TaskService.php b/app/Services/TaskService.php index 59f8fd95feb0..ae575e3b2a7b 100644 --- a/app/Services/TaskService.php +++ b/app/Services/TaskService.php @@ -1,8 +1,10 @@ taskRepo->find($clientPublicId, $search); + if(!Utils::hasPermission('view_all')){ + $query->where('tasks.user_id', '=', Auth::user()->id); + } + return $this->createDatatable(ENTITY_TASK, $query, !$clientPublicId); } @@ -82,7 +88,7 @@ class TaskService extends BaseService return URL::to('tasks/'.$model->public_id.'/edit'); }, function ($model) { - return !$model->deleted_at || $model->deleted_at == '0000-00-00'; + return (!$model->deleted_at || $model->deleted_at == '0000-00-00') && Task::canEditItem($model); } ], [ @@ -91,7 +97,7 @@ class TaskService extends BaseService return URL::to("/invoices/{$model->invoice_public_id}/edit"); }, function ($model) { - return $model->invoice_number; + return $model->invoice_number && Invoice::canEditItemById($model->invoice_number); } ], [ @@ -100,7 +106,7 @@ class TaskService extends BaseService return "javascript:stopTask({$model->public_id})"; }, function ($model) { - return $model->is_running; + return $model->is_running && Task::canEditItem($model); } ], [ @@ -109,7 +115,7 @@ class TaskService extends BaseService return "javascript:invoiceEntity({$model->public_id})"; }, function ($model) { - return ! $model->invoice_number && (!$model->deleted_at || $model->deleted_at == '0000-00-00'); + return ! $model->invoice_number && (!$model->deleted_at || $model->deleted_at == '0000-00-00') && Invoice::canCreate(); } ] ]; diff --git a/app/Services/UserService.php b/app/Services/UserService.php index fcc022003658..8e31b4c30d2d 100644 --- a/app/Services/UserService.php +++ b/app/Services/UserService.php @@ -53,11 +53,15 @@ class UserService extends BaseService 'confirmed', function ($model) { if (!$model->public_id) { - return self::getStatusLabel(USER_STATE_ADMIN); + return self::getStatusLabel(USER_STATE_OWNER); } elseif ($model->deleted_at) { return self::getStatusLabel(USER_STATE_DISABLED); } elseif ($model->confirmed) { - return self::getStatusLabel(USER_STATE_ACTIVE); + if($model->is_admin){ + return self::getStatusLabel(USER_STATE_ADMIN); + } else { + return self::getStatusLabel(USER_STATE_ACTIVE); + } } else { return self::getStatusLabel(USER_STATE_PENDING); } @@ -96,17 +100,20 @@ class UserService extends BaseService $class = 'default'; switch ($state) { case USER_STATE_PENDING: - $class = 'info'; + $class = 'default'; break; case USER_STATE_ACTIVE: - $class = 'primary'; + $class = 'info'; break; case USER_STATE_DISABLED: $class = 'warning'; break; - case USER_STATE_ADMIN: + case USER_STATE_OWNER: $class = 'success'; break; + case USER_STATE_ADMIN: + $class = 'primary'; + break; } return "

    $label

    "; } diff --git a/app/Services/VendorService.php b/app/Services/VendorService.php index cd2dcf8d1844..44e27ce44f2a 100644 --- a/app/Services/VendorService.php +++ b/app/Services/VendorService.php @@ -3,6 +3,8 @@ use Utils; use URL; use Auth; +use App\Models\Vendor; +use App\Models\Expense; use App\Services\BaseService; use App\Ninja\Repositories\VendorRepository; use App\Ninja\Repositories\NinjaRepository; @@ -83,13 +85,25 @@ class VendorService extends BaseService trans('texts.edit_vendor'), function ($model) { return URL::to("vendors/{$model->public_id}/edit"); + }, + function ($model) { + return Vendor::canEditItem($model); } ], - [], + [ + '--divider--', function(){return false;}, + function ($model) { + return Vendor::canEditItem($model) && Expense::canCreate(); + } + + ], [ trans('texts.enter_expense'), function ($model) { return URL::to("expenses/create/{$model->public_id}"); + }, + function ($model) { + return Expense::canCreate(); } ] ]; diff --git a/database/migrations/2016_03_14_066181_add_user_permissions.php b/database/migrations/2016_03_14_066181_add_user_permissions.php new file mode 100644 index 000000000000..2cdd5994825d --- /dev/null +++ b/database/migrations/2016_03_14_066181_add_user_permissions.php @@ -0,0 +1,33 @@ +boolean('is_admin')->default(true); + $table->unsignedInteger('permissions')->default(0); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function($table) { + $table->dropColumn('is_admin'); + $table->dropColumn('permissions'); + }); + } +} diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index e587699b927f..217841af7c05 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -1063,6 +1063,14 @@ $LANG = array( 'invalid_expiry' => 'The expiration date is not valid.', 'invalid_cvv' => 'The CVV is not valid.', + // User Permissions + 'owner' => 'Owner', + 'administrator' => 'Administrator', + 'administrator_help' => 'Allow user to manage users, change settings, and view and modify all data', + 'user_create_all' => 'Create clients, invoices, etc.', + 'user_view_all' => 'View All clients, invoices, etc.', + 'user_edit_all' => 'Edit all clients, invoices, etc.', + ); return $LANG; diff --git a/resources/views/accounts/user_details.blade.php b/resources/views/accounts/user_details.blade.php index 57bfdb8eb5a4..c9426a8b3bf4 100644 --- a/resources/views/accounts/user_details.blade.php +++ b/resources/views/accounts/user_details.blade.php @@ -21,7 +21,9 @@ {{ Former::populateField('referral_code', true) }} @endif - @include('accounts.nav', ['selected' => ACCOUNT_USER_DETAILS]) + @if (Utils::isAdmin()) + @include('accounts.nav', ['selected' => ACCOUNT_USER_DETAILS]) + @endif
    diff --git a/resources/views/clients/show.blade.php b/resources/views/clients/show.blade.php index ee7ac115dfc4..3166f9b800da 100644 --- a/resources/views/clients/show.blade.php +++ b/resources/views/clients/show.blade.php @@ -43,8 +43,11 @@ @endif @if ($client->trashed()) - {!! Button::primary(trans('texts.restore_client'))->withAttributes(['onclick' => 'onRestoreClick()']) !!} + @if ($client->canEdit()) + {!! Button::primary(trans('texts.restore_client'))->withAttributes(['onclick' => 'onRestoreClick()']) !!} + @endif @else + @if ($client->canEdit()) {!! DropdownButton::normal(trans('texts.edit_client')) ->withAttributes(['class'=>'normalDropDown']) ->withContents([ @@ -52,10 +55,12 @@ ['label' => trans('texts.delete_client'), 'url' => "javascript:onDeleteClick()"], ] )->split() !!} - - {!! DropdownButton::primary(trans('texts.new_invoice')) - ->withAttributes(['class'=>'primaryDropDown']) - ->withContents($actionLinks)->split() !!} + @endif + @if (\App\Models\Invoice::canCreate()) + {!! DropdownButton::primary(trans('texts.new_invoice')) + ->withAttributes(['class'=>'primaryDropDown']) + ->withContents($actionLinks)->split() !!} + @endif @endif {!! Former::close() !!} diff --git a/resources/views/header.blade.php b/resources/views/header.blade.php index 46bebbd68732..6fa6cb75a800 100644 --- a/resources/views/header.blade.php +++ b/resources/views/header.blade.php @@ -472,11 +472,13 @@ 'selected' => true, ]) @endif -
  • - @if (count(session(SESSION_USER_ACCOUNTS)) > 1) -
  • {!! link_to('/manage_companies', trans('texts.manage_companies')) !!}
  • - @elseif (!session(SESSION_USER_ACCOUNTS) || count(session(SESSION_USER_ACCOUNTS)) < 5) -
  • {!! link_to('/login?new_company=true', trans('texts.add_company')) !!}
  • +
  • + @if (Utils::isAdmin()) + @if (count(session(SESSION_USER_ACCOUNTS)) > 1) +
  • {!! link_to('/manage_companies', trans('texts.manage_companies')) !!}
  • + @elseif (!session(SESSION_USER_ACCOUNTS) || count(session(SESSION_USER_ACCOUNTS)) < 5) +
  • {!! link_to('/login?new_company=true', trans('texts.add_company')) !!}
  • + @endif @endif
  • {!! link_to('#', trans('texts.logout'), array('onclick'=>'logout()')) !!}
  • @@ -490,10 +492,14 @@ diff --git a/resources/views/list.blade.php b/resources/views/list.blade.php index 6e567bf3de75..ffe6c12bc1bc 100644 --- a/resources/views/list.blade.php +++ b/resources/views/list.blade.php @@ -38,7 +38,9 @@ {!! 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')) !!} + @if (Auth::user()->hasPermission('create_all')) + {!! Button::primary(trans("texts.new_$entityType"))->asLinkTo(URL::to("/{$entityType}s/create"))->appendIcon(Icon::create('plus-sign')) !!} + @endif
    diff --git a/resources/views/user_account.blade.php b/resources/views/user_account.blade.php index 9ab41f845b7a..9be002ada05c 100644 --- a/resources/views/user_account.blade.php +++ b/resources/views/user_account.blade.php @@ -1,8 +1,12 @@
  • - @if (isset($user_id) && $user_id != Auth::user()->id) - - @else - + @if (Utils::isAdmin()) + @if (isset($user_id) && $user_id != Auth::user()->id) + + @else + + @endif + @else + @endif @if (file_exists($logo_path)) @@ -23,7 +27,6 @@ @if (isset($selected) && $selected) @endif -
  • \ No newline at end of file diff --git a/resources/views/users/edit.blade.php b/resources/views/users/edit.blade.php index 71efd2fee6a8..3e77e9144283 100644 --- a/resources/views/users/edit.blade.php +++ b/resources/views/users/edit.blade.php @@ -23,7 +23,26 @@ {!! Former::text('first_name') !!} {!! Former::text('last_name') !!} {!! Former::text('email') !!} - + {!! Former::checkbox('is_admin') + ->label(' ') + ->text(trans('texts.administrator')) + ->help(trans('texts.administrator_help')) !!} + {!! Former::checkbox('permissions[create_all]') + ->value('create_all') + ->label(' ') + ->id('permissions_create_all') + ->text(trans('texts.user_create_all')) !!} + {!! Former::checkbox('permissions[view_all]') + ->value('view_all') + ->label(' ') + ->id('permissions_view_all') + ->text(trans('texts.user_view_all')) !!} + {!! Former::checkbox('permissions[edit_all]') + ->value('edit_all') + ->label(' ') + ->id('permissions_edit_all') + ->text(trans('texts.user_edit_all')) !!} +
    @@ -38,4 +57,14 @@ @section('onReady') $('#first_name').focus(); + $('#is_admin, #permissions_view_all').change(fixCheckboxes); + function fixCheckboxes(){ + var adminChecked = $('#is_admin').is(':checked'); + var viewChecked = $('#permissions_view_all').is(':checked'); + + $('#permissions_view_all').prop('disabled', adminChecked); + $('#permissions_create_all').prop('disabled', adminChecked); + $('#permissions_edit_all').prop('disabled', adminChecked || !viewChecked); + } + fixCheckboxes(); @stop \ No newline at end of file From 90e1f6695ce3398b83ed1635eaeb7b886d3bdb9a Mon Sep 17 00:00:00 2001 From: Joshua Dwire Date: Tue, 15 Mar 2016 22:07:11 -0400 Subject: [PATCH 02/13] Improve user permission support * Hide links based on permissions * Restrict editing/creating within other UI --- app/Http/Controllers/InvoiceController.php | 16 +++++++-- app/Http/Controllers/PaymentController.php | 10 ++++++ app/Http/Controllers/TaskController.php | 10 +++--- app/Http/Controllers/VendorController.php | 16 +++++++-- app/Models/EntityModel.php | 18 +++++++++- app/Models/Product.php | 1 + app/Ninja/Repositories/CreditRepository.php | 1 + app/Ninja/Repositories/ExpenseRepository.php | 4 +++ app/Ninja/Repositories/InvoiceRepository.php | 36 +++++++++++++------- app/Ninja/Repositories/PaymentRepository.php | 2 ++ app/Ninja/Repositories/TaskRepository.php | 2 ++ app/Services/BaseService.php | 8 +++-- app/Services/CreditService.php | 13 +++++++ app/Services/DatatableService.php | 4 ++- app/Services/ExpenseService.php | 14 +++++++- app/Services/InvoiceService.php | 28 ++++++++++++--- app/Services/PaymentService.php | 10 ++++++ app/Services/TaskService.php | 7 +++- app/Services/VendorService.php | 4 +++ resources/lang/en/texts.php | 2 +- resources/views/invoices/edit.blade.php | 9 +++-- 21 files changed, 182 insertions(+), 33 deletions(-) diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index 089953f84c67..8c31cce798c8 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -345,10 +345,16 @@ class InvoiceController extends BaseController */ public function store(SaveInvoiceWithClientRequest $request) { + $data = $request->input(); + + if(!$this->checkUpdatePermission($data, $response)){ + return $response; + } + $action = Input::get('action'); $entityType = Input::get('entityType'); - $invoice = $this->invoiceService->save($request->input()); + $invoice = $this->invoiceService->save($data, true); $entityType = $invoice->getEntityType(); $message = trans("texts.created_{$entityType}"); @@ -379,10 +385,16 @@ class InvoiceController extends BaseController */ public function update(SaveInvoiceWithClientRequest $request) { + $data = $request->input(); + + if(!$this->checkUpdatePermission($data, $response)){ + return $response; + } + $action = Input::get('action'); $entityType = Input::get('entityType'); - $invoice = $this->invoiceService->save($request->input()); + $invoice = $this->invoiceService->save($data, true); $entityType = $invoice->getEntityType(); $message = trans("texts.updated_{$entityType}"); Session::flash('message', $message); diff --git a/app/Http/Controllers/PaymentController.php b/app/Http/Controllers/PaymentController.php index 7dc13ec6ed56..7e675d023f29 100644 --- a/app/Http/Controllers/PaymentController.php +++ b/app/Http/Controllers/PaymentController.php @@ -584,6 +584,11 @@ class PaymentController extends BaseController public function store(CreatePaymentRequest $request) { $input = $request->input(); + + if(!$this->checkUpdatePermission($input, $response)){ + return $response; + } + $input['invoice_id'] = Invoice::getPrivateId($input['invoice']); $input['client_id'] = Client::getPrivateId($input['client']); $payment = $this->paymentRepo->save($input); @@ -601,6 +606,11 @@ class PaymentController extends BaseController public function update(UpdatePaymentRequest $request) { $input = $request->input(); + + if(!$this->checkUpdatePermission($input, $response)){ + return $response; + } + $payment = $this->paymentRepo->save($input); Session::flash('message', trans('texts.updated_payment')); diff --git a/app/Http/Controllers/TaskController.php b/app/Http/Controllers/TaskController.php index ea3001dfc538..46178f09ddb2 100644 --- a/app/Http/Controllers/TaskController.php +++ b/app/Http/Controllers/TaskController.php @@ -68,10 +68,6 @@ class TaskController extends BaseController */ public function store() { - if(!$this->checkCreatePermission($response)){ - return $response; - } - return $this->save(); } @@ -187,6 +183,12 @@ class TaskController extends BaseController private function save($publicId = null) { $action = Input::get('action'); + + $input = $request->input(); + + if(!$this->checkUpdatePermission($input, $response)){ + return $response; + } if (in_array($action, ['archive', 'delete', 'restore'])) { return self::bulk(); diff --git a/app/Http/Controllers/VendorController.php b/app/Http/Controllers/VendorController.php index bab1479b446e..989246f824ca 100644 --- a/app/Http/Controllers/VendorController.php +++ b/app/Http/Controllers/VendorController.php @@ -77,7 +77,13 @@ class VendorController extends BaseController */ public function store(CreateVendorRequest $request) { - $vendor = $this->vendorService->save($request->input()); + $data = $request->input(); + + if(!$this->checkUpdatePermission($data, $response)){ + return $response; + } + + $vendor = $this->vendorService->save($data); Session::flash('message', trans('texts.created_vendor')); @@ -195,7 +201,13 @@ class VendorController extends BaseController */ public function update(UpdateVendorRequest $request) { - $vendor = $this->vendorService->save($request->input()); + $data = $request->input(); + + if(!$this->checkUpdatePermission($data, $response)){ + return $response; + } + + $vendor = $this->vendorService->save($data); Session::flash('message', trans('texts.updated_vendor')); diff --git a/app/Models/EntityModel.php b/app/Models/EntityModel.php index 6006bcbd4071..aa6544e52f8b 100644 --- a/app/Models/EntityModel.php +++ b/app/Models/EntityModel.php @@ -134,8 +134,16 @@ class EntityModel extends Eloquent return static::whereId($item_id)->first()->user_id == Auth::user()->id; } + public static function canEditItemByOwner($user_id) { + if(Auth::user()->hasPermission('edit_all')) { + return true; + } + + return Auth::user()->id == $user_id; + } + public function canView() { - return static::canEdit($this); + return static::canViewItem($this); } public static function canViewItem($item) { @@ -149,4 +157,12 @@ class EntityModel extends Eloquent return static::whereId($item_id)->first()->user_id == Auth::user()->id; } + + public static function canViewItemByOwner($user_id) { + if(Auth::user()->hasPermission('view_all')) { + return true; + } + + return Auth::user()->id == $user_id; + } } diff --git a/app/Models/Product.php b/app/Models/Product.php index e00d0feebec8..4098c67063d2 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -1,5 +1,6 @@ make(); } - public function save($data) + public function save($data, $checkSubPermissions = false) { $account = \Auth::user()->account; $publicId = isset($data['public_id']) ? $data['public_id'] : false; @@ -406,29 +407,40 @@ class InvoiceRepository extends BaseRepository $task = false; if (isset($item['task_public_id']) && $item['task_public_id']) { $task = Task::scope($item['task_public_id'])->where('invoice_id', '=', null)->firstOrFail(); - $task->invoice_id = $invoice->id; - $task->client_id = $invoice->client_id; - $task->save(); + if(!$checkSubPermissions || $task->canEdit()){ + $task->invoice_id = $invoice->id; + $task->client_id = $invoice->client_id; + $task->save(); + } } $expense = false; if (isset($item['expense_public_id']) && $item['expense_public_id']) { $expense = Expense::scope($item['expense_public_id'])->where('invoice_id', '=', null)->firstOrFail(); - $expense->invoice_id = $invoice->id; - $expense->client_id = $invoice->client_id; - $expense->save(); + if(!$checkSubPermissions || $expense->canEdit()){ + $expense->invoice_id = $invoice->id; + $expense->client_id = $invoice->client_id; + $expense->save(); + } } if ($productKey = trim($item['product_key'])) { if (\Auth::user()->account->update_products && ! strtotime($productKey)) { $product = Product::findProductByKey($productKey); if (!$product) { - $product = Product::createNew(); - $product->product_key = trim($item['product_key']); + if(!$checkSubPermissions || Product::canCreate()){ + $product = Product::createNew(); + $product->product_key = trim($item['product_key']); + } + else{ + $product = null; + } + } + if($product && (!$checkSubPermissions || $product->canEdit())){ + $product->notes = ($task || $expense) ? '' : $item['notes']; + $product->cost = $expense ? 0 : $item['cost']; + $product->save(); } - $product->notes = ($task || $expense) ? '' : $item['notes']; - $product->cost = $expense ? 0 : $item['cost']; - $product->save(); } } diff --git a/app/Ninja/Repositories/PaymentRepository.php b/app/Ninja/Repositories/PaymentRepository.php index 838def01d338..a027cb62aedb 100644 --- a/app/Ninja/Repositories/PaymentRepository.php +++ b/app/Ninja/Repositories/PaymentRepository.php @@ -36,9 +36,11 @@ class PaymentRepository extends BaseRepository 'payments.transaction_reference', 'clients.name as client_name', 'clients.public_id as client_public_id', + 'clients.user_id as client_user_id', 'payments.amount', 'payments.payment_date', 'invoices.public_id as invoice_public_id', + 'invoices.user_id as invoice_user_id', 'invoices.invoice_number', 'contacts.first_name', 'contacts.last_name', diff --git a/app/Ninja/Repositories/TaskRepository.php b/app/Ninja/Repositories/TaskRepository.php index 8f913c705a0d..a7655a8abd9e 100644 --- a/app/Ninja/Repositories/TaskRepository.php +++ b/app/Ninja/Repositories/TaskRepository.php @@ -27,6 +27,7 @@ class TaskRepository 'tasks.public_id', 'clients.name as client_name', 'clients.public_id as client_public_id', + 'clients.user_id as client_user_id', 'contacts.first_name', 'contacts.email', 'contacts.last_name', @@ -36,6 +37,7 @@ class TaskRepository 'tasks.deleted_at', 'invoices.invoice_number', 'invoices.public_id as invoice_public_id', + 'invoices.user_id as invoice_user_id', 'tasks.is_running', 'tasks.time_log', 'tasks.created_at', diff --git a/app/Services/BaseService.php b/app/Services/BaseService.php index ca1944d4e5ef..1e2f99c7dc34 100644 --- a/app/Services/BaseService.php +++ b/app/Services/BaseService.php @@ -5,6 +5,8 @@ use App\Services\DatatableService; class BaseService { + public static $bulk_actions = array('archive', 'restore', 'delete'); + use DispatchesJobs; protected function getRepo() @@ -14,14 +16,16 @@ class BaseService public function bulk($ids, $action) { - if ( ! $ids) { + if ( ! $ids || ! in_array($action, static::$bulk_actions) ) { return 0; } $entities = $this->getRepo()->findByPublicIdsWithTrashed($ids); foreach ($entities as $entity) { - $this->getRepo()->$action($entity); + if($entity->canEdit()){ + $this->getRepo()->$action($entity); + } } return count($entities); diff --git a/app/Services/CreditService.php b/app/Services/CreditService.php index 70c2d13048c2..2a130c551780 100644 --- a/app/Services/CreditService.php +++ b/app/Services/CreditService.php @@ -3,6 +3,8 @@ use Utils; use URL; use App\Services\BaseService; +use App\Models\Client; +use App\Models\Payment; use App\Ninja\Repositories\CreditRepository; @@ -30,6 +32,10 @@ class CreditService extends BaseService public function getDatatable($clientPublicId, $search) { $query = $this->creditRepo->find($clientPublicId, $search); + + if(!Utils::hasPermission('view_all')){ + $query->where('expenses.user_id', '=', Auth::user()->id); + } return $this->createDatatable(ENTITY_CREDIT, $query, !$clientPublicId); } @@ -40,6 +46,10 @@ class CreditService extends BaseService [ 'client_name', function ($model) { + if(!Client::canViewItemByOwner($model->client_user_id)){ + return Utils::getClientDisplayName($model); + } + return $model->client_public_id ? link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml() : ''; }, ! $hideClient @@ -78,6 +88,9 @@ class CreditService extends BaseService trans('texts.apply_credit'), function ($model) { return URL::to("payments/create/{$model->client_public_id}") . '?paymentTypeId=1'; + }, + function ($model) { + return Payment::canCreate(); } ] ]; diff --git a/app/Services/DatatableService.php b/app/Services/DatatableService.php index 456c619aef04..2f9af2856cf3 100644 --- a/app/Services/DatatableService.php +++ b/app/Services/DatatableService.php @@ -14,7 +14,9 @@ class DatatableService if ($actions && $showCheckbox) { $table->addColumn('checkbox', function ($model) { - return ''; }); } diff --git a/app/Services/ExpenseService.php b/app/Services/ExpenseService.php index e2150ac96829..2fc2afbc84b1 100644 --- a/app/Services/ExpenseService.php +++ b/app/Services/ExpenseService.php @@ -70,6 +70,10 @@ class ExpenseService extends BaseService function ($model) { if ($model->vendor_public_id) { + if(!Vendor::canViewItemByOwner($model->vendor_user_id)){ + return $model->vendor_name; + } + return link_to("vendors/{$model->vendor_public_id}", $model->vendor_name)->toHtml(); } else { return ''; @@ -81,6 +85,10 @@ class ExpenseService extends BaseService function ($model) { if ($model->client_public_id) { + if(!Client::canViewItemByOwner($model->client_user_id)){ + return Utils::getClientDisplayName($model); + } + return link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml(); } else { return ''; @@ -90,6 +98,10 @@ class ExpenseService extends BaseService [ 'expense_date', function ($model) { + if(!Expense::canEditItemByOwner($model->user_id)){ + return Utils::fromSqlDate($model->expense_date); + } + return link_to("expenses/{$model->public_id}/edit", Utils::fromSqlDate($model->expense_date))->toHtml(); } ], @@ -169,7 +181,7 @@ class ExpenseService extends BaseService return URL::to("/invoices/{$model->invoice_public_id}/edit"); }, function ($model) { - return $model->invoice_public_id && Invoice::canEditItemById($model->invoice_public_id); + return $model->invoice_public_id && Invoice::canEditItemByOwner($model->invoice_user_id); } ], [ diff --git a/app/Services/InvoiceService.php b/app/Services/InvoiceService.php index aea19ffc0803..c7c0be72acc0 100644 --- a/app/Services/InvoiceService.php +++ b/app/Services/InvoiceService.php @@ -9,6 +9,7 @@ use App\Ninja\Repositories\ClientRepository; use App\Events\QuoteInvitationWasApproved; use App\Models\Invitation; use App\Models\Invoice; +use App\Models\Client; use App\Models\Payment; class InvoiceService extends BaseService @@ -29,14 +30,26 @@ class InvoiceService extends BaseService return $this->invoiceRepo; } - public function save($data) + public function save($data, $checkSubPermissions = false) { if (isset($data['client'])) { - $client = $this->clientRepo->save($data['client']); - $data['client_id'] = $client->id; + $can_save_client = !$checkSubPermissions; + if(!$can_save_client){ + if(empty($data['client']['public_id']) || $data['client']['public_id']=='-1'){ + $can_save_client = Client::canCreate(); + } + else{ + $can_save_client = Client::wherePublicId($data['client']['public_id'])->first()->canEdit(); + } + } + + if($can_save_client){ + $client = $this->clientRepo->save($data['client']); + $data['client_id'] = $client->id; + } } - $invoice = $this->invoiceRepo->save($data); + $invoice = $this->invoiceRepo->save($data, $checkSubPermissions); $client = $invoice->client; $client->load('contacts'); @@ -124,12 +137,19 @@ class InvoiceService extends BaseService [ 'invoice_number', function ($model) use ($entityType) { + if(!Invoice::canEditItem($model)){ + return $model->invoice_number; + } + return link_to("{$entityType}s/{$model->public_id}/edit", $model->invoice_number, ['class' => Utils::getEntityRowClass($model)])->toHtml(); } ], [ 'client_name', function ($model) { + if(!Client::canViewItemByOwner($model->client_user_id)){ + return Utils::getClientDisplayName($model); + } return link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml(); }, ! $hideClient diff --git a/app/Services/PaymentService.php b/app/Services/PaymentService.php index 10db86800628..3bf26d8ba206 100644 --- a/app/Services/PaymentService.php +++ b/app/Services/PaymentService.php @@ -11,6 +11,8 @@ use CreditCard; use App\Models\Payment; use App\Models\Account; use App\Models\Country; +use App\Models\Client; +use App\Models\Invoice; use App\Models\AccountGatewayToken; use App\Ninja\Repositories\PaymentRepository; use App\Ninja\Repositories\AccountRepository; @@ -300,12 +302,20 @@ class PaymentService extends BaseService [ 'invoice_number', function ($model) { + if(!Invoice::canEditItemByOwner($model->invoice_user_id)){ + return $model->invoice_number; + } + return link_to("invoices/{$model->invoice_public_id}/edit", $model->invoice_number, ['class' => Utils::getEntityRowClass($model)])->toHtml(); } ], [ 'client_name', function ($model) { + if(!Client::canViewItemByOwner($model->client_user_id)){ + return Utils::getClientDisplayName($model); + } + return $model->client_public_id ? link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml() : ''; }, ! $hideClient diff --git a/app/Services/TaskService.php b/app/Services/TaskService.php index ae575e3b2a7b..70e7e22c7c88 100644 --- a/app/Services/TaskService.php +++ b/app/Services/TaskService.php @@ -5,6 +5,7 @@ use URL; use Utils; use App\Models\Task; use App\Models\Invoice; +use App\Models\Client; use App\Ninja\Repositories\TaskRepository; use App\Services\BaseService; @@ -48,6 +49,10 @@ class TaskService extends BaseService [ 'client_name', function ($model) { + if(!Client::canViewItemByOwner($model->client_user_id)){ + return Utils::getClientDisplayName($model); + } + return $model->client_public_id ? link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml() : ''; }, ! $hideClient @@ -97,7 +102,7 @@ class TaskService extends BaseService return URL::to("/invoices/{$model->invoice_public_id}/edit"); }, function ($model) { - return $model->invoice_number && Invoice::canEditItemById($model->invoice_number); + return $model->invoice_number && Invoice::canEditItemByOwner($model->invoice_user_id); } ], [ diff --git a/app/Services/VendorService.php b/app/Services/VendorService.php index 44e27ce44f2a..6f9b4420d772 100644 --- a/app/Services/VendorService.php +++ b/app/Services/VendorService.php @@ -38,6 +38,10 @@ class VendorService extends BaseService public function getDatatable($search) { $query = $this->vendorRepo->find($search); + + if(!Utils::hasPermission('view_all')){ + $query->where('vendors.user_id', '=', Auth::user()->id); + } return $this->createDatatable(ENTITY_VENDOR, $query); } diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 217841af7c05..ff129b37e0e1 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -1068,7 +1068,7 @@ $LANG = array( 'administrator' => 'Administrator', 'administrator_help' => 'Allow user to manage users, change settings, and view and modify all data', 'user_create_all' => 'Create clients, invoices, etc.', - 'user_view_all' => 'View All clients, invoices, etc.', + 'user_view_all' => 'View all clients, invoices, etc.', 'user_edit_all' => 'Edit all clients, invoices, etc.', ); diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index ded91d7e0c90..e226d7a45b38 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -63,8 +63,13 @@

    - {{ trans('texts.edit_client') }} | - {!! link_to('/clients/'.$invoice->client->public_id, trans('texts.view_client'), ['target' => '_blank']) !!} + + @if($invoice->client->canView() || true) + @if ($invoice->client->canEdit() || true) + {{ trans('texts.edit_client') }} | + @endif + {!! link_to('/clients/'.$invoice->client->public_id, trans('texts.view_client'), ['target' => '_blank']) !!} + @endif
    From 9337d4b9a439b353608843ccca1e281f7deed9e5 Mon Sep 17 00:00:00 2001 From: Joshua Dwire Date: Tue, 15 Mar 2016 22:29:30 -0400 Subject: [PATCH 03/13] Only show relevant data on Dashboard --- app/Http/Controllers/DashboardController.php | 42 +++++++++++++++----- resources/views/dashboard.blade.php | 20 ++++++++-- 2 files changed, 49 insertions(+), 13 deletions(-) diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index 1e26fd4a715a..fb42733162a0 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -63,9 +63,15 @@ class DashboardController extends BaseController ->get(); $activities = Activity::where('activities.account_id', '=', Auth::user()->account_id) + ->where('activities.activity_type_id', '>', 0); + + if(!Auth::user()->hasPermission('view_all')){ + $user_id = Auth::user()->id; + $activities = $activities->where('activities.user_id', '=', $user_id); + } + + $activities = $activities->orderBy('activities.created_at', 'desc') ->with('client.contacts', 'user', 'invoice', 'payment', 'credit', 'account') - ->where('activity_type_id', '>', 0) - ->orderBy('created_at', 'desc') ->take(50) ->get(); @@ -81,8 +87,14 @@ class DashboardController extends BaseController ->where('invoices.is_deleted', '=', false) ->where('invoices.deleted_at', '=', null) ->where('contacts.is_primary', '=', true) - ->where('invoices.due_date', '<', date('Y-m-d')) - ->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'is_quote']) + ->where('invoices.due_date', '<', date('Y-m-d')); + + if(!Auth::user()->hasPermission('view_all')){ + $user_id = Auth::user()->id; + $pastDue = $pastDue->where('invoices.user_id', '=', $user_id); + } + + $pastDue = $pastDue->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'clients.user_id as client_user_id', 'is_quote']) ->orderBy('invoices.due_date', 'asc') ->take(50) ->get(); @@ -100,9 +112,15 @@ class DashboardController extends BaseController ->where('invoices.is_deleted', '=', false) ->where('contacts.is_primary', '=', true) ->where('invoices.due_date', '>=', date('Y-m-d')) - ->orderBy('invoices.due_date', 'asc') - ->take(50) - ->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'is_quote']) + ->orderBy('invoices.due_date', 'asc'); + + if(!Auth::user()->hasPermission('view_all')){ + $user_id = Auth::user()->id; + $upcoming = $upcoming->where('invoices.user_id', '=', $user_id); + } + + $upcoming = $upcoming->take(50) + ->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'clients.user_id as client_user_id', 'is_quote']) ->get(); $payments = DB::table('payments') @@ -114,8 +132,14 @@ class DashboardController extends BaseController ->where('invoices.is_deleted', '=', false) ->where('clients.is_deleted', '=', false) ->where('contacts.deleted_at', '=', null) - ->where('contacts.is_primary', '=', true) - ->select(['payments.payment_date', 'payments.amount', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id']) + ->where('contacts.is_primary', '=', true); + + if(!Auth::user()->hasPermission('view_all')){ + $user_id = Auth::user()->id; + $payments = $payments->where('payments.user_id', '=', $user_id); + } + + $payments = $payments->select(['payments.payment_date', 'payments.amount', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'clients.user_id as client_user_id']) ->orderBy('payments.payment_date', 'desc') ->take(50) ->get(); diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php index d36c8334cc2a..ad8b8c4e097b 100644 --- a/resources/views/dashboard.blade.php +++ b/resources/views/dashboard.blade.php @@ -116,7 +116,11 @@ @foreach ($payments as $payment) {!! \App\Models\Invoice::calcLink($payment) !!} - {!! link_to('/clients/'.$payment->client_public_id, trim($payment->client_name) ?: (trim($payment->first_name . ' ' . $payment->last_name) ?: $payment->email)) !!} + @if (\App\Models\Client::canViewItemByOwner($payment->client_user_id)) + {!! link_to('/clients/'.$payment->client_public_id, trim($payment->client_name) ?: (trim($payment->first_name . ' ' . $payment->last_name) ?: $payment->email)) !!} + @else + {{ trim($payment->client_name) ?: (trim($payment->first_name . ' ' . $payment->last_name) ?: $payment->email) }} + @endif {{ Utils::fromSqlDate($payment->payment_date) }} {{ Utils::formatMoney($payment->amount, $payment->currency_id ?: ($account->currency_id ?: DEFAULT_CURRENCY)) }} @@ -149,7 +153,11 @@ @if (!$invoice->is_quote) {!! \App\Models\Invoice::calcLink($invoice) !!} - {!! link_to('/clients/'.$invoice->client_public_id, trim($invoice->client_name) ?: (trim($invoice->first_name . ' ' . $invoice->last_name) ?: $invoice->email)) !!} + @if (\App\Models\Client::canViewItemByOwner($payment->client_user_id)) + {!! link_to('/clients/'.$payment->client_public_id, trim($payment->client_name) ?: (trim($payment->first_name . ' ' . $payment->last_name) ?: $payment->email)) !!} + @else + {{ trim($payment->client_name) ?: (trim($payment->first_name . ' ' . $payment->last_name) ?: $payment->email) }} + @endif {{ Utils::fromSqlDate($invoice->due_date) }} {{ Utils::formatMoney($invoice->balance, $invoice->currency_id ?: ($account->currency_id ?: DEFAULT_CURRENCY)) }} @@ -160,7 +168,7 @@
    -
    +

    @@ -180,7 +188,11 @@ @if (!$invoice->is_quote) {!! \App\Models\Invoice::calcLink($invoice) !!} - {!! link_to('/clients/'.$invoice->client_public_id, trim($invoice->client_name) ?: (trim($invoice->first_name . ' ' . $invoice->last_name) ?: $invoice->email)) !!} + @if (\App\Models\Client::canViewItemByOwner($payment->client_user_id)) + {!! link_to('/clients/'.$payment->client_public_id, trim($payment->client_name) ?: (trim($payment->first_name . ' ' . $payment->last_name) ?: $payment->email)) !!} + @else + {{ trim($payment->client_name) ?: (trim($payment->first_name . ' ' . $payment->last_name) ?: $payment->email) }} + @endif {{ Utils::fromSqlDate($invoice->due_date) }} {{ Utils::formatMoney($invoice->balance, $invoice->currency_id ?: ($account->currency_id ?: DEFAULT_CURRENCY)) }} From 36284431c9f94475d3b83aae01ac9ef471df519e Mon Sep 17 00:00:00 2001 From: Joshua Dwire Date: Tue, 15 Mar 2016 22:29:39 -0400 Subject: [PATCH 04/13] Remove testing code --- resources/views/invoices/edit.blade.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index e226d7a45b38..6ab8228da649 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -64,8 +64,8 @@

    - @if($invoice->client->canView() || true) - @if ($invoice->client->canEdit() || true) + @if($invoice->client->canView()) + @if ($invoice->client->canEdit()) {{ trans('texts.edit_client') }} | @endif {!! link_to('/clients/'.$invoice->client->public_id, trans('texts.view_client'), ['target' => '_blank']) !!} From 9ef448c2b816826252672a86d0645c4c3bbc0ae9 Mon Sep 17 00:00:00 2001 From: Joshua Dwire Date: Tue, 15 Mar 2016 22:38:35 -0400 Subject: [PATCH 05/13] Only show invoice button for expenses and tasks if an invoice can be created --- resources/views/list.blade.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/resources/views/list.blade.php b/resources/views/list.blade.php index ffe6c12bc1bc..92f7d22cbd51 100644 --- a/resources/views/list.blade.php +++ b/resources/views/list.blade.php @@ -9,12 +9,14 @@ {!! Former::text('public_id') !!}
    - @if ($entityType == ENTITY_TASK) - {!! Button::primary(trans('texts.invoice'))->withAttributes(['class'=>'invoice', 'onclick' =>'submitForm("invoice")'])->appendIcon(Icon::create('check')) !!} - @endif - @if ($entityType == ENTITY_EXPENSE) - {!! Button::primary(trans('texts.invoice'))->withAttributes(['class'=>'invoice', 'onclick' =>'submitForm("invoice")'])->appendIcon(Icon::create('check')) !!} - @endif + @if (\App\Models\Invoice::canCreate()) + @if ($entityType == ENTITY_TASK) + {!! Button::primary(trans('texts.invoice'))->withAttributes(['class'=>'invoice', 'onclick' =>'submitForm("invoice")'])->appendIcon(Icon::create('check')) !!} + @endif + @if ($entityType == ENTITY_EXPENSE) + {!! Button::primary(trans('texts.invoice'))->withAttributes(['class'=>'invoice', 'onclick' =>'submitForm("invoice")'])->appendIcon(Icon::create('check')) !!} + @endif + @endif {!! DropdownButton::normal(trans('texts.archive'))->withContents([ ['label' => trans('texts.archive_'.$entityType), 'url' => 'javascript:submitForm("archive")'], From 67cc5599cc7bbe013237a402cd3dd874b2379986 Mon Sep 17 00:00:00 2001 From: Joshua Dwire Date: Tue, 15 Mar 2016 22:38:42 -0400 Subject: [PATCH 06/13] Remove unneeded check --- app/Services/BaseService.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/Services/BaseService.php b/app/Services/BaseService.php index 1e2f99c7dc34..b68b06482a04 100644 --- a/app/Services/BaseService.php +++ b/app/Services/BaseService.php @@ -5,8 +5,6 @@ use App\Services\DatatableService; class BaseService { - public static $bulk_actions = array('archive', 'restore', 'delete'); - use DispatchesJobs; protected function getRepo() @@ -16,7 +14,7 @@ class BaseService public function bulk($ids, $action) { - if ( ! $ids || ! in_array($action, static::$bulk_actions) ) { + if ( ! $ids ) { return 0; } From 6ea4f168a26185bf3c804456e1af5bd6114d3cc6 Mon Sep 17 00:00:00 2001 From: Joshua Dwire Date: Tue, 15 Mar 2016 22:43:30 -0400 Subject: [PATCH 07/13] Remove link to settings page for non-admins --- resources/views/header.blade.php | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/resources/views/header.blade.php b/resources/views/header.blade.php index 6fa6cb75a800..57f0409a9f01 100644 --- a/resources/views/header.blade.php +++ b/resources/views/header.blade.php @@ -488,19 +488,21 @@ From 2cd7228074e517c1e827506c46b05a3c8d1da2bf Mon Sep 17 00:00:00 2001 From: Joshua Dwire Date: Tue, 15 Mar 2016 22:45:11 -0400 Subject: [PATCH 08/13] Restrict access to tax rates and products --- app/Http/routes.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/Http/routes.php b/app/Http/routes.php index 75e7ce9ab2ad..55f65e99a491 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -108,14 +108,6 @@ Route::group(['middleware' => 'auth:user'], function() { Route::get('settings/user_details', 'AccountController@showUserDetails'); Route::post('settings/user_details', 'AccountController@saveUserDetails'); - Route::get('api/products', array('as'=>'api.products', 'uses'=>'ProductController@getDatatable')); - Route::resource('products', 'ProductController'); - Route::post('products/bulk', 'ProductController@bulk'); - - Route::get('api/tax_rates', array('as'=>'api.tax_rates', 'uses'=>'TaxRateController@getDatatable')); - Route::resource('tax_rates', 'TaxRateController'); - Route::post('tax_rates/bulk', 'TaxRateController@bulk'); - Route::resource('clients', 'ClientController'); Route::get('api/clients', array('as'=>'api.clients', 'uses'=>'ClientController@getDatatable')); Route::get('api/activities/{client_id?}', array('as'=>'api.activities', 'uses'=>'ActivityController@getDatatable')); @@ -196,6 +188,14 @@ Route::group([ Route::resource('tokens', 'TokenController'); Route::post('tokens/bulk', 'TokenController@bulk'); + Route::get('api/products', array('as'=>'api.products', 'uses'=>'ProductController@getDatatable')); + Route::resource('products', 'ProductController'); + Route::post('products/bulk', 'ProductController@bulk'); + + Route::get('api/tax_rates', array('as'=>'api.tax_rates', 'uses'=>'TaxRateController@getDatatable')); + Route::resource('tax_rates', 'TaxRateController'); + Route::post('tax_rates/bulk', 'TaxRateController@bulk'); + Route::get('company/{section}/{subSection?}', 'AccountController@redirectLegacy'); Route::get('settings/data_visualizations', 'ReportController@d3'); Route::get('settings/charts_and_reports', 'ReportController@showReports'); From 8cd56fa969592278888f455dcbb74a9ebe57c5be Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Thu, 17 Mar 2016 09:54:32 +0200 Subject: [PATCH 09/13] Minor fixes --- LICENSE | 2 +- database/seeds/CurrenciesSeeder.php | 1 + public/built.js | 1 + public/js/script.js | 1 + resources/views/header.blade.php | 1 + 5 files changed, 5 insertions(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 2f9d7d69a9b5..9fe9b899c55a 100644 --- a/LICENSE +++ b/LICENSE @@ -13,7 +13,7 @@ open-source software. 1. Redistributions of source code, in whole or part and with or without modification requires the express permission of the author and must prominently -display "Powered by InvoiceNinja" or the Invoice Ninja logo in verifiable form +display "Powered by InvoiceNinja" and the Invoice Ninja logo in verifiable form with hyperlink to said site. 2. Neither the name nor any trademark of the Author may be used to endorse or promote products derived from this software without specific diff --git a/database/seeds/CurrenciesSeeder.php b/database/seeds/CurrenciesSeeder.php index c26c5acd12ab..9a8304b181f6 100644 --- a/database/seeds/CurrenciesSeeder.php +++ b/database/seeds/CurrenciesSeeder.php @@ -54,6 +54,7 @@ class CurrenciesSeeder extends Seeder ['name' => 'Croatian Kuna', 'code' => 'HKR', 'symbol' => 'kn', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','], ['name' => 'Saudi Riyal', 'code' => 'SAR', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], ['name' => 'Japanese Yen', 'code' => 'JPY', 'symbol' => '¥', 'precision' => '0', 'thousand_separator' => ',', 'decimal_separator' => '.'], + ['name' => 'Maldivian Rufiyaa', 'code' => 'MVR', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], ]; foreach ($currencies as $currency) { diff --git a/public/built.js b/public/built.js index 9d1e0d3a3ee2..cbb00de5285e 100644 --- a/public/built.js +++ b/public/built.js @@ -30487,6 +30487,7 @@ function calculateAmounts(invoice) { for (var i=0; i + {{-- Per our license, please do not remove or modify this link. --}}

    From 9b9e99ee78c05081c00681e142fa5c96a2f3541d Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Thu, 17 Mar 2016 15:28:28 +0200 Subject: [PATCH 10/13] Added SagePay referral --- app/Http/routes.php | 2 ++ app/Models/Gateway.php | 2 ++ resources/lang/en/texts.php | 2 ++ .../views/accounts/account_gateway.blade.php | 18 +++++++++--------- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/app/Http/routes.php b/app/Http/routes.php index 55f65e99a491..f43317ed10f3 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -508,6 +508,8 @@ if (!defined('CONTACT_EMAIL')) { define('GATEWAY_PAYFAST', 13); define('GATEWAY_PAYPAL_EXPRESS', 17); define('GATEWAY_PAYPAL_PRO', 18); + define('GATEWAY_SAGE_PAY_DIRECT', 20); + define('GATEWAY_SAGE_PAY_SERVER', 21); define('GATEWAY_STRIPE', 23); define('GATEWAY_GOCARDLESS', 6); define('GATEWAY_TWO_CHECKOUT', 27); diff --git a/app/Models/Gateway.php b/app/Models/Gateway.php index 06621518403b..681e8315c512 100644 --- a/app/Models/Gateway.php +++ b/app/Models/Gateway.php @@ -75,6 +75,8 @@ class Gateway extends Eloquent $link = 'https://bitpay.com/dashboard/signup'; } elseif ($this->id == GATEWAY_DWOLLA) { $link = 'https://www.dwolla.com/register'; + } elseif ($this->id == GATEWAY_SAGE_PAY_DIRECT || $this->id == GATEWAY_SAGE_PAY_SERVER) { + $link = 'https://applications.sagepay.com/apply/2C02C252-0F8A-1B84-E10D-CF933EFCAA99'; } $key = 'texts.gateway_help_'.$this->id; diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 1afab6f99815..5913944f9d79 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -1072,6 +1072,8 @@ $LANG = array( 'user_create_all' => 'Create clients, invoices, etc.', 'user_view_all' => 'View all clients, invoices, etc.', 'user_edit_all' => 'Edit all clients, invoices, etc.', + 'gateway_help_20' => ':link to sign up for Sage Pay.', + 'gateway_help_21' => ':link to sign up for Sage Pay.', ); diff --git a/resources/views/accounts/account_gateway.blade.php b/resources/views/accounts/account_gateway.blade.php index f07b62f2ffb7..c19268c258fd 100644 --- a/resources/views/accounts/account_gateway.blade.php +++ b/resources/views/accounts/account_gateway.blade.php @@ -52,6 +52,15 @@ @foreach ($gateways as $gateway)
    @endif - - {!! Former::select('client')->addOption('', '')->data_bind("dropdown: client")->addClass('client-input')->addGroupClass('client_select closer-row') !!} + + {!! Former::select('client')->addOption('', '')->data_bind("dropdown: client")->addClass('client-input')->addGroupClass('client_select closer-row') !!}
    diff --git a/resources/views/users/edit.blade.php b/resources/views/users/edit.blade.php index 3e77e9144283..542d40b737a8 100644 --- a/resources/views/users/edit.blade.php +++ b/resources/views/users/edit.blade.php @@ -65,6 +65,7 @@ $('#permissions_view_all').prop('disabled', adminChecked); $('#permissions_create_all').prop('disabled', adminChecked); $('#permissions_edit_all').prop('disabled', adminChecked || !viewChecked); + if(!viewChecked)$('#permissions_edit_all').prop('checked',false) } fixCheckboxes(); @stop \ No newline at end of file From 4d4f4140b65f2dea27a46990c0eff36d136c61ca Mon Sep 17 00:00:00 2001 From: Joshua Dwire Date: Thu, 17 Mar 2016 18:04:03 -0400 Subject: [PATCH 12/13] Set sorting columns --- app/Http/Controllers/ExpenseController.php | 2 +- app/Http/Controllers/InvoiceController.php | 1 + app/Http/Controllers/PaymentController.php | 1 + app/Http/Controllers/QuoteController.php | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/ExpenseController.php b/app/Http/Controllers/ExpenseController.php index eb9fa0c437ee..b1fd28e41941 100644 --- a/app/Http/Controllers/ExpenseController.php +++ b/app/Http/Controllers/ExpenseController.php @@ -45,7 +45,7 @@ class ExpenseController extends BaseController return View::make('list', array( 'entityType' => ENTITY_EXPENSE, 'title' => trans('texts.expenses'), - 'sortCol' => '1', + 'sortCol' => '3', 'columns' => Utils::trans([ 'checkbox', 'vendor', diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index 05e784695634..dc9cc501db74 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -52,6 +52,7 @@ class InvoiceController extends BaseController $data = [ 'title' => trans('texts.invoices'), 'entityType' => ENTITY_INVOICE, + 'sortCol' => '3', 'columns' => Utils::trans([ 'checkbox', 'invoice_number', diff --git a/app/Http/Controllers/PaymentController.php b/app/Http/Controllers/PaymentController.php index 7e675d023f29..1ba681ba57dc 100644 --- a/app/Http/Controllers/PaymentController.php +++ b/app/Http/Controllers/PaymentController.php @@ -48,6 +48,7 @@ class PaymentController extends BaseController return View::make('list', array( 'entityType' => ENTITY_PAYMENT, 'title' => trans('texts.payments'), + 'sortCol' => '6', 'columns' => Utils::trans([ 'checkbox', 'invoice', diff --git a/app/Http/Controllers/QuoteController.php b/app/Http/Controllers/QuoteController.php index 7777c5a9d006..0c2dc3a8a5ba 100644 --- a/app/Http/Controllers/QuoteController.php +++ b/app/Http/Controllers/QuoteController.php @@ -54,6 +54,7 @@ class QuoteController extends BaseController $data = [ 'title' => trans('texts.quotes'), 'entityType' => ENTITY_QUOTE, + 'sortCol' => '3', 'columns' => Utils::trans([ 'checkbox', 'quote_number', From c3c7f25b3246c54f7d0c8fa21b7942e1fa88a6b7 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Fri, 18 Mar 2016 14:34:46 +0200 Subject: [PATCH 13/13] Clarified partial labels --- app/Http/Controllers/AccountController.php | 2 +- app/Models/Account.php | 2 +- app/Ninja/Presenters/InvoicePresenter.php | 2 +- public/built.js | 28 +++++++++++++------ public/js/pdf.pdfmake.js | 28 +++++++++++++------ resources/lang/en/texts.php | 3 +- .../views/accounts/invoice_design.blade.php | 15 ++++++++-- resources/views/invoices/edit.blade.php | 13 +++++++-- resources/views/invoices/knockout.blade.php | 6 +++- 9 files changed, 71 insertions(+), 28 deletions(-) diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 755932691d18..912ade56f962 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -760,7 +760,7 @@ class AccountController extends BaseController } $labels = []; - foreach (['item', 'description', 'unit_cost', 'quantity', 'line_total', 'terms'] as $field) { + foreach (['item', 'description', 'unit_cost', 'quantity', 'line_total', 'terms', 'balance_due', 'partial_due'] as $field) { $labels[$field] = Input::get("labels_{$field}"); } $account->invoice_labels = json_encode($labels); diff --git a/app/Models/Account.php b/app/Models/Account.php index b2d5a7d22dce..f6a7194bdf6f 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -683,7 +683,7 @@ class Account extends Eloquent 'subtotal', 'paid_to_date', 'balance_due', - 'amount_due', + 'partial_due', 'terms', 'your_invoice', 'quote', diff --git a/app/Ninja/Presenters/InvoicePresenter.php b/app/Ninja/Presenters/InvoicePresenter.php index 560f2d49e6f4..ebb3297d5c1a 100644 --- a/app/Ninja/Presenters/InvoicePresenter.php +++ b/app/Ninja/Presenters/InvoicePresenter.php @@ -19,7 +19,7 @@ class InvoicePresenter extends Presenter { public function balanceDueLabel() { if ($this->entity->partial) { - return 'amount_due'; + return 'partial_due'; } elseif ($this->entity->is_quote) { return 'total'; } else { diff --git a/public/built.js b/public/built.js index cbb00de5285e..9fccf73e0bff 100644 --- a/public/built.js +++ b/public/built.js @@ -31183,7 +31183,7 @@ NINJA.decodeJavascript = function(invoice, javascript) var value = getDescendantProp(invoice, field); if (match.indexOf('?') < 0 || value) { if (invoice.partial && field == 'balance_due') { - field = 'amount_due'; + field = 'partial_due'; } else if (invoice.is_quote) { field = field.replace('invoice', 'quote'); } @@ -31440,12 +31440,22 @@ NINJA.subtotals = function(invoice, hideBalance) data.push([{text:invoiceLabels.paid_to_date}, {text:formatMoneyInvoice(paid, invoice)}]); } - if (!hideBalance) { - var isPartial = NINJA.parseFloat(invoice.partial); + var isPartial = NINJA.parseFloat(invoice.partial); + + if (!hideBalance || isPartial) { data.push([ - {text: isPartial ? invoiceLabels.amount_due : invoiceLabels.balance_due, style:['balanceDueLabel']}, - {text: formatMoneyInvoice(invoice.balance_amount, invoice), style:['balanceDue']} + { text: invoiceLabels.balance_due, style: [isPartial ? '' : 'balanceDueLabel'] }, + { text: formatMoneyInvoice(invoice.total_amount, invoice), style: [isPartial ? '' : 'balanceDue'] } + ]); + } + + if (!hideBalance) { + if (isPartial) { + data.push([ + { text: invoiceLabels.partial_due, style: ['balanceDueLabel'] }, + { text: formatMoneyInvoice(invoice.balance_amount, invoice), style: ['balanceDue'] } ]); + } } return NINJA.prepareDataPairs(data, 'subtotals'); @@ -31454,7 +31464,7 @@ NINJA.subtotals = function(invoice, hideBalance) NINJA.subtotalsBalance = function(invoice) { var isPartial = NINJA.parseFloat(invoice.partial); return [[ - {text: isPartial ? invoiceLabels.amount_due : invoiceLabels.balance_due, style:['balanceDueLabel']}, + {text: isPartial ? invoiceLabels.partial_due : invoiceLabels.balance_due, style:['balanceDueLabel']}, {text: formatMoneyInvoice(invoice.balance_amount, invoice), style:['balanceDue']} ]]; } @@ -31532,18 +31542,18 @@ NINJA.invoiceDetails = function(invoice) { if (NINJA.parseFloat(invoice.balance) < NINJA.parseFloat(invoice.amount)) { data.push([ - {text: invoiceLabels.total}, + {text: invoiceLabels.balance_due}, {text: formatMoneyInvoice(invoice.amount, invoice)} ]); } else if (isPartial) { data.push([ - {text: invoiceLabels.total}, + {text: invoiceLabels.balance_due}, {text: formatMoneyInvoice(invoice.total_amount, invoice)} ]); } data.push([ - {text: isPartial ? invoiceLabels.amount_due : invoiceLabels.balance_due, style: ['invoiceDetailBalanceDueLabel']}, + {text: isPartial ? invoiceLabels.partial_due : invoiceLabels.balance_due, style: ['invoiceDetailBalanceDueLabel']}, {text: formatMoneyInvoice(invoice.balance_amount, invoice), style: ['invoiceDetailBalanceDue']} ]) diff --git a/public/js/pdf.pdfmake.js b/public/js/pdf.pdfmake.js index d6e7906ca6d6..f8f30f4b546f 100644 --- a/public/js/pdf.pdfmake.js +++ b/public/js/pdf.pdfmake.js @@ -190,7 +190,7 @@ NINJA.decodeJavascript = function(invoice, javascript) var value = getDescendantProp(invoice, field); if (match.indexOf('?') < 0 || value) { if (invoice.partial && field == 'balance_due') { - field = 'amount_due'; + field = 'partial_due'; } else if (invoice.is_quote) { field = field.replace('invoice', 'quote'); } @@ -447,12 +447,22 @@ NINJA.subtotals = function(invoice, hideBalance) data.push([{text:invoiceLabels.paid_to_date}, {text:formatMoneyInvoice(paid, invoice)}]); } - if (!hideBalance) { - var isPartial = NINJA.parseFloat(invoice.partial); + var isPartial = NINJA.parseFloat(invoice.partial); + + if (!hideBalance || isPartial) { data.push([ - {text: isPartial ? invoiceLabels.amount_due : invoiceLabels.balance_due, style:['balanceDueLabel']}, - {text: formatMoneyInvoice(invoice.balance_amount, invoice), style:['balanceDue']} + { text: invoiceLabels.balance_due, style: [isPartial ? '' : 'balanceDueLabel'] }, + { text: formatMoneyInvoice(invoice.total_amount, invoice), style: [isPartial ? '' : 'balanceDue'] } + ]); + } + + if (!hideBalance) { + if (isPartial) { + data.push([ + { text: invoiceLabels.partial_due, style: ['balanceDueLabel'] }, + { text: formatMoneyInvoice(invoice.balance_amount, invoice), style: ['balanceDue'] } ]); + } } return NINJA.prepareDataPairs(data, 'subtotals'); @@ -461,7 +471,7 @@ NINJA.subtotals = function(invoice, hideBalance) NINJA.subtotalsBalance = function(invoice) { var isPartial = NINJA.parseFloat(invoice.partial); return [[ - {text: isPartial ? invoiceLabels.amount_due : invoiceLabels.balance_due, style:['balanceDueLabel']}, + {text: isPartial ? invoiceLabels.partial_due : invoiceLabels.balance_due, style:['balanceDueLabel']}, {text: formatMoneyInvoice(invoice.balance_amount, invoice), style:['balanceDue']} ]]; } @@ -539,18 +549,18 @@ NINJA.invoiceDetails = function(invoice) { if (NINJA.parseFloat(invoice.balance) < NINJA.parseFloat(invoice.amount)) { data.push([ - {text: invoiceLabels.total}, + {text: invoiceLabels.balance_due}, {text: formatMoneyInvoice(invoice.amount, invoice)} ]); } else if (isPartial) { data.push([ - {text: invoiceLabels.total}, + {text: invoiceLabels.balance_due}, {text: formatMoneyInvoice(invoice.total_amount, invoice)} ]); } data.push([ - {text: isPartial ? invoiceLabels.amount_due : invoiceLabels.balance_due, style: ['invoiceDetailBalanceDueLabel']}, + {text: isPartial ? invoiceLabels.partial_due : invoiceLabels.balance_due, style: ['invoiceDetailBalanceDueLabel']}, {text: formatMoneyInvoice(invoice.balance_amount, invoice), style: ['invoiceDetailBalanceDue']} ]) diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 5913944f9d79..f31e61a03263 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -1073,7 +1073,8 @@ $LANG = array( 'user_view_all' => 'View all clients, invoices, etc.', 'user_edit_all' => 'Edit all clients, invoices, etc.', 'gateway_help_20' => ':link to sign up for Sage Pay.', - 'gateway_help_21' => ':link to sign up for Sage Pay.', + 'gateway_help_21' => ':link to sign up for Sage Pay.', + 'partial_due' => 'Partial Due', ); diff --git a/resources/views/accounts/invoice_design.blade.php b/resources/views/accounts/invoice_design.blade.php index 09bb55bfb1ba..4af173818dad 100644 --- a/resources/views/accounts/invoice_design.blade.php +++ b/resources/views/accounts/invoice_design.blade.php @@ -60,7 +60,16 @@ @else NINJA.headerFont = NINJA.bodyFont = 'Roboto'; @endif - var fields = ['item', 'description', 'unit_cost', 'quantity', 'line_total', 'terms']; + var fields = [ + 'item', + 'description', + 'unit_cost', + 'quantity', + 'line_total', + 'terms', + 'balance_due', + 'partial_due' + ]; invoiceLabels.old = {}; for (var i=0; ilabel(trans('texts.item')) !!} {!! Former::text('labels_description')->label(trans('texts.description')) !!} {!! Former::text('labels_unit_cost')->label(trans('texts.unit_cost')) !!} + {!! Former::text('labels_quantity')->label(trans('texts.quantity')) !!}
    - {!! Former::text('labels_quantity')->label(trans('texts.quantity')) !!} {!! Former::text('labels_line_total')->label(trans('texts.line_total')) !!} {!! Former::text('labels_terms')->label(trans('texts.terms')) !!} + {!! Former::text('labels_balance_due')->label(trans('texts.balance_due')) !!} + {!! Former::text('labels_partial_due')->label(trans('texts.partial_due')) !!}
    diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index c3c21fd5f50a..ce8160d25c02 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -385,11 +385,18 @@ @endif - + - {{ trans($entityType == ENTITY_INVOICE ? 'texts.balance_due' : 'texts.total') }} - + {{ $entityType == ENTITY_INVOICE ? $invoiceLabels['balance_due'] : trans('texts.total') }} + + + + + + + {{ $invoiceLabels['partial_due'] }} + diff --git a/resources/views/invoices/knockout.blade.php b/resources/views/invoices/knockout.blade.php index 3798cb458131..029a81bc0b67 100644 --- a/resources/views/invoices/knockout.blade.php +++ b/resources/views/invoices/knockout.blade.php @@ -512,7 +512,11 @@ function InvoiceModel(data) { }); self.totals.total = ko.computed(function() { - return self.formatMoney(self.partial() ? self.partial() : self.totals.rawTotal()); + return self.formatMoney(self.totals.rawTotal()); + }); + + self.totals.partial = ko.computed(function() { + return self.formatMoney(self.partial()); }); self.onDragged = function(item) {