From c122987bc72baf5e129eae3d9616897e897d625c Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 31 May 2016 23:15:37 +0300 Subject: [PATCH 1/5] Enabled santizing input before validation --- app/Http/Requests/CreateDocumentRequest.php | 18 +++++++++++++ app/Http/Requests/Request.php | 28 ++++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/app/Http/Requests/CreateDocumentRequest.php b/app/Http/Requests/CreateDocumentRequest.php index dca98a3ec3ed..d00576478191 100644 --- a/app/Http/Requests/CreateDocumentRequest.php +++ b/app/Http/Requests/CreateDocumentRequest.php @@ -23,4 +23,22 @@ class CreateDocumentRequest extends DocumentRequest //'file' => 'mimes:jpg' ]; } + + /** + * Sanitize input before validation. + * + * @return array + */ + /* + public function sanitize() + { + $input = $this->all(); + + $input['phone'] = 'test123'; + + $this->replace($input); + + return $this->all(); + } + */ } diff --git a/app/Http/Requests/Request.php b/app/Http/Requests/Request.php index 4516ab2bb566..b0f5a5d85a95 100644 --- a/app/Http/Requests/Request.php +++ b/app/Http/Requests/Request.php @@ -2,8 +2,34 @@ use Illuminate\Foundation\Http\FormRequest; +// https://laracasts.com/discuss/channels/general-discussion/laravel-5-modify-input-before-validation/replies/34366 abstract class Request extends FormRequest { - // + /** + * Validate the input. + * + * @param \Illuminate\Validation\Factory $factory + * @return \Illuminate\Validation\Validator + */ + public function validator($factory) + { + return $factory->make( + $this->sanitizeInput(), $this->container->call([$this, 'rules']), $this->messages() + ); + } + /** + * Sanitize the input. + * + * @return array + */ + protected function sanitizeInput() + { + if (method_exists($this, 'sanitize')) + { + return $this->container->call([$this, 'sanitize']); + } + + return $this->all(); + } } From 42ac9507e5e98cd3b8cb2c5297e3f8603452c219 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 31 May 2016 23:15:55 +0300 Subject: [PATCH 2/5] Enabled importing products --- app/Models/Product.php | 18 +++++++ app/Ninja/Import/BaseTransformer.php | 27 +++++++++-- app/Ninja/Repositories/ProductRepository.php | 13 +++-- app/Services/ImportService.php | 48 ++++++++++++------- resources/lang/en/texts.php | 7 +++ .../views/accounts/import_export.blade.php | 14 +++--- resources/views/accounts/import_map.blade.php | 18 ++++--- 7 files changed, 105 insertions(+), 40 deletions(-) diff --git a/app/Models/Product.php b/app/Models/Product.php index 05944c9fff94..548d7b384521 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -16,6 +16,24 @@ class Product extends EntityModel 'default_tax_rate_id', ]; + public static function getImportColumns() + { + return [ + 'product_key', + 'notes', + 'cost', + ]; + } + + public static function getImportMap() + { + return [ + 'product|item' => 'product_key', + 'notes|description|details' => 'notes', + 'cost|amount|price' => 'cost', + ]; + } + public function getEntityType() { return ENTITY_PRODUCT; diff --git a/app/Ninja/Import/BaseTransformer.php b/app/Ninja/Import/BaseTransformer.php index 8e17bfeec36f..a06f420ca8c6 100644 --- a/app/Ninja/Import/BaseTransformer.php +++ b/app/Ninja/Import/BaseTransformer.php @@ -15,21 +15,38 @@ class BaseTransformer extends TransformerAbstract protected function hasClient($name) { - $name = strtolower($name); + $name = trim(strtolower($name)); return isset($this->maps[ENTITY_CLIENT][$name]); } + protected function hasProduct($key) + { + $key = trim(strtolower($key)); + return isset($this->maps[ENTITY_PRODUCT][$key]); + } + protected function getString($data, $field) { return (isset($data->$field) && $data->$field) ? $data->$field : ''; } + protected function getNumber($data, $field) + { + return (isset($data->$field) && $data->$field) ? $data->$field : 0; + } + protected function getClientId($name) { $name = strtolower($name); return isset($this->maps[ENTITY_CLIENT][$name]) ? $this->maps[ENTITY_CLIENT][$name] : null; } + protected function getProductId($name) + { + $name = strtolower($name); + return isset($this->maps[ENTITY_PRODUCT][$name]) ? $this->maps[ENTITY_PRODUCT][$name] : null; + } + protected function getCountryId($name) { $name = strtolower($name); @@ -53,7 +70,7 @@ class BaseTransformer extends TransformerAbstract if ( ! $date instanceof DateTime) { $date = DateTime::createFromFormat($format, $date); } - + return $date ? $date->format('Y-m-d') : null; } @@ -87,11 +104,11 @@ class BaseTransformer extends TransformerAbstract return isset($this->maps[ENTITY_INVOICE.'_'.ENTITY_CLIENT][$invoiceNumber])? $this->maps[ENTITY_INVOICE.'_'.ENTITY_CLIENT][$invoiceNumber] : null; } - + protected function getVendorId($name) { $name = strtolower($name); return isset($this->maps[ENTITY_VENDOR][$name]) ? $this->maps[ENTITY_VENDOR][$name] : null; } - -} \ No newline at end of file + +} diff --git a/app/Ninja/Repositories/ProductRepository.php b/app/Ninja/Repositories/ProductRepository.php index eb0e7383e9e5..968f9ca44398 100644 --- a/app/Ninja/Repositories/ProductRepository.php +++ b/app/Ninja/Repositories/ProductRepository.php @@ -11,6 +11,13 @@ class ProductRepository extends BaseRepository return 'App\Models\Product'; } + public function all() + { + return Product::scope() + ->withTrashed() + ->get(); + } + public function find($accountId) { return DB::table('products') @@ -30,11 +37,11 @@ class ProductRepository extends BaseRepository 'products.deleted_at' ); } - + public function save($data, $product = null) { $publicId = isset($data['public_id']) ? $data['public_id'] : false; - + if ($product) { // do nothing } elseif ($publicId) { @@ -50,4 +57,4 @@ class ProductRepository extends BaseRepository return $product; } -} \ No newline at end of file +} diff --git a/app/Services/ImportService.php b/app/Services/ImportService.php index c17a8e359a80..61eb0a1881f9 100644 --- a/app/Services/ImportService.php +++ b/app/Services/ImportService.php @@ -13,6 +13,7 @@ use App\Ninja\Repositories\ContactRepository; use App\Ninja\Repositories\ClientRepository; use App\Ninja\Repositories\InvoiceRepository; use App\Ninja\Repositories\PaymentRepository; +use App\Ninja\Repositories\ProductRepository; use App\Ninja\Serializers\ArraySerializer; use App\Models\Client; use App\Models\Invoice; @@ -23,6 +24,7 @@ class ImportService protected $invoiceRepo; protected $clientRepo; protected $contactRepo; + protected $productRepo; protected $processedRows = array(); public static $entityTypes = [ @@ -31,6 +33,8 @@ class ImportService ENTITY_INVOICE, ENTITY_PAYMENT, ENTITY_TASK, + ENTITY_PRODUCT, + ENTITY_EXPENSE, ]; public static $sources = [ @@ -45,7 +49,14 @@ class ImportService IMPORT_ZOHO, ]; - public function __construct(Manager $manager, ClientRepository $clientRepo, InvoiceRepository $invoiceRepo, PaymentRepository $paymentRepo, ContactRepository $contactRepo) + public function __construct( + Manager $manager, + ClientRepository $clientRepo, + InvoiceRepository $invoiceRepo, + PaymentRepository $paymentRepo, + ContactRepository $contactRepo, + ProductRepository $productRepo + ) { $this->fractal = $manager; $this->fractal->setSerializer(new ArraySerializer()); @@ -54,6 +65,7 @@ class ImportService $this->invoiceRepo = $invoiceRepo; $this->paymentRepo = $paymentRepo; $this->contactRepo = $contactRepo; + $this->productRepo = $productRepo; } public function import($source, $files) @@ -216,8 +228,11 @@ class ImportService 'invoice_number' => 'required|unique:invoices,invoice_number,,id,account_id,'.Auth::user()->account_id, 'discount' => 'positive', ]; - } else { - return true; + } + if ($entityType === ENTITY_PRODUCT) { + $rules = [ + 'product_key' => 'required', + ]; } $validator = Validator::make($data, $rules); @@ -251,6 +266,14 @@ class ImportService } } + $productMap = []; + $products = $this->productRepo->all(); + foreach ($products as $product) { + if ($key = strtolower(trim($product->product_key))) { + $productMap[$key] = $product->id; + } + } + $countryMap = []; $countryMap2 = []; $countries = Cache::get('countries'); @@ -269,6 +292,7 @@ class ImportService ENTITY_CLIENT => $clientMap, ENTITY_INVOICE => $invoiceMap, ENTITY_INVOICE.'_'.ENTITY_CLIENT => $invoiceClientMap, + ENTITY_PRODUCT => $productMap, 'countries' => $countryMap, 'countries2' => $countryMap2, 'currencies' => $currencyMap, @@ -280,13 +304,9 @@ class ImportService $data = []; foreach ($files as $entityType => $filename) { - if ($entityType === ENTITY_CLIENT) { - $columns = Client::getImportColumns(); - $map = Client::getImportMap(); - } else { - $columns = Invoice::getImportColumns(); - $map = Invoice::getImportMap(); - } + $class = "App\\Models\\" . ucwords($entityType); + $columns = $class::getImportColumns(); + $map = $class::getImportMap(); // Lookup field translations foreach ($columns as $key => $value) { @@ -452,12 +472,8 @@ class ImportService private function convertToObject($entityType, $data, $map) { $obj = new stdClass(); - - if ($entityType === ENTITY_CLIENT) { - $columns = Client::getImportColumns(); - } else { - $columns = Invoice::getImportColumns(); - } + $class = "App\\Models\\" . ucwords($entityType); + $columns = $class::getImportColumns(); foreach ($columns as $column) { $obj->$column = false; diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 9bf85f0202ae..b96ed265e9d3 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -1314,6 +1314,13 @@ $LANG = array( 'wait_for_upload' => 'Please wait for the document upload to complete.', 'upgrade_for_permissions' => 'Upgrade to our Enterprise plan to enable permissions.', 'enable_second_tax_rate' => 'Enable specifying a second tax rate', + 'payment_file' => 'Payment File', + 'expense_file' => 'Expense File', + 'product_file' => 'Product File', + 'import_products' => 'Import Products', + 'products_will_create' => 'products will be created.', + 'product_key' => 'Product', + 'created_products' => 'Successfully created :count product(s)', ); diff --git a/resources/views/accounts/import_export.blade.php b/resources/views/accounts/import_export.blade.php index d2f482e0515b..b0f2fd5d7c26 100644 --- a/resources/views/accounts/import_export.blade.php +++ b/resources/views/accounts/import_export.blade.php @@ -4,9 +4,7 @@ @parent @@ -34,7 +32,7 @@ @foreach (\App\Services\ImportService::$entityTypes as $entityType) {!! Former::file("{$entityType}_file") - ->addGroupClass("{$entityType}-file") !!} + ->addGroupClass("import-file {$entityType}-file") !!} @endforeach {!! Former::actions( Button::info(trans('texts.upload'))->submit()->large()->appendIcon(Icon::create('open'))) !!} @@ -67,13 +65,17 @@ trans('texts.payments') => array('name' => ENTITY_PAYMENT, 'value' => 1), ])->check(ENTITY_CLIENT)->check(ENTITY_TASK)->check(ENTITY_INVOICE)->check(ENTITY_PAYMENT) !!} - {!! Former::actions( Button::primary(trans('texts.download'))->submit()->large()->appendIcon(Icon::create('download-alt'))) !!} + {!! Former::actions( Button::primary(trans('texts.download'))->submit()->large()->appendIcon(Icon::create('download-alt'))) !!} {!! Former::close() !!} -@stop \ No newline at end of file +@stop diff --git a/resources/views/accounts/import_map.blade.php b/resources/views/accounts/import_map.blade.php index 12ac6766d693..5c3c76ac7c20 100644 --- a/resources/views/accounts/import_map.blade.php +++ b/resources/views/accounts/import_map.blade.php @@ -7,18 +7,16 @@ {!! Former::open('/import_csv')->addClass('warn-on-exit') !!} - @if (isset($data[ENTITY_CLIENT])) - @include('accounts.partials.map', $data[ENTITY_CLIENT]) - @endif + @foreach (App\Services\ImportService::$entityTypes as $entityType) + @if (isset($data[$entityType])) + @include('accounts.partials.map', $data[$entityType]) + @endif + @endforeach - @if (isset($data[ENTITY_INVOICE])) - @include('accounts.partials.map', $data[ENTITY_INVOICE]) - @endif - - {!! Former::actions( + {!! Former::actions( Button::normal(trans('texts.cancel'))->large()->asLinkTo(URL::to('/settings/import_export'))->appendIcon(Icon::create('remove-circle')), Button::success(trans('texts.import'))->submit()->large()->appendIcon(Icon::create('floppy-disk'))) !!} - + {!! Former::close() !!} -@stop \ No newline at end of file +@stop From e6a05509b19a94f0ce5be754a08632a540ed5204 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 31 May 2016 23:16:05 +0300 Subject: [PATCH 3/5] Enabled importing products --- app/Ninja/Import/CSV/ProductTransformer.php | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 app/Ninja/Import/CSV/ProductTransformer.php diff --git a/app/Ninja/Import/CSV/ProductTransformer.php b/app/Ninja/Import/CSV/ProductTransformer.php new file mode 100644 index 000000000000..248d3ed09cba --- /dev/null +++ b/app/Ninja/Import/CSV/ProductTransformer.php @@ -0,0 +1,22 @@ +product_key) || $this->hasProduct($data->product_key)) { + return false; + } + + return new Item($data, function ($data) { + return [ + 'product_key' => $this->getString($data, 'product_key'), + 'notes' => $this->getString($data, 'notes'), + 'cost' => $this->getNumber($data, 'cost'), + ]; + }); + } +} From 51d9b2b4271ce40559c3603c90996cd4436e26b0 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 1 Jun 2016 10:56:44 +0300 Subject: [PATCH 4/5] Prevent converting a qoute from incrementing the counter --- app/Models/Account.php | 62 ++++++++++---------- app/Ninja/Repositories/InvoiceRepository.php | 2 +- 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/app/Models/Account.php b/app/Models/Account.php index 5dd8a12c1851..61526c9095d4 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -652,30 +652,34 @@ class Account extends Eloquent public function getNextInvoiceNumber($invoice) { if ($this->hasNumberPattern($invoice->invoice_type_id)) { - return $this->getNumberPattern($invoice); + $number = $this->getNumberPattern($invoice); + } else { + $counter = $this->getCounter($invoice->invoice_type_id); + $prefix = $this->getNumberPrefix($invoice->invoice_type_id); + $counterOffset = 0; + + // confirm the invoice number isn't already taken + do { + $number = $prefix . str_pad($counter, $this->invoice_number_padding, '0', STR_PAD_LEFT); + $check = Invoice::scope(false, $this->id)->whereInvoiceNumber($number)->withTrashed()->first(); + $counter++; + $counterOffset++; + } while ($check); + + // update the invoice counter to be caught up + if ($counterOffset > 1) { + if ($invoice->isType(INVOICE_TYPE_QUOTE) && !$this->share_counter) { + $this->quote_number_counter += $counterOffset - 1; + } else { + $this->invoice_number_counter += $counterOffset - 1; + } + + $this->save(); + } } - $counter = $this->getCounter($invoice->invoice_type_id); - $prefix = $this->getNumberPrefix($invoice->invoice_type_id); - $counterOffset = 0; - - // confirm the invoice number isn't already taken - do { - $number = $prefix . str_pad($counter, $this->invoice_number_padding, '0', STR_PAD_LEFT); - $check = Invoice::scope(false, $this->id)->whereInvoiceNumber($number)->withTrashed()->first(); - $counter++; - $counterOffset++; - } while ($check); - - // update the invoice counter to be caught up - if ($counterOffset > 1) { - if ($invoice->isType(INVOICE_TYPE_QUOTE) && !$this->share_counter) { - $this->quote_number_counter += $counterOffset - 1; - } else { - $this->invoice_number_counter += $counterOffset - 1; - } - - $this->save(); + if ($invoice->recurring_invoice_id) { + $number = $this->recurring_invoice_number_prefix . $number; } return $number; @@ -683,17 +687,15 @@ class Account extends Eloquent public function incrementCounter($invoice) { + // if they didn't use the counter don't increment it + if ($invoice->invoice_number != $this->getNextInvoiceNumber($invoice)) { + return; + } + if ($invoice->isType(INVOICE_TYPE_QUOTE) && !$this->share_counter) { $this->quote_number_counter += 1; } else { - $default = $this->invoice_number_counter; - $actual = Utils::parseInt($invoice->invoice_number); - - if ( ! $this->hasFeature(FEATURE_INVOICE_SETTINGS) && $default != $actual) { - $this->invoice_number_counter = $actual + 1; - } else { - $this->invoice_number_counter += 1; - } + $this->invoice_number_counter += 1; } $this->save(); diff --git a/app/Ninja/Repositories/InvoiceRepository.php b/app/Ninja/Repositories/InvoiceRepository.php index cb687430ebc5..c15e261848d0 100644 --- a/app/Ninja/Repositories/InvoiceRepository.php +++ b/app/Ninja/Repositories/InvoiceRepository.php @@ -741,7 +741,7 @@ class InvoiceRepository extends BaseRepository $invoice = Invoice::createNew($recurInvoice); $invoice->client_id = $recurInvoice->client_id; $invoice->recurring_invoice_id = $recurInvoice->id; - $invoice->invoice_number = $recurInvoice->account->recurring_invoice_number_prefix . $recurInvoice->account->getNextInvoiceNumber($recurInvoice); + $invoice->invoice_number = $recurInvoice->account->getNextInvoiceNumber($invoice); $invoice->amount = $recurInvoice->amount; $invoice->balance = $recurInvoice->amount; $invoice->invoice_date = date_create()->format('Y-m-d'); From 17eb2a7a79cd0686cf8ddf99efb7fa5861f96d97 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 1 Jun 2016 12:39:42 +0300 Subject: [PATCH 5/5] Support saving invoice_id and expense_id with the document --- .../Controllers/DocumentAPIController.php | 2 +- app/Http/Controllers/DocumentController.php | 2 +- app/Http/Requests/CreateDocumentRequest.php | 39 ++++----- app/Http/Requests/Request.php | 22 +++++- app/Models/Document.php | 79 ++++++++++--------- app/Ninja/Repositories/DocumentRepository.php | 10 ++- app/Providers/AppServiceProvider.php | 16 ++-- 7 files changed, 100 insertions(+), 70 deletions(-) diff --git a/app/Http/Controllers/DocumentAPIController.php b/app/Http/Controllers/DocumentAPIController.php index e4ca7c7fb5de..194c32e07d81 100644 --- a/app/Http/Controllers/DocumentAPIController.php +++ b/app/Http/Controllers/DocumentAPIController.php @@ -33,7 +33,7 @@ class DocumentAPIController extends BaseAPIController public function store(CreateDocumentRequest $request) { - $document = $this->documentRepo->upload($request->file); + $document = $this->documentRepo->upload($request->all()); return $this->itemResponse($document); } diff --git a/app/Http/Controllers/DocumentController.php b/app/Http/Controllers/DocumentController.php index f0871a2408f2..6f6b0bd0889d 100644 --- a/app/Http/Controllers/DocumentController.php +++ b/app/Http/Controllers/DocumentController.php @@ -102,7 +102,7 @@ class DocumentController extends BaseController public function postUpload(CreateDocumentRequest $request) { - $result = $this->documentRepo->upload($request->file, $doc_array); + $result = $this->documentRepo->upload($request->all(), $doc_array); if(is_string($result)){ return Response::json([ diff --git a/app/Http/Requests/CreateDocumentRequest.php b/app/Http/Requests/CreateDocumentRequest.php index d00576478191..5037ae3beb68 100644 --- a/app/Http/Requests/CreateDocumentRequest.php +++ b/app/Http/Requests/CreateDocumentRequest.php @@ -1,7 +1,15 @@ user()->can('create', ENTITY_DOCUMENT) && $this->user()->hasFeature(FEATURE_DOCUMENTS); + if ( ! $this->user()->hasFeature(FEATURE_DOCUMENTS)) { + return false; + } + + if ($this->invoice && $this->user()->cannot('edit', $this->invoice)) { + return false; + } + + if ($this->expense && $this->user()->cannot('edit', $this->expense)) { + return false; + } + + return $this->user()->can('create', ENTITY_DOCUMENT); } /** @@ -24,21 +44,4 @@ class CreateDocumentRequest extends DocumentRequest ]; } - /** - * Sanitize input before validation. - * - * @return array - */ - /* - public function sanitize() - { - $input = $this->all(); - - $input['phone'] = 'test123'; - - $this->replace($input); - - return $this->all(); - } - */ } diff --git a/app/Http/Requests/Request.php b/app/Http/Requests/Request.php index b0f5a5d85a95..c704e409f373 100644 --- a/app/Http/Requests/Request.php +++ b/app/Http/Requests/Request.php @@ -5,6 +5,9 @@ use Illuminate\Foundation\Http\FormRequest; // https://laracasts.com/discuss/channels/general-discussion/laravel-5-modify-input-before-validation/replies/34366 abstract class Request extends FormRequest { + // populate in subclass to auto load record + protected $autoload = []; + /** * Validate the input. * @@ -25,11 +28,24 @@ abstract class Request extends FormRequest { */ protected function sanitizeInput() { - if (method_exists($this, 'sanitize')) - { - return $this->container->call([$this, 'sanitize']); + if (method_exists($this, 'sanitize')) { + $input = $this->container->call([$this, 'sanitize']); + } else { + $input = $this->all(); } + // autoload referenced entities + foreach ($this->autoload as $entityType) { + if ($id = $this->input("{$entityType}_public_id") ?: $this->input("{$entityType}_id")) { + $class = "App\\Models\\" . ucwords($entityType); + $entity = $class::scope($id)->firstOrFail(); + $input[$entityType] = $entity; + $input[$entityType . '_id'] = $entity->id; + } + } + + $this->replace($input); + return $this->all(); } } diff --git a/app/Models/Document.php b/app/Models/Document.php index 6d9c24857143..cc455fed0d79 100644 --- a/app/Models/Document.php +++ b/app/Models/Document.php @@ -6,20 +6,25 @@ use Auth; class Document extends EntityModel { + protected $fillable = [ + 'invoice_id', + 'expense_id', + ]; + public static $extraExtensions = array( 'jpg' => 'jpeg', 'tif' => 'tiff', ); - + public static $allowedMimes = array(// Used by Dropzone.js; does not affect what the server accepts 'image/png', 'image/jpeg', 'image/tiff', 'application/pdf', 'image/gif', 'image/vnd.adobe.photoshop', 'text/plain', 'application/zip', 'application/msword', - 'application/excel', 'application/vnd.ms-excel', 'application/x-excel', 'application/x-msexcel', + 'application/excel', 'application/vnd.ms-excel', 'application/x-excel', 'application/x-msexcel', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet','application/postscript', 'image/svg+xml', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/vnd.ms-powerpoint', ); - + public static $types = array( 'png' => array( 'mime' => 'image/png', @@ -70,18 +75,18 @@ class Document extends EntityModel 'mime' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', ), ); - + public function fill(array $attributes) { parent::fill($attributes); - + if(empty($this->attributes['disk'])){ $this->attributes['disk'] = env('DOCUMENT_FILESYSTEM', 'documents'); } - + return $this; } - + public function account() { return $this->belongsTo('App\Models\Account'); @@ -101,7 +106,7 @@ class Document extends EntityModel { return $this->belongsTo('App\Models\Invoice')->withTrashed(); } - + public function getDisk(){ return Storage::disk(!empty($this->disk)?$this->disk:env('DOCUMENT_FILESYSTEM', 'documents')); } @@ -110,19 +115,19 @@ class Document extends EntityModel { $this->attributes['disk'] = $value?$value:env('DOCUMENT_FILESYSTEM', 'documents'); } - + public function getDirectUrl(){ return static::getDirectFileUrl($this->path, $this->getDisk()); } - + public function getDirectPreviewUrl(){ return $this->preview?static::getDirectFileUrl($this->preview, $this->getDisk(), true):null; } - + public static function getDirectFileUrl($path, $disk, $prioritizeSpeed = false){ $adapter = $disk->getAdapter(); $fullPath = $adapter->applyPathPrefix($path); - + if($adapter instanceof \League\Flysystem\AwsS3v3\AwsS3Adapter) { $client = $adapter->getClient(); $command = $client->getCommand('GetObject', [ @@ -136,12 +141,12 @@ class Document extends EntityModel $secret = env('RACKSPACE_TEMP_URL_SECRET'); if($secret){ $object = $adapter->getContainer()->getObject($fullPath); - + if(env('RACKSPACE_TEMP_URL_SECRET_SET')){ // Go ahead and set the secret too $object->getService()->getAccount()->setTempUrlSecret($secret); - } - + } + $url = $object->getUrl(); $expiry = strtotime('+10 minutes'); $urlPath = urldecode($url->getPath()); @@ -150,64 +155,64 @@ class Document extends EntityModel return sprintf('%s?temp_url_sig=%s&temp_url_expires=%d', $url, $hash, $expiry); } } - + return null; } - + public function getRaw(){ $disk = $this->getDisk(); - + return $disk->get($this->path); } - + public function getStream(){ $disk = $this->getDisk(); - + return $disk->readStream($this->path); } - + public function getRawPreview(){ $disk = $this->getDisk(); - + return $disk->get($this->preview); } - + public function getUrl(){ return url('documents/'.$this->public_id.'/'.$this->name); } - + public function getClientUrl($invitation){ return url('client/documents/'.$invitation->invitation_key.'/'.$this->public_id.'/'.$this->name); } - + public function isPDFEmbeddable(){ return $this->type == 'jpeg' || $this->type == 'png' || $this->preview; } - + public function getVFSJSUrl(){ if(!$this->isPDFEmbeddable())return null; return url('documents/js/'.$this->public_id.'/'.$this->name.'.js'); } - + public function getClientVFSJSUrl(){ if(!$this->isPDFEmbeddable())return null; return url('client/documents/js/'.$this->public_id.'/'.$this->name.'.js'); } - + public function getPreviewUrl(){ return $this->preview?url('documents/preview/'.$this->public_id.'/'.$this->name.'.'.pathinfo($this->preview, PATHINFO_EXTENSION)):null; } - + public function toArray() { $array = parent::toArray(); - + if(empty($this->visible) || in_array('url', $this->visible))$array['url'] = $this->getUrl(); if(empty($this->visible) || in_array('preview_url', $this->visible))$array['preview_url'] = $this->getPreviewUrl(); - + return $array; } - + public function cloneDocument(){ $document = Document::createNew($this); $document->path = $this->path; @@ -219,7 +224,7 @@ class Document extends EntityModel $document->size = $this->size; $document->width = $this->width; $document->height = $this->height; - + return $document; } } @@ -230,11 +235,11 @@ Document::deleted(function ($document) { ->where('documents.path', '=', $document->path) ->where('documents.disk', '=', $document->disk) ->count(); - + if(!$same_path_count){ $document->getDisk()->delete($document->path); } - + if($document->preview){ $same_preview_count = DB::table('documents') ->where('documents.account_id', '=', $document->account_id) @@ -245,5 +250,5 @@ Document::deleted(function ($document) { $document->getDisk()->delete($document->preview); } } - -}); \ No newline at end of file + +}); diff --git a/app/Ninja/Repositories/DocumentRepository.php b/app/Ninja/Repositories/DocumentRepository.php index a2278b1843ae..0724144a7f09 100644 --- a/app/Ninja/Repositories/DocumentRepository.php +++ b/app/Ninja/Repositories/DocumentRepository.php @@ -57,8 +57,9 @@ class DocumentRepository extends BaseRepository return $query; } - public function upload($uploaded, &$doc_array=null) + public function upload($data, &$doc_array=null) { + $uploaded = $data['file']; $extension = strtolower($uploaded->getClientOriginalExtension()); if(empty(Document::$types[$extension]) && !empty(Document::$extraExtensions[$extension])){ $documentType = Document::$extraExtensions[$extension]; @@ -81,12 +82,17 @@ class DocumentRepository extends BaseRepository return 'File too large'; } - + // don't allow a document to be linked to both an invoice and an expense + if (array_get($data, 'invoice_id') && array_get($data, 'expense_id')) { + unset($data['expense_id']); + } $hash = sha1_file($filePath); $filename = \Auth::user()->account->account_key.'/'.$hash.'.'.$documentType; $document = Document::createNew(); + $document->fill($data); + $disk = $document->getDisk(); if(!$disk->exists($filename)){// Have we already stored the same file $stream = fopen($filePath, 'r'); diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index e7587e48b11a..79e0f5dc032d 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -29,8 +29,8 @@ class AppServiceProvider extends ServiceProvider { else{ $contents = $image; } - - return 'data:image/jpeg;base64,' . base64_encode($contents); + + return 'data:image/jpeg;base64,' . base64_encode($contents); }); Form::macro('nav_link', function($url, $text, $url2 = '', $extra = '') { @@ -58,11 +58,11 @@ class AppServiceProvider extends ServiceProvider { $str = '
  • '.trans("texts.new_$type").'
  • '; - + if ($type == ENTITY_INVOICE) { if(!empty($items))$items[] = '
  • '; $items[] = '
  • '.trans("texts.recurring_invoices").'
  • '; @@ -81,7 +81,7 @@ class AppServiceProvider extends ServiceProvider { $items[] = '
  • '.trans("texts.vendors").'
  • '; if($user->can('create', ENTITY_VENDOR))$items[] = '
  • '.trans("texts.new_vendor").'
  • '; } - + if(!empty($items)){ $str.= ''; } @@ -157,14 +157,14 @@ class AppServiceProvider extends ServiceProvider { return $str . ''; }); - + Form::macro('human_filesize', function($bytes, $decimals = 1) { $size = array('B','kB','MB','GB','TB','PB','EB','ZB','YB'); $factor = floor((strlen($bytes) - 1) / 3); if($factor == 0)$decimals=0;// There aren't fractional bytes return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . ' ' . @$size[$factor]; }); - + Validator::extend('positive', function($attribute, $value, $parameters) { return Utils::parseFloat($value) >= 0; });