diff --git a/.all-contributorsrc b/.all-contributorsrc index 1b38ea278c..8a2330391d 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -1839,6 +1839,15 @@ "contributions": [ "code" ] + }, + { + "login": "mskrip", + "name": "Marián Skrip", + "avatar_url": "https://avatars0.githubusercontent.com/u/17459600?v=4", + "profile": "https://github.com/mskrip", + "contributions": [ + "code" + ] } ] } diff --git a/.env.example b/.env.example index 347570b795..b7684b9174 100644 --- a/.env.example +++ b/.env.example @@ -43,6 +43,7 @@ MAIL_FROM_ADDR=you@example.com MAIL_FROM_NAME='Snipe-IT' MAIL_REPLYTO_ADDR=you@example.com MAIL_REPLYTO_NAME='Snipe-IT' +MAIL_BACKUP_NOTIFICATION_ADDRESS=you@example.com MAIL_AUTO_EMBED=true MAIL_AUTO_EMBED_METHOD=base64 @@ -66,8 +67,11 @@ SESSION_PATH=null # -------------------------------------------- # OPTIONAL: SECURITY HEADER SETTINGS # -------------------------------------------- +APP_TRUSTED_PROXIES=192.168.1.1,10.0.0.1 +ALLOW_IFRAMING=false REFERRER_POLICY=same-origin ENABLE_CSP=false +CORS_ALLOWED_ORIGINS=null # -------------------------------------------- # OPTIONAL: CACHE SETTINGS @@ -113,8 +117,6 @@ APP_LOG=single APP_LOG_MAX_FILES=10 APP_LOG_LEVEL=debug FILESYSTEM_DISK=local -APP_TRUSTED_PROXIES=192.168.1.1,10.0.0.1 -ALLOW_IFRAMING=false APP_CIPHER=AES-256-CBC GOOGLE_MAPS_API= BACKUP_ENV=true diff --git a/README.md b/README.md index bbb1d2a3bc..9ed53724fe 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ [![Build Status](https://travis-ci.org/snipe/snipe-it.svg?branch=master)](https://travis-ci.org/snipe/snipe-it) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.svg)](https://crowdin.com/project/snipe-it) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/snipe/snipe-it?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Docker Pulls](https://img.shields.io/docker/pulls/snipe/snipe-it.svg)](https://hub.docker.com/r/snipe/snipe-it/) [![Twitter Follow](https://img.shields.io/twitter/follow/snipeitapp.svg?style=social)](https://twitter.com/snipeitapp) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/553ce52037fc43ea99149785afcfe641)](https://www.codacy.com/app/snipe/snipe-it?utm_source=github.com&utm_medium=referral&utm_content=snipe/snipe-it&utm_campaign=Badge_Grade) -[![All Contributors](https://img.shields.io/badge/all_contributors-180-orange.svg?style=flat-square)](#contributors) [![Open Source Helpers](https://www.codetriage.com/snipe/snipe-it/badges/users.svg)](https://www.codetriage.com/snipe/snipe-it) +[![All Contributors](https://img.shields.io/badge/all_contributors-182-orange.svg?style=flat-square)](#contributors) [![Open Source Helpers](https://www.codetriage.com/snipe/snipe-it/badges/users.svg)](https://www.codetriage.com/snipe/snipe-it) ## Snipe-IT - Open Source Asset Management System This is a FOSS project for asset management in IT Operations. Knowing who has which laptop, when it was purchased in order to depreciate it correctly, handling software licenses, etc. -It is built on [Laravel 5.4](http://laravel.com). +It is built on [Laravel 5.5](http://laravel.com). -Snipe-IT is actively developed and we're [releasing quite frequently](https://github.com/snipe/snipe-it/releases). ([Check out the live demo here](https://snipeitapp.com/demo/).) +Snipe-IT is actively developed and we [release quite frequently](https://github.com/snipe/snipe-it/releases). ([Check out the live demo here](https://snipeitapp.com/demo/).) __This is web-based software__. This means there is no executable file (aka no .exe files), and it must be run on a web server and accessed through a web browser. It runs on any Mac OSX, flavor of Linux, as well as Windows, and we have a [Docker image](https://snipe-it.readme.io/docs/docker) available if that's what you're into. @@ -58,8 +58,10 @@ Since the release of the JSON REST API, several third-party developers have been - [SnipeSharp - .NET module in C#](https://github.com/barrycarey/SnipeSharp) by [@barrycarey](https://github.com/barrycarey) - [InQRy](https://github.com/Microsoft/InQRy) by [@Microsoft](https://github.com/Microsoft) - [SnipeitPS](https://github.com/snazy2000/SnipeitPS) by [@snazy2000](https://github.com/snazy2000) - Powershell API Wrapper for Snipe-it -- [jamf2snipe](https://github.com/ParadoxGuitarist/jamf2snipe) by [@ParadoxGuitarist](https://github.com/ParadoxGuitarist) - Python script to sync assets between a JAMFPro instance and a Snipe-II instance +- [jamf2snipe](https://github.com/ParadoxGuitarist/jamf2snipe) by [@ParadoxGuitarist](https://github.com/ParadoxGuitarist) - Python script to sync assets between a JAMFPro instance and a Snipe-IT instance - [Marksman](https://github.com/Scope-IT/marksman) - A Windows agent for Snipe-IT +- [Snipe-IT plugin for Jira Service Desk (beta)](https://marketplace.atlassian.com/apps/1220379/snipe-it-for-jira-service-desk-beta?hosting=cloud&tab=overview) - for the upcoming Snipe-IT v5 only +- [Python 3 CSV importer](https://github.com/gastamper/snipeit-csvimporter) - allows importing assets into Snipe-IT based on Item Name rather than Asset Tag. As these were created by third-parties, Snipe-IT cannot provide support for these project, and you should contact the developers directly if you need assistance. Additionally, Snipe-IT makes no guarantees as to the reliability, accuracy or maintainability of these libraries. Use at your own risk. :) diff --git a/app/Console/Commands/ImportLocations.php b/app/Console/Commands/ImportLocations.php new file mode 100644 index 0000000000..b037b011bf --- /dev/null +++ b/app/Console/Commands/ImportLocations.php @@ -0,0 +1,160 @@ +argument('filename'); + $csv = Reader::createFromPath(storage_path('private_uploads/imports/').$filename, 'r'); + $this->info('Attempting to process: '.storage_path('private_uploads/imports/').$filename); + $csv->setHeaderOffset(0); //because we don't want to insert the header + $results = $csv->getRecords(); + + // Import parent location names first if they don't exist + foreach ($results as $parent_index => $parent_row) { + + if (array_key_exists('Parent Name', $parent_row)) { + $parent_name = trim($parent_row['Parent Name']); + if (array_key_exists('Name', $parent_row)) { + $this->info('- Parent: ' . $parent_name . ' in row as: ' . trim($parent_row['Parent Name'])); + } + + // Save parent location name + // This creates a sort of name-stub that we'll update later on in this script + $parent_location = Location::firstOrCreate(array('name' => $parent_name)); + if (array_key_exists('Name', $parent_row)) { + $this->info('Parent for ' . $parent_row['Name'] . ' is ' . $parent_name . '. Attempting to save ' . $parent_name . '.'); + } + + // Check if the record was updated or created. + // This is mostly for clearer debugging. + if ($parent_location->exists) { + $this->info('- Parent location '.$parent_name.' already exists.'); + } else { + $this->info('- Parent location '.$parent_name.' was created.'); + } + + } else { + $this->info('- No Parent Name provided, so no parent location will be created.'); + } + + } + + $this->info('----- Parents Created.... backfilling additional details... --------'); + // Loop through ALL records and add/update them if there are additional fields + // besides name + foreach ($results as $index => $row) { + + if (array_key_exists('Parent Name', $row)) { + $parent_name = trim($row['Parent Name']); + } + + // Set the location attributes to save + if (array_key_exists('Name', $row)) { + $location = Location::firstOrNew(array('name' => trim($row['Name']))); + $location->name = trim($row['Name']); + $this->info('Checking location: '.$location->name); + } else { + $this->error('Location name is required and is missing from at least one row in this dataset. Check your CSV for extra trailing rows and try again.'); + return false; + } + if (array_key_exists('Currency', $row)) { + $location->currency = trim($row['Currency']); + } + if (array_key_exists('Address 1', $row)) { + $location->address = trim($row['Address 1']); + } + if (array_key_exists('Address 2', $row)) { + $location->address2 = trim($row['Address 2']); + } + if (array_key_exists('City', $row)) { + $location->city = trim($row['City']); + } + if (array_key_exists('State', $row)) { + $location->state = trim($row['State']); + } + if (array_key_exists('Zip', $row)) { + $location->zip = trim($row['Zip']); + } + if (array_key_exists('Country', $row)) { + $location->country = trim($row['Country']); + } + if (array_key_exists('Country', $row)) { + $location->ldap_ou = trim($row['OU']); + } + + + // If a parent name is provided, we created it earlier in the script, + // so let's grab that ID + if ($parent_name) { + $this->info('-- Searching for Parent Name: '.$parent_name); + $parent = Location::where('name', '=', $parent_name)->first(); + $location->parent_id = $parent->id; + $this->info('Parent: '.$parent_name.' - ID: '.$parent->id); + } + + // Make sure the more advanced (non-name) fields pass validation + if (($location->isValid()) && ($location->save())) { + + // Check if the record was updated or created. + // This is mostly for clearer debugging. + if ($location->exists) { + $this->info('Location ' . $location->name . ' already exists. Updating...'); + } else { + $this->info('- Location '.$location->name.' was created. '); + } + + // If there's a validation error, display that + } else { + $this->error('- Non-parent Location '.$location->name.' could not be created: '.$location->getErrors() ); + } + + + + + } + + + } +} diff --git a/app/Console/Commands/Purge.php b/app/Console/Commands/Purge.php index 0c4829c94c..a9f1da7374 100644 --- a/app/Console/Commands/Purge.php +++ b/app/Console/Commands/Purge.php @@ -30,7 +30,7 @@ class Purge extends Command * * @var string */ - protected $description = 'Purge all soft-deleted deleted records in the database. This will rewrite history for items that have been edited, or checked in or out. It will also reqrite history for users associated with deleted items.'; + protected $description = 'Purge all soft-deleted deleted records in the database. This will rewrite history for items that have been edited, or checked in or out. It will also rewrite history for users associated with deleted items.'; /** * Create a new command instance. diff --git a/app/Console/Commands/ReEncodeCustomFieldNames.php b/app/Console/Commands/ReEncodeCustomFieldNames.php new file mode 100644 index 0000000000..14276b1bd3 --- /dev/null +++ b/app/Console/Commands/ReEncodeCustomFieldNames.php @@ -0,0 +1,126 @@ +convertUnicodeDbSlug() is + * - the actual db_column name in the customfields table + * - the physical column name that was created on the assets table + * + * For some people who upgraded their version of PHP, the unicode converter now behaves + * differently in than it did when their custom fields were first created, specifically as it + * relates to handling slashes, ampersands, etc. This can result in the field names no longer + * matching up, as an older version of the PHP extension simply dropped slashes, etc, while the + * newer version of the PHP extension will convert them to underscores. + * + * @return mixed + */ + public function handle() + { + + if ($this->confirm('This will regenerate all of the custom field database fieldnames in your database. THIS WILL CHANGE YOUR SCHEMA AND SHOULD NOT BE DONE WITHOUT MAKING A BACKUP FIRST. Do you wish to continue?')) + { + + /** Get all of the custom fields */ + $fields = CustomField::get(); + + $asset_columns = \DB::getSchemaBuilder()->getColumnListing('assets'); + $custom_field_columns = array(); + + /** Loop through the columns on the assets table */ + foreach ($asset_columns as $asset_column) { + + /** Add ones that start with _snipeit_ to an array for handling */ + if (strpos($asset_column, '_snipeit_') === 0) { + + /** + * Get the ID of the custom field based on the fieldname. + * For example, in _snipeit_mac_address_1, we grab the 1 because we know + * that's the ID of the custom field that created the column. + * Then use that ID as the array key for use comparing the actual assets field name + * and the db_column value from the custom fields table. + */ + $last_part = substr(strrchr($asset_column, "_snipeit_"), 1); + $custom_field_columns[$last_part] = $asset_column; + } + } + + foreach ($fields as $field) { + + $this->info($field->name .' ('.$field->id.') column should be '. $field->convertUnicodeDbSlug().''); + + /** The assets table has the column it should have, all is well */ + if (\Schema::hasColumn('assets', $field->convertUnicodeDbSlug())) + { + $this->info('-- ✓ This field exists - all good'); + + /** + * There is a mismatch between the fieldname on the assets table and + * what $field->convertUnicodeDbSlug() is *now* expecting. + */ + } else { + $this->warn('-- X Field mismatch: updating... '); + + /** Make sure the custom_field_columns array has the ID */ + if (array_key_exists($field->id, $custom_field_columns)) { + + /** + * Update the asset schema to the corrected fieldname that will be recognized by the + * system elsewhere that we use $field->convertUnicodeDbSlug() + */ + \Schema::table('assets', function($table) use ($custom_field_columns, $field) { + $table->renameColumn($custom_field_columns[$field->id], $field->convertUnicodeDbSlug()); + }); + + $this->warn('-- ✓ Field updated from '.$custom_field_columns[$field->id].' to '.$field->convertUnicodeDbSlug()); + + } else { + $this->warn('-- X WARNING: There is no field on the assets table ending in '.$field->id.'. This may require more in-depth investigation and may mean the schema was altered manually.'); + } + } + + /** Update the db_column property in the custom fields table, just in case it doesn't match the other + * things. + */ + $field->db_column = $field->convertUnicodeDbSlug(); + $field->save(); + + + } + + } + + + } +} diff --git a/app/Console/Commands/SyncAssetLocations.php b/app/Console/Commands/SyncAssetLocations.php index e5fb76bbd7..b5d3cd33e0 100644 --- a/app/Console/Commands/SyncAssetLocations.php +++ b/app/Console/Commands/SyncAssetLocations.php @@ -62,7 +62,7 @@ class SyncAssetLocations extends Command $output['info'][] = 'There are '.$assigned_user_assets->count().' assets checked out to users.'; foreach ($assigned_user_assets as $assigned_user_asset) { if (($assigned_user_asset->assignedTo) && ($assigned_user_asset->assignedTo->userLoc)) { - $new_location=$assigned_user_asset->assignedTo->userloc->id; + $new_location = $assigned_user_asset->assignedTo->userLoc->id; $output['info'][] ='Setting User Asset ' . $assigned_user_asset->id . ' ('.$assigned_user_asset->asset_tag.') to ' . $assigned_user_asset->assignedTo->userLoc->name . ' which is id: ' . $new_location; } else { $output['warn'][] ='Asset ' . $assigned_user_asset->id . ' ('.$assigned_user_asset->asset_tag.') still has no location! '; diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 7ca813351d..bafa469d0f 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -2,7 +2,6 @@ namespace App\Console; -use App\Console\Commands\RestoreDeletedUsers; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; @@ -35,6 +34,8 @@ class Kernel extends ConsoleKernel Commands\SendCurrentInventoryToUsers::class, Commands\MoveUploadsToNewDisk::class, Commands\SendUpcomingAuditReport::class, + Commands\ImportLocations::class, + Commands\ReEncodeCustomFieldNames::class, ]; /** @@ -60,5 +61,6 @@ class Kernel extends ConsoleKernel protected function commands() { require base_path('routes/console.php'); + $this->load(__DIR__.'/Commands'); } } diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 8d9e71bb28..f6449b25de 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -22,6 +22,7 @@ class Handler extends ExceptionHandler \Illuminate\Database\Eloquent\ModelNotFoundException::class, \Illuminate\Session\TokenMismatchException::class, \Illuminate\Validation\ValidationException::class, + \Intervention\Image\Exception\NotSupportedException::class, ]; /** @@ -65,10 +66,6 @@ class Handler extends ExceptionHandler return response()->json(Helper::formatStandardApiResponse('error', null, $className . ' not found'), 200); } - if ($e instanceof \Illuminate\Validation\ValidationException) { - return response()->json(Helper::formatStandardApiResponse('error', null, $e->response['messages'], 400)); - } - if ($this->isHttpException($e)) { $statusCode = $e->getStatusCode(); @@ -83,11 +80,6 @@ class Handler extends ExceptionHandler } } - // Try to parse 500 Errors in a bit nicer way when debug is enabled. - if (config('app.debug')) { - return response()->json(Helper::formatStandardApiResponse('error', null, "An Error has occured! " . $e->getMessage()), 500); - } - } @@ -116,4 +108,16 @@ class Handler extends ExceptionHandler return redirect()->guest('login'); } + + /** + * Convert a validation exception into a JSON response. + * + * @param \Illuminate\Http\Request $request + * @param \Illuminate\Validation\ValidationException $exception + * @return \Illuminate\Http\JsonResponse + */ + protected function invalidJson($request, ValidationException $exception) + { + return response()->json(Helper::formatStandardApiResponse('error', null, $exception->errors(), 400)); + } } diff --git a/app/Http/Controllers/Api/AccessoriesController.php b/app/Http/Controllers/Api/AccessoriesController.php index a0743c16c2..bb7e8271e7 100644 --- a/app/Http/Controllers/Api/AccessoriesController.php +++ b/app/Http/Controllers/Api/AccessoriesController.php @@ -8,6 +8,10 @@ use App\Http\Transformers\AccessoriesTransformer; use App\Http\Transformers\SelectlistTransformer; use App\Models\Accessory; use App\Models\Company; +use App\Models\User; +use Carbon\Carbon; +use Auth; +use DB; use Illuminate\Http\Request; class AccessoriesController extends Controller @@ -210,7 +214,96 @@ class AccessoriesController extends Controller return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.delete.success'))); } - + + /** + * Save the Accessory checkout information. + * + * If Slack is enabled and/or asset acceptance is enabled, it will also + * trigger a Slack message and send an email. + * + * @author [A. Gianotto] [] + * @param int $accessoryId + * @return Redirect + */ + public function checkout(Request $request, $accessoryId) + { + // Check if the accessory exists + if (is_null($accessory = Accessory::find($accessoryId))) { + return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist'))); + } + + $this->authorize('checkout', $accessory); + + + if ($accessory->numRemaining() > 0) { + + if (!$user = User::find($request->input('assigned_to'))) { + return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.checkout.user_does_not_exist'))); + } + + // Update the accessory data + $accessory->assigned_to = $request->input('assigned_to'); + + $accessory->users()->attach($accessory->id, [ + 'accessory_id' => $accessory->id, + 'created_at' => Carbon::now(), + 'user_id' => Auth::id(), + 'assigned_to' => $request->get('assigned_to') + ]); + + $accessory->logCheckout($request->input('note'), $user); + + return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkout.success'))); + } + + return response()->json(Helper::formatStandardApiResponse('error', null, 'No accessories remaining')); + + } + + /** + * Check in the item so that it can be checked out again to someone else + * + * @uses Accessory::checkin_email() to determine if an email can and should be sent + * @author [A. Gianotto] [] + * @param Request $request + * @param integer $accessoryUserId + * @param string $backto + * @return Redirect + * @internal param int $accessoryId + */ + public function checkin(Request $request, $accessoryUserId = null) + { + if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) { + return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist'))); + } + + $accessory = Accessory::find($accessory_user->accessory_id); + $this->authorize('checkin', $accessory); + + $logaction = $accessory->logCheckin(User::find($accessoryUserId), $request->input('note')); + + // Was the accessory updated? + if (DB::table('accessories_users')->where('id', '=', $accessory_user->id)->delete()) { + if (!is_null($accessory_user->assigned_to)) { + $user = User::find($accessory_user->assigned_to); + } + + $data['log_id'] = $logaction->id; + $data['first_name'] = $user->first_name; + $data['last_name'] = $user->last_name; + $data['item_name'] = $accessory->name; + $data['checkin_date'] = $logaction->created_at; + $data['item_tag'] = ''; + $data['note'] = $logaction->note; + + return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkin.success'))); + } + + return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.checkin.error'))); + + } + + /** * Gets a paginated collection for the select2 menus * @@ -234,4 +327,7 @@ class AccessoriesController extends Controller return (new SelectlistTransformer)->transformSelectlist($accessories); } + + + } diff --git a/app/Http/Controllers/Api/AssetsController.php b/app/Http/Controllers/Api/AssetsController.php index ed51044751..a07f186620 100644 --- a/app/Http/Controllers/Api/AssetsController.php +++ b/app/Http/Controllers/Api/AssetsController.php @@ -464,7 +464,13 @@ class AssetsController extends Controller $model = AssetModel::find($request->get('model_id')); if (($model) && ($model->fieldset)) { foreach ($model->fieldset->fields as $field) { - $asset->{$field->convertUnicodeDbSlug()} = e($request->input($field->convertUnicodeDbSlug(), null)); + if ($field->field_encrypted=='1') { + if (Gate::allows('admin')) { + $asset->{$field->convertUnicodeDbSlug()} = \Crypt::encrypt($request->input($field->convertUnicodeDbSlug())); + } + } else { + $asset->{$field->convertUnicodeDbSlug()} = $request->input($field->convertUnicodeDbSlug()); + } } } @@ -514,7 +520,7 @@ class AssetsController extends Controller ($request->filled('company_id')) ? $asset->company_id = Company::getIdForCurrentUser($request->get('company_id')) : ''; - if ($request->has('image_source')) { + if ($request->filled('image_source')) { if ($request->input('image_source') == "") { $asset->image = null; } else { @@ -537,8 +543,14 @@ class AssetsController extends Controller // Update custom fields if (($model = AssetModel::find($asset->model_id)) && (isset($model->fieldset))) { foreach ($model->fieldset->fields as $field) { - if ($request->filled($field->convertUnicodeDbSlug())) { - $asset->{$field->convertUnicodeDbSlug()} = e($request->input($field->convertUnicodeDbSlug())); + if ($request->has($field->convertUnicodeDbSlug())) { + if ($field->field_encrypted=='1') { + if (Gate::allows('admin')) { + $asset->{$field->convertUnicodeDbSlug()} = \Crypt::encrypt($request->input($field->convertUnicodeDbSlug())); + } + } else { + $asset->{$field->convertUnicodeDbSlug()} = $request->input($field->convertUnicodeDbSlug()); + } } } } @@ -706,7 +718,11 @@ class AssetsController extends Controller $asset->assigned_to = null; $asset->assignedTo()->disassociate($asset); $asset->accepted = null; - $asset->name = Input::get('name'); + + if ($request->filled('name')) { + $asset->name = $request->input('name'); + } + $asset->location_id = $asset->rtd_location_id; if ($request->filled('location_id')) { diff --git a/app/Http/Controllers/Api/ConsumablesController.php b/app/Http/Controllers/Api/ConsumablesController.php index 46f36c58ba..221255ce2d 100644 --- a/app/Http/Controllers/Api/ConsumablesController.php +++ b/app/Http/Controllers/Api/ConsumablesController.php @@ -166,7 +166,7 @@ class ConsumablesController extends Controller { $consumable = Consumable::with(array('consumableAssignments'=> function ($query) { - $query->orderBy('created_at', 'DESC'); + $query->orderBy($query->getModel()->getTable().'.created_at', 'DESC'); }, 'consumableAssignments.admin'=> function ($query) { }, diff --git a/app/Http/Controllers/Api/LocationsController.php b/app/Http/Controllers/Api/LocationsController.php index 10cca5dc8d..3410a880b7 100644 --- a/app/Http/Controllers/Api/LocationsController.php +++ b/app/Http/Controllers/Api/LocationsController.php @@ -124,9 +124,9 @@ class LocationsController extends Controller 'locations.image', 'locations.currency' ]) - ->withCount('assignedAssets') - ->withCount('assets') - ->withCount('users')->findOrFail($id); + ->withCount('assignedAssets as assigned_assets_count') + ->withCount('assets as assets_count') + ->withCount('users as users_count')->findOrFail($id); return (new LocationsTransformer)->transformLocation($location); } diff --git a/app/Http/Controllers/Api/ManufacturersController.php b/app/Http/Controllers/Api/ManufacturersController.php index a1cf9027ba..b4e8d71753 100644 --- a/app/Http/Controllers/Api/ManufacturersController.php +++ b/app/Http/Controllers/Api/ManufacturersController.php @@ -83,7 +83,7 @@ class ManufacturersController extends Controller public function show($id) { $this->authorize('view', Manufacturer::class); - $manufacturer = Manufacturer::withCount('assets as assets_count', 'licenses as licenses_count', 'consumables as consumables_count', 'accessories as accessories_count', 'models as models_count' )->findOrFail($id); + $manufacturer = Manufacturer::withCount('assets as assets_count')->withCount('licenses as licenses_count')->withCount('consumables as consumables_count')->withCount('accessories as accessories_count')->findOrFail($id); return (new ManufacturersTransformer)->transformManufacturer($manufacturer); } diff --git a/app/Http/Controllers/Api/SuppliersController.php b/app/Http/Controllers/Api/SuppliersController.php index 17b2b36beb..9e7d7b546a 100644 --- a/app/Http/Controllers/Api/SuppliersController.php +++ b/app/Http/Controllers/Api/SuppliersController.php @@ -25,7 +25,7 @@ class SuppliersController extends Controller $allowed_columns = ['id','name','address','phone','contact','fax','email','image','assets_count','licenses_count', 'accessories_count','url']; $suppliers = Supplier::select( - array('id','name','address','address2','city','state','country','fax', 'phone','email','contact','created_at','updated_at','deleted_at','image','notes', 'url') + array('id','name','address','address2','city','state','country','fax', 'phone','email','contact','created_at','updated_at','deleted_at','image','notes') )->withCount('assets as assets_count')->withCount('licenses as licenses_count')->withCount('accessories as accessories_count'); diff --git a/app/Http/Controllers/Api/UsersController.php b/app/Http/Controllers/Api/UsersController.php index ea01ec9c2e..85182ed3b4 100644 --- a/app/Http/Controllers/Api/UsersController.php +++ b/app/Http/Controllers/Api/UsersController.php @@ -60,12 +60,12 @@ class UsersController extends Controller 'users.zip', ])->with('manager', 'groups', 'userloc', 'company', 'department','assets','licenses','accessories','consumables') - ->withCount('assets as assets_count','licenses as licneses_count','accessories as accessories_count','consumables as consumables_count'); + ->withCount('assets as assets_count','licenses as licenses_count','accessories as accessories_count','consumables as consumables_count'); $users = Company::scopeCompanyables($users); if (($request->filled('deleted')) && ($request->input('deleted')=='true')) { - $users = $users->onlyTrashed(); + $users = $users->GetDeleted(); } if ($request->filled('company_id')) { @@ -102,6 +102,9 @@ class UsersController extends Controller case 'department': $users = $users->OrderDepartment($order); break; + case 'company': + $users = $users->OrderCompany($order); + break; default: $allowed_columns = [ @@ -204,7 +207,7 @@ class UsersController extends Controller if ($user->save()) { - if ($request->has('groups')) { + if ($request->filled('groups')) { $user->groups()->sync($request->input('groups')); } else { $user->groups()->sync(array()); @@ -269,9 +272,22 @@ class UsersController extends Controller ->where('assigned_to', $user->id)->update(['location_id' => $request->input('location_id', null)]); if ($user->save()) { + + // Sync group memberships: + // This was changed in Snipe-IT v4.6.x to 4.7, since we upgraded to Laravel 5.5 + // which changes the behavior of has vs filled. + // The $request->has method will now return true even if the input value is an empty string or null. + // A new $request->filled method has was added that provides the previous behavior of the has method. + + // Check if the request has groups passed and has a value if ($request->filled('groups')) { $user->groups()->sync($request->input('groups')); + // The groups field has been passed but it is null, so we should blank it out + } elseif ($request->has('groups')) { + $user->groups()->sync(array()); } + + return response()->json(Helper::formatStandardApiResponse('success', (new UsersTransformer)->transformUser($user), trans('admin/users/message.success.update'))); } @@ -293,20 +309,32 @@ class UsersController extends Controller $this->authorize('delete', $user); - if ($user->assets()->count() > 0) { + if (($user->assets) && ($user->assets->count() > 0)) { return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.error.delete_has_assets'))); } - // Remove the user's avatar if they have one - if (Storage::disk('public')->exists('avatars/'.$user->avatar)) { - try { - Storage::disk('public')->delete('avatars/'.$user->avatar); - } catch (\Exception $e) { - \Log::debug($e); - } + if (($user->licenses) && ($user->licenses->count() > 0)) { + return response()->json(Helper::formatStandardApiResponse('error', null, 'This user still has ' . $user->licenses->count() . ' license(s) associated with them and cannot be deleted.')); + } + + if (($user->accessories) && ($user->accessories->count() > 0)) { + return response()->json(Helper::formatStandardApiResponse('error', null, 'This user still has ' . $user->accessories->count() . ' accessories associated with them.')); + } + + if (($user->managedLocations()) && ($user->managedLocations()->count() > 0)) { + return response()->json(Helper::formatStandardApiResponse('error', null, 'This user still has ' . $user->managedLocations()->count() . ' locations that they manage.')); } if ($user->delete()) { + + // Remove the user's avatar if they have one + if (Storage::disk('public')->exists('avatars/'.$user->avatar)) { + try { + Storage::disk('public')->delete('avatars/'.$user->avatar); + } catch (\Exception $e) { + \Log::debug($e); + } + return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.success.delete'))); } return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.error.delete'))); diff --git a/app/Http/Controllers/AssetModelsController.php b/app/Http/Controllers/AssetModelsController.php index 491de8325d..29698a04f6 100755 --- a/app/Http/Controllers/AssetModelsController.php +++ b/app/Http/Controllers/AssetModelsController.php @@ -292,6 +292,145 @@ class AssetModelsController extends Controller } + + + /** + * Returns a view that allows the user to bulk edit model attrbutes + * + * @author [A. Gianotto] [] + * @since [v1.7] + * @return \Illuminate\Contracts\View\View + */ + public function postBulkEdit(Request $request) + { + + $models_raw_array = Input::get('ids'); + + // Make sure some IDs have been selected + if ((is_array($models_raw_array)) && (count($models_raw_array) > 0)) { + + + $models = AssetModel::whereIn('id', $models_raw_array)->withCount('assets as assets_count')->orderBy('assets_count', 'ASC')->get(); + + // If deleting.... + if ($request->input('bulk_actions')=='delete') { + $valid_count = 0; + foreach ($models as $model) { + if ($model->assets_count == 0) { + $valid_count++; + } + } + return view('models/bulk-delete', compact('models'))->with('valid_count', $valid_count); + + // Otherwise display the bulk edit screen + } else { + + $nochange = ['NC' => 'No Change']; + $fieldset_list = $nochange + Helper::customFieldsetList(); + $depreciation_list = $nochange + Helper::depreciationList(); + + return view('models/bulk-edit', compact('models')) + ->with('fieldset_list', $fieldset_list) + ->with('depreciation_list', $depreciation_list); + } + + } + + return redirect()->route('models.index') + ->with('error', 'You must select at least one model to edit.'); + + } + + + + /** + * Returns a view that allows the user to bulk edit model attrbutes + * + * @author [A. Gianotto] [] + * @since [v1.7] + * @return \Illuminate\Contracts\View\View + */ + public function postBulkEditSave(Request $request) + { + + $models_raw_array = Input::get('ids'); + $update_array = array(); + + + if (($request->filled('manufacturer_id') && ($request->input('manufacturer_id')!='NC'))) { + $update_array['manufacturer_id'] = $request->input('manufacturer_id'); + } + if (($request->filled('category_id') && ($request->input('category_id')!='NC'))) { + $update_array['category_id'] = $request->input('category_id'); + } + if ($request->input('fieldset_id')!='NC') { + $update_array['fieldset_id'] = $request->input('fieldset_id'); + } + if ($request->input('depreciation_id')!='NC') { + $update_array['depreciation_id'] = $request->input('depreciation_id'); + } + + + + if (count($update_array) > 0) { + AssetModel::whereIn('id', $models_raw_array)->update($update_array); + return redirect()->route('models.index') + ->with('success', trans('admin/models/message.bulkedit.success')); + } + + return redirect()->route('models.index') + ->with('warning', trans('admin/models/message.bulkedit.error')); + + } + + /** + * Validate and delete the given Asset Models. An Asset Model + * cannot be deleted if there are associated assets. + * + * @author [A. Gianotto] [] + * @since [v1.0] + * @param int $modelId + * @return Redirect + */ + public function postBulkDelete(Request $request) + { + $models_raw_array = Input::get('ids'); + + if ((is_array($models_raw_array)) && (count($models_raw_array) > 0)) { + + $models = AssetModel::whereIn('id', $models_raw_array)->withCount('assets as assets_count')->get(); + + $del_error_count = 0; + $del_count = 0; + + foreach ($models as $model) { + \Log::debug($model->id); + + if ($model->assets_count > 0) { + $del_error_count++; + } else { + $model->delete(); + $del_count++; + } + } + + \Log::debug($del_count); + \Log::debug($del_error_count); + + if ($del_error_count == 0) { + return redirect()->route('models.index') + ->with('success', trans('admin/models/message.bulkdelete.success',['success_count'=> $del_count] )); + } + + return redirect()->route('models.index') + ->with('warning', trans('admin/models/message.bulkdelete.success_partial', ['fail_count'=>$del_error_count, 'success_count'=> $del_count])); + } + + return redirect()->route('models.index') + ->with('error', trans('admin/models/message.bulkdelete.error')); + + } + /** * Returns true if a fieldset is set, 'add default values' is ticked and if * any default values were entered into the form. diff --git a/app/Http/Controllers/Assets/AssetCheckinController.php b/app/Http/Controllers/Assets/AssetCheckinController.php index c573389c9b..8526de08fd 100644 --- a/app/Http/Controllers/Assets/AssetCheckinController.php +++ b/app/Http/Controllers/Assets/AssetCheckinController.php @@ -70,7 +70,7 @@ class AssetCheckinController extends Controller $asset->assignedTo()->disassociate($asset); $asset->assigned_type = null; $asset->accepted = null; - $asset->name = e($request->get('name')); + $asset->name = $request->get('name'); if ($request->filled('status_id')) { $asset->status_id = e($request->get('status_id')); @@ -92,7 +92,7 @@ class AssetCheckinController extends Controller event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note'), $checkin_at)); - if ($backto=='user') { + if ((isset($user)) && ($backto =='user')) { return redirect()->route("users.show", $user->id)->with('success', trans('admin/hardware/message.checkin.success')); } return redirect()->route("hardware.index")->with('success', trans('admin/hardware/message.checkin.success')); diff --git a/app/Http/Controllers/Assets/AssetCheckoutController.php b/app/Http/Controllers/Assets/AssetCheckoutController.php index f9fc0032e6..a6d049874a 100644 --- a/app/Http/Controllers/Assets/AssetCheckoutController.php +++ b/app/Http/Controllers/Assets/AssetCheckoutController.php @@ -27,7 +27,6 @@ class AssetCheckoutController extends Controller { // Check if the asset exists if (is_null($asset = Asset::find(e($assetId)))) { - // Redirect to the asset management page with error return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist')); } @@ -38,7 +37,6 @@ class AssetCheckoutController extends Controller } return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkout.not_available')); - // Get the dropdown of users and then pass it to the checkout view } diff --git a/app/Http/Controllers/Assets/AssetsController.php b/app/Http/Controllers/Assets/AssetsController.php index 98e8dbdacd..8f67158acf 100755 --- a/app/Http/Controllers/Assets/AssetsController.php +++ b/app/Http/Controllers/Assets/AssetsController.php @@ -144,11 +144,9 @@ class AssetsController extends Controller $asset->location_id = $request->input('rtd_location_id', null); } - $asset->asset_tag = $asset_tags[$a]; - - // Create the image (if one was chosen.) - if ($request->hasFile('image')) { - $image = $request->input('image'); + // Create the image (if one was chosen.) + if ($request->has('image')) { + $image = $request->input('image'); $asset = $request->handleImages($asset); } @@ -157,17 +155,17 @@ class AssetsController extends Controller // Validation for these fields is handled through the AssetRequest form request $model = AssetModel::find($request->get('model_id')); - if (($model) && ($model->fieldset)) { - foreach ($model->fieldset->fields as $field) { - if ($field->field_encrypted=='1') { - if (Gate::allows('admin')) { - $asset->{$field->convertUnicodeDbSlug()} = \Crypt::encrypt($request->input($field->convertUnicodeDbSlug())); - } - } else { - $asset->{$field->convertUnicodeDbSlug()} = $request->input($field->convertUnicodeDbSlug()); + if ($model->fieldset) { + foreach ($model->fieldset->fields as $field) { + if ($field->field_encrypted=='1') { + if (Gate::allows('admin')) { + $asset->{$field->convertUnicodeDbSlug()} = \Crypt::encrypt($request->input($field->convertUnicodeDbSlug())); } + } else { + $asset->{$field->convertUnicodeDbSlug()} = $request->input($field->convertUnicodeDbSlug()); } } + } // Validate the asset before saving if ($asset->isValid() && $asset->save()) { @@ -333,7 +331,7 @@ class AssetsController extends Controller // FIXME: No idea why this is returning a Builder error on db_column_name. // Need to investigate and fix. Using static method for now. $model = AssetModel::find($request->get('model_id')); - if ($model->fieldset) { + if (($model) && ($model->fieldset)) { foreach ($model->fieldset->fields as $field) { if ($field->field_encrypted=='1') { if (Gate::allows('admin')) { @@ -530,6 +528,11 @@ class AssetsController extends Controller */ public function postImportHistory(Request $request) { + + if (!$request->hasFile('user_import_csv')) { + return back()->with('error', 'No file provided. Please select a file for import and try again. '); + } + if (!ini_get("auto_detect_line_endings")) { ini_set("auto_detect_line_endings", '1'); } @@ -558,49 +561,105 @@ class AssetsController extends Controller foreach ($results as $record) { - $asset_tag = $record['Asset Tag']; - - try { - $checkoutdate = Carbon::parse($record['Checkout Date'])->format('Y-m-d H:i:s'); - $checkindate = Carbon::parse($record['Checkin Date'])->format('Y-m-d H:i:s'); - } - catch (\Exception $err) { - $status['error'][]['asset'][$asset_tag]['msg'] = 'Your dates are screwed up. Format needs to be Y-m-d H:i:s'; - continue; - } - - if($asset = Cache::remember('asset:' . $asset_tag, $cachetime, function () use( &$asset_tag) { - $tocache = Asset::where('asset_tag', '=', $asset_tag)->value('id'); - return is_null($tocache) ? false : $tocache;})) - { - //we've found our asset, now lets look for a user - if($base_username != User::generateFormattedNameFromFullName($record['Full Name'], Setting::getSettings()->username_format)) { - - $base_username = User::generateFormattedNameFromFullName($record['Full Name'], Setting::getSettings()->username_format); - - if(!$user = Cache::remember('user:' . $base_username['username'], $cachetime, function () use( &$base_username) { - $tocache = User::where('username', '=', $base_username['username'])->value('id'); - return is_null($tocache) ? false : $tocache;})) - { - $status['error'][]['asset'][$asset_tag]['msg'] = 'Asset was found but user (' . $record['Full Name'] . ') not matched'; - $base_username = null; - continue; - } + foreach ($results as $row) { + if (is_array($row)) { + $row = array_change_key_case($row, CASE_LOWER); + $asset_tag = Helper::array_smart_fetch($row, "asset tag"); + if (!array_key_exists($asset_tag, $item)) { + $item[$asset_tag] = array(); } + $batch_counter = count($item[$asset_tag]); - if($checkoutdate < $checkindate) { + $item[$asset_tag][$batch_counter]['checkout_date'] = Carbon::parse(Helper::array_smart_fetch($row, "checkout date"))->format('Y-m-d H:i:s'); + $item[$asset_tag][$batch_counter]['checkin_date'] = Carbon::parse(Helper::array_smart_fetch($row, "checkin date"))->format('Y-m-d H:i:s'); + \Log::debug($item[$asset_tag][$batch_counter]['checkin_date']); + + $item[$asset_tag][$batch_counter]['asset_tag'] = Helper::array_smart_fetch($row, "asset tag"); + $item[$asset_tag][$batch_counter]['name'] = Helper::array_smart_fetch($row, "name"); + $item[$asset_tag][$batch_counter]['email'] = Helper::array_smart_fetch($row, "email"); + + if ($asset = Asset::where('asset_tag', '=', $asset_tag)->first()) { + $item[$asset_tag][$batch_counter]['asset_id'] = $asset->id; + + $base_username = User::generateFormattedNameFromFullName(Setting::getSettings()->username_format, $item[$asset_tag][$batch_counter]['name']); + $user = User::where('username', '=', $base_username['username']); + $user_query = ' on username '.$base_username['username']; + + if ($request->input('match_firstnamelastname')=='1') { + $firstnamedotlastname = User::generateFormattedNameFromFullName('firstname.lastname', $item[$asset_tag][$batch_counter]['name']); + $item[$asset_tag][$batch_counter]['username'][] = $firstnamedotlastname['username']; + $user->orWhere('username', '=', $firstnamedotlastname['username']); + $user_query .= ', or on username '.$firstnamedotlastname['username']; + } + + if ($request->input('match_flastname')=='1') { + $flastname = User::generateFormattedNameFromFullName('filastname', $item[$asset_tag][$batch_counter]['name']); + $item[$asset_tag][$batch_counter]['username'][] = $flastname['username']; + $user->orWhere('username', '=', $flastname['username']); + $user_query .= ', or on username '.$flastname['username']; + } + if ($request->input('match_firstname')=='1') { + $firstname = User::generateFormattedNameFromFullName('firstname', $item[$asset_tag][$batch_counter]['name']); + $item[$asset_tag][$batch_counter]['username'][] = $firstname['username']; + $user->orWhere('username', '=', $firstname['username']); + $user_query .= ', or on username '.$firstname['username']; + } + if ($request->input('match_email')=='1') { + if ($item[$asset_tag][$batch_counter]['email']=='') { + $item[$asset_tag][$batch_counter]['username'][] = $user_email = User::generateEmailFromFullName($item[$asset_tag][$batch_counter]['name']); + $user->orWhere('username', '=', $user_email); + $user_query .= ', or on username '.$user_email; + } + } + + // A matching user was found + if ($user = $user->first()) { + $item[$asset_tag][$batch_counter]['checkedout_to'] = $user->id; + $item[$asset_tag][$batch_counter]['user_id'] = $user->id; Actionlog::firstOrCreate(array( - 'item_id' => $asset, + 'item_id' => $asset->id, 'item_type' => Asset::class, - 'user_id' => Auth::user()->id, - 'note' => 'Historical record added by ' . Auth::user()->present()->fullName(), - 'target_id' => $user, + 'user_id' => Auth::user()->id, + 'note' => 'Checkout imported by '.Auth::user()->present()->fullName().' from history importer', + 'target_id' => $item[$asset_tag][$batch_counter]['user_id'], 'target_type' => User::class, - 'created_at' => $checkoutdate, - 'action_type' => 'checkout', + 'created_at' => $item[$asset_tag][$batch_counter]['checkout_date'], + 'action_type' => 'checkout', )); + $asset->assigned_to = $user->id; + + if ($asset->save()) { + $status['success'][]['asset'][$asset_tag]['msg'] = 'Asset successfully matched for '.Helper::array_smart_fetch($row, "name").$user_query.' on '.$item[$asset_tag][$batch_counter]['checkout_date']; + } else { + $status['error'][]['asset'][$asset_tag]['msg'] = 'Asset and user was matched but could not be saved.'; + } + } else { + $item[$asset_tag][$batch_counter]['checkedout_to'] = null; + $status['error'][]['user'][Helper::array_smart_fetch($row, "name")]['msg'] = 'User does not exist so no checkin log was created.'; + } + } else { + $item[$asset_tag][$batch_counter]['asset_id'] = null; + $status['error'][]['asset'][$asset_tag]['msg'] = 'Asset does not exist so no match was attempted.'; + } + } + } + + // Loop through and backfill the checkins + foreach ($item as $key => $asset_batch) { + $total_in_batch = count($asset_batch); + for ($x = 0; $x < $total_in_batch; $x++) { + $next = $x + 1; + + // Only do this if a matching user was found + if ((array_key_exists('checkedout_to', $asset_batch[$x])) && ($asset_batch[$x]['checkedout_to']!='')) { + if (($total_in_batch > 1) && ($x < $total_in_batch) && (array_key_exists($next, $asset_batch))) { + $checkin_date = Carbon::parse($asset_batch[$next]['checkin_date'])->format('Y-m-d H:i:s'); + $asset_batch[$x]['real_checkin'] = $checkin_date; + + \Log::debug($asset_batch[$next]['checkin_date']); + \Log::debug($checkin_date); Actionlog::firstOrCreate(array( 'item_id' => $asset, 'item_type' => Asset::class, @@ -678,11 +737,6 @@ class AssetsController extends Controller return view('hardware/audit')->with('asset', $asset)->with('next_audit_date', $dt)->with('locations_list'); } - public function dueForAudit() - { - $this->authorize('audit', Asset::class); - return view('hardware/audit-due'); - } public function overdueForAudit() { diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index ff1c93f383..6dc691be92 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -179,8 +179,8 @@ class LoginController extends Controller } if ($user = Auth::user()) { - $user->last_login = Carbon::now(); - Log::debug('Last login:'.$user->last_login); + $user->last_login = \Carbon::now(); + \Log::debug('Last login:'.$user->last_login); $user->save(); } // Redirect to the users page @@ -277,7 +277,7 @@ class LoginController extends Controller return redirect()->route('login')->with('error', trans('auth/general.login_prompt')); } - if (!$request->has('two_factor_secret')) { + if (!$request->filled('two_factor_secret')) { return redirect()->route('two-factor')->with('error', trans('auth/message.two_factor.code_required')); } @@ -320,7 +320,7 @@ class LoginController extends Controller return redirect()->away($customLogoutUrl); } - return redirect()->route('login')->with('success', trans('auth/general.logout.success')); + return redirect()->route('login')->with('success', trans('auth/message.logout.success')); } diff --git a/app/Http/Controllers/CustomFieldsetsController.php b/app/Http/Controllers/CustomFieldsetsController.php index 785b221db5..18323f03bb 100644 --- a/app/Http/Controllers/CustomFieldsetsController.php +++ b/app/Http/Controllers/CustomFieldsetsController.php @@ -44,19 +44,18 @@ class CustomFieldsetsController extends Controller $maxid = 0; - foreach ($cfset->fields() as $field) { - - if ($field) { - if ($field->pivot->order > $maxid) { - $maxid=$field->pivot->order; - } - if (isset($custom_fields_list[$field->id])) { - unset($custom_fields_list[$field->id]); - } + foreach ($cfset->fields as $field) { + if ($field->pivot->order > $maxid) { + $maxid=$field->pivot->order; + } + if (isset($custom_fields_list[$field->id])) { + unset($custom_fields_list[$field->id]); } - } + return view("custom_fields.fieldsets.view")->with("custom_fieldset", $cfset)->with("maxid", $maxid+1)->with("custom_fields_list", $custom_fields_list); + } + return view("custom_fields.fieldsets.view") ->with("custom_fieldset", $cfset) ->with("maxid", $maxid+1) @@ -176,14 +175,13 @@ class CustomFieldsetsController extends Controller /** - * Associate the custom field with a custom fieldset. - * - * @author [Brady Wetherington] [] - * @since [v1.8] - * @return View - * @throws \Illuminate\Auth\Access\AuthorizationException - */ - public function associate($id) + * Associate the custom field with a custom fieldset. + * + * @author [Brady Wetherington] [] + * @since [v1.8] + * @return View + */ + public function associate(Request $request, $id) { $set = CustomFieldset::find($id); @@ -191,12 +189,12 @@ class CustomFieldsetsController extends Controller $this->authorize('update', $set); foreach ($set->fields as $field) { - if ($field->id == Input::get('field_id')) { + if ($field->id == $request->input('field_id')) { return redirect()->route("fieldsets.show", [$id])->withInput()->withErrors(['field_id' => trans('admin/custom_fields/message.field.already_added')]); } } - $results=$set->fields()->attach(Input::get('field_id'), ["required" => (Input::get('required') == "on"),"order" => Input::get('order')]); + $results = $set->fields()->attach(Input::get('field_id'), ["required" => ($request->input('required') == "on"),"order" => $request->input('order', 1)]); return redirect()->route("fieldsets.show", [$id])->with("success", trans('admin/custom_fields/message.field.create.assoc_success')); } diff --git a/app/Http/Controllers/DepartmentsController.php b/app/Http/Controllers/DepartmentsController.php index 9c0cd8d4bb..434ccc26dc 100644 --- a/app/Http/Controllers/DepartmentsController.php +++ b/app/Http/Controllers/DepartmentsController.php @@ -52,8 +52,8 @@ class DepartmentsController extends Controller $this->authorize('create', Department::class); $department = new Department; $department->fill($request->all()); - $department->user_id = Auth::id(); - $department->manager_id = $request->input('manager_id', null); + $department->user_id = Auth::user()->id; + $department->manager_id = ($request->has('manager_id' ) ? $request->input('manager_id') : null); $department = $request->handleImages($department); diff --git a/app/Http/Controllers/SettingsController.php b/app/Http/Controllers/SettingsController.php index 147ee0808b..d9f4f3deb8 100755 --- a/app/Http/Controllers/SettingsController.php +++ b/app/Http/Controllers/SettingsController.php @@ -320,7 +320,8 @@ class SettingsController extends Controller $setting->modellist_displays = ''; - if (($request->filled('show_in_model_list')) && (count($request->input('show_in_model_list')) > 0)) { + if (($request->has('show_in_model_list')) && (count($request->input('show_in_model_list')) > 0)) + { $setting->modellist_displays = implode(',', $request->input('show_in_model_list')); } @@ -577,8 +578,9 @@ class SettingsController extends Controller $setting->pwd_secure_min = (int) $request->input('pwd_secure_min'); $setting->pwd_secure_complexity = ''; - if ($request->filled('pwd_secure_complexity')) { - $setting->pwd_secure_complexity = implode('|', $request->input('pwd_secure_complexity')); + + if ($request->has('pwd_secure_complexity')) { + $setting->pwd_secure_complexity = implode('|', $request->input('pwd_secure_complexity')); } if ($setting->save()) { @@ -884,7 +886,9 @@ class SettingsController extends Controller $setting->labels_pageheight = $request->input('labels_pageheight'); $setting->labels_display_company_name = $request->input('labels_display_company_name', '0'); - if ($request->filled('labels_display_name')) { + + + if ($request->has('labels_display_name')) { $setting->labels_display_name = 1; } else { $setting->labels_display_name = 0; @@ -900,13 +904,8 @@ class SettingsController extends Controller $setting->labels_display_tag = 1; } else { $setting->labels_display_tag = 0; - } + } - if ($request->filled('labels_display_tag')) { - $setting->labels_display_tag = 1; - } else { - $setting->labels_display_tag = 0; - } if ($request->filled('labels_display_model')) { $setting->labels_display_model = 1; diff --git a/app/Http/Controllers/ViewAssetsController.php b/app/Http/Controllers/ViewAssetsController.php index 9efb9d24d4..918b76e62c 100755 --- a/app/Http/Controllers/ViewAssetsController.php +++ b/app/Http/Controllers/ViewAssetsController.php @@ -36,13 +36,19 @@ class ViewAssetsController extends Controller 'licenses', 'userloc', 'userlog' - )->withTrashed()->find(Auth::id()); + )->withTrashed()->find(Auth::user()->id); $userlog = $user->userlog->load('item', 'user', 'target'); if (isset($user->id)) { return view('account/view-assets', compact('user', 'userlog')); + } else { + // Prepare the error message + $error = trans('admin/users/message.user_not_found', compact('id')); + + // Redirect to the user management page + return redirect()->route('users.index')->with('error', $error); } // Redirect to the user management page return redirect()->route('users.index') @@ -117,15 +123,17 @@ class ViewAssetsController extends Controller return redirect()->route('requestable-assets')->with('success')->with('success', trans('admin/hardware/message.requests.canceled')); - } - $item->request(); - if (($settings->alert_email!='') && ($settings->alerts_enabled=='1') && (!config('app.lock_passwords'))) { - $logaction->logaction('requested'); - $settings->notify(new RequestAssetNotification($data)); - } + } else { + $item->request(); + if (($settings->alert_email!='') && ($settings->alerts_enabled=='1') && (!config('app.lock_passwords'))) { + $logaction->logaction('requested'); + $settings->notify(new RequestAssetNotification($data)); + } - return redirect()->route('requestable-assets')->with('success')->with('success', trans('admin/hardware/message.requests.success')); + + return redirect()->route('requestable-assets')->with('success')->with('success', trans('admin/hardware/message.requests.success')); + } } @@ -197,6 +205,124 @@ class ViewAssetsController extends Controller // Get the acceptance screen public function getAcceptAsset($logID = null) { - return redirect()->route('account.accept'); + + $findlog = Actionlog::where('id', $logID)->first(); + + if (!$findlog) { + return redirect()->to('account/view-assets')->with('error', 'No matching record.'); + } + + if ($findlog->accepted_id!='') { + return redirect()->to('account/view-assets')->with('error', trans('admin/users/message.error.asset_already_accepted')); + } + + $user = Auth::user(); + + + // TODO - Fix this for non-assets + if (($findlog->item_type==Asset::class) && ($user->id != $findlog->item->assigned_to)) { + return redirect()->to('account/view-assets')->with('error', trans('admin/users/message.error.incorrect_user_accepted')); + } + + + $item = $findlog->item; + + // Check if the asset exists + if (is_null($item)) { + // Redirect to the asset management page + return redirect()->to('account')->with('error', trans('admin/hardware/message.does_not_exist')); + } elseif (!Company::isCurrentUserHasAccess($item)) { + return redirect()->route('requestable-assets')->with('error', trans('general.insufficient_permissions')); + } else { + return view('account/accept-asset', compact('item'))->with('findlog', $findlog)->with('item', $item); + } + } + + // Save the acceptance + public function postAcceptAsset(Request $request, $logID = null) + { + + // Check if the asset exists + if (is_null($findlog = Actionlog::where('id', $logID)->first())) { + // Redirect to the asset management page + return redirect()->to('account/view-assets')->with('error', trans('admin/hardware/message.does_not_exist')); + } + + + if ($findlog->accepted_id!='') { + // Redirect to the asset management page + return redirect()->to('account/view-assets')->with('error', trans('admin/users/message.error.asset_already_accepted')); + } + + if (!Input::has('asset_acceptance')) { + return redirect()->back()->with('error', trans('admin/users/message.error.accept_or_decline')); + } + + $user = Auth::user(); + + if (($findlog->item_type==Asset::class) && ($user->id != $findlog->item->assigned_to)) { + return redirect()->to('account/view-assets')->with('error', trans('admin/users/message.error.incorrect_user_accepted')); + } + + if ($request->filled('signature_output')) { + $path = config('app.private_uploads').'/signatures'; + $sig_filename = "siglog-".$findlog->id.'-'.date('Y-m-d-his').".png"; + $data_uri = e($request->get('signature_output')); + $encoded_image = explode(",", $data_uri); + $decoded_image = base64_decode($encoded_image[1]); + file_put_contents($path."/".$sig_filename, $decoded_image); + } + + + $logaction = new Actionlog(); + + if (Input::get('asset_acceptance')=='accepted') { + $logaction_msg = 'accepted'; + $accepted="accepted"; + $return_msg = trans('admin/users/message.accepted'); + } else { + $logaction_msg = 'declined'; + $accepted="rejected"; + $return_msg = trans('admin/users/message.declined'); + } + $logaction->item_id = $findlog->item_id; + $logaction->item_type = $findlog->item_type; + + // Asset + if (($findlog->item_id!='') && ($findlog->item_type==Asset::class)) { + if (Input::get('asset_acceptance')!='accepted') { + DB::table('assets') + ->where('id', $findlog->item_id) + ->update(array('assigned_to' => null)); + } + } + + $logaction->target_id = $findlog->target_id; + $logaction->target_type = User::class; + $logaction->note = e(Input::get('note')); + $logaction->updated_at = date("Y-m-d H:i:s"); + + + if (isset($sig_filename)) { + $logaction->accept_signature = $sig_filename; + } + $log = $logaction->logaction($logaction_msg); + + $update_checkout = DB::table('action_logs') + ->where('id', $findlog->id) + ->update(array('accepted_id' => $logaction->id)); + + if (($findlog->item_id!='') && ($findlog->item_type==Asset::class)) { + $affected_asset = $logaction->item; + $affected_asset->accepted = $accepted; + $affected_asset->save(); + } + + if ($update_checkout) { + return redirect()->to('account/view-assets')->with('success', $return_msg); + + } else { + return redirect()->to('account/view-assets')->with('error', 'Something went wrong '); + } } } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 4b46e1dd9c..da3c5092b9 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -44,7 +44,8 @@ class Kernel extends HttpKernel ], 'api' => [ - 'throttle:60,1', + \Barryvdh\Cors\HandleCors::class, + 'throttle:120,1', 'auth:api', ], ]; diff --git a/app/Http/Middleware/EncryptCookies.php b/app/Http/Middleware/EncryptCookies.php index e3fe0f8b69..01de91fbe8 100644 --- a/app/Http/Middleware/EncryptCookies.php +++ b/app/Http/Middleware/EncryptCookies.php @@ -18,4 +18,13 @@ class EncryptCookies extends BaseEncrypter protected $except = [ // ]; + + /** + * Indicates if cookies should be serialized. + * + * @var bool + */ + protected static $serialize = true; + + } diff --git a/app/Http/Requests/SaveUserRequest.php b/app/Http/Requests/SaveUserRequest.php index fac0acdc60..0de6a3bc02 100644 --- a/app/Http/Requests/SaveUserRequest.php +++ b/app/Http/Requests/SaveUserRequest.php @@ -3,8 +3,11 @@ namespace App\Http\Requests; use App\Models\Setting; +use Illuminate\Http\Exceptions\HttpResponseException; +use Illuminate\Foundation\Http\FormRequest; +use Illuminate\Contracts\Validation\Validator; -class SaveUserRequest extends Request +class SaveUserRequest extends FormRequest { /** * Determine if the user is authorized to make this request. @@ -61,4 +64,5 @@ class SaveUserRequest extends Request return $rules; } + } diff --git a/app/Http/Transformers/UsersTransformer.php b/app/Http/Transformers/UsersTransformer.php index 3a690704b4..07b5fc1809 100644 --- a/app/Http/Transformers/UsersTransformer.php +++ b/app/Http/Transformers/UsersTransformer.php @@ -34,6 +34,7 @@ class UsersTransformer ] : null, 'jobtitle' => ($user->jobtitle) ? e($user->jobtitle) : null, 'phone' => ($user->phone) ? e($user->phone) : null, + 'website' => ($user->website) ? e($user->website) : null, 'address' => ($user->address) ? e($user->address) : null, 'city' => ($user->city) ? e($user->city) : null, 'state' => ($user->state) ? e($user->state) : null, diff --git a/app/Importer/Importer.php b/app/Importer/Importer.php index e917974f8f..2aeaf7db91 100644 --- a/app/Importer/Importer.php +++ b/app/Importer/Importer.php @@ -5,6 +5,7 @@ use App\Models\CustomField; use App\Models\Department; use App\Models\Setting; use App\Models\User; +use Illuminate\Support\Facades\Auth; use ForceUTF8\Encoding; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\DB; @@ -126,8 +127,8 @@ abstract class Importer public function import() { $headerRow = $this->csv->fetchOne(); - $this->csv->setHeaderOffset(0); - $results = $this->normalizeInputArray($this->csv->getRecords()); + $this->csv->setHeaderOffset(0); //explicitly sets the CSV document header record + $results = $this->normalizeInputArray($this->csv->getRecords($headerRow)); $this->populateCustomFields($headerRow); diff --git a/app/Models/Asset.php b/app/Models/Asset.php index e5ff4156eb..0741734778 100644 --- a/app/Models/Asset.php +++ b/app/Models/Asset.php @@ -82,7 +82,7 @@ class Asset extends Depreciable 'model_id' => 'required|integer|exists:models,id', 'status_id' => 'required|integer|exists:status_labels,id', 'company_id' => 'integer|nullable', - 'warranty_months' => 'numeric|nullable', + 'warranty_months' => 'numeric|nullable|digits_between:0,240', 'physical' => 'numeric|max:1|nullable', 'checkout_date' => 'date|max:10|min:10|nullable', 'checkin_date' => 'date|max:10|min:10|nullable', @@ -1069,7 +1069,7 @@ class Asset extends Depreciable public function scopeDueOrOverdueForAudit($query, $settings) { $interval = $settings->audit_warning_days ?? 0; - + return $query->whereNotNull('assets.next_audit_date') ->whereRaw("DATE_SUB(assets.next_audit_date, INTERVAL $interval DAY) <= '".Carbon::now()."'") ->where('assets.archived', '=', 0) @@ -1388,7 +1388,7 @@ class Asset extends Depreciable * * In short, this set of statements tells the query builder to ONLY query against an * actual field that's being passed if it doesn't meet known relational fields. This - * allows us to query custom fields directly in the assets table + * allows us to query custom fields directly in the assetsv table * (regardless of their name) and *skip* any fields that we already know can only be * searched through relational searches that we do earlier in this method. * @@ -1397,10 +1397,9 @@ class Asset extends Depreciable * assets.location would fail, as that field doesn't exist -- plus we're already searching * against those relationships earlier in this method. * - * - snipe + * - snipe * */ - if (($fieldname!='category') && ($fieldname!='model_number') && ($fieldname!='rtd_location') && ($fieldname!='location') && ($fieldname!='supplier') && ($fieldname!='status_label') && ($fieldname!='model') && ($fieldname!='company') && ($fieldname!='manufacturer')) { $query->orWhere('assets.'.$fieldname, 'LIKE', '%' . $search_val . '%'); diff --git a/app/Models/Company.php b/app/Models/Company.php index 0cbdbd5671..a1094b60ea 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -78,7 +78,13 @@ final class Company extends SnipeModel } $table = ($table_name) ? DB::getTablePrefix().$table_name."." : ''; - return $query->where($table.$column, '=', $company_id); + + if(\Schema::hasColumn($query->getModel()->getTable(), $column)){ + return $query->where($table.$column, '=', $company_id); + } else { + return $query->join('users as users_comp', 'users_comp.id', 'user_id')->where('users_comp.company_id', '=', $company_id); + } + } public static function getIdFromInput($unescaped_input) diff --git a/app/Models/Department.php b/app/Models/Department.php index c054f66371..eecf5712cd 100644 --- a/app/Models/Department.php +++ b/app/Models/Department.php @@ -21,7 +21,6 @@ class Department extends SnipeModel protected $rules = [ 'name' => 'required|max:255', - 'user_id' => 'nullable|exists:users,id', 'location_id' => 'numeric|nullable', 'company_id' => 'numeric|nullable', 'manager_id' => 'numeric|nullable', diff --git a/app/Models/Group.php b/app/Models/Group.php index 0c492b7752..25c2505101 100755 --- a/app/Models/Group.php +++ b/app/Models/Group.php @@ -6,10 +6,10 @@ use Watson\Validating\ValidatingTrait; class Group extends SnipeModel { - protected $table = 'groups'; + protected $table = 'permission_groups'; public $rules = array( - 'name' => 'required|min:3|max:255', + 'name' => 'required|min:2|max:255', ); /** diff --git a/app/Models/User.php b/app/Models/User.php index 34268be455..c17128d940 100755 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -70,7 +70,6 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo 'email' => 'email|nullable', 'password' => 'required|min:6', 'locale' => 'max:10|nullable', - 'manager_id' => 'exists:users,id|nullable' ]; use Searchable; @@ -635,7 +634,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo */ public function scopeByGroup($query, $id) { return $query->whereHas('groups', function ($query) use ($id) { - $query->where('groups.id', '=', $id); + $query->where('permission_groups.id', '=', $id); }); } @@ -681,7 +680,22 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo return $query->leftJoin('departments as departments_users', 'users.department_id', '=', 'departments_users.id')->orderBy('departments_users.name', $order); } + /** + * Query builder scope to order on company + * + * @param Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order + * + * @return Illuminate\Database\Query\Builder Modified query builder + */ + public function scopeOrderCompany($query, $order) + { + return $query->leftJoin('companies as companies_user', 'users.company_id', '=', 'companies_user.id')->orderBy('companies_user.name', $order); + } + public function preferredLocale(){ return $this->locale; } + + } diff --git a/app/Policies/LicensePolicy.php b/app/Policies/LicensePolicy.php index 2667b5e119..0fc097e4d8 100644 --- a/app/Policies/LicensePolicy.php +++ b/app/Policies/LicensePolicy.php @@ -12,16 +12,27 @@ class LicensePolicy extends CheckoutablePermissionsPolicy return 'licenses'; } - /** - * Determine whether the user can view license keys - * - * @param \App\Models\User $user - * @param \App\Models\License $license - * @return mixed - */ + /** + * Determine whether the user can view license keys. + * This gets a little tricky, UX/logic-wise. If a user has the ability + * to create a license (which requires a product key), shouldn't they + * have the ability to see the product key as well? + * + * Example: I create the license, realize I need to change + * something (maybe I got the product key wrong), and now I can never + * see/edit that product key. + * + * @see https://github.com/snipe/snipe-it/issues/6956 + * @param \App\Models\User $user + * @param \App\Models\License $license + * @return mixed + */ public function viewKeys(User $user, License $license = null) { - return $user->hasAccess('licenses.keys'); + if ($user->hasAccess('licenses.keys') || $user->hasAccess('licenses.create') || $user->hasAccess('licenses.edit')) { + return true; + } + return false; } } diff --git a/app/Presenters/AssetPresenter.php b/app/Presenters/AssetPresenter.php index 689a87201a..144ed4d75b 100644 --- a/app/Presenters/AssetPresenter.php +++ b/app/Presenters/AssetPresenter.php @@ -492,9 +492,13 @@ class AssetPresenter extends Presenter */ public function warrantee_expires() { - $date = date_create($this->purchase_date); - date_add($date, date_interval_create_from_date_string($this->warranty_months . ' months')); - return date_format($date, 'Y-m-d'); + if (($this->purchase_date) && ($this->warranty_months)) { + $date = date_create($this->purchase_date); + date_add($date, date_interval_create_from_date_string($this->warranty_months . ' months')); + return date_format($date, 'Y-m-d'); + } + + return false; } /** diff --git a/app/Providers/SettingsServiceProvider.php b/app/Providers/SettingsServiceProvider.php index bcdd5a49c4..e636846e32 100644 --- a/app/Providers/SettingsServiceProvider.php +++ b/app/Providers/SettingsServiceProvider.php @@ -118,6 +118,34 @@ class SettingsServiceProvider extends ServiceProvider return 'storage/public_uploads/companies/'; }); + // Accessories paths and URLs + \App::singleton('accessories_upload_path', function(){ + return public_path('/uploads/accessories/'); + }); + + \App::singleton('accessories_upload_url', function(){ + return url('/').'/uploads/accessories/'; + }); + + // Consumables paths and URLs + \App::singleton('consumables_upload_path', function(){ + return public_path('/uploads/consumables/'); + }); + + \App::singleton('consumables_upload_url', function(){ + return url('/').'/uploads/consumables/'; + }); + + + // Components paths and URLs + \App::singleton('components_upload_path', function(){ + return public_path('/uploads/components/'); + }); + + \App::singleton('components_upload_url', function(){ + return url('/').'/uploads/components/'; + }); + // Set the monetary locale to the configured locale to make helper::parseFloat work. diff --git a/composer.json b/composer.json index 027998a3c4..22ac3087c3 100644 --- a/composer.json +++ b/composer.json @@ -13,49 +13,50 @@ "ext-pdo": "*", "adldap2/adldap2": "^9.1", "bacon/bacon-qr-code": "^1.0", - "doctrine/cache": "^1.6", - "doctrine/common": "^2.7", - "doctrine/dbal": "^2.8.0", + "barryvdh/laravel-cors": "^0.11.3", + "barryvdh/laravel-debugbar": "^3.2", + "doctrine/cache": "^1.8", + "doctrine/common": "^2.10", + "doctrine/dbal": "^2.9.0", "doctrine/inflector": "1.3.*", - "doctrine/instantiator": "1.1.*", + "doctrine/instantiator": "^1.2", "eduardokum/laravel-mail-auto-embed": "^1.0", - "erusev/parsedown": "^1.6", - "fideloper/proxy": "~4.0", - "intervention/image": "^2.3", + "erusev/parsedown": "^1.7", + "fideloper/proxy": "^4.1", + "guzzlehttp/guzzle": "^6.3", + "intervention/image": "^2.4", "javiereguiluz/easyslugger": "^1.0", "laravel/framework": "5.7.*", - "laravel/passport": "~6.0", + "laravel/passport": ~6.0", "laravel/tinker": "^1.0", - "laravelcollective/html": "^5.3", - "league/csv": "^9.0", + "laravelcollective/html": "^5.5", + "league/csv": "^9.2", "league/flysystem-aws-s3-v3": "~1.0", "league/flysystem-cached-adapter": "^1.0", "league/flysystem-rackspace": "^1.0", - "league/flysystem-sftp": "~1.0", + "league/flysystem-sftp": "~1.0", "maknz/slack": "^1.7", "neitanod/forceutf8": "^2.0", "paragonie/constant_time_encoding": "^1.0", - "patchwork/utf8": "~1.2", + "patchwork/utf8": "^1.3", "phpdocumentor/reflection-docblock": "3.2.2", - "phpspec/prophecy": "1.7.5", + "phpspec/prophecy": "^1.8", "pragmarx/google2fa": "^5.0", - "pragmarx/google2fa-laravel": "^0.3.0", + "pragmarx/google2fa-laravel": "^1.0", "predis/predis": "^1.1", "rollbar/rollbar-laravel": "^4.0", "schuppo/password-strength": "~1.5", - "spatie/laravel-backup": "^5.6", + "spatie/laravel-backup": "^5.12",, "tecnickcom/tc-lib-barcode": "^1.15", - "tightenco/ziggy": "^0.6.3", + "tightenco/ziggy": "^0.7.1", "unicodeveloper/laravel-password": "^1.0", "watson/validating": "3.1.7" }, - "require-dev": { - "barryvdh/laravel-debugbar": "^3.2", - "barryvdh/laravel-ide-helper": "^2.6", + "require-dev": { "codeception/codeception": "^2.4", - "filp/whoops": "~2.0", "fzaninotto/faker": "~1.4", - "roave/security-advisories": "dev-master", + "phpunit/php-token-stream": "1.4.11", + "phpunit/phpunit": "~6.0", "squizlabs/php_codesniffer": "*", "symfony/css-selector": "4.0.*", "symfony/dom-crawler": "4.0.*" @@ -100,6 +101,7 @@ "preferred-install": "dist", "sort-packages": true, "optimize-autoloader": true, + "process-timeout":3000, "platform": { "php": "7.1.3" } diff --git a/config/app.php b/config/app.php index bad512be5f..d8a0a57f0a 100755 --- a/config/app.php +++ b/config/app.php @@ -117,6 +117,54 @@ return [ 'cipher' => env('APP_CIPHER', 'AES-256-CBC'), + /* + |-------------------------------------------------------------------------- + | Logging Configuration + |-------------------------------------------------------------------------- + | + | Here you may configure the log settings for your application. Out of + | the box, Laravel uses the Monolog PHP logging library. This gives + | you a variety of powerful log handlers / formatters to utilize. + | + | Available Settings: "single", "daily", "syslog", "errorlog" + | + */ + + 'log' => env('APP_LOG', 'single'), + + /* + |-------------------------------------------------------------------------- + | Logging Max Files + |-------------------------------------------------------------------------- + | + | When using the daily log mode, Laravel will only retain 5 + | days of log files by default. + | + | To change this, set the APP_LOG_MAX_FILES option in your .env. + | + */ + + 'log_max_files' => env('APP_LOG_MAX_FILES', 5), + + /* + |-------------------------------------------------------------------------- + | Logging Detail + |-------------------------------------------------------------------------- + | + | By default, Laravel writes all log levels to storage. However, in your + | production environment, you may wish to configure the minimum severity that + | should be logged by editing your APP_LOG_LEVEL env config. + | + | Laravel will log all levels greater than or equal to the specified severity. + | For example, a default log_level of error will log error, critical, alert, + | and emergency messages. + | + | APP_LOG_LEVEL options are: + | "debug", "info", "notice", "warning", "error", "critical", "alert", "emergency" + | + */ + + 'log_level' => env('APP_LOG_LEVEL', 'error'), /* @@ -250,7 +298,7 @@ return [ * Package Service Providers... */ - // Barryvdh\Debugbar\ServiceProvider::class, // should be auto-discovered + Barryvdh\Debugbar\ServiceProvider::class, Intervention\Image\ImageServiceProvider::class, Collective\Html\HtmlServiceProvider::class, Spatie\Backup\BackupServiceProvider::class, diff --git a/config/backup.php b/config/backup.php index 898f467ff2..f4d0f3b975 100644 --- a/config/backup.php +++ b/config/backup.php @@ -9,12 +9,27 @@ */ +// This is janky, but necessary to figure out whether to include the .env in the backup +$included_dirs = [ + base_path('public/uploads'), + base_path('config'), + base_path('storage/private_uploads'), + base_path('storage/oauth-private.key'), + base_path('storage/oauth-public.key'), + +]; + +if (env('BACKUP_ENV')=='true') { + $included_dirs[] = base_path('.env'); +} + return [ 'backup' => [ /* - * I don't know why they call it name - it's used in the path for uploads + * The name of this application. You can use this name to monitor + * the backups. */ 'name' => 'backups', @@ -25,12 +40,7 @@ return [ /* * The list of directories and files that will be included in the backup. */ - 'include' => [ - storage_path('oauth-private.key'), - storage_path('oauth-public.key'), - (env('BACKUP_ENV')=='true') ? base_path('.env') : base_path('.env.example'), - - ], + 'include' => $included_dirs, /* * These directories and files will be excluded from the backup. @@ -38,8 +48,8 @@ return [ * Directories used by the backup process will automatically be excluded. */ 'exclude' => [ - // base_path('vendor'), - // base_path('node_modules'), + base_path('vendor'), + base_path('node_modules'), ], /* @@ -51,6 +61,21 @@ return [ /* * The names of the connections to the databases that should be backed up * MySQL, PostgreSQL, SQLite and Mongo databases are supported. + * + * The content of the database dump may be customized for each connection + * by adding a 'dump' key to the connection settings in config/database.php. + * E.g. + * 'mysql' => [ + * ... + * 'dump' => [ + * 'excludeTables' => [ + * 'table_to_exclude_from_backup', + * 'another_table_to_exclude' + * ] + * ] + * ], + * + * For a complete list of available customization options, see https://github.com/spatie/db-dumper */ 'databases' => [ 'mysql', @@ -58,24 +83,37 @@ return [ ], /* - * The database dump can be gzipped to decrease diskspace usage. + * The database dump can be compressed to decrease diskspace usage. + * + * Out of the box Laravel-backup supplies + * Spatie\DbDumper\Compressors\GzipCompressor::class. + * + * You can also create custom compressor. More info on that here: + * https://github.com/spatie/db-dumper#using-compression + * + * If you do not want any compressor at all, set it to null. */ - 'gzip_database_dump' => true, + 'database_dump_compressor' => null, 'destination' => [ /* * The filename prefix used for the backup zip file. */ - 'filename_prefix' => 'snipe-it-backup-', + 'filename_prefix' => 'snipe-it-', /* * The disk names on which the backups will be stored. */ 'disks' => [ - env('FILESYSTEM_DISK'), + 'local', ], ], + + /* + * The directory where the temporary files will be stored. + */ + 'temporary_directory' => storage_path('app/backup-temp'), ], /* @@ -103,7 +141,7 @@ return [ 'notifiable' => \Spatie\Backup\Notifications\Notifiable::class, 'mail' => [ - 'to' => null, + 'to' => env('MAIL_BACKUP_NOTIFICATION_ADDRESS', null), ], 'slack' => [ @@ -113,6 +151,11 @@ return [ * If this is set to null the default channel of the webhook will be used. */ 'channel' => null, + + 'username' => null, + + 'icon' => null, + ], ], @@ -123,7 +166,7 @@ return [ */ 'monitorBackups' => [ [ - 'name' => env('APP_NAME'), + 'name' => config('app.name'), 'disks' => ['local'], 'newestBackupsShouldNotBeOlderThanDays' => 1, 'storageUsedMayNotBeHigherThanMegabytes' => 5000, @@ -186,4 +229,3 @@ return [ ], ], ]; - diff --git a/config/cors.php b/config/cors.php new file mode 100644 index 0000000000..0aa4b8cef4 --- /dev/null +++ b/config/cors.php @@ -0,0 +1,48 @@ + false, + 'allowedOrigins' => $allowed_origins, + 'allowedOriginsPatterns' => [], + 'allowedHeaders' => ['*'], + 'allowedMethods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'], + 'exposedHeaders' => [], + 'maxAge' => 0, + +]; diff --git a/database/migrations/2012_12_06_225929_migration_cartalyst_sentry_install_groups.php b/database/migrations/2012_12_06_225929_migration_cartalyst_sentry_install_groups.php index 034a6a16d8..d929a62707 100644 --- a/database/migrations/2012_12_06_225929_migration_cartalyst_sentry_install_groups.php +++ b/database/migrations/2012_12_06_225929_migration_cartalyst_sentry_install_groups.php @@ -29,7 +29,7 @@ class MigrationCartalystSentryInstallGroups extends Migration { */ public function up() { - Schema::create('groups', function($table) + Schema::create('permission_groups', function($table) { $table->increments('id'); $table->string('name'); @@ -46,7 +46,7 @@ class MigrationCartalystSentryInstallGroups extends Migration { */ public function down() { - Schema::drop('groups'); + Schema::drop('permission_groups'); } } diff --git a/database/migrations/2014_11_04_231416_update_group_field_for_reporting.php b/database/migrations/2014_11_04_231416_update_group_field_for_reporting.php index 80b23f8124..248e26dde2 100644 --- a/database/migrations/2014_11_04_231416_update_group_field_for_reporting.php +++ b/database/migrations/2014_11_04_231416_update_group_field_for_reporting.php @@ -2,39 +2,44 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; +use App\Models\Group; class UpdateGroupFieldForReporting extends Migration { - /** - * Run the migrations. - * - * @return void - */ - public function up() - { - // - // Schema::table('groups', function(Blueprint $table) - // { - // // - // }); + /** + * Run the migrations. + * + * @return void + */ + public function up() + { - DB::update('update '.DB::getTablePrefix().'groups set permissions = ? where id = ?', ['{"admin":1,"users":1,"reports":1}', 1]); + // This is janky because we had to do a back in time change to handle a MySQL 8+ + // compatibility issue. - DB::update('update '.DB::getTablePrefix().'groups set permissions = ? where id = ?', ['{"users":1,"reports":1}', 2]); + // Ideally we'd be using the model here, but since we can't really know whether this is an upgrade + // or a fresh install, we have to check which table is being used. + + if (Schema::hasTable('permission_groups')) { - // DB::statement('UPDATE '.$prefix.'groups SET permissions="{\"admin\":1,\"users\":1,\"reports\":1}" where id=1'); - // DB::statement('UPDATE '.$prefix.'groups SET permissions="{\"users\":1,\"reports\":1}" where id=2'); + Group::where('id', 1)->update(['permissions' => '{"users-poop":1,"reports":1}']); + Group::where('id', 2)->update(['permissions' => '{"users-pop":1,"reports":1}']); - } + } elseif (Schema::hasTable('groups')) { + DB::update('update '.DB::getTablePrefix().'groups set permissions = ? where id = ?', ['{"admin-farts":1,"users":1,"reports":1}', 1]); + DB::update('update '.DB::getTablePrefix().'groups set permissions = ? where id = ?', ['{"users-farts":1,"reports":1}', 2]); + } - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - // - } + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } } diff --git a/database/migrations/2019_06_12_184327_rename_groups_table.php b/database/migrations/2019_06_12_184327_rename_groups_table.php new file mode 100644 index 0000000000..3c50971614 --- /dev/null +++ b/database/migrations/2019_06_12_184327_rename_groups_table.php @@ -0,0 +1,44 @@ +=0.18.1", "babel-preset-latest": "^6.24.1", "cross-env": "^5.0.5", "jquery": "^3.1.1", diff --git a/public/js/build/all.js b/public/js/build/all.js index 116840800b..ad10edb1d7 100644 --- a/public/js/build/all.js +++ b/public/js/build/all.js @@ -7605,7 +7605,7 @@ var pieOptions = { //- END PIE CHART - //----------------- - +var baseUrl = $('meta[name="baseUrl"]').attr('content'); (function($, settings) { var Components = {}; @@ -7662,7 +7662,6 @@ $(document).ready(function () { * Slideout help menu */ $('.slideout-menu-toggle').on('click', function(event){ - console.log('clicked'); event.preventDefault(); // create menu variables var slideoutMenu = $('.slideout-menu'); @@ -8206,5 +8205,8 @@ function formatDatalist (datalist) { } function formatDataSelection (datalist) { - return datalist.text; + return datalist.text.replace(/>/g, '>') + .replace(/=i?($(".content-wrapper, .right-side").css("min-height",e-t),n=e-t):($(".content-wrapper, .right-side").css("min-height",i),n=i);var s=$($.AdminLTE.options.controlSidebarOptions.selector);void 0!==s&&s.height()>n&&$(".content-wrapper, .right-side").css("min-height",s.height())}},fixSidebar:function(){if(!$("body").hasClass("fixed"))return void(void 0!==$.fn.slimScroll&&$(".sidebar").slimScroll({destroy:!0}).height("auto"));void 0===$.fn.slimScroll&&window.console&&window.console.error("Error: the fixed layout requires the slimscroll plugin!"),$.AdminLTE.options.sidebarSlimScroll&&void 0!==$.fn.slimScroll&&($(".sidebar").slimScroll({destroy:!0}).height("auto"),$(".sidebar").slimscroll({height:$(window).height()-$(".main-header").height()+"px",color:"rgba(0,0,0,0.2)",size:"3px"}))}},$.AdminLTE.pushMenu={activate:function(t){var e=$.AdminLTE.options.screenSizes;$(t).on("click",function(t){t.preventDefault(),$(window).width()>e.sm-1?$("body").hasClass("sidebar-collapse")?$("body").removeClass("sidebar-collapse").trigger("expanded.pushMenu"):$("body").addClass("sidebar-collapse").trigger("collapsed.pushMenu"):$("body").hasClass("sidebar-open")?$("body").removeClass("sidebar-open").removeClass("sidebar-collapse").trigger("collapsed.pushMenu"):$("body").addClass("sidebar-open").trigger("expanded.pushMenu")}),$(".content-wrapper").click(function(){$(window).width()<=e.sm-1&&$("body").hasClass("sidebar-open")&&$("body").removeClass("sidebar-open")}),($.AdminLTE.options.sidebarExpandOnHover||$("body").hasClass("fixed")&&$("body").hasClass("sidebar-mini"))&&this.expandOnHover()},expandOnHover:function(){var t=this,e=$.AdminLTE.options.screenSizes.sm-1;$(".main-sidebar").hover(function(){$("body").hasClass("sidebar-mini")&&$("body").hasClass("sidebar-collapse")&&$(window).width()>e&&t.expand()},function(){$("body").hasClass("sidebar-mini")&&$("body").hasClass("sidebar-expanded-on-hover")&&$(window).width()>e&&t.collapse()})},expand:function(){$("body").removeClass("sidebar-collapse").addClass("sidebar-expanded-on-hover")},collapse:function(){$("body").hasClass("sidebar-expanded-on-hover")&&$("body").removeClass("sidebar-expanded-on-hover").addClass("sidebar-collapse")}},$.AdminLTE.tree=function(t){var e=this,i=$.AdminLTE.options.animationSpeed;$(document).on("click",t+" li a",function(t){var n=$(this),s=n.next();if(s.is(".treeview-menu")&&s.is(":visible"))s.slideUp(i,function(){s.removeClass("menu-open")}),s.parent("li").removeClass("active");else if(s.is(".treeview-menu")&&!s.is(":visible")){var o=n.parents("ul").first(),r=o.find("ul:visible").slideUp(i);r.removeClass("menu-open");var a=n.parent("li");s.slideDown(i,function(){s.addClass("menu-open"),o.find("li.active").removeClass("active"),a.addClass("active"),e.layout.fix()})}s.is(".treeview-menu")&&t.preventDefault()})},$.AdminLTE.controlSidebar={activate:function(){var t=this,e=$.AdminLTE.options.controlSidebarOptions,i=$(e.selector);$(e.toggleBtnSelector).on("click",function(n){n.preventDefault(),i.hasClass("control-sidebar-open")||$("body").hasClass("control-sidebar-open")?t.close(i,e.slide):t.open(i,e.slide)});var n=$(".control-sidebar-bg");t._fix(n),$("body").hasClass("fixed")?t._fixForFixed(i):$(".content-wrapper, .right-side").height() .box-body, > .box-footer, > form >.box-body, > form > .box-footer");i.hasClass("collapsed-box")?(t.children(":first").removeClass(e.icons.open).addClass(e.icons.collapse),n.slideDown(e.animationSpeed,function(){i.removeClass("collapsed-box")})):(t.children(":first").removeClass(e.icons.collapse).addClass(e.icons.open),n.slideUp(e.animationSpeed,function(){i.addClass("collapsed-box")}))},remove:function(t){t.parents(".box").first().slideUp(this.animationSpeed)}}}function formatDatalist(t){if(t.loading)return' Loading...';var e="
";return e+="
",t.image?e+="
":e+="
",e+="
"+t.text+"
",e+="
"}function formatDataSelection(t){return t.text}if(function(t){function e(n){if(i[n])return i[n].exports;var s=i[n]={i:n,l:!1,exports:{}};return t[n].call(s.exports,s,s.exports,e),s.l=!0,s.exports}var i={};e.m=t,e.c=i,e.d=function(t,i,n){e.o(t,i)||Object.defineProperty(t,i,{configurable:!1,enumerable:!0,get:n})},e.n=function(t){var i=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(i,"a",i),i},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="/",e(e.s=0)}({"+sje":function(t,e,i){var n=i("VU/8")(i("eOaq"),i("lafA"),!1,function(t){i("4UNm")},"data-v-3bdd24a5",null);t.exports=n.exports},0:function(t,e,i){i("GDnL"),i("Bqz+"),t.exports=i("1CH1")},"0DKT":function(t,e){t.exports={render:function(){var t=this.$createElement;return(this._self._c||t)("select",{staticStyle:{width:"100%"}},[this._t("default")],2)},staticRenderFns:[]}},1:function(t,e){},"1CH1":function(t,e){},"1t9x":function(t,e,i){var n=i("wD+l");"string"==typeof n&&(n=[[t.i,n,""]]),n.locals&&(t.exports=n.locals),i("rjj0")("6522937c",n,!0,{})},"20cu":function(t,e,i){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default={props:["clientsUrl","tokensUrl"],data:function(){return{tokens:[]}},ready:function(){this.prepareComponent()},mounted:function(){this.prepareComponent()},methods:{prepareComponent:function(){this.getTokens()},getTokens:function(){var t=this;this.$http.get(this.tokensUrl).then(function(e){t.tokens=e.data})},revoke:function(t){var e=this;this.$http.delete(this.tokensUrl+"/"+t.id).then(function(t){e.getTokens()})}}}},"265i":function(t,e,i){(t.exports=i("FZ+f")(!1)).push([t.i,"",""])},"3IRH":function(t,e){t.exports=function(t){return t.webpackPolyfill||(t.deprecate=function(){},t.paths=[],t.children||(t.children=[]),Object.defineProperty(t,"loaded",{enumerable:!0,get:function(){return t.l}}),Object.defineProperty(t,"id",{enumerable:!0,get:function(){return t.i}}),t.webpackPolyfill=1),t}},"4UNm":function(t,e,i){var n=i("HB0T");"string"==typeof n&&(n=[[t.i,n,""]]),n.locals&&(t.exports=n.locals),i("rjj0")("5d321c9c",n,!0,{})},"5F58":function(t,e,i){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),i("FHXl"),e.default={data:function(){return{files:[],displayImportModal:!1,activeFile:null,alert:{type:null,message:null,visible:!1},importErrors:null,progress:{currentClass:"progress-bar-warning",currentPercent:"0",statusText:"",visible:!1},customFields:[]}},mounted:function(){window.eventHub.$on("importErrors",this.updateImportErrors),this.fetchFiles(),this.fetchCustomFields();var t=this;$("#fileupload").fileupload({dataType:"json",done:function(e,i){t.progress.currentClass="progress-bar-success",t.progress.statusText="Success!",t.files=i.result.files.concat(t.files),console.log(i.result.header_row)},add:function(e,i){i.headers={"X-Requested-With":"XMLHttpRequest","X-CSRF-TOKEN":Laravel.csrfToken},i.process().done(function(){i.submit()}),t.progress.visible=!0},progress:function(e,i){var n=parseInt((i.loaded,i.total,10));t.progress.currentPercent=n,t.progress.statusText=n+"% Complete"},fail:function(e,i){t.progress.currentClass="progress-bar-danger",t.progress.statusText=i.jqXHR.responseJSON.messages}})},methods:{fetchFiles:function(){var t=this;this.$http.get(route("api.imports.index")).then(function(e){var i=e.data;return t.files=i},function(e){t.alert.type="danger",t.alert.visible=!0,t.alert.message="Something went wrong fetching files..."})},fetchCustomFields:function(){var t=this;this.$http.get(route("api.customfields.index")).then(function(e){var i=e.data;(i=i.rows).forEach(function(e){t.customFields.push({id:e.db_column_name,text:e.name})})})},deleteFile:function(t,e){var i=this;this.$http.delete(route("api.imports.destroy",t.id)).then(function(t){i.files.splice(e,1),i.alert.type=t.body.status,i.alert.visible=!0,i.alert.message=t.body.messages},function(t){i.alert.type="error",i.alert.visible=!0,i.alert.message=t.body.messages})},toggleEvent:function(t){window.eventHub.$emit("showDetails",t)},updateAlert:function(t){this.alert=t},updateImportErrors:function(t){this.importErrors=t}},computed:{progressWidth:function(){return"width: "+10*this.progress.currentPercent+"%"}},components:{alert:i("b7rt"),errors:i("yDVJ"),importFile:i("sJcK")}}},"5k6B":function(t,e){t.exports={render:function(){var t=this,e=t.$createElement,i=t._self._c||e;return i("tr",{directives:[{name:"show",rawName:"v-show",value:t.processDetail,expression:"processDetail"}]},[i("td",{attrs:{colspan:"5"}},[i("div",{staticClass:"col-md-2 text-left"}),t._v(" "),i("div",{staticClass:"col-md-8 col-md-offset-2 text-center",staticStyle:{"padding-top":"30px",margin:"0 auto"}},[i("div",{staticClass:"col-md-12 text-left"},[i("h4",{staticClass:"modal-title"},[t._v("Import File:")]),t._v(" "),i("div",{staticClass:"dynamic-form-row"},[t._m(0),t._v(" "),i("div",{staticClass:"col-md-7 col-xs-12"},[i("select2",{attrs:{options:t.options.importTypes,required:""},model:{value:t.options.importType,callback:function(e){t.options.importType=e},expression:"options.importType"}},[i("option",{attrs:{disabled:"",value:"0"}})])],1)]),t._v(" "),i("div",{staticClass:"dynamic-form-row"},[t._m(1),t._v(" "),i("div",{staticClass:"col-md-7 col-xs-12"},[i("input",{directives:[{name:"model",rawName:"v-model",value:t.options.update,expression:"options.update"}],attrs:{type:"checkbox",name:"import-update"},domProps:{checked:Array.isArray(t.options.update)?t._i(t.options.update,null)>-1:t.options.update},on:{__c:function(e){var i=t.options.update,n=e.target,s=!!n.checked;if(Array.isArray(i)){var o=t._i(i,null);n.checked?o<0&&(t.options.update=i.concat([null])):o>-1&&(t.options.update=i.slice(0,o).concat(i.slice(o+1)))}else t.options.update=s}}})])]),t._v(" "),i("div",{staticClass:"dynamic-form-row"},[t._m(2),t._v(" "),i("div",{staticClass:"col-md-7 col-xs-12"},[i("input",{directives:[{name:"model",rawName:"v-model",value:t.options.send_welcome,expression:"options.send_welcome"}],attrs:{type:"checkbox",name:"send-welcome"},domProps:{checked:Array.isArray(t.options.send_welcome)?t._i(t.options.send_welcome,null)>-1:t.options.send_welcome},on:{__c:function(e){var i=t.options.send_welcome,n=e.target,s=!!n.checked;if(Array.isArray(i)){var o=t._i(i,null);n.checked?o<0&&(t.options.send_welcome=i.concat([null])):o>-1&&(t.options.send_welcome=i.slice(0,o).concat(i.slice(o+1)))}else t.options.send_welcome=s}}})])])]),t._v(" "),t.statusText?i("div",{staticClass:"alert col-md-12",class:t.alertClass,staticStyle:{"text-align":"left"}},[t._v("\n "+t._s(this.statusText)+"\n ")]):t._e(),t._v(" "),i("div",{staticClass:"text-left",staticStyle:{"padding-top":"30px"}},[i("table",{staticClass:"table table-striped snipe-table"},[t._m(3),t._v(" "),i("tbody",[t._l(t.file.header_row,function(e,n){return[i("tr",[i("td",[i("label",{staticClass:"controllabel",attrs:{for:e}},[t._v(t._s(e))])]),t._v(" "),i("td",[i("div",{attrs:{required:""}},[i("select2",{attrs:{options:t.columns},model:{value:t.columnMappings[e],callback:function(i){t.$set(t.columnMappings,e,i)},expression:"columnMappings[header]"}},[i("option",{attrs:{value:"0"}},[t._v("Do Not Import")])])],1)]),t._v(" "),i("td",[i("div",[t._v(t._s(t.activeFile.first_row[n]))])])])]})],2)]),t._v(" "),i("br"),t._v(" "),i("div",{staticClass:"col-md-8 col-md-offset-2 text-right"},[i("button",{staticClass:"btn btn-sm btn-default",attrs:{type:"button"},on:{click:function(e){t.processDetail=!1}}},[t._v("Cancel")]),t._v(" "),i("button",{staticClass:"btn btn-sm btn-primary",attrs:{type:"submit"},on:{click:t.postSave}},[t._v("Import")]),t._v(" "),i("br"),i("br")]),t._v(" "),t.statusText?i("div",{staticClass:"alert col-md-12",class:t.alertClass,staticStyle:{"text-align":"left"},attrs:{style:"text-align:left"}},[t._v("\n "+t._s(this.statusText)+"\n ")]):t._e()])])])])},staticRenderFns:[function(){var t=this.$createElement,e=this._self._c||t;return e("div",{staticClass:"col-md-5 col-xs-12"},[e("label",{attrs:{for:"import-type"}},[this._v("Import Type:")])])},function(){var t=this.$createElement,e=this._self._c||t;return e("div",{staticClass:"col-md-5 col-xs-12"},[e("label",{attrs:{for:"import-update"}},[this._v("Update Existing Values?:")])])},function(){var t=this.$createElement,e=this._self._c||t;return e("div",{staticClass:"col-md-5 col-xs-12"},[e("label",{attrs:{for:"send-welcome"}},[this._v("Send Welcome Email for new Users?")])])},function(){var t=this.$createElement,e=this._self._c||t;return e("thead",[e("th",[this._v("Header Field")]),this._v(" "),e("th",[this._v("Import Field")]),this._v(" "),e("th",[this._v("Sample Value")])])}]}},"5m3O":function(t,e,i){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t};e.default={props:["clientsUrl"],data:function(){return{clients:[],createForm:{errors:[],name:"",redirect:""},editForm:{errors:[],name:"",redirect:""}}},ready:function(){this.prepareComponent()},mounted:function(){this.prepareComponent()},methods:{prepareComponent:function(){this.getClients(),$("#modal-create-client").on("shown.bs.modal",function(){$("#create-client-name").focus()}),$("#modal-edit-client").on("shown.bs.modal",function(){$("#edit-client-name").focus()})},getClients:function(){var t=this;this.$http.get(this.clientsUrl).then(function(e){t.clients=e.data})},showCreateClientForm:function(){$("#modal-create-client").modal("show")},store:function(){this.persistClient("post",this.clientsUrl,this.createForm,"#modal-create-client")},edit:function(t){this.editForm.id=t.id,this.editForm.name=t.name,this.editForm.redirect=t.redirect,$("#modal-edit-client").modal("show")},update:function(){this.persistClient("put",this.clientsUrl+"/"+this.editForm.id,this.editForm,"#modal-edit-client")},persistClient:function(t,e,i,s){var o=this;console.log("persisting"),i.errors=[],console.log("method: "+t),this.$http[t](e,i).then(function(t){o.getClients(),i.name="",i.redirect="",i.errors=[],$(s).modal("hide")}).catch(function(t){"object"===n(t.data)?i.errors=_.flatten(_.toArray(t.data)):i.errors=["Something went wrong. Please try again."]})},destroy:function(t){var e=this;this.$http.delete(this.clientsUrl+"/"+t.id).then(function(t){e.getClients()})}}}},"7t+N":function(t,e,i){var n;!function(e,i){"use strict";"object"==typeof t&&"object"==typeof t.exports?t.exports=e.document?i(e,!0):function(t){if(!t.document)throw new Error("jQuery requires a window with a document");return i(t)}:i(e)}("undefined"!=typeof window?window:this,function(i,s){"use strict";function o(t,e,i){var n,s=(e=e||et).createElement("script");if(s.text=t,i)for(n in gt)i[n]&&(s[n]=i[n]);e.head.appendChild(s).parentNode.removeChild(s)}function r(t){return null==t?t+"":"object"==typeof t||"function"==typeof t?at[lt.call(t)]||"object":typeof t}function a(t){var e=!!t&&"length"in t&&t.length,i=r(t);return!pt(t)&&!ft(t)&&("array"===i||0===e||"number"==typeof e&&e>0&&e-1 in t)}function l(t,e){return t.nodeName&&t.nodeName.toLowerCase()===e.toLowerCase()}function u(t,e,i){return pt(e)?mt.grep(t,function(t,n){return!!e.call(t,n,t)!==i}):e.nodeType?mt.grep(t,function(t){return t===e!==i}):"string"!=typeof e?mt.grep(t,function(t){return rt.call(e,t)>-1!==i}):mt.filter(e,t,i)}function c(t,e){for(;(t=t[e])&&1!==t.nodeType;);return t}function h(t){return t}function d(t){throw t}function p(t,e,i,n){var s;try{t&&pt(s=t.promise)?s.call(t).done(e).fail(i):t&&pt(s=t.then)?s.call(t,e,i):e.apply(void 0,[t].slice(n))}catch(t){i.apply(void 0,[t])}}function f(){et.removeEventListener("DOMContentLoaded",f),i.removeEventListener("load",f),mt.ready()}function g(t,e){return e.toUpperCase()}function m(t){return t.replace(It,"ms-").replace(Ot,g)}function v(){this.expando=mt.expando+v.uid++}function _(t,e,i){var n;if(void 0===i&&1===t.nodeType)if(n="data-"+e.replace(Ft,"-$&").toLowerCase(),"string"==typeof(i=t.getAttribute(n))){try{i=function(t){return"true"===t||"false"!==t&&("null"===t?null:t===+t+""?+t:jt.test(t)?JSON.parse(t):t)}(i)}catch(t){}Nt.set(t,e,i)}else i=void 0;return i}function y(t,e,i,n){var s,o,r=20,a=n?function(){return n.cur()}:function(){return mt.css(t,e,"")},l=a(),u=i&&i[3]||(mt.cssNumber[e]?"":"px"),c=(mt.cssNumber[e]||"px"!==u&&+l)&&Lt.exec(mt.css(t,e));if(c&&c[3]!==u){for(l/=2,u=u||c[3],c=+l||1;r--;)mt.style(t,e,c+u),(1-o)*(1-(o=a()/l||.5))<=0&&(r=0),c/=o;c*=2,mt.style(t,e,c+u),i=i||[]}return i&&(c=+c||+l||0,s=i[1]?c+(i[1]+1)*i[2]:+i[2],n&&(n.unit=u,n.start=c,n.end=s)),s}function b(t){var e,i=t.ownerDocument,n=t.nodeName,s=Ut[n];return s||(e=i.body.appendChild(i.createElement(n)),s=mt.css(e,"display"),e.parentNode.removeChild(e),"none"===s&&(s="block"),Ut[n]=s,s)}function w(t,e){for(var i,n,s=[],o=0,r=t.length;o-1)s&&s.push(o);else if(c=mt.contains(o.ownerDocument,o),a=x(d.appendChild(o),"script"),c&&C(a),i)for(h=0;o=a[h++];)Vt.test(o.type||"")&&i.push(o);return d}function D(){return!0}function T(){return!1}function S(){try{return et.activeElement}catch(t){}}function A(t,e,i,n,s,o){var r,a;if("object"==typeof e){for(a in"string"!=typeof i&&(n=n||i,i=void 0),e)A(t,a,i,n,e[a],o);return t}if(null==n&&null==s?(s=i,n=i=void 0):null==s&&("string"==typeof i?(s=n,n=void 0):(s=n,n=i,i=void 0)),!1===s)s=T;else if(!s)return t;return 1===o&&(r=s,(s=function(t){return mt().off(t),r.apply(this,arguments)}).guid=r.guid||(r.guid=mt.guid++)),t.each(function(){mt.event.add(this,e,s,n,i)})}function E(t,e){return l(t,"table")&&l(11!==e.nodeType?e:e.firstChild,"tr")&&mt(t).children("tbody")[0]||t}function $(t){return t.type=(null!==t.getAttribute("type"))+"/"+t.type,t}function I(t){return"true/"===(t.type||"").slice(0,5)?t.type=t.type.slice(5):t.removeAttribute("type"),t}function O(t,e){var i,n,s,o,r,a,l,u;if(1===e.nodeType){if(Mt.hasData(t)&&(o=Mt.access(t),r=Mt.set(e,o),u=o.events))for(s in delete r.handle,r.events={},u)for(i=0,n=u[s].length;i1&&"string"==typeof f&&!dt.checkClone&&ne.test(f))return t.each(function(s){var o=t.eq(s);g&&(e[0]=f.call(this,s,o.html())),P(o,e,i,n)});if(d&&(r=(s=k(e,t[0].ownerDocument,!1,t,n)).firstChild,1===s.childNodes.length&&(s=r),r||n)){for(l=(a=mt.map(x(s,"script"),$)).length;h=0&&(l+=Math.max(0,Math.ceil(t["offset"+e[0].toUpperCase()+e.slice(1)]-o-l-a-.5))),l}function R(t,e,i){var n=re(t),s=N(t,e,n),o="border-box"===mt.css(t,"boxSizing",!1,n),r=o;if(oe.test(s)){if(!i)return s;s="auto"}return r=r&&(dt.boxSizingReliable()||s===t.style[e]),("auto"===s||!parseFloat(s)&&"inline"===mt.css(t,"display",!1,n))&&(s=t["offset"+e[0].toUpperCase()+e.slice(1)],r=!0),(s=parseFloat(s)||0)+L(t,e,i||(o?"border":"content"),r,n,s)+"px"}function z(t,e,i,n,s){return new z.prototype.init(t,e,i,n,s)}function W(){ge&&(!1===et.hidden&&i.requestAnimationFrame?i.requestAnimationFrame(W):i.setTimeout(W,mt.fx.interval),mt.fx.tick())}function U(){return i.setTimeout(function(){fe=void 0}),fe=Date.now()}function B(t,e){var i,n=0,s={height:t};for(e=e?1:0;n<4;n+=2-e)s["margin"+(i=Rt[n])]=s["padding"+i]=t;return e&&(s.opacity=s.width=t),s}function q(t,e,i){for(var n,s=(V.tweeners[e]||[]).concat(V.tweeners["*"]),o=0,r=s.length;o=0&&iy.cacheLength&&delete e[t.shift()],e[i+" "]=n}}function n(t){return t[j]=!0,t}function s(t){var e=E.createElement("fieldset");try{return!!t(e)}catch(t){return!1}finally{e.parentNode&&e.parentNode.removeChild(e),e=null}}function o(t,e){for(var i=t.split("|"),n=i.length;n--;)y.attrHandle[i[n]]=e}function r(t,e){var i=e&&t,n=i&&1===t.nodeType&&1===e.nodeType&&t.sourceIndex-e.sourceIndex;if(n)return n;if(i)for(;i=i.nextSibling;)if(i===e)return-1;return t?1:-1}function a(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&bt(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function l(t){return n(function(e){return e=+e,n(function(i,n){for(var s,o=t([],i.length,e),r=o.length;r--;)i[s=o[r]]&&(i[s]=!(n[s]=i[s]))})})}function u(t){return t&&void 0!==t.getElementsByTagName&&t}function c(){}function h(t){for(var e=0,i=t.length,n="";e1?function(e,i,n){ -for(var s=t.length;s--;)if(!t[s](e,i,n))return!1;return!0}:t[0]}function f(t,e,i,n,s){for(var o,r=[],a=0,l=t.length,u=null!=e;a-1&&(n[c]=!(a[c]=d))}}else y=f(y===a?y.splice(m,y.length):y),r?r(null,a,y,u):X.apply(a,y)})}function m(t){for(var e,i,n,s=t.length,o=y.relative[t[0].type],r=o||y.relative[" "],a=o?1:0,l=d(function(t){return t===e},r,!0),u=d(function(t){return G(e,t)>-1},r,!0),c=[function(t,i,n){var s=!o&&(n||i!==D)||((e=i).nodeType?l(t,i,n):u(t,i,n));return e=null,s}];a1&&p(c),a>1&&h(t.slice(0,a-1).concat({value:" "===t[a-2].type?"*":""})).replace(nt,"$1"),i,a+~]|"+Q+")"+Q+"*"),rt=new RegExp("="+Q+"*([^\\]'\"]*?)"+Q+"*\\]","g"),at=new RegExp(et),lt=new RegExp("^"+J+"$"),ut={ID:new RegExp("^#("+J+")"),CLASS:new RegExp("^\\.("+J+")"),TAG:new RegExp("^("+J+"|[*])"),ATTR:new RegExp("^"+tt),PSEUDO:new RegExp("^"+et),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+Q+"*(even|odd|(([+-]|)(\\d*)n|)"+Q+"*(?:([+-]|)"+Q+"*(\\d+)|))"+Q+"*\\)|)","i"),bool:new RegExp("^(?:"+Z+")$","i"),needsContext:new RegExp("^"+Q+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+Q+"*((?:-\\d)?\\d*)"+Q+"*\\)|)(?=[^-]|$)","i")},ct=/^(?:input|select|textarea|button)$/i,ht=/^h\d$/i,dt=/^[^{]+\{\s*\[native \w/,pt=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ft=/[+~]/,gt=new RegExp("\\\\([\\da-f]{1,6}"+Q+"?|("+Q+")|.)","ig"),mt=function(t,e,i){var n="0x"+e-65536;return n!=n||i?e:n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320)},vt=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,_t=function(t,e){return e?"\0"===t?"�":t.slice(0,-1)+"\\"+t.charCodeAt(t.length-1).toString(16)+" ":"\\"+t},yt=function(){A()},bt=d(function(t){return!0===t.disabled&&("form"in t||"label"in t)},{dir:"parentNode",next:"legend"});try{X.apply(q=K.call(F.childNodes),F.childNodes),q[F.childNodes.length].nodeType}catch(t){X={apply:q.length?function(t,e){Y.apply(t,K.call(e))}:function(t,e){for(var i=t.length,n=0;t[i++]=e[n++];);t.length=i-1}}}for(v in _=e.support={},w=e.isXML=function(t){var e=t&&(t.ownerDocument||t).documentElement;return!!e&&"HTML"!==e.nodeName},A=e.setDocument=function(t){var e,i,n=t?t.ownerDocument||t:F;return n!==E&&9===n.nodeType&&n.documentElement?($=(E=n).documentElement,I=!w(E),F!==E&&(i=E.defaultView)&&i.top!==i&&(i.addEventListener?i.addEventListener("unload",yt,!1):i.attachEvent&&i.attachEvent("onunload",yt)),_.attributes=s(function(t){return t.className="i",!t.getAttribute("className")}),_.getElementsByTagName=s(function(t){return t.appendChild(E.createComment("")),!t.getElementsByTagName("*").length}),_.getElementsByClassName=dt.test(E.getElementsByClassName),_.getById=s(function(t){return $.appendChild(t).id=j,!E.getElementsByName||!E.getElementsByName(j).length}),_.getById?(y.filter.ID=function(t){var e=t.replace(gt,mt);return function(t){return t.getAttribute("id")===e}},y.find.ID=function(t,e){if(void 0!==e.getElementById&&I){var i=e.getElementById(t);return i?[i]:[]}}):(y.filter.ID=function(t){var e=t.replace(gt,mt);return function(t){var i=void 0!==t.getAttributeNode&&t.getAttributeNode("id");return i&&i.value===e}},y.find.ID=function(t,e){if(void 0!==e.getElementById&&I){var i,n,s,o=e.getElementById(t);if(o){if((i=o.getAttributeNode("id"))&&i.value===t)return[o];for(s=e.getElementsByName(t),n=0;o=s[n++];)if((i=o.getAttributeNode("id"))&&i.value===t)return[o]}return[]}}),y.find.TAG=_.getElementsByTagName?function(t,e){return void 0!==e.getElementsByTagName?e.getElementsByTagName(t):_.qsa?e.querySelectorAll(t):void 0}:function(t,e){var i,n=[],s=0,o=e.getElementsByTagName(t);if("*"===t){for(;i=o[s++];)1===i.nodeType&&n.push(i);return n}return o},y.find.CLASS=_.getElementsByClassName&&function(t,e){if(void 0!==e.getElementsByClassName&&I)return e.getElementsByClassName(t)},P=[],O=[],(_.qsa=dt.test(E.querySelectorAll))&&(s(function(t){$.appendChild(t).innerHTML="",t.querySelectorAll("[msallowcapture^='']").length&&O.push("[*^$]="+Q+"*(?:''|\"\")"),t.querySelectorAll("[selected]").length||O.push("\\["+Q+"*(?:value|"+Z+")"),t.querySelectorAll("[id~="+j+"-]").length||O.push("~="),t.querySelectorAll(":checked").length||O.push(":checked"),t.querySelectorAll("a#"+j+"+*").length||O.push(".#.+[+~]")}),s(function(t){t.innerHTML="";var e=E.createElement("input");e.setAttribute("type","hidden"),t.appendChild(e).setAttribute("name","D"),t.querySelectorAll("[name=d]").length&&O.push("name"+Q+"*[*^$|!~]?="),2!==t.querySelectorAll(":enabled").length&&O.push(":enabled",":disabled"),$.appendChild(t).disabled=!0,2!==t.querySelectorAll(":disabled").length&&O.push(":enabled",":disabled"),t.querySelectorAll("*,:x"),O.push(",.*:")})),(_.matchesSelector=dt.test(M=$.matches||$.webkitMatchesSelector||$.mozMatchesSelector||$.oMatchesSelector||$.msMatchesSelector))&&s(function(t){_.disconnectedMatch=M.call(t,"*"),M.call(t,"[s!='']:x"),P.push("!=",et)}),O=O.length&&new RegExp(O.join("|")),P=P.length&&new RegExp(P.join("|")),e=dt.test($.compareDocumentPosition),N=e||dt.test($.contains)?function(t,e){var i=9===t.nodeType?t.documentElement:t,n=e&&e.parentNode;return t===n||!(!n||1!==n.nodeType||!(i.contains?i.contains(n):t.compareDocumentPosition&&16&t.compareDocumentPosition(n)))}:function(t,e){if(e)for(;e=e.parentNode;)if(e===t)return!0;return!1},U=e?function(t,e){if(t===e)return S=!0,0;var i=!t.compareDocumentPosition-!e.compareDocumentPosition;return i||(1&(i=(t.ownerDocument||t)===(e.ownerDocument||e)?t.compareDocumentPosition(e):1)||!_.sortDetached&&e.compareDocumentPosition(t)===i?t===E||t.ownerDocument===F&&N(F,t)?-1:e===E||e.ownerDocument===F&&N(F,e)?1:T?G(T,t)-G(T,e):0:4&i?-1:1)}:function(t,e){if(t===e)return S=!0,0;var i,n=0,s=t.parentNode,o=e.parentNode,a=[t],l=[e];if(!s||!o)return t===E?-1:e===E?1:s?-1:o?1:T?G(T,t)-G(T,e):0;if(s===o)return r(t,e);for(i=t;i=i.parentNode;)a.unshift(i);for(i=e;i=i.parentNode;)l.unshift(i);for(;a[n]===l[n];)n++;return n?r(a[n],l[n]):a[n]===F?-1:l[n]===F?1:0},E):E},e.matches=function(t,i){return e(t,null,null,i)},e.matchesSelector=function(t,i){if((t.ownerDocument||t)!==E&&A(t),i=i.replace(rt,"='$1']"),_.matchesSelector&&I&&!W[i+" "]&&(!P||!P.test(i))&&(!O||!O.test(i)))try{var n=M.call(t,i);if(n||_.disconnectedMatch||t.document&&11!==t.document.nodeType)return n}catch(t){}return e(i,E,null,[t]).length>0},e.contains=function(t,e){return(t.ownerDocument||t)!==E&&A(t),N(t,e)},e.attr=function(t,e){(t.ownerDocument||t)!==E&&A(t);var i=y.attrHandle[e.toLowerCase()],n=i&&B.call(y.attrHandle,e.toLowerCase())?i(t,e,!I):void 0;return void 0!==n?n:_.attributes||!I?t.getAttribute(e):(n=t.getAttributeNode(e))&&n.specified?n.value:null},e.escape=function(t){return(t+"").replace(vt,_t)},e.error=function(t){throw new Error("Syntax error, unrecognized expression: "+t)},e.uniqueSort=function(t){var e,i=[],n=0,s=0;if(S=!_.detectDuplicates,T=!_.sortStable&&t.slice(0),t.sort(U),S){for(;e=t[s++];)e===t[s]&&(n=i.push(s));for(;n--;)t.splice(i[n],1)}return T=null,t},b=e.getText=function(t){var e,i="",n=0,s=t.nodeType;if(s){if(1===s||9===s||11===s){if("string"==typeof t.textContent)return t.textContent;for(t=t.firstChild;t;t=t.nextSibling)i+=b(t)}else if(3===s||4===s)return t.nodeValue}else for(;e=t[n++];)i+=b(e);return i},(y=e.selectors={cacheLength:50,createPseudo:n,match:ut,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(t){return t[1]=t[1].replace(gt,mt),t[3]=(t[3]||t[4]||t[5]||"").replace(gt,mt),"~="===t[2]&&(t[3]=" "+t[3]+" "),t.slice(0,4)},CHILD:function(t){return t[1]=t[1].toLowerCase(),"nth"===t[1].slice(0,3)?(t[3]||e.error(t[0]),t[4]=+(t[4]?t[5]+(t[6]||1):2*("even"===t[3]||"odd"===t[3])),t[5]=+(t[7]+t[8]||"odd"===t[3])):t[3]&&e.error(t[0]),t},PSEUDO:function(t){var e,i=!t[6]&&t[2];return ut.CHILD.test(t[0])?null:(t[3]?t[2]=t[4]||t[5]||"":i&&at.test(i)&&(e=x(i,!0))&&(e=i.indexOf(")",i.length-e)-i.length)&&(t[0]=t[0].slice(0,e),t[2]=i.slice(0,e)),t.slice(0,3))}},filter:{TAG:function(t){var e=t.replace(gt,mt).toLowerCase();return"*"===t?function(){return!0}:function(t){return t.nodeName&&t.nodeName.toLowerCase()===e}},CLASS:function(t){var e=R[t+" "];return e||(e=new RegExp("(^|"+Q+")"+t+"("+Q+"|$)"))&&R(t,function(t){return e.test("string"==typeof t.className&&t.className||void 0!==t.getAttribute&&t.getAttribute("class")||"")})},ATTR:function(t,i,n){return function(s){var o=e.attr(s,t);return null==o?"!="===i:!i||(o+="","="===i?o===n:"!="===i?o!==n:"^="===i?n&&0===o.indexOf(n):"*="===i?n&&o.indexOf(n)>-1:"$="===i?n&&o.slice(-n.length)===n:"~="===i?(" "+o.replace(it," ")+" ").indexOf(n)>-1:"|="===i&&(o===n||o.slice(0,n.length+1)===n+"-"))}},CHILD:function(t,e,i,n,s){var o="nth"!==t.slice(0,3),r="last"!==t.slice(-4),a="of-type"===e;return 1===n&&0===s?function(t){return!!t.parentNode}:function(e,i,l){var u,c,h,d,p,f,g=o!==r?"nextSibling":"previousSibling",m=e.parentNode,v=a&&e.nodeName.toLowerCase(),_=!l&&!a,y=!1;if(m){if(o){for(;g;){for(d=e;d=d[g];)if(a?d.nodeName.toLowerCase()===v:1===d.nodeType)return!1;f=g="only"===t&&!f&&"nextSibling"}return!0}if(f=[r?m.firstChild:m.lastChild],r&&_){for(y=(p=(u=(c=(h=(d=m)[j]||(d[j]={}))[d.uniqueID]||(h[d.uniqueID]={}))[t]||[])[0]===H&&u[1])&&u[2],d=p&&m.childNodes[p];d=++p&&d&&d[g]||(y=p=0)||f.pop();)if(1===d.nodeType&&++y&&d===e){c[t]=[H,p,y];break}}else if(_&&(y=p=(u=(c=(h=(d=e)[j]||(d[j]={}))[d.uniqueID]||(h[d.uniqueID]={}))[t]||[])[0]===H&&u[1]),!1===y)for(;(d=++p&&d&&d[g]||(y=p=0)||f.pop())&&((a?d.nodeName.toLowerCase()!==v:1!==d.nodeType)||!++y||(_&&((c=(h=d[j]||(d[j]={}))[d.uniqueID]||(h[d.uniqueID]={}))[t]=[H,y]),d!==e)););return(y-=s)===n||y%n==0&&y/n>=0}}},PSEUDO:function(t,i){var s,o=y.pseudos[t]||y.setFilters[t.toLowerCase()]||e.error("unsupported pseudo: "+t);return o[j]?o(i):o.length>1?(s=[t,t,"",i],y.setFilters.hasOwnProperty(t.toLowerCase())?n(function(t,e){for(var n,s=o(t,i),r=s.length;r--;)t[n=G(t,s[r])]=!(e[n]=s[r])}):function(t){return o(t,0,s)}):o}},pseudos:{not:n(function(t){var e=[],i=[],s=C(t.replace(nt,"$1"));return s[j]?n(function(t,e,i,n){for(var o,r=s(t,null,n,[]),a=t.length;a--;)(o=r[a])&&(t[a]=!(e[a]=o))}):function(t,n,o){return e[0]=t,s(e,null,o,i),e[0]=null,!i.pop()}}),has:n(function(t){return function(i){return e(t,i).length>0}}),contains:n(function(t){return t=t.replace(gt,mt),function(e){return(e.textContent||e.innerText||b(e)).indexOf(t)>-1}}),lang:n(function(t){return lt.test(t||"")||e.error("unsupported lang: "+t),t=t.replace(gt,mt).toLowerCase(),function(e){var i;do{if(i=I?e.lang:e.getAttribute("xml:lang")||e.getAttribute("lang"))return(i=i.toLowerCase())===t||0===i.indexOf(t+"-")}while((e=e.parentNode)&&1===e.nodeType);return!1}}),target:function(e){var i=t.location&&t.location.hash;return i&&i.slice(1)===e.id},root:function(t){return t===$},focus:function(t){return t===E.activeElement&&(!E.hasFocus||E.hasFocus())&&!!(t.type||t.href||~t.tabIndex)},enabled:a(!1),disabled:a(!0),checked:function(t){var e=t.nodeName.toLowerCase();return"input"===e&&!!t.checked||"option"===e&&!!t.selected},selected:function(t){return t.parentNode&&t.parentNode.selectedIndex,!0===t.selected},empty:function(t){for(t=t.firstChild;t;t=t.nextSibling)if(t.nodeType<6)return!1;return!0},parent:function(t){return!y.pseudos.empty(t)},header:function(t){return ht.test(t.nodeName)},input:function(t){return ct.test(t.nodeName)},button:function(t){var e=t.nodeName.toLowerCase();return"input"===e&&"button"===t.type||"button"===e},text:function(t){var e;return"input"===t.nodeName.toLowerCase()&&"text"===t.type&&(null==(e=t.getAttribute("type"))||"text"===e.toLowerCase())},first:l(function(){return[0]}),last:l(function(t,e){return[e-1]}),eq:l(function(t,e,i){return[i<0?i+e:i]}),even:l(function(t,e){for(var i=0;i=0;)t.push(n);return t}),gt:l(function(t,e,i){for(var n=i<0?i+e:i;++n0,o=t.length>0,r=function(n,r,a,l,u){var c,h,d,p=0,g="0",m=n&&[],v=[],_=D,b=n||o&&y.find.TAG("*",u),w=H+=null==_?1:Math.random()||.1,x=b.length;for(u&&(D=r===E||r||u);g!==x&&null!=(c=b[g]);g++){if(o&&c){for(h=0,r||c.ownerDocument===E||(A(c),a=!I);d=t[h++];)if(d(c,r||E,a)){l.push(c);break}u&&(H=w)}s&&((c=!d&&c)&&p--,n&&m.push(c))}if(p+=g,s&&g!==p){for(h=0;d=i[h++];)d(m,v,r,a);if(n){if(p>0)for(;g--;)m[g]||v[g]||(v[g]=V.call(l));v=f(v)}X.apply(l,v),u&&!n&&v.length>0&&p+i.length>1&&e.uniqueSort(l)}return u&&(H=w,D=_),m};return s?n(r):r}(r,o))).selector=t}return a},k=e.select=function(t,e,i,n){var s,o,r,a,l,c="function"==typeof t&&t,d=!n&&x(t=c.selector||t);if(i=i||[],1===d.length){if((o=d[0]=d[0].slice(0)).length>2&&"ID"===(r=o[0]).type&&9===e.nodeType&&I&&y.relative[o[1].type]){if(!(e=(y.find.ID(r.matches[0].replace(gt,mt),e)||[])[0]))return i;c&&(e=e.parentNode),t=t.slice(o.shift().value.length)}for(s=ut.needsContext.test(t)?0:o.length;s--&&(r=o[s],!y.relative[a=r.type]);)if((l=y.find[a])&&(n=l(r.matches[0].replace(gt,mt),ft.test(o[0].type)&&u(e.parentNode)||e))){if(o.splice(s,1),!(t=n.length&&h(o)))return X.apply(i,n),i;break}}return(c||C(t,d))(n,e,!I,i,!e||ft.test(t)&&u(e.parentNode)||e),i},_.sortStable=j.split("").sort(U).join("")===j,_.detectDuplicates=!!S,A(),_.sortDetached=s(function(t){return 1&t.compareDocumentPosition(E.createElement("fieldset"))}),s(function(t){return t.innerHTML="","#"===t.firstChild.getAttribute("href")})||o("type|href|height|width",function(t,e,i){if(!i)return t.getAttribute(e,"type"===e.toLowerCase()?1:2)}),_.attributes&&s(function(t){return t.innerHTML="",t.firstChild.setAttribute("value",""),""===t.firstChild.getAttribute("value")})||o("value",function(t,e,i){if(!i&&"input"===t.nodeName.toLowerCase())return t.defaultValue}),s(function(t){return null==t.getAttribute("disabled")})||o(Z,function(t,e,i){var n;if(!i)return!0===t[e]?e.toLowerCase():(n=t.getAttributeNode(e))&&n.specified?n.value:null}),e}(i);mt.find=_t,mt.expr=_t.selectors,mt.expr[":"]=mt.expr.pseudos,mt.uniqueSort=mt.unique=_t.uniqueSort,mt.text=_t.getText,mt.isXMLDoc=_t.isXML,mt.contains=_t.contains,mt.escapeSelector=_t.escape;var yt=function(t,e,i){for(var n=[],s=void 0!==i;(t=t[e])&&9!==t.nodeType;)if(1===t.nodeType){if(s&&mt(t).is(i))break;n.push(t)}return n},bt=function(t,e){for(var i=[];t;t=t.nextSibling)1===t.nodeType&&t!==e&&i.push(t);return i},wt=mt.expr.match.needsContext,xt=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;mt.filter=function(t,e,i){var n=e[0];return i&&(t=":not("+t+")"),1===e.length&&1===n.nodeType?mt.find.matchesSelector(n,t)?[n]:[]:mt.find.matches(t,mt.grep(e,function(t){return 1===t.nodeType}))},mt.fn.extend({find:function(t){var e,i,n=this.length,s=this;if("string"!=typeof t)return this.pushStack(mt(t).filter(function(){for(e=0;e1?mt.uniqueSort(i):i},filter:function(t){return this.pushStack(u(this,t||[],!1))},not:function(t){return this.pushStack(u(this,t||[],!0))},is:function(t){return!!u(this,"string"==typeof t&&wt.test(t)?mt(t):t||[],!1).length}});var Ct,kt=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(mt.fn.init=function(t,e,i){var n,s;if(!t)return this;if(i=i||Ct,"string"==typeof t){if(!(n="<"===t[0]&&">"===t[t.length-1]&&t.length>=3?[null,t,null]:kt.exec(t))||!n[1]&&e)return!e||e.jquery?(e||i).find(t):this.constructor(e).find(t);if(n[1]){if(e=e instanceof mt?e[0]:e,mt.merge(this,mt.parseHTML(n[1],e&&e.nodeType?e.ownerDocument||e:et,!0)),xt.test(n[1])&&mt.isPlainObject(e))for(n in e)pt(this[n])?this[n](e[n]):this.attr(n,e[n]);return this}return(s=et.getElementById(n[2]))&&(this[0]=s,this.length=1),this}return t.nodeType?(this[0]=t,this.length=1,this):pt(t)?void 0!==i.ready?i.ready(t):t(mt):mt.makeArray(t,this)}).prototype=mt.fn,Ct=mt(et);var Dt=/^(?:parents|prev(?:Until|All))/,Tt={children:!0,contents:!0,next:!0,prev:!0};mt.fn.extend({has:function(t){var e=mt(t,this),i=e.length;return this.filter(function(){for(var t=0;t-1:1===i.nodeType&&mt.find.matchesSelector(i,t))){o.push(i);break}return this.pushStack(o.length>1?mt.uniqueSort(o):o)},index:function(t){return t?"string"==typeof t?rt.call(mt(t),this[0]):rt.call(this,t.jquery?t[0]:t):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(t,e){return this.pushStack(mt.uniqueSort(mt.merge(this.get(),mt(t,e))))},addBack:function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}}),mt.each({parent:function(t){var e=t.parentNode;return e&&11!==e.nodeType?e:null},parents:function(t){return yt(t,"parentNode")},parentsUntil:function(t,e,i){return yt(t,"parentNode",i)},next:function(t){return c(t,"nextSibling")},prev:function(t){return c(t,"previousSibling")},nextAll:function(t){return yt(t,"nextSibling")},prevAll:function(t){return yt(t,"previousSibling")},nextUntil:function(t,e,i){return yt(t,"nextSibling",i)},prevUntil:function(t,e,i){return yt(t,"previousSibling",i)},siblings:function(t){return bt((t.parentNode||{}).firstChild,t)},children:function(t){return bt(t.firstChild)},contents:function(t){return l(t,"iframe")?t.contentDocument:(l(t,"template")&&(t=t.content||t),mt.merge([],t.childNodes))}},function(t,e){mt.fn[t]=function(i,n){var s=mt.map(this,e,i);return"Until"!==t.slice(-5)&&(n=i),n&&"string"==typeof n&&(s=mt.filter(n,s)),this.length>1&&(Tt[t]||mt.uniqueSort(s),Dt.test(t)&&s.reverse()),this.pushStack(s)}});var St=/[^\x20\t\r\n\f]+/g;mt.Callbacks=function(t){t="string"==typeof t?function(t){var e={};return mt.each(t.match(St)||[],function(t,i){e[i]=!0}),e}(t):mt.extend({},t);var e,i,n,s,o=[],a=[],l=-1,u=function(){for(s=s||t.once,n=e=!0;a.length;l=-1)for(i=a.shift();++l-1;)o.splice(i,1),i<=l&&l--}),this},has:function(t){return t?mt.inArray(t,o)>-1:o.length>0},empty:function(){return o&&(o=[]),this},disable:function(){return s=a=[],o=i="",this},disabled:function(){return!o},lock:function(){return s=a=[],i||e||(o=i=""),this},locked:function(){return!!s},fireWith:function(t,i){return s||(i=[t,(i=i||[]).slice?i.slice():i],a.push(i),e||u()),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!n}};return c},mt.extend({Deferred:function(t){var e=[["notify","progress",mt.Callbacks("memory"),mt.Callbacks("memory"),2],["resolve","done",mt.Callbacks("once memory"),mt.Callbacks("once memory"),0,"resolved"],["reject","fail",mt.Callbacks("once memory"),mt.Callbacks("once memory"),1,"rejected"]],n="pending",s={state:function(){return n},always:function(){return o.done(arguments).fail(arguments),this},catch:function(t){return s.then(null,t)},pipe:function(){var t=arguments;return mt.Deferred(function(i){mt.each(e,function(e,n){var s=pt(t[n[4]])&&t[n[4]];o[n[1]](function(){var t=s&&s.apply(this,arguments);t&&pt(t.promise)?t.promise().progress(i.notify).done(i.resolve).fail(i.reject):i[n[0]+"With"](this,s?[t]:arguments)})}),t=null}).promise()},then:function(t,n,s){function o(t,e,n,s){return function(){var a=this,l=arguments,u=function(){var i,u;if(!(t=r&&(n!==d&&(a=void 0,l=[i]),e.rejectWith(a,l))}};t?c():(mt.Deferred.getStackHook&&(c.stackTrace=mt.Deferred.getStackHook()),i.setTimeout(c))}}var r=0;return mt.Deferred(function(i){e[0][3].add(o(0,i,pt(s)?s:h,i.notifyWith)),e[1][3].add(o(0,i,pt(t)?t:h)),e[2][3].add(o(0,i,pt(n)?n:d))}).promise()},promise:function(t){return null!=t?mt.extend(t,s):s}},o={};return mt.each(e,function(t,i){var r=i[2],a=i[5];s[i[1]]=r.add,a&&r.add(function(){n=a},e[3-t][2].disable,e[3-t][3].disable,e[0][2].lock,e[0][3].lock),r.add(i[3].fire),o[i[0]]=function(){return o[i[0]+"With"](this===o?void 0:this,arguments),this},o[i[0]+"With"]=r.fireWith}),s.promise(o),t&&t.call(o,o),o},when:function(t){var e=arguments.length,i=e,n=Array(i),s=nt.call(arguments),o=mt.Deferred(),r=function(t){return function(i){n[t]=this,s[t]=arguments.length>1?nt.call(arguments):i,--e||o.resolveWith(n,s)}};if(e<=1&&(p(t,o.done(r(i)).resolve,o.reject,!e),"pending"===o.state()||pt(s[i]&&s[i].then)))return o.then();for(;i--;)p(s[i],r(i),o.reject);return o.promise()}});var At=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;mt.Deferred.exceptionHook=function(t,e){i.console&&i.console.warn&&t&&At.test(t.name)&&i.console.warn("jQuery.Deferred exception: "+t.message,t.stack,e)},mt.readyException=function(t){i.setTimeout(function(){throw t})};var Et=mt.Deferred();mt.fn.ready=function(t){return Et.then(t).catch(function(t){mt.readyException(t)}),this},mt.extend({isReady:!1,readyWait:1,ready:function(t){(!0===t?--mt.readyWait:mt.isReady)||(mt.isReady=!0,!0!==t&&--mt.readyWait>0||Et.resolveWith(et,[mt]))}}),mt.ready.then=Et.then,"complete"===et.readyState||"loading"!==et.readyState&&!et.documentElement.doScroll?i.setTimeout(mt.ready):(et.addEventListener("DOMContentLoaded",f),i.addEventListener("load",f));var $t=function(t,e,i,n,s,o,a){var l=0,u=t.length,c=null==i;if("object"===r(i))for(l in s=!0,i)$t(t,e,l,i[l],!0,o,a);else if(void 0!==n&&(s=!0,pt(n)||(a=!0),c&&(a?(e.call(t,n),e=null):(c=e,e=function(t,e,i){return c.call(mt(t),i)})),e))for(;l1,null,!0)},removeData:function(t){return this.each(function(){Nt.remove(this,t)})}}),mt.extend({queue:function(t,e,i){var n;if(t)return e=(e||"fx")+"queue",n=Mt.get(t,e),i&&(!n||Array.isArray(i)?n=Mt.access(t,e,mt.makeArray(i)):n.push(i)),n||[]},dequeue:function(t,e){e=e||"fx";var i=mt.queue(t,e),n=i.length,s=i.shift(),o=mt._queueHooks(t,e);"inprogress"===s&&(s=i.shift(),n--),s&&("fx"===e&&i.unshift("inprogress"),delete o.stop,s.call(t,function(){mt.dequeue(t,e)},o)),!n&&o&&o.empty.fire()},_queueHooks:function(t,e){var i=e+"queueHooks";return Mt.get(t,i)||Mt.access(t,i,{empty:mt.Callbacks("once memory").add(function(){Mt.remove(t,[e+"queue",i])})})}}),mt.fn.extend({queue:function(t,e){var i=2;return"string"!=typeof t&&(e=t,t="fx",i--),arguments.length\x20\t\r\n\f]+)/i,Vt=/^$|^module$|\/(?:java|ecma)script/i,Yt={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};Yt.optgroup=Yt.option,Yt.tbody=Yt.tfoot=Yt.colgroup=Yt.caption=Yt.thead,Yt.th=Yt.td;var Xt,Kt,Gt=/<|&#?\w+;/;Xt=et.createDocumentFragment().appendChild(et.createElement("div")),(Kt=et.createElement("input")).setAttribute("type","radio"),Kt.setAttribute("checked","checked"),Kt.setAttribute("name","t"),Xt.appendChild(Kt),dt.checkClone=Xt.cloneNode(!0).cloneNode(!0).lastChild.checked,Xt.innerHTML="",dt.noCloneChecked=!!Xt.cloneNode(!0).lastChild.defaultValue;var Zt=et.documentElement,Qt=/^key/,Jt=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,te=/^([^.]*)(?:\.(.+)|)/;mt.event={global:{},add:function(t,e,i,n,s){var o,r,a,l,u,c,h,d,p,f,g,m=Mt.get(t);if(m)for(i.handler&&(i=(o=i).handler,s=o.selector),s&&mt.find.matchesSelector(Zt,s),i.guid||(i.guid=mt.guid++),(l=m.events)||(l=m.events={}),(r=m.handle)||(r=m.handle=function(e){return void 0!==mt&&mt.event.triggered!==e.type?mt.event.dispatch.apply(t,arguments):void 0}),u=(e=(e||"").match(St)||[""]).length;u--;)p=g=(a=te.exec(e[u])||[])[1],f=(a[2]||"").split(".").sort(),p&&(h=mt.event.special[p]||{},p=(s?h.delegateType:h.bindType)||p,h=mt.event.special[p]||{},c=mt.extend({type:p,origType:g,data:n,handler:i,guid:i.guid,selector:s,needsContext:s&&mt.expr.match.needsContext.test(s),namespace:f.join(".")},o),(d=l[p])||((d=l[p]=[]).delegateCount=0,h.setup&&!1!==h.setup.call(t,n,f,r)||t.addEventListener&&t.addEventListener(p,r)),h.add&&(h.add.call(t,c),c.handler.guid||(c.handler.guid=i.guid)),s?d.splice(d.delegateCount++,0,c):d.push(c),mt.event.global[p]=!0)},remove:function(t,e,i,n,s){var o,r,a,l,u,c,h,d,p,f,g,m=Mt.hasData(t)&&Mt.get(t);if(m&&(l=m.events)){for(u=(e=(e||"").match(St)||[""]).length;u--;)if(p=g=(a=te.exec(e[u])||[])[1],f=(a[2]||"").split(".").sort(),p){for(h=mt.event.special[p]||{},d=l[p=(n?h.delegateType:h.bindType)||p]||[],a=a[2]&&new RegExp("(^|\\.)"+f.join("\\.(?:.*\\.|)")+"(\\.|$)"),r=o=d.length;o--;)c=d[o],!s&&g!==c.origType||i&&i.guid!==c.guid||a&&!a.test(c.namespace)||n&&n!==c.selector&&("**"!==n||!c.selector)||(d.splice(o,1),c.selector&&d.delegateCount--,h.remove&&h.remove.call(t,c));r&&!d.length&&(h.teardown&&!1!==h.teardown.call(t,f,m.handle)||mt.removeEvent(t,p,m.handle),delete l[p])}else for(p in l)mt.event.remove(t,p+e[u],i,n,!0);mt.isEmptyObject(l)&&Mt.remove(t,"handle events")}},dispatch:function(t){var e,i,n,s,o,r,a=mt.event.fix(t),l=new Array(arguments.length),u=(Mt.get(this,"events")||{})[a.type]||[],c=mt.event.special[a.type]||{};for(l[0]=a,e=1;e=1))for(;u!==this;u=u.parentNode||this)if(1===u.nodeType&&("click"!==t.type||!0!==u.disabled)){for(o=[],r={},i=0;i-1:mt.find(s,this,null,[u]).length),r[s]&&o.push(n);o.length&&a.push({elem:u,handlers:o})}return u=this,l\x20\t\r\n\f]*)[^>]*)\/>/gi,ie=/\s*$/g;mt.extend({htmlPrefilter:function(t){return t.replace(ee,"<$1>")},clone:function(t,e,i){var n,s,o,r,a,l,u,c=t.cloneNode(!0),h=mt.contains(t.ownerDocument,t);if(!(dt.noCloneChecked||1!==t.nodeType&&11!==t.nodeType||mt.isXMLDoc(t)))for(r=x(c),n=0,s=(o=x(t)).length;n0&&C(r,!h&&x(t,"script")),c},cleanData:function(t){for(var e,i,n,s=mt.event.special,o=0;void 0!==(i=t[o]);o++)if(Pt(i)){if(e=i[Mt.expando]){if(e.events)for(n in e.events)s[n]?mt.event.remove(i,n):mt.removeEvent(i,n,e.handle);i[Mt.expando]=void 0}i[Nt.expando]&&(i[Nt.expando]=void 0)}}}),mt.fn.extend({detach:function(t){return M(this,t,!0)},remove:function(t){return M(this,t)},text:function(t){return $t(this,function(t){return void 0===t?mt.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=t)})},null,t,arguments.length)},append:function(){return P(this,arguments,function(t){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||E(this,t).appendChild(t)})},prepend:function(){return P(this,arguments,function(t){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var e=E(this,t);e.insertBefore(t,e.firstChild)}})},before:function(){return P(this,arguments,function(t){this.parentNode&&this.parentNode.insertBefore(t,this)})},after:function(){return P(this,arguments,function(t){this.parentNode&&this.parentNode.insertBefore(t,this.nextSibling)})},empty:function(){for(var t,e=0;null!=(t=this[e]);e++)1===t.nodeType&&(mt.cleanData(x(t,!1)),t.textContent="");return this},clone:function(t,e){return t=null!=t&&t,e=null==e?t:e,this.map(function(){return mt.clone(this,t,e)})},html:function(t){return $t(this,function(t){var e=this[0]||{},i=0,n=this.length;if(void 0===t&&1===e.nodeType)return e.innerHTML;if("string"==typeof t&&!ie.test(t)&&!Yt[(qt.exec(t)||["",""])[1].toLowerCase()]){t=mt.htmlPrefilter(t);try{for(;i1)}}),mt.Tween=z,z.prototype={constructor:z,init:function(t,e,i,n,s,o){this.elem=t,this.prop=i,this.easing=s||mt.easing._default,this.options=e,this.start=this.now=this.cur(),this.end=n,this.unit=o||(mt.cssNumber[i]?"":"px")},cur:function(){var t=z.propHooks[this.prop];return t&&t.get?t.get(this):z.propHooks._default.get(this)},run:function(t){var e,i=z.propHooks[this.prop];return this.options.duration?this.pos=e=mt.easing[this.easing](t,this.options.duration*t,0,1,this.options.duration):this.pos=e=t,this.now=(this.end-this.start)*e+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),i&&i.set?i.set(this):z.propHooks._default.set(this),this}},z.prototype.init.prototype=z.prototype,z.propHooks={_default:{get:function(t){var e;return 1!==t.elem.nodeType||null!=t.elem[t.prop]&&null==t.elem.style[t.prop]?t.elem[t.prop]:(e=mt.css(t.elem,t.prop,""))&&"auto"!==e?e:0},set:function(t){mt.fx.step[t.prop]?mt.fx.step[t.prop](t):1!==t.elem.nodeType||null==t.elem.style[mt.cssProps[t.prop]]&&!mt.cssHooks[t.prop]?t.elem[t.prop]=t.now:mt.style(t.elem,t.prop,t.now+t.unit)}}},z.propHooks.scrollTop=z.propHooks.scrollLeft={set:function(t){t.elem.nodeType&&t.elem.parentNode&&(t.elem[t.prop]=t.now)}},mt.easing={linear:function(t){return t},swing:function(t){return.5-Math.cos(t*Math.PI)/2},_default:"swing"},mt.fx=z.prototype.init,mt.fx.step={};var fe,ge,me=/^(?:toggle|show|hide)$/,ve=/queueHooks$/;mt.Animation=mt.extend(V,{tweeners:{"*":[function(t,e){var i=this.createTween(t,e);return y(i.elem,t,Lt.exec(e),i),i}]},tweener:function(t,e){pt(t)?(e=t,t=["*"]):t=t.match(St);for(var i,n=0,s=t.length;n1)},removeAttr:function(t){return this.each(function(){mt.removeAttr(this,t)})}}),mt.extend({attr:function(t,e,i){var n,s,o=t.nodeType;if(3!==o&&8!==o&&2!==o)return void 0===t.getAttribute?mt.prop(t,e,i):(1===o&&mt.isXMLDoc(t)||(s=mt.attrHooks[e.toLowerCase()]||(mt.expr.match.bool.test(e)?_e:void 0)),void 0!==i?null===i?void mt.removeAttr(t,e):s&&"set"in s&&void 0!==(n=s.set(t,i,e))?n:(t.setAttribute(e,i+""),i):s&&"get"in s&&null!==(n=s.get(t,e))?n:null==(n=mt.find.attr(t,e))?void 0:n)},attrHooks:{type:{set:function(t,e){if(!dt.radioValue&&"radio"===e&&l(t,"input")){var i=t.value;return t.setAttribute("type",e),i&&(t.value=i),e}}}},removeAttr:function(t,e){var i,n=0,s=e&&e.match(St);if(s&&1===t.nodeType)for(;i=s[n++];)t.removeAttribute(i)}}),_e={set:function(t,e,i){return!1===e?mt.removeAttr(t,i):t.setAttribute(i,i),i}},mt.each(mt.expr.match.bool.source.match(/\w+/g),function(t,e){var i=ye[e]||mt.find.attr;ye[e]=function(t,e,n){var s,o,r=e.toLowerCase();return n||(o=ye[r],ye[r]=s,s=null!=i(t,e,n)?r:null,ye[r]=o),s}});var be=/^(?:input|select|textarea|button)$/i,we=/^(?:a|area)$/i;mt.fn.extend({prop:function(t,e){return $t(this,mt.prop,t,e,arguments.length>1)},removeProp:function(t){return this.each(function(){delete this[mt.propFix[t]||t]})}}),mt.extend({prop:function(t,e,i){var n,s,o=t.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&mt.isXMLDoc(t)||(e=mt.propFix[e]||e,s=mt.propHooks[e]),void 0!==i?s&&"set"in s&&void 0!==(n=s.set(t,i,e))?n:t[e]=i:s&&"get"in s&&null!==(n=s.get(t,e))?n:t[e]},propHooks:{tabIndex:{get:function(t){var e=mt.find.attr(t,"tabindex");return e?parseInt(e,10):be.test(t.nodeName)||we.test(t.nodeName)&&t.href?0:-1}}},propFix:{for:"htmlFor",class:"className"}}),dt.optSelected||(mt.propHooks.selected={get:function(t){var e=t.parentNode;return e&&e.parentNode&&e.parentNode.selectedIndex,null},set:function(t){var e=t.parentNode;e&&(e.selectedIndex,e.parentNode&&e.parentNode.selectedIndex)}}),mt.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){mt.propFix[this.toLowerCase()]=this}),mt.fn.extend({addClass:function(t){var e,i,n,s,o,r,a,l=0;if(pt(t))return this.each(function(e){mt(this).addClass(t.call(this,e,X(this)))});if((e=K(t)).length)for(;i=this[l++];)if(s=X(i),n=1===i.nodeType&&" "+Y(s)+" "){for(r=0;o=e[r++];)n.indexOf(" "+o+" ")<0&&(n+=o+" ");s!==(a=Y(n))&&i.setAttribute("class",a)}return this},removeClass:function(t){var e,i,n,s,o,r,a,l=0;if(pt(t))return this.each(function(e){mt(this).removeClass(t.call(this,e,X(this)))});if(!arguments.length)return this.attr("class","");if((e=K(t)).length)for(;i=this[l++];)if(s=X(i),n=1===i.nodeType&&" "+Y(s)+" "){for(r=0;o=e[r++];)for(;n.indexOf(" "+o+" ")>-1;)n=n.replace(" "+o+" "," ");s!==(a=Y(n))&&i.setAttribute("class",a)}return this},toggleClass:function(t,e){var i=typeof t,n="string"===i||Array.isArray(t);return"boolean"==typeof e&&n?e?this.addClass(t):this.removeClass(t):pt(t)?this.each(function(i){mt(this).toggleClass(t.call(this,i,X(this),e),e)}):this.each(function(){var e,s,o,r;if(n)for(s=0,o=mt(this),r=K(t);e=r[s++];)o.hasClass(e)?o.removeClass(e):o.addClass(e);else void 0!==t&&"boolean"!==i||((e=X(this))&&Mt.set(this,"__className__",e),this.setAttribute&&this.setAttribute("class",e||!1===t?"":Mt.get(this,"__className__")||""))})},hasClass:function(t){var e,i,n=0;for(e=" "+t+" ";i=this[n++];)if(1===i.nodeType&&(" "+Y(X(i))+" ").indexOf(e)>-1)return!0;return!1}});var xe=/\r/g;mt.fn.extend({val:function(t){var e,i,n,s=this[0];return arguments.length?(n=pt(t),this.each(function(i){var s;1===this.nodeType&&(null==(s=n?t.call(this,i,mt(this).val()):t)?s="":"number"==typeof s?s+="":Array.isArray(s)&&(s=mt.map(s,function(t){return null==t?"":t+""})),(e=mt.valHooks[this.type]||mt.valHooks[this.nodeName.toLowerCase()])&&"set"in e&&void 0!==e.set(this,s,"value")||(this.value=s))})):s?(e=mt.valHooks[s.type]||mt.valHooks[s.nodeName.toLowerCase()])&&"get"in e&&void 0!==(i=e.get(s,"value"))?i:"string"==typeof(i=s.value)?i.replace(xe,""):null==i?"":i:void 0}}),mt.extend({valHooks:{option:{get:function(t){var e=mt.find.attr(t,"value");return null!=e?e:Y(mt.text(t))}},select:{get:function(t){var e,i,n,s=t.options,o=t.selectedIndex,r="select-one"===t.type,a=r?null:[],u=r?o+1:s.length;for(n=o<0?u:r?o:0;n-1)&&(i=!0);return i||(t.selectedIndex=-1),o}}}}),mt.each(["radio","checkbox"],function(){mt.valHooks[this]={set:function(t,e){if(Array.isArray(e))return t.checked=mt.inArray(mt(t).val(),e)>-1}},dt.checkOn||(mt.valHooks[this].get=function(t){return null===t.getAttribute("value")?"on":t.value})}),dt.focusin="onfocusin"in i;var Ce=/^(?:focusinfocus|focusoutblur)$/,ke=function(t){t.stopPropagation()};mt.extend(mt.event,{trigger:function(t,e,n,s){var o,r,a,l,u,c,h,d,p=[n||et],f=ut.call(t,"type")?t.type:t,g=ut.call(t,"namespace")?t.namespace.split("."):[];if(r=d=a=n=n||et,3!==n.nodeType&&8!==n.nodeType&&!Ce.test(f+mt.event.triggered)&&(f.indexOf(".")>-1&&(f=(g=f.split(".")).shift(),g.sort()),u=f.indexOf(":")<0&&"on"+f,(t=t[mt.expando]?t:new mt.Event(f,"object"==typeof t&&t)).isTrigger=s?2:3,t.namespace=g.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+g.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=n),e=null==e?[t]:mt.makeArray(e,[t]),h=mt.event.special[f]||{},s||!h.trigger||!1!==h.trigger.apply(n,e))){if(!s&&!h.noBubble&&!ft(n)){for(l=h.delegateType||f,Ce.test(l+f)||(r=r.parentNode);r;r=r.parentNode)p.push(r),a=r;a===(n.ownerDocument||et)&&p.push(a.defaultView||a.parentWindow||i)}for(o=0;(r=p[o++])&&!t.isPropagationStopped();)d=r,t.type=o>1?l:h.bindType||f,(c=(Mt.get(r,"events")||{})[t.type]&&Mt.get(r,"handle"))&&c.apply(r,e),(c=u&&r[u])&&c.apply&&Pt(r)&&(t.result=c.apply(r,e),!1===t.result&&t.preventDefault());return t.type=f,s||t.isDefaultPrevented()||h._default&&!1!==h._default.apply(p.pop(),e)||!Pt(n)||u&&pt(n[f])&&!ft(n)&&((a=n[u])&&(n[u]=null),mt.event.triggered=f,t.isPropagationStopped()&&d.addEventListener(f,ke),n[f](),t.isPropagationStopped()&&d.removeEventListener(f,ke),mt.event.triggered=void 0,a&&(n[u]=a)),t.result}},simulate:function(t,e,i){var n=mt.extend(new mt.Event,i,{type:t,isSimulated:!0});mt.event.trigger(n,null,e)}}),mt.fn.extend({trigger:function(t,e){return this.each(function(){mt.event.trigger(t,e,this)})},triggerHandler:function(t,e){var i=this[0];if(i)return mt.event.trigger(t,e,i,!0)}}),dt.focusin||mt.each({focus:"focusin",blur:"focusout"},function(t,e){var i=function(t){mt.event.simulate(e,t.target,mt.event.fix(t))};mt.event.special[e]={setup:function(){var n=this.ownerDocument||this,s=Mt.access(n,e);s||n.addEventListener(t,i,!0),Mt.access(n,e,(s||0)+1)},teardown:function(){var n=this.ownerDocument||this,s=Mt.access(n,e)-1;s?Mt.access(n,e,s):(n.removeEventListener(t,i,!0),Mt.remove(n,e))}}});var De=i.location,Te=Date.now(),Se=/\?/;mt.parseXML=function(t){var e;if(!t||"string"!=typeof t)return null;try{e=(new i.DOMParser).parseFromString(t,"text/xml")}catch(t){e=void 0}return e&&!e.getElementsByTagName("parsererror").length||mt.error("Invalid XML: "+t),e};var Ae=/\[\]$/,Ee=/\r?\n/g,$e=/^(?:submit|button|image|reset|file)$/i,Ie=/^(?:input|select|textarea|keygen)/i;mt.param=function(t,e){var i,n=[],s=function(t,e){var i=pt(e)?e():e;n[n.length]=encodeURIComponent(t)+"="+encodeURIComponent(null==i?"":i)};if(Array.isArray(t)||t.jquery&&!mt.isPlainObject(t))mt.each(t,function(){s(this.name,this.value)});else for(i in t)G(i,t[i],e,s);return n.join("&")},mt.fn.extend({serialize:function(){return mt.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var t=mt.prop(this,"elements");return t?mt.makeArray(t):this}).filter(function(){var t=this.type;return this.name&&!mt(this).is(":disabled")&&Ie.test(this.nodeName)&&!$e.test(t)&&(this.checked||!Bt.test(t))}).map(function(t,e){var i=mt(this).val();return null==i?null:Array.isArray(i)?mt.map(i,function(t){return{name:e.name,value:t.replace(Ee,"\r\n")}}):{name:e.name,value:i.replace(Ee,"\r\n")}}).get()}});var Oe=/%20/g,Pe=/#.*$/,Me=/([?&])_=[^&]*/,Ne=/^(.*?):[ \t]*([^\r\n]*)$/gm,je=/^(?:GET|HEAD)$/,Fe=/^\/\//,He={},Le={},Re="*/".concat("*"),ze=et.createElement("a");ze.href=De.href,mt.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:De.href,type:"GET",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(De.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Re,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":mt.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(t,e){return e?J(J(t,mt.ajaxSettings),e):J(mt.ajaxSettings,t)},ajaxPrefilter:Z(He),ajaxTransport:Z(Le),ajax:function(t,e){function n(t,e,n,a){var u,d,p,b,w,x=e;c||(c=!0,l&&i.clearTimeout(l),s=void 0,r=a||"",C.readyState=t>0?4:0,u=t>=200&&t<300||304===t,n&&(b=function(t,e,i){for(var n,s,o,r,a=t.contents,l=t.dataTypes;"*"===l[0];)l.shift(),void 0===n&&(n=t.mimeType||e.getResponseHeader("Content-Type"));if(n)for(s in a)if(a[s]&&a[s].test(n)){l.unshift(s);break}if(l[0]in i)o=l[0];else{for(s in i){if(!l[0]||t.converters[s+" "+l[0]]){o=s;break}r||(r=s)}o=o||r}if(o)return o!==l[0]&&l.unshift(o),i[o]}(f,C,n)),b=function(t,e,i,n){var s,o,r,a,l,u={},c=t.dataTypes.slice();if(c[1])for(r in t.converters)u[r.toLowerCase()]=t.converters[r];for(o=c.shift();o;)if(t.responseFields[o]&&(i[t.responseFields[o]]=e),!l&&n&&t.dataFilter&&(e=t.dataFilter(e,t.dataType)),l=o,o=c.shift())if("*"===o)o=l;else if("*"!==l&&l!==o){if(!(r=u[l+" "+o]||u["* "+o]))for(s in u)if((a=s.split(" "))[1]===o&&(r=u[l+" "+a[0]]||u["* "+a[0]])){!0===r?r=u[s]:!0!==u[s]&&(o=a[0],c.unshift(a[1]));break}if(!0!==r)if(r&&t.throws)e=r(e);else try{e=r(e)}catch(t){return{state:"parsererror",error:r?t:"No conversion from "+l+" to "+o}}}return{state:"success",data:e}}(f,b,C,u),u?(f.ifModified&&((w=C.getResponseHeader("Last-Modified"))&&(mt.lastModified[o]=w),(w=C.getResponseHeader("etag"))&&(mt.etag[o]=w)),204===t||"HEAD"===f.type?x="nocontent":304===t?x="notmodified":(x=b.state,d=b.data,u=!(p=b.error))):(p=x,!t&&x||(x="error",t<0&&(t=0))),C.status=t,C.statusText=(e||x)+"",u?v.resolveWith(g,[d,x,C]):v.rejectWith(g,[C,x,p]),C.statusCode(y),y=void 0,h&&m.trigger(u?"ajaxSuccess":"ajaxError",[C,f,u?d:p]),_.fireWith(g,[C,x]),h&&(m.trigger("ajaxComplete",[C,f]),--mt.active||mt.event.trigger("ajaxStop")))}"object"==typeof t&&(e=t,t=void 0),e=e||{};var s,o,r,a,l,u,c,h,d,p,f=mt.ajaxSetup({},e),g=f.context||f,m=f.context&&(g.nodeType||g.jquery)?mt(g):mt.event,v=mt.Deferred(),_=mt.Callbacks("once memory"),y=f.statusCode||{},b={},w={},x="canceled",C={readyState:0,getResponseHeader:function(t){var e;if(c){if(!a)for(a={};e=Ne.exec(r);)a[e[1].toLowerCase()]=e[2];e=a[t.toLowerCase()]}return null==e?null:e},getAllResponseHeaders:function(){return c?r:null},setRequestHeader:function(t,e){return null==c&&(t=w[t.toLowerCase()]=w[t.toLowerCase()]||t,b[t]=e),this},overrideMimeType:function(t){return null==c&&(f.mimeType=t),this},statusCode:function(t){var e;if(t)if(c)C.always(t[C.status]);else for(e in t)y[e]=[y[e],t[e]];return this},abort:function(t){var e=t||x;return s&&s.abort(e),n(0,e),this}};if(v.promise(C),f.url=((t||f.url||De.href)+"").replace(Fe,De.protocol+"//"),f.type=e.method||e.type||f.method||f.type,f.dataTypes=(f.dataType||"*").toLowerCase().match(St)||[""],null==f.crossDomain){u=et.createElement("a");try{u.href=f.url,u.href=u.href,f.crossDomain=ze.protocol+"//"+ze.host!=u.protocol+"//"+u.host}catch(t){f.crossDomain=!0}}if(f.data&&f.processData&&"string"!=typeof f.data&&(f.data=mt.param(f.data,f.traditional)),Q(He,f,e,C),c)return C;for(d in(h=mt.event&&f.global)&&0==mt.active++&&mt.event.trigger("ajaxStart"),f.type=f.type.toUpperCase(),f.hasContent=!je.test(f.type),o=f.url.replace(Pe,""),f.hasContent?f.data&&f.processData&&0===(f.contentType||"").indexOf("application/x-www-form-urlencoded")&&(f.data=f.data.replace(Oe,"+")):(p=f.url.slice(o.length),f.data&&(f.processData||"string"==typeof f.data)&&(o+=(Se.test(o)?"&":"?")+f.data,delete f.data),!1===f.cache&&(o=o.replace(Me,"$1"),p=(Se.test(o)?"&":"?")+"_="+Te+++p),f.url=o+p),f.ifModified&&(mt.lastModified[o]&&C.setRequestHeader("If-Modified-Since",mt.lastModified[o]),mt.etag[o]&&C.setRequestHeader("If-None-Match",mt.etag[o])),(f.data&&f.hasContent&&!1!==f.contentType||e.contentType)&&C.setRequestHeader("Content-Type",f.contentType),C.setRequestHeader("Accept",f.dataTypes[0]&&f.accepts[f.dataTypes[0]]?f.accepts[f.dataTypes[0]]+("*"!==f.dataTypes[0]?", "+Re+"; q=0.01":""):f.accepts["*"]),f.headers)C.setRequestHeader(d,f.headers[d]);if(f.beforeSend&&(!1===f.beforeSend.call(g,C,f)||c))return C.abort();if(x="abort",_.add(f.complete),C.done(f.success),C.fail(f.error),s=Q(Le,f,e,C)){if(C.readyState=1,h&&m.trigger("ajaxSend",[C,f]),c)return C;f.async&&f.timeout>0&&(l=i.setTimeout(function(){C.abort("timeout")},f.timeout));try{c=!1,s.send(b,n)}catch(t){if(c)throw t;n(-1,t)}}else n(-1,"No Transport");return C},getJSON:function(t,e,i){return mt.get(t,e,i,"json")},getScript:function(t,e){return mt.get(t,void 0,e,"script")}}),mt.each(["get","post"],function(t,e){mt[e]=function(t,i,n,s){return pt(i)&&(s=s||n,n=i,i=void 0),mt.ajax(mt.extend({url:t,type:e,dataType:s,data:i,success:n},mt.isPlainObject(t)&&t))}}),mt._evalUrl=function(t){return mt.ajax({url:t,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,throws:!0})},mt.fn.extend({wrapAll:function(t){var e;return this[0]&&(pt(t)&&(t=t.call(this[0])),e=mt(t,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&e.insertBefore(this[0]),e.map(function(){for(var t=this;t.firstElementChild;)t=t.firstElementChild;return t}).append(this)),this},wrapInner:function(t){return pt(t)?this.each(function(e){mt(this).wrapInner(t.call(this,e))}):this.each(function(){var e=mt(this),i=e.contents();i.length?i.wrapAll(t):e.append(t)})},wrap:function(t){var e=pt(t);return this.each(function(i){mt(this).wrapAll(e?t.call(this,i):t)})},unwrap:function(t){return this.parent(t).not("body").each(function(){mt(this).replaceWith(this.childNodes)}),this}}),mt.expr.pseudos.hidden=function(t){return!mt.expr.pseudos.visible(t)},mt.expr.pseudos.visible=function(t){return!!(t.offsetWidth||t.offsetHeight||t.getClientRects().length)},mt.ajaxSettings.xhr=function(){try{return new i.XMLHttpRequest}catch(t){}};var We={0:200,1223:204},Ue=mt.ajaxSettings.xhr();dt.cors=!!Ue&&"withCredentials"in Ue,dt.ajax=Ue=!!Ue,mt.ajaxTransport(function(t){var e,n;if(dt.cors||Ue&&!t.crossDomain)return{send:function(s,o){var r,a=t.xhr();if(a.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(r in t.xhrFields)a[r]=t.xhrFields[r];for(r in t.mimeType&&a.overrideMimeType&&a.overrideMimeType(t.mimeType), -t.crossDomain||s["X-Requested-With"]||(s["X-Requested-With"]="XMLHttpRequest"),s)a.setRequestHeader(r,s[r]);e=function(t){return function(){e&&(e=n=a.onload=a.onerror=a.onabort=a.ontimeout=a.onreadystatechange=null,"abort"===t?a.abort():"error"===t?"number"!=typeof a.status?o(0,"error"):o(a.status,a.statusText):o(We[a.status]||a.status,a.statusText,"text"!==(a.responseType||"text")||"string"!=typeof a.responseText?{binary:a.response}:{text:a.responseText},a.getAllResponseHeaders()))}},a.onload=e(),n=a.onerror=a.ontimeout=e("error"),void 0!==a.onabort?a.onabort=n:a.onreadystatechange=function(){4===a.readyState&&i.setTimeout(function(){e&&n()})},e=e("abort");try{a.send(t.hasContent&&t.data||null)}catch(t){if(e)throw t}},abort:function(){e&&e()}}}),mt.ajaxPrefilter(function(t){t.crossDomain&&(t.contents.script=!1)}),mt.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(t){return mt.globalEval(t),t}}}),mt.ajaxPrefilter("script",function(t){void 0===t.cache&&(t.cache=!1),t.crossDomain&&(t.type="GET")}),mt.ajaxTransport("script",function(t){var e,i;if(t.crossDomain)return{send:function(n,s){e=mt(" +@stop diff --git a/resources/views/hardware/view.blade.php b/resources/views/hardware/view.blade.php index 7e60bb33df..e814e249b3 100755 --- a/resources/views/hardware/view.blade.php +++ b/resources/views/hardware/view.blade.php @@ -496,6 +496,7 @@
+ @if ($asset->image) @elseif (($asset->model) && ($asset->model->image!='')) @@ -564,19 +565,21 @@ @foreach ($asset->licenseseats as $seat) - - {{ $seat->license->name }} - - @can('viewKeys', $seat->license) - {!! nl2br(e($seat->license->serial)) !!} - @else - ------------ - @endcan - - - {{ trans('general.checkin') }} - - + @if ($seat->license) + + {{ $seat->license->name }} + + @can('viewKeys', $seat->license) + {!! nl2br(e($seat->license->serial)) !!} + @else + ------------ + @endcan + + + {{ trans('general.checkin') }} + + + @endif @endforeach diff --git a/resources/views/layouts/default.blade.php b/resources/views/layouts/default.blade.php index 49a20a200c..eabddf976c 100644 --- a/resources/views/layouts/default.blade.php +++ b/resources/views/layouts/default.blade.php @@ -10,8 +10,15 @@ - - + + + + + + + + +