From af472b21c12323d740014dfae4a9de001f41f1a9 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 29 Jan 2021 23:05:03 +1100 Subject: [PATCH] Working on filtering responses based on user permissions --- README.md | 19 ++- app/DataMapper/CompanySettings.php | 4 +- app/Http/Controllers/BaseController.php | 123 +++++++++++--- app/Models/User.php | 25 +-- app/Utils/Traits/GeneratesCounter.php | 205 ++++++------------------ 5 files changed, 178 insertions(+), 198 deletions(-) diff --git a/README.md b/README.md index 7c8727f8c0ab..77af4773cb0b 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,18 @@ ![v5-develop phpunit](https://github.com/invoiceninja/invoiceninja/workflows/phpunit/badge.svg?branch=v5-develop) ![v5-stable phpunit](https://github.com/invoiceninja/invoiceninja/workflows/phpunit/badge.svg?branch=v5-stable) -[![codecov](https://codecov.io/gh/invoiceninja/invoiceninja/branch/v2/graph/badge.svg)](https://codecov.io/gh/invoiceninja/invoiceninja) + [![Codacy Badge](https://api.codacy.com/project/badge/Grade/d39acb4bf0f74a0698dc77f382769ba5)](https://www.codacy.com/app/turbo124/invoiceninja?utm_source=github.com&utm_medium=referral&utm_content=invoiceninja/invoiceninja&utm_campaign=Badge_Grade) -# Invoice Ninja version 5 is in Beta! +# Invoice Ninja version 5.1 RC2! -We will be using the lessons learnt in Invoice Ninja 4.0 to build a bigger better platform to work from. If you would like to contribute to the project we will gladly accept contributions for code, user guides, bug tracking and feedback! Please consider the following guidelines prior to submitting a pull request: +Invoice Ninja version 5.1 has now reached Release Candidate 2! + +What does this mean exactly? We consider this version _almost_ stable. There may be some remaining small issues which we would love to get feedback on. We would really appreciate the community booting up this version and attempting the migration from their Invoice Ninja V4 application and inspect the migrated data. + +We'd also like feedback on any issues that you can see, and help us nail down the few remaining issues before Version 5 graduates to Stable Gold Release. + +Please note we do not consider this version ready for production use, please stick with your V4 installation for your production clients! ## Quick Start @@ -17,13 +23,10 @@ Currently the client portal and API are of alpha quality, to get started: ```bash git clone https://github.com/invoiceninja/invoiceninja.git -git checkout v2 +git checkout v5-stable cp .env.example .env -cp .env.dusk.example .env.dusk.local php artisan key:generate composer update -npm i -npm run production ``` Please Note: Your APP_KEY in the .env file is used to encrypt data, if you lose this you will not be able to run the application. @@ -33,7 +36,7 @@ Run if you want to load sample data, remember to configure .env php artisan migrate:fresh --seed && php artisan db:seed && php artisan ninja:create-test-data ``` -To Run the web server +To run the web server ``` php artisan serve ``` diff --git a/app/DataMapper/CompanySettings.php b/app/DataMapper/CompanySettings.php index 45f6ce34af0e..e1aa1975cc29 100644 --- a/app/DataMapper/CompanySettings.php +++ b/app/DataMapper/CompanySettings.php @@ -235,8 +235,8 @@ class CompanySettings extends BaseSettings public $font_size = 7; //@implemented public $primary_font = 'Roboto'; public $secondary_font = 'Roboto'; - public $primary_color = '#4caf50'; - public $secondary_color = '#2196f3'; + public $primary_color = '#142cb5'; + public $secondary_color = '#7081e0'; public $hide_paid_to_date = false; //@TODO where? public $embed_documents = false; //@TODO where? diff --git a/app/Http/Controllers/BaseController.php b/app/Http/Controllers/BaseController.php index 68ac5202d06d..7ebcda5eba44 100644 --- a/app/Http/Controllers/BaseController.php +++ b/app/Http/Controllers/BaseController.php @@ -184,7 +184,9 @@ class BaseController extends Controller protected function refreshResponse($query) { - if (auth()->user()->getCompany()->is_large) + $user = auth()->user(); + + if ($user->getCompany()->is_large) $this->manager->parseIncludes($this->mini_load); else $this->manager->parseIncludes($this->first_load); @@ -200,74 +202,145 @@ class BaseController extends Controller $transformer = new $this->entity_transformer($this->serializer); $updated_at = request()->has('updated_at') ? request()->input('updated_at') : 0; - // if (auth()->user()->getCompany()->is_large && ! request()->has('updated_at')) { - // return response()->json(['message' => ctrans('texts.large_account_update_parameter'), 'errors' =>[]], 401); - // } - $updated_at = date('Y-m-d H:i:s', $updated_at); $query->with( [ - 'company' => function ($query) use ($updated_at) { + 'company' => function ($query) use ($updated_at, $user) { $query->whereNotNull('updated_at')->with('documents'); }, - 'company.clients' => function ($query) use ($updated_at) { + 'company.clients' => function ($query) use ($updated_at, $user) { $query->where('clients.updated_at', '>=', $updated_at)->with('contacts.company', 'gateway_tokens', 'documents'); + + if(!$user->hasPermission('view_client')) + $query->where('clients.user_id', $user->id)->orWhere('clients.assigned_user_id', $user->id); + }, - 'company.company_gateways' => function ($query) { + 'company.company_gateways' => function ($query) use ($user) { $query->whereNotNull('updated_at'); + + if(!$user->isAdmin()) + $query->where('company_gateways.user_id', $user->id); + }, - 'company.credits'=> function ($query) use ($updated_at) { + 'company.credits'=> function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at)->with('invitations', 'documents'); + + if(!$user->hasPermission('view_credit')) + $query->where('credits.user_id', $user->id)->orWhere('credits.assigned_user_id', $user->id); + }, - 'company.designs'=> function ($query) use ($updated_at) { + 'company.designs'=> function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at)->with('company'); + + if(!$user->isAdmin()) + $query->where('designs.user_id', $user->id); }, - 'company.documents'=> function ($query) use ($updated_at) { + 'company.documents'=> function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at); }, - 'company.expenses'=> function ($query) use ($updated_at) { + 'company.expenses'=> function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at)->with('documents'); + + if(!$user->hasPermission('view_expense')) + $query->where('expenses.user_id', $user->id)->orWhere('expenses.assigned_user_id', $user->id); }, - 'company.groups' => function ($query) use ($updated_at) { + 'company.groups' => function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at); + + if(!$user->isAdmin()) + $query->where('group_settings.user_id', $user->id); }, - 'company.invoices'=> function ($query) use ($updated_at) { + 'company.invoices'=> function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at)->with('invitations', 'documents'); + + if(!$user->hasPermission('view_invoice')) + $query->where('invoices.user_id', $user->id)->orWhere('invoices.assigned_user_id', $user->id); + }, - 'company.payments'=> function ($query) use ($updated_at) { + 'company.payments'=> function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at)->with('paymentables', 'documents'); + + if(!$user->hasPermission('view_payment')) + $query->where('payments.user_id', $user->id)->orWhere('payments.assigned_user_id', $user->id); + }, - 'company.payment_terms'=> function ($query) use ($updated_at) { + 'company.payment_terms'=> function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at); + + if(!$user->isAdmin()) + $query->where('payment_terms.user_id', $user->id); + }, - 'company.products' => function ($query) use ($updated_at) { + 'company.products' => function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at)->with('documents'); + + if(!$user->hasPermission('view_product')) + $query->where('products.user_id', $user->id)->orWhere('products.assigned_user_id', $user->id); + }, - 'company.projects'=> function ($query) use ($updated_at) { + 'company.projects'=> function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at)->with('documents'); + + if(!$user->hasPermission('view_project')) + $query->where('projects.user_id', $user->id)->orWhere('projects.assigned_user_id', $user->id); + }, - 'company.quotes'=> function ($query) use ($updated_at) { + 'company.quotes'=> function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at)->with('invitations', 'documents'); + + if(!$user->hasPermission('view_quote')) + $query->where('quotes.user_id', $user->id)->orWhere('quotes.assigned_user_id', $user->id); + }, - 'company.recurring_invoices'=> function ($query) use ($updated_at) { + 'company.recurring_invoices'=> function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at)->with('invitations', 'documents'); + + if(!$user->hasPermission('view_recurring_invoice')) + $query->where('recurring_invoices.user_id', $user->id)->orWhere('recurring_invoices.assigned_user_id', $user->id); + }, - 'company.tasks'=> function ($query) use ($updated_at) { + 'company.tasks'=> function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at)->with('documents'); + + if(!$user->hasPermission('view_task')) + $query->where('tasks.user_id', $user->id)->orWhere('tasks.assigned_user_id', $user->id); + }, - 'company.tax_rates' => function ($query) use ($updated_at) { + 'company.tax_rates' => function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at); + + if(!$user->isAdmin()) + $query->where('tax_rates.user_id', $user->id); + }, - 'company.vendors'=> function ($query) use ($updated_at) { + 'company.vendors'=> function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at)->with('contacts', 'documents'); + + if(!$user->hasPermission('view_vendor')) + $query->where('vendors.user_id', $user->id)->orWhere('vendors.assigned_user_id', $user->id); + }, - 'company.expense_categories'=> function ($query) use ($updated_at) { + 'company.expense_categories'=> function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at); + + if(!$user->isAdmin()) + $query->where('expense_categories.user_id', $user->id); + }, - 'company.task_statuses'=> function ($query) use ($updated_at) { + 'company.task_statuses'=> function ($query) use ($updated_at, $user) { $query->where('updated_at', '>=', $updated_at); + + if(!$user->isAdmin()) + $query->where('task_statuses.user_id', $user->id); + }, + 'company.activities'=> function ($query) use($user) { + + if(!$user->isAdmin()) + $query->where('activities.user_id', $user->id); + + } ] ); diff --git a/app/Models/User.php b/app/Models/User.php index e77c9a611fa3..9e0133057188 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -202,15 +202,22 @@ class User extends Authenticatable implements MustVerifyEmail $this->id = auth()->user()->id; } - if (request()->header('X-API-TOKEN')) { - return $this->hasOneThrough(CompanyUser::class, CompanyToken::class, 'user_id', 'company_id', 'id', 'company_id') - ->where('company_tokens.token', request()->header('X-API-TOKEN')) - ->withTrashed(); - } else { - return $this->hasOneThrough(CompanyUser::class, CompanyToken::class, 'user_id', 'company_id', 'id', 'company_id') - ->where('company_user.user_id', $this->id) - ->withTrashed(); - } + return $this->hasOneThrough(CompanyUser::class, CompanyToken::class, 'user_id', 'user_id', 'id', 'user_id') + ->withTrashed(); + + // if (request()->header('X-API-TOKEN')) { + + // nlog("with an API token"); + // nlog(request()->header('X-API-TOKEN')); + + // return $this->hasOneThrough(CompanyUser::class, CompanyToken::class, 'user_id', 'company_id', 'id', 'company_id') + // ->where('company_tokens.token', request()->header('X-API-TOKEN')) + // ->withTrashed(); + // } else { + // return $this->hasOneThrough(CompanyUser::class, CompanyToken::class, 'user_id', 'company_id', 'id', 'company_id') + // ->where('company_user.user_id', $this->id) + // ->withTrashed(); + // } } /** diff --git a/app/Utils/Traits/GeneratesCounter.php b/app/Utils/Traits/GeneratesCounter.php index c5b907bc843f..0364ee0fbd3c 100644 --- a/app/Utils/Traits/GeneratesCounter.php +++ b/app/Utils/Traits/GeneratesCounter.php @@ -33,74 +33,12 @@ trait GeneratesCounter //todo in the form validation, we need to ensure that if a prefix and pattern is set we throw a validation error, //only one type is allow else this will cause confusion to the end user - /** - * Gets the next invoice number. - * - * @param Client $client The client - * - * @param Invoice|null $invoice - * @return string The next invoice number. - */ - public function getNextInvoiceNumber(Client $client, ?Invoice $invoice) :string - { - //Reset counters if enabled - $this->resetCounters($client); - //todo handle if we have specific client patterns in the future - $pattern = $client->getSetting('invoice_number_pattern'); - //Determine if we are using client_counters - if (strpos($pattern, 'clientCounter') || strpos($pattern, 'client_counter')) { - if (property_exists($client->settings, 'invoice_number_counter')) { - $counter = $client->settings->invoice_number_counter; - } else { - $counter = 1; - } - - $counter_entity = $client; - } elseif (strpos($pattern, 'groupCounter') || strpos($pattern, 'group_counter')) { - $counter = $client->group_settings->invoice_number_counter; - $counter_entity = $client->group_settings; - } else { - $counter = $client->company->settings->invoice_number_counter; - $counter_entity = $client->company; - } - - //Return a valid counter - $pattern = $client->getSetting('invoice_number_pattern'); - $padding = $client->getSetting('counter_padding'); - $prefix = ''; - - if ($invoice && $invoice->recurring_id) { - $prefix = $client->getSetting('recurring_number_prefix'); - } - - $invoice_number = $this->checkEntityNumber(Invoice::class, $client, $counter, $padding, $pattern, $prefix); - - $this->incrementCounter($counter_entity, 'invoice_number_counter'); - - return $invoice_number; - } - - /** - * Gets the next credit number. - * - * @param Client $client The client - * - * @return string The next credit number. - */ - public function getNextCreditNumber(Client $client) :string - { - return $this->getNextEntityNumber(Credit::class, $client); - } - - public function getNextQuoteNumber(Client $client) - { - return $this->getNextEntityNumber(Quote::class, $client); - } private function getNextEntityNumber($entity, Client $client) { - //Reset counters if enabled + $prefix = ''; + $this->resetCounters($client); $is_client_counter = false; @@ -108,9 +46,6 @@ trait GeneratesCounter $counter_string = $this->getEntityCounter($entity, $client); $pattern = $this->getNumberPattern($entity, $client); -nlog("counter string = {$counter_string}"); -nlog("pattern = {$pattern}"); - if (strpos($pattern, 'clientCounter') || strpos($pattern, 'client_counter')) { if (property_exists($client->settings, $counter_string)) { @@ -135,14 +70,15 @@ nlog("pattern = {$pattern}"); $counter_entity = $client->company; } -nlog($counter_entity->toArray()); - //If it is a quote - we need to $pattern = $this->getNumberPattern($entity, $client); $padding = $client->getSetting('counter_padding'); - $entity_number = $this->checkEntityNumber($entity, $client, $counter, $padding, $pattern); + if($entity instanceof Invoice && $entity && $entity->recurring_id) + $prefix = $client->getSetting('recurring_number_prefix'); + + $entity_number = $this->checkEntityNumber($entity, $client, $counter, $padding, $pattern, $prefix); $this->incrementCounter($counter_entity, $counter_string); @@ -210,97 +146,58 @@ nlog($counter_entity->toArray()); } } - public function getNextRecurringInvoiceNumber(Client $client) + /** + * Gets the next invoice number. + * + * @param Client $client The client + * + * @param Invoice|null $invoice + * @return string The next invoice number. + */ + public function getNextInvoiceNumber(Client $client, ?Invoice $invoice) :string { - - //Reset counters if enabled - $this->resetCounters($client); - - $is_client_counter = false; - - //todo handle if we have specific client patterns in the future - $pattern = $client->company->settings->recurring_invoice_number_pattern; - - if (strpos($pattern, 'clientCounter') || strpos($pattern, 'client_counter')) { - - if (property_exists($client->settings, 'recurring_invoice_number_counter')) { - $counter = $client->settings->recurring_invoice_number_counter; - } else { - $counter = 1; - } - - $counter_entity = $client; - - } elseif (strpos($pattern, 'groupCounter') || strpos($pattern, 'group_counter')) { - - if (property_exists($client->group_settings, 'recurring_invoice_number_counter')) { - $counter = $client->group_settings->recurring_invoice_number_counter; - } else { - $counter = 1; - } - - $counter_entity = $client->group_settings; - } else { - $counter = $client->company->settings->recurring_invoice_number_counter; - $counter_entity = $client->company; - } - - $padding = $client->getSetting('counter_padding'); - $invoice_number = $this->checkEntityNumber(RecurringInvoice::class, $client, $counter, $padding, $pattern); - - //increment the correct invoice_number Counter (company vs client) - if ($is_client_counter) { - $this->incrementCounter($client, 'recurring_invoice_number_counter'); - } else { - $this->incrementCounter($client->company, 'recurring_invoice_number_counter'); - } - - return $invoice_number; + return $this->getNextEntityNumber(Invoice::class, $client); } /** - * Payment Number Generator. - * @param Client $client - * @return string The payment number + * Gets the next credit number. + * + * @param Client $client The client + * + * @return string The next credit number. + */ + public function getNextCreditNumber(Client $client) :string + { + return $this->getNextEntityNumber(Credit::class, $client); + } + + /** + * Gets the next quote number. + * + * @param Client $client The client + * + * @return string The next credit number. + */ + public function getNextQuoteNumber(Client $client) + { + return $this->getNextEntityNumber(Quote::class, $client); + } + + public function getNextRecurringInvoiceNumber(Client $client) + { + return $this->getNextEntityNumber(RecurringInvoice::class, $client); + } + + /** + * Gets the next Payment number. + * + * @param Client $client The client + * + * @return string The next payment number. */ public function getNextPaymentNumber(Client $client) :string { - - //Reset counters if enabled - $this->resetCounters($client); - - $is_client_counter = false; - - //todo handle if we have specific client patterns in the future - $pattern = $client->company->settings->payment_number_pattern; - - //Determine if we are using client_counters - if (strpos($pattern, 'client_counter') === false) { - $counter = $client->company->settings->payment_number_counter; - } else { - - if (property_exists($client->settings, 'payment_number_counter')) { - $counter = $client->settings->payment_number_counter; - } else { - $counter = 1; - } - - $is_client_counter = true; - } - - //Return a valid counter - $pattern = ''; - $padding = $client->getSetting('counter_padding'); - $payment_number = $this->checkEntityNumber(Payment::class, $client, $counter, $padding, $pattern); - - //increment the correct invoice_number Counter (company vs client) - if ($is_client_counter) { - $this->incrementCounter($client, 'payment_number_counter'); - } else { - $this->incrementCounter($client->company, 'payment_number_counter'); - } - - return (string) $payment_number; + return $this->getNextEntityNumber(Payment::class, $client); } /**