From 441ae69f5cf3e873ed5a13107f032a25e4df3ddc Mon Sep 17 00:00:00 2001 From: snipe Date: Wed, 14 Aug 2019 21:48:14 -0700 Subject: [PATCH] Integrations/develop into master (#7352) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fixes #6204 - added email alerts and web/API access to assets due for audits (#6992) * Added upcoming audit report TODO: Fid diff/threshold math * Added route to list overdue / upcoming assets via API * Controller/API methods for due/overdue audits We could probably skip this and just handle it via view in the routes… * Added query scopes for due and overdue audits * Added audit due console command to kernel * Added ability to pass audit specs to main API asset search method * Added audit presenter * Added bootstrap-tables presenter formatter to display an audit button * Added gated sidenav items to left nav * Added audit due/overdue blades * Cleanup on audit due/overdue console command * Added language strings for audit views * Fixed :threshold placeholder * Removed unused setting variable * Fixed next audit date math * Added scope for both overdue and upcoming * Derp. Wrong version * Bumped version (I will release this version officially tomorrow) * Leave the activated state for users alone in normal LDAP synchronisation. (#6988) * Fixed #7003 - crash when warranty months or purchase date is null * Fixed #6956 - viewKeys policy inconsistent (#7009) * Fixed #6956 - Added additional gates show showing/hiding license keys * Modified gate to allow user to see licenses if they can create or edit the license as well * Added API middleware to API routes to enable throttling TODO: Figure out how to make this costumizable without touching the code * Import locations from CSV via command line (#7021) * Added import locations command * Small fixes to location importer * Added country, LDAP OU * Cleaned up comments, added more clarification to what the script does * Added ability to update groups via API Fixes [ch9139] * Bumped version * Fixed #6883 - remove escaping of fields on LDAP import * Fixed #6880 - correctly encrypt encrypted fields via the API * Fixes #5054: LDAP users deactivated for none-ad (#7032) When using none-AD ldap, users are automatically deactivated every LDAP sync. This commit changes the behaviour so that if the active flag isn't set, the users are enabled. Fixed #5054, at least for 4.X * Updated packages - Updating erusev/parsedown (v1.7.2 => 1.7.3): Downloading (100%) - Updating squizlabs/php_codesniffer (3.4.1 => 3.4.2): Downloading (100%) - Updating symfony/polyfill-mbstring (v1.10.0 => v1.11.0): Downloading (100%) - Updating symfony/var-dumper (v3.4.23 => v3.4.27): Downloading (100%) - Updating league/flysystem (1.0.50 => 1.0.51): Downloading (100%) - Updating symfony/translation (v3.4.23 => v3.4.27): Downloading (100%) - Updating nesbot/carbon (1.36.2 => 1.37.1): Downloading (100%) - Updating symfony/debug (v3.4.23 => v3.4.27): Downloading (100%) - Updating symfony/console (v3.4.23 => v3.4.27): Downloading (100%) - Updating symfony/finder (v3.4.23 => v3.4.27): Downloading (100%) - Updating symfony/polyfill-ctype (v1.10.0 => v1.11.0): Downloading (100%) - Updating symfony/polyfill-php70 (v1.10.0 => v1.11.0): Downloading (100%) - Updating symfony/http-foundation (v3.4.23 => v3.4.27): Downloading (100%) - Updating symfony/event-dispatcher (v3.4.23 => v3.4.27): Downloading (100%) - Updating symfony/http-kernel (v3.4.23 => v3.4.27): Downloading (100%) - Updating symfony/process (v3.4.23 => v3.4.27): Downloading (100%) - Updating symfony/routing (v3.4.23 => v3.4.27): Downloading (100%) - Updating symfony/polyfill-util (v1.10.0 => v1.11.0): Downloading (100%) - Updating symfony/polyfill-php56 (v1.10.0 => v1.11.0): Downloading (100%) - Updating symfony/psr-http-message-bridge (v1.1.1 => v1.1.2): Downloading (failed) Downloading (100%) - Updating rollbar/rollbar (v1.7.5 => v1.8.1): Downloading (100%) - Updating symfony/yaml (v3.4.23 => v3.4.27): Downloading (100%) - Updating symfony/browser-kit (v3.4.23 => v3.4.27): Downloading (100%) * Fixed #7044 - API update deleted custom fields if they are not re-presented * Fixed XSS vulnerability when creating a new categories, etc via modal on create Same fix as before, because of the weird select2 post-parsing ajax behavior * Updated email strings * Fixed #7046 - added user website url back into UI * Updated language strings * Bumped version * Updated packages * New backups config for spatie * Removed debugbar service provider (autodiscovery) * Use laravel v5.5 withCount manual aliases * Added spatie language files * Removed old laravel backups config This config file was renamed in a newer version of spatie laravel-backup * Set the serialization * Added the command loader to console kernel * Renamed fire() to handle() * Updated withCount to use manual naming * Updated backup path in backup admin * Updated travis with new php versions * Bumped laravel version in readme * Fixed custom field edit screen * Fixed baseUrl is undefined error I literally cannot figure out how this ever worked before. * Fix for included files in backup * Bumped version * Switch has() to filled() * Change ->has() to ->filled() * Removed cosole log * Bumped packages * Use getReader instead of fetchAssoc for CSV parser https://csv.thephpleague.com/9.0/upgrading/ * Handle JSON validation errors like 5.4 * Handle JSON validation errors like 5.4 * Handle JSON validation errors like 5.4 * Trying to fix ajax asset validation This I think gets us closer, but still not handling the validation on the asset properly. When I do a print_r of the validation in the other items, its looking for an error bag that looks something like this: ``` Illuminate\Support\MessageBag Object ( [messages:protected] => Array ( [name] => Array ( [0] => The name field is required. ) [seats] => Array ( [0] => The seats field is required. ) [category_id] => Array ( [0] => The category id field is required. ) ) [format:protected] => :message ) ``` Currently the Assets ajax returns: ``` [2019-05-24 06:52:06] develop.ERROR: array ( 'messages' => array ( 'model_id' => array ( 0 => 'The model id field is required.', ), 'status_id' => array ( 0 => 'The status id field is required.', ), 'asset_tag' => array ( 0 => 'The asset tag field is required.', ), ), ) ``` So not sure why it’s not working. * Fixed missing asset validation * Check that a model exists before trying to fiddle with fieldsets * Tidied up license check * Removed extra escaping on checkin * Updated importer to work with newer CSV Reader::getRecords() method * Fixed field mapping * Small fix for reordering fields Fixes Illuminate\Database\QueryException: SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'order' cannot be null (SQL: insert into `custom_field_custom_fieldset` (`custom_field_id`, `custom_fieldset_id`, `order`, `required`) values (12, 7, , 0)) [ch1151] This needs revisiting for a more solid fix, especially for data that was already entered bad. * Fixed bug where sorting by company name in Users API did not work Fixes [ch9200] * Removed custom fields from AssignedSearch to prevent confusing data in selectlist Fixes [ch9193] * Removed alert-danger from tests * Fixed missed consumables_count withCount() statement * Fixed Undefined variable user in $backto if checked out to a non-user Fixes [ch9194] * Check for valid model before attempting to access fieldsets Fixes [ch1249] * Only build the log upload destination path if there is a matching record Fixes [ch1232] * Fixed free_seats_count variable name (I forgot that Laravel switched camel case to snake case for their old 5.4 withCount variables) * Only gtry to delete the file if a record is found in the log * Only try to get fieldset if model is valid * Fixed more camel-casing -> snake-casing * Only display the file if the log record can be found * Fixed casing in sync command * Updated README * Derp - typo * Added link to Atlassian plugin * More Atlassian clarifications * Show accessory image on view page * Increased image size to 800px, added lightboxes * Fixed #7083 - Removed user_exists constraint on department save If the user has been deleted, this prevented the department from being successfully saved on edit * Updated branch in version file * Dockerfile update to bring us up to php v7.1 for Laravel 5.5 (#7084) * bump up to php7.1 & change deprecated MAINTAINER to a LABEL so it is visible with `docker inspect` * AND modapache >< * 2 updates required to get software-properties+ppa * Bumped version * Bumped release again :( * Missed one * Fixed #7098 - updated backup config for deleteFile() method * Fixed #7092 - handle weird port forwarding/port numbers for baseUrl * Bumped version * Fixed #7099 - set email to null by default for backup notifications * Removed old comments * Fixed #7100 - Check if $user isset on checkin * Increased throttle to 120 requests per minute * Added Filipino, corrected order for Spanish variations * Update language strings * Bumped hash * Changed has to filled to fix bulk asset editing * Bumped point version * Small fixes for phpleague CSB reader v9 * Improved error checking in locations importer * Fixed #7145 - rename groups table to permissions_group for mysql 8 reserved word compatibility * Reduce minimum group name length to 2 (from 3) eg: IT * Back in time fix FOR #7145 for new installs on MySQL 8+ * Fixed permission insert //TODO Handle this via model * Possible fix for reporting/admin migration back in time * Fixed #7164 - change table name to permission_groups * Fixed LDAP password blanking on save * fixing previous commit's actual wiping of password (#7183) replaced Input::fille('ldap_pword') with _filled_. Should be good to go. https://github.com/snipe/snipe-it/issues/7179 https://github.com/snipe/snipe-it/issues/7169 * Bumped version * Downgrading rollbar for Laravel 5.5 * Spelling Correction (#7206) Fixed Spelling for the word reqrite, to be rewrite. * Fix #6910: Add logic to manipulate the eloquent query. (#7006) * Added company_id to consumables_users table * Added logic to manage when a pivot table doesn't have the column company_id trough a join with users * Remove a migration that tries to fix this problem, but is not longer necessary * Addresses #7238 - add PWA code to layout Needs additional UX testing * Better log message for bad LDAP connection * Fixed #7186 - has vs filled in User’s API blanking out groups if no group_ids are passed * Comment clarification on #7186 * Check for valid seat on hardware view * Added space between footer and custom message * Cap warranty months to three characters Filles rollbar 209 * Cap warranty months to 3 on the frontend blade * Fixed countable() strings on user destroy * Check that the user has assets and that the aset model is valid * Bumped hash * Caps asset warranty to 20 years * Command to fix custom field unicode conversion differences between PHP versions (#7263) * Fixes #7252 form request changes (#7272) * Fixes for #7252 - custom fields not validating / no validaton messages in API w/form requests * Removed debug info * More fixes for #7252 This is mostly working as intended, if not yet the way Laravel wants us to do it. Right now, the API returns correctly, and the form UI will return highlighted errors, with the input filled in ~sometimes~. I’m not sure why it’s only sometimes yet, but this is potentially progress. * Removed experimental method * Check for digits_between:0,240 for warranty * Removed debug code * Apply fix from PR #7273 to master * Bumped hash * Fixed #7250 - permission issue for API fieldsets and fields endpoints This applies the change from #7294 to master * Add @mskrip as a contributor * Fixed #7270 - Checking-in Assets via API Removes the Item's Asset Name * CORS for api (#7292) * Added CORS support to API * Changed order so CORS will still work if throttle hit * Added APP_CORS_ALLOWED_ORIGINS env option * Fixed typo * Clarified header comments * More clarification * DIsable CORS allowed origins by default to replicate existing behavior * Change variable name to be clearer * Bumped version * Added condition to deal with fieldname 'rtd_location' which can be tried to be queried in some places and doesn't exist in database (#7317) * Added comments to the ByFilter query scope for clarity * Added accessories checkout/checkin API endpoint * Fixed CVE-2019-10742 https://nvd.nist.gov/vuln/detail/CVE-2019-10742 * Update README.md (#7334) Add reference to CSV importer. * Group related variables in .env * History importer fixes * Fixes to history importer --- .all-contributorsrc | 9 + .env.example | 6 +- README.md | 10 +- app/Console/Commands/ImportLocations.php | 160 +++++++++++++ app/Console/Commands/Purge.php | 2 +- .../Commands/ReEncodeCustomFieldNames.php | 126 ++++++++++ app/Console/Commands/SyncAssetLocations.php | 2 +- app/Console/Kernel.php | 4 +- app/Exceptions/Handler.php | 22 +- .../Controllers/Api/AccessoriesController.php | 98 +++++++- app/Http/Controllers/Api/AssetsController.php | 26 +- .../Controllers/Api/ConsumablesController.php | 2 +- .../Controllers/Api/LocationsController.php | 6 +- .../Api/ManufacturersController.php | 2 +- .../Controllers/Api/SuppliersController.php | 2 +- app/Http/Controllers/Api/UsersController.php | 50 +++- .../Controllers/AssetModelsController.php | 139 +++++++++++ .../Assets/AssetCheckinController.php | 4 +- .../Assets/AssetCheckoutController.php | 2 - .../Controllers/Assets/AssetsController.php | 158 ++++++++---- app/Http/Controllers/Auth/LoginController.php | 8 +- .../Controllers/CustomFieldsetsController.php | 38 ++- .../Controllers/DepartmentsController.php | 4 +- app/Http/Controllers/SettingsController.php | 19 +- app/Http/Controllers/ViewAssetsController.php | 144 ++++++++++- app/Http/Kernel.php | 3 +- app/Http/Middleware/EncryptCookies.php | 9 + app/Http/Requests/SaveUserRequest.php | 6 +- app/Http/Transformers/UsersTransformer.php | 1 + app/Importer/Importer.php | 5 +- app/Models/Asset.php | 9 +- app/Models/Company.php | 8 +- app/Models/Department.php | 1 - app/Models/Group.php | 4 +- app/Models/User.php | 18 +- app/Policies/LicensePolicy.php | 27 ++- app/Presenters/AssetPresenter.php | 10 +- app/Providers/SettingsServiceProvider.php | 28 +++ composer.json | 44 ++-- config/app.php | 50 +++- config/backup.php | 74 ++++-- config/cors.php | 48 ++++ ...ration_cartalyst_sentry_install_groups.php | 4 +- ...31416_update_group_field_for_reporting.php | 67 +++--- .../2019_06_12_184327_rename_groups_table.php | 44 ++++ package.json | 2 +- public/js/build/all.js | 8 +- public/js/dist/all.js | 59 ++--- public/mix-manifest.json | 27 +-- resources/assets/js/snipeit.js | 3 +- resources/assets/js/snipeit_modals.js | 5 +- resources/lang/en/mail.php | 12 +- .../lang/es-MX/admin/settings/general.php | 14 +- resources/lang/es-MX/admin/users/general.php | 4 +- resources/lang/es-MX/general.php | 2 +- resources/lang/es-MX/mail.php | 4 +- .../lang/fa/admin/categories/general.php | 4 +- resources/lang/fa/admin/hardware/general.php | 2 +- resources/lang/fa/admin/hardware/message.php | 2 +- resources/lang/fa/mail.php | 8 +- resources/lang/fa/passwords.php | 2 +- .../lang/fr/admin/categories/general.php | 4 +- resources/lang/fr/admin/settings/general.php | 60 ++--- .../lang/fr/admin/statuslabels/table.php | 2 +- resources/lang/fr/general.php | 10 +- resources/lang/fr/mail.php | 6 +- resources/lang/fr/passwords.php | 2 +- .../lang/hu/admin/categories/general.php | 4 +- resources/lang/ja/admin/settings/general.php | 4 +- .../lang/lt/admin/categories/general.php | 4 +- .../lang/lt/admin/custom_fields/general.php | 4 +- resources/lang/lt/admin/hardware/form.php | 2 +- resources/lang/lt/admin/hardware/general.php | 2 +- resources/lang/lt/admin/hardware/message.php | 2 +- .../lang/lt/admin/manufacturers/message.php | 4 +- resources/lang/lt/admin/models/general.php | 8 +- resources/lang/lt/admin/models/message.php | 6 +- resources/lang/lt/admin/settings/general.php | 70 +++--- .../lang/lt/admin/statuslabels/table.php | 4 +- resources/lang/lt/admin/suppliers/message.php | 6 +- resources/lang/lt/admin/users/general.php | 6 +- resources/lang/lt/button.php | 2 +- resources/lang/lt/general.php | 32 +-- resources/lang/lt/mail.php | 14 +- resources/lang/lt/passwords.php | 2 +- resources/lang/lt/validation.php | 4 +- .../lang/mn/admin/categories/general.php | 2 +- .../lang/no/admin/custom_fields/general.php | 2 +- .../lang/no/admin/manufacturers/message.php | 4 +- resources/lang/no/admin/models/general.php | 2 +- resources/lang/no/admin/settings/general.php | 35 +-- resources/lang/no/admin/suppliers/message.php | 6 +- resources/lang/no/general.php | 8 +- resources/lang/no/mail.php | 6 +- resources/lang/no/passwords.php | 2 +- resources/lang/pt-PT/admin/models/general.php | 2 +- resources/lang/pt-PT/passwords.php | 2 +- .../lang/sv-SE/admin/hardware/general.php | 4 +- .../lang/sv-SE/admin/settings/general.php | 2 +- .../lang/sv-SE/admin/statuslabels/table.php | 4 +- .../lang/sv-SE/admin/suppliers/message.php | 4 +- resources/lang/sv-SE/general.php | 4 +- resources/lang/th/admin/settings/general.php | 4 +- resources/lang/th/passwords.php | 2 +- resources/lang/vi/admin/settings/general.php | 22 +- resources/lang/vi/button.php | 2 +- resources/lang/vi/validation.php | 4 +- resources/macros/macros.php | 4 +- resources/views/accessories/edit.blade.php | 1 + resources/views/accessories/view.blade.php | 19 +- resources/views/components/view.blade.php | 5 +- resources/views/consumables/view.blade.php | 11 +- resources/views/hardware/history.blade.php | 224 +++++++++++------- resources/views/hardware/view.blade.php | 29 ++- resources/views/layouts/default.blade.php | 13 +- resources/views/licenses/checkin.blade.php | 8 +- resources/views/licenses/checkout.blade.php | 8 +- resources/views/licenses/edit.blade.php | 4 + .../partials/forms/edit/warranty.blade.php | 4 +- resources/views/reports/licenses.blade.php | 8 +- resources/views/users/edit.blade.php | 9 + resources/views/users/print.blade.php | 26 +- resources/views/users/view.blade.php | 15 +- routes/api.php | 24 +- routes/web/hardware.php | 10 + sample_csvs/MOCK_ASSETS.csv | 202 +--------------- tests/api/ApiLicensesCest.php | 2 +- tests/api/ApiManufacturersCest.php | 2 +- tests/functional/AssetsCest.php | 1 - 129 files changed, 1817 insertions(+), 860 deletions(-) create mode 100644 app/Console/Commands/ImportLocations.php create mode 100644 app/Console/Commands/ReEncodeCustomFieldNames.php create mode 100644 config/cors.php create mode 100644 database/migrations/2019_06_12_184327_rename_groups_table.php diff --git a/.all-contributorsrc b/.all-contributorsrc index 1b38ea278c4c..8a2330391dca 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 347570b79568..b7684b91744f 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 bbb1d2a3bc64..9ed53724fed5 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 000000000000..b037b011bf06 --- /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 0c4829c94c73..a9f1da737498 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 000000000000..14276b1bd381 --- /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 e5fb76bbd7b8..b5d3cd33e06d 100644 --- a/app/Console/Commands/SyncAssetLocations.php +++ b/app/Console/Commands/SyncAssetLocations.php @@ -62,7 +62,7 @@ public function handle() $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 7ca813351d14..bafa469d0f01 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 @@ protected function schedule(Schedule $schedule) 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 8d9e71bb2890..f6449b25de37 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 @@ public function render($request, Exception $e) 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 @@ public function render($request, Exception $e) } } - // 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 @@ protected function unauthenticated($request, AuthenticationException $exception) 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 a0743c16c2ce..bb7e8271e708 100644 --- a/app/Http/Controllers/Api/AccessoriesController.php +++ b/app/Http/Controllers/Api/AccessoriesController.php @@ -8,6 +8,10 @@ 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 @@ public function destroy($id) 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 @@ public function selectlist(Request $request) return (new SelectlistTransformer)->transformSelectlist($accessories); } + + + } diff --git a/app/Http/Controllers/Api/AssetsController.php b/app/Http/Controllers/Api/AssetsController.php index ed5104475159..a07f18662013 100644 --- a/app/Http/Controllers/Api/AssetsController.php +++ b/app/Http/Controllers/Api/AssetsController.php @@ -464,7 +464,13 @@ public function store(Request $request) $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 @@ public function update(Request $request, $id) ($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 @@ public function update(Request $request, $id) // 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 @@ public function checkin(Request $request, $asset_id) $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 46f36c58ba14..221255ce2dc2 100644 --- a/app/Http/Controllers/Api/ConsumablesController.php +++ b/app/Http/Controllers/Api/ConsumablesController.php @@ -166,7 +166,7 @@ public function getDataView($consumableId) { $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 10cca5dc8d28..3410a880b7ac 100644 --- a/app/Http/Controllers/Api/LocationsController.php +++ b/app/Http/Controllers/Api/LocationsController.php @@ -124,9 +124,9 @@ public function show($id) '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 a1cf9027ba8e..b4e8d7175351 100644 --- a/app/Http/Controllers/Api/ManufacturersController.php +++ b/app/Http/Controllers/Api/ManufacturersController.php @@ -83,7 +83,7 @@ public function store(Request $request) 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 17b2b36bebb8..9e7d7b546a35 100644 --- a/app/Http/Controllers/Api/SuppliersController.php +++ b/app/Http/Controllers/Api/SuppliersController.php @@ -25,7 +25,7 @@ public function index(Request $request) $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 ea01ec9c2e11..85182ed3b4f5 100644 --- a/app/Http/Controllers/Api/UsersController.php +++ b/app/Http/Controllers/Api/UsersController.php @@ -60,12 +60,12 @@ public function index(Request $request) '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 @@ public function index(Request $request) case 'department': $users = $users->OrderDepartment($order); break; + case 'company': + $users = $users->OrderCompany($order); + break; default: $allowed_columns = [ @@ -204,7 +207,7 @@ public function store(SaveUserRequest $request) 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 @@ public function update(SaveUserRequest $request, $id) ->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 @@ public function destroy($id) $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 491de8325d82..29698a04f62e 100755 --- a/app/Http/Controllers/AssetModelsController.php +++ b/app/Http/Controllers/AssetModelsController.php @@ -292,6 +292,145 @@ public function getCustomFields($modelId) } + + + /** + * 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 c573389c9b11..8526de08fd14 100644 --- a/app/Http/Controllers/Assets/AssetCheckinController.php +++ b/app/Http/Controllers/Assets/AssetCheckinController.php @@ -70,7 +70,7 @@ public function store(AssetCheckinRequest $request, $assetId = null, $backto = n $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 @@ public function store(AssetCheckinRequest $request, $assetId = null, $backto = n 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 f9fc0032e6ef..a6d049874aea 100644 --- a/app/Http/Controllers/Assets/AssetCheckoutController.php +++ b/app/Http/Controllers/Assets/AssetCheckoutController.php @@ -27,7 +27,6 @@ public function create($assetId) { // 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 @@ public function create($assetId) } 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 98e8dbdacd5f..8f67158acf94 100755 --- a/app/Http/Controllers/Assets/AssetsController.php +++ b/app/Http/Controllers/Assets/AssetsController.php @@ -144,11 +144,9 @@ public function store(ImageUploadRequest $request) $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 @@ public function store(ImageUploadRequest $request) // 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 @@ public function update(ImageUploadRequest $request, $assetId = null) // 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 @@ public function getImportHistory() */ 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 @@ public function postImportHistory(Request $request) foreach ($results as $record) { - $asset_tag = $record['Asset Tag']; + 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]); - 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; - } + $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; - 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; + $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($checkoutdate < $checkindate) { + 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 @@ public function audit($id) 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 ff1c93f3837b..6dc691be9293 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -179,8 +179,8 @@ public function login(Request $request) } 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 @@ public function postTwoFactorAuth(Request $request) 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 @@ public function logout(Request $request) 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 785b221db59c..18323f03bb9f 100644 --- a/app/Http/Controllers/CustomFieldsetsController.php +++ b/app/Http/Controllers/CustomFieldsetsController.php @@ -44,19 +44,18 @@ public function show($id) $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 @@ public function destroy($id) /** - * 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 @@ public function associate($id) $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 9c0cd8d4bb92..434ccc26dcdc 100644 --- a/app/Http/Controllers/DepartmentsController.php +++ b/app/Http/Controllers/DepartmentsController.php @@ -52,8 +52,8 @@ public function store(ImageUploadRequest $request) $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 147ee0808b55..d9f4f3deb8ad 100755 --- a/app/Http/Controllers/SettingsController.php +++ b/app/Http/Controllers/SettingsController.php @@ -320,7 +320,8 @@ public function postSettings(Request $request) $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 @@ public function postSecurity(Request $request) $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 @@ public function postLabels(Request $request) $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 @@ public function postLabels(Request $request) $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 9efb9d24d467..918b76e62cb1 100755 --- a/app/Http/Controllers/ViewAssetsController.php +++ b/app/Http/Controllers/ViewAssetsController.php @@ -36,13 +36,19 @@ public function getIndex() '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 @@ public function getRequestItem($itemType, $itemId = null) 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 @@ public function getRequestedAssets() // 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 4b46e1dd9cf8..da3c5092b910 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 e3fe0f8b691d..01de91fbe8b6 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 fac0acdc603d..0de6a3bc0208 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 @@ public function rules() return $rules; } + } diff --git a/app/Http/Transformers/UsersTransformer.php b/app/Http/Transformers/UsersTransformer.php index 3a690704b487..07b5fc1809bb 100644 --- a/app/Http/Transformers/UsersTransformer.php +++ b/app/Http/Transformers/UsersTransformer.php @@ -34,6 +34,7 @@ public function transformUser (User $user) ] : 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 e917974f8f80..2aeaf7db91e7 100644 --- a/app/Importer/Importer.php +++ b/app/Importer/Importer.php @@ -5,6 +5,7 @@ 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 @@ public function __construct($file) 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 e5ff4156eb5e..07417347781c 100644 --- a/app/Models/Asset.php +++ b/app/Models/Asset.php @@ -82,7 +82,7 @@ public function declinedCheckout(User $declinedBy, $signature) { '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 @@ public function scopeOverdueForAudit($query) 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 @@ public function scopeByFilter($query, $filter) * * 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 @@ public function scopeByFilter($query, $filter) * 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 0cbdbd5671e4..a1094b60eab6 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -78,7 +78,13 @@ private static function scopeCompanyablesDirectly($query, $column = 'company_id' } $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 c054f66371ee..eecf5712cd80 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 0c492b77525a..25c2505101ed 100755 --- a/app/Models/Group.php +++ b/app/Models/Group.php @@ -6,10 +6,10 @@ 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 34268be455b6..c17128d9406f 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 @@ public function advancedTextSearch(Builder $query, array $terms) { */ 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 @@ public function scopeOrderDepartment($query, $order) 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 2667b5e119d7..0fc097e4d887 100644 --- a/app/Policies/LicensePolicy.php +++ b/app/Policies/LicensePolicy.php @@ -12,16 +12,27 @@ protected function columnName() 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 689a87201a34..144ed4d75b50 100644 --- a/app/Presenters/AssetPresenter.php +++ b/app/Presenters/AssetPresenter.php @@ -492,9 +492,13 @@ public function fullStatusText() { */ 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 bcdd5a49c455..e636846e3254 100644 --- a/app/Providers/SettingsServiceProvider.php +++ b/app/Providers/SettingsServiceProvider.php @@ -118,6 +118,34 @@ public function boot() 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 027998a3c407..22ac3087c39c 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 bad512be5f51..d8a0a57f0a6b 100755 --- a/config/app.php +++ b/config/app.php @@ -117,6 +117,54 @@ '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 @@ * 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 898f467ff246..f4d0f3b97584 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 @@ /* * 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 @@ * 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 @@ /* * 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 @@ ], /* - * 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 @@ 'notifiable' => \Spatie\Backup\Notifications\Notifiable::class, 'mail' => [ - 'to' => null, + 'to' => env('MAIL_BACKUP_NOTIFICATION_ADDRESS', null), ], 'slack' => [ @@ -113,6 +151,11 @@ * 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 @@ */ 'monitorBackups' => [ [ - 'name' => env('APP_NAME'), + 'name' => config('app.name'), 'disks' => ['local'], 'newestBackupsShouldNotBeOlderThanDays' => 1, 'storageUsedMayNotBeHigherThanMegabytes' => 5000, @@ -186,4 +229,3 @@ ], ], ]; - diff --git a/config/cors.php b/config/cors.php new file mode 100644 index 000000000000..0aa4b8cef486 --- /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 034a6a16d88c..d929a6270771 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 @@ public function up() */ 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 80b23f81242e..248e26dde2c7 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) - // { - // // - // }); - - DB::update('update '.DB::getTablePrefix().'groups set permissions = ? where id = ?', ['{"admin":1,"users":1,"reports":1}', 1]); - - DB::update('update '.DB::getTablePrefix().'groups set permissions = ? where id = ?', ['{"users":1,"reports":1}', 2]); - - // 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'); - - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - // - } + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + + // This is janky because we had to do a back in time change to handle a MySQL 8+ + // compatibility issue. + + // 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')) { + + 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() + { + // + } } 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 000000000000..3c5097161443 --- /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 116840800bfc..ad10edb1d7c6 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 7e60bb33dfde..e814e249b3d6 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 49a20a200c81..eabddf976c5e 100644 --- a/resources/views/layouts/default.blade.php +++ b/resources/views/layouts/default.blade.php @@ -10,8 +10,15 @@ - - + + + + + + + + +