diff --git a/.dev/vagrant/motd.txt b/.dev/vagrant/motd.txt index 823172e45..22089d55a 100644 --- a/.dev/vagrant/motd.txt +++ b/.dev/vagrant/motd.txt @@ -13,5 +13,5 @@ Default panel users: MySQL is accessible using root/pterodactyl or pterodactyl/pterodactyl -Services for pteroq and mailhog are running +Service for pteroq and mailhog are running ##################################################### diff --git a/.env.example b/.env.example index fa7e20965..45644374d 100644 --- a/.env.example +++ b/.env.example @@ -16,6 +16,9 @@ DB_PASSWORD=secret CACHE_DRIVER=file SESSION_DRIVER=database +HASHIDS_SALT= +HASHIDS_LENGTH=8 + MAIL_DRIVER=smtp MAIL_HOST=mailtrap.io MAIL_PORT=2525 @@ -24,10 +27,6 @@ MAIL_PASSWORD=null MAIL_ENCRYPTION=null MAIL_FROM=you@example.com -API_PREFIX=api -API_VERSION=v1 -API_DEBUG=false - QUEUE_DRIVER=database QUEUE_HIGH=high QUEUE_STANDARD=standard diff --git a/.env.travis b/.env.travis new file mode 100644 index 000000000..22a0c3047 --- /dev/null +++ b/.env.travis @@ -0,0 +1,18 @@ +APP_ENV=testing +APP_DEBUG=true +APP_KEY=SomeRandomString3232RandomString +APP_THEME=pterodactyl +APP_TIMEZONE=UTC +APP_URL=http://localhost/ + +DB_HOST=127.0.0.1 +DB_DATABASE=travis +DB_USERNAME=root +DB_PASSWORD="" + +CACHE_DRIVER=array +SESSION_DRIVER=array +MAIL_DRIVER=array +QUEUE_DRIVER=sync + +HASHIDS_SALT=test123 diff --git a/.gitignore b/.gitignore index 9d8eca6a5..d03bf7687 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ docker-compose.yml # for image related files misc .phpstorm.meta.php +.php_cs.cache diff --git a/.php_cs b/.php_cs index 791899b72..b6a20178c 100644 --- a/.php_cs +++ b/.php_cs @@ -1,7 +1,48 @@ in([ + 'app', + 'bootstrap', + 'config', + 'database', + 'resources/lang', + 'routes', + 'tests', + ]); -use SLLH\StyleCIBridge\ConfigBridge; - -return ConfigBridge::create(); +return PhpCsFixer\Config::create() + ->setRules([ + '@Symfony' => true, + '@PSR1' => true, + '@PSR2' => true, + 'align_multiline_comment' => ['comment_type' => 'phpdocs_like'], + 'array_syntax' => ['syntax' => 'short'], + 'blank_line_before_return' => true, + 'blank_line_before_statement' => false, + 'combine_consecutive_unsets' => true, + 'concat_space' => ['spacing' => 'one'], + 'declare_equal_normalize' => ['space' => 'single'], + 'heredoc_to_nowdoc' => true, + 'linebreak_after_opening_tag' => true, + 'method_argument_space' => [ + 'ensure_fully_multiline' => false, + 'keep_multiple_spaces_after_comma' => false, + ], + 'new_with_braces' => false, + 'no_alias_functions' => true, + 'no_multiline_whitespace_before_semicolons' => true, + 'no_unreachable_default_argument_value' => true, + 'no_useless_return' => true, + 'not_operator_with_successor_space' => true, + 'ordered_imports' => [ + 'sortAlgorithm' => 'length', + ], + 'phpdoc_align' => ['tags' => ['param']], + 'phpdoc_separation' => false, + 'protected_to_private' => false, + 'psr0' => ['dir' => 'app'], + 'psr4' => true, + 'random_api_migration' => true, + 'standardize_not_equals' => true, + ])->setRiskyAllowed(true)->setFinder($finder); diff --git a/.styleci.yml b/.styleci.yml index 7595f5546..87848d020 100644 --- a/.styleci.yml +++ b/.styleci.yml @@ -4,3 +4,4 @@ disabled: - concat_without_spaces enabled: - concat_with_spaces + - no_unused_imports diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..b0590806e --- /dev/null +++ b/.travis.yml @@ -0,0 +1,32 @@ +language: php +dist: trusty +php: + - '7.0' + - '7.1' +# - '7.2' +sudo: false +cache: + directories: + - $HOME/.composer/cache +services: + - mysql +before_install: + - mysql -e 'CREATE DATABASE IF NOT EXISTS travis;' +before_script: + - cp .env.travis .env + - composer install --no-interaction --prefer-dist --no-suggest --verbose + - php artisan migrate --seed -v +script: + - vendor/bin/phpunit --coverage-clover coverage.xml +notifications: + email: false + webhooks: + urls: + - https://misc.schrej.net/travistodiscord/pterodev.php + on_success: change + on_failure: always + on_error: always + on_cancel: always + on_start: never +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8399f84b4..1cca04bbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -249,7 +249,7 @@ spatie/laravel-fractal (4.0.0 => 4.0.1) * New theme applied to Admin CP. Many graphical changes were made, some data was moved around and some display data changed. Too much was changed to feasibly log it all in here. Major breaking changes or notable new features will be logged. * New server creation page now makes significantly less AJAX calls and is much quicker to respond. * Server and Node view pages wee modified to split tabs into individual pages to make re-themeing and modifications significantly easier, and reduce MySQL query loads on page. -* `[pre.4]` — Services and Pack magement overhauled to be faster, cleaner, and more extensible in the future. +* `[pre.4]` — Service and Pack magement overhauled to be faster, cleaner, and more extensible in the future. * Most of the backend `UnhandledException` display errors now include a clearer error that directs admins to the program's logs. * Table seeders for services now can be run during upgrades and will attempt to locate and update, or create new if not found in the database. * Many structural changes to the database and `Pterodactyl\Models` classes that would flood this changelog if they were all included. All required migrations included to handle database changes. diff --git a/README.md b/README.md index 393dff90c..f586b3317 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ [![Logo Image](https://cdn.pterodactyl.io/logos/Banner%20Logo%20Black@2x.png)](https://pterodactyl.io) +[![Build Status](https://travis-ci.org/Pterodactyl/Panel.svg?branch=develop)](https://travis-ci.org/Pterodactyl/Panel) [![StyleCI](https://styleci.io/repos/47508644/shield?branch=develop)](https://styleci.io/repos/47508644) [![codecov](https://codecov.io/gh/Pterodactyl/Panel/branch/develop/graph/badge.svg)](https://codecov.io/gh/Pterodactyl/Panel) + ## Pterodactyl Panel Pterodactyl Panel is the free, open-source, game agnostic, self-hosted control panel for users, networks, and game service providers. Pterodactyl supports games and servers such as Minecraft (including Spigot, Bungeecord, and Sponge), ARK: Evolution Evolved, CS:GO, Team Fortress 2, Insurgency, Teamspeak 3, Mumble, and many more. Control all of your games from one unified interface. @@ -33,42 +35,44 @@ SOFTWARE. ![](http://static.s3.pterodactyl.io/PhraseApp-parrot.png) A huge thanks to [PhraseApp](https://phraseapp.com) who provide us the software to help translate this project. -Ace Editor - [license](https://github.com/ajaxorg/ace/blob/master/LICENSE) - [homepage](https://ace.c9.io) +Ace Editor — [license](https://github.com/ajaxorg/ace/blob/master/LICENSE) — [homepage](https://ace.c9.io) -AdminLTE - [license](https://github.com/almasaeed2010/AdminLTE/blob/master/LICENSE) - [homepage](https://almsaeedstudio.com) +AdminLTE — [license](https://github.com/almasaeed2010/AdminLTE/blob/master/LICENSE) — [homepage](https://almsaeedstudio.com) -Animate.css - [license](https://github.com/daneden/animate.css/blob/master/LICENSE) - [homepage](http://daneden.github.io/animate.css/) +Animate.css — [license](https://github.com/daneden/animate.css/blob/master/LICENSE) — [homepage](http://daneden.github.io/animate.css/) -AnsiUp - [license](https://github.com/drudru/ansi_up/blob/master/Readme.md#license) - [homepage](https://github.com/drudru/ansi_up) +AnsiUp — [license](https://github.com/drudru/ansi_up/blob/master/Readme.md#license) — [homepage](https://github.com/drudru/ansi_up) -Async.js - [license](https://github.com/caolan/async/blob/master/LICENSE) - [homepage](https://github.com/caolan/async/) +Async.js — [license](https://github.com/caolan/async/blob/master/LICENSE) — [homepage](https://github.com/caolan/async/) -Bootstrap - [license](https://github.com/twbs/bootstrap/blob/master/LICENSE) - [homepage](http://getbootstrap.com) +Bootstrap — [license](https://github.com/twbs/bootstrap/blob/master/LICENSE) — [homepage](http://getbootstrap.com) -BootStrap Notify - [license](https://github.com/mouse0270/bootstrap-notify/blob/master/LICENSE) - [homepage](http://bootstrap-notify.remabledesigns.com) +BootStrap Notify — [license](https://github.com/mouse0270/bootstrap-notify/blob/master/LICENSE) — [homepage](http://bootstrap-notify.remabledesigns.com) -Chart.js - [license](https://github.com/chartjs/Chart.js/blob/master/LICENSE.md) - [homepage](http://www.chartjs.org) +Chart.js — [license](https://github.com/chartjs/Chart.js/blob/master/LICENSE.md) — [homepage](http://www.chartjs.org) -FontAwesome - [license](http://fontawesome.io/license/) - [homepage](http://fontawesome.io) +FontAwesome — [license](http://fontawesome.io/license/) — [homepage](http://fontawesome.io) -FontAwesome Animations - [license](https://github.com/l-lin/font-awesome-animation#license) - [homepage](https://github.com/l-lin/font-awesome-animation) +FontAwesome Animations — [license](https://github.com/l-lin/font-awesome-animation#license) — [homepage](https://github.com/l-lin/font-awesome-animation) -jQuery - [license](https://github.com/jquery/jquery/blob/master/LICENSE.txt) - [homepage](http://jquery.com) +jQuery — [license](https://github.com/jquery/jquery/blob/master/LICENSE.txt) — [homepage](http://jquery.com) -Laravel Framework - [license](https://github.com/laravel/framework/blob/5.4/LICENSE.md) - [homepage](https://laravel.com) +Laravel Framework — [license](https://github.com/laravel/framework/blob/5.4/LICENSE.md) — [homepage](https://laravel.com) -Lodash - [license](https://github.com/lodash/lodash/blob/master/LICENSE) - [homepage](https://lodash.com/) +Lodash — [license](https://github.com/lodash/lodash/blob/master/LICENSE) — [homepage](https://lodash.com/) -Select2 - [license](https://github.com/select2/select2/blob/master/LICENSE.md) - [homepage](https://select2.github.io) +Select2 — [license](https://github.com/select2/select2/blob/master/LICENSE.md) — [homepage](https://select2.github.io) -Socket.io - [license](https://github.com/socketio/socket.io/blob/master/LICENSE) - [homepage](http://socket.io) +Socket.io — [license](https://github.com/socketio/socket.io/blob/master/LICENSE) — [homepage](http://socket.io) -Socket.io File Upload - [license](https://github.com/vote539/socketio-file-upload/blob/master/server.js#L1-L27) - [homepage](https://github.com/vote539/socketio-file-upload) +Socket.io File Upload — [license](https://github.com/vote539/socketio-file-upload/blob/master/server.js#L1-L27) — [homepage](https://github.com/vote539/socketio-file-upload) -SweetAlert - [license](https://github.com/t4t5/sweetalert/blob/master/LICENSE) - [homepage](http://t4t5.github.io/sweetalert/) +SweetAlert — [license](https://github.com/t4t5/sweetalert/blob/master/LICENSE) — [homepage](http://t4t5.github.io/sweetalert/) Typeahead — [license](https://github.com/bassjobsen/Bootstrap-3-Typeahead/blob/master/bootstrap3-typeahead.js) — [homepage](https://github.com/bassjobsen/Bootstrap-3-Typeahead) +particles.js — [license](https://github.com/VincentGarreau/particles.js/blob/master/LICENSE.md) — [homepage](http://vincentgarreau.com/particles.js/) + ### Additional License Information Some Javascript and CSS used within the panel is licensed under a `MIT` or `Apache 2.0`. Please check their respective header files for more information. diff --git a/app/Console/Commands/AddLocation.php b/app/Console/Commands/AddLocation.php index d9da92466..80338a33f 100644 --- a/app/Console/Commands/AddLocation.php +++ b/app/Console/Commands/AddLocation.php @@ -31,12 +31,12 @@ class AddLocation extends Command { protected $data = []; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'pterodactyl:location + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'pterodactyl:location {--short= : The shortcode name of this location (ex. us1).} {--long= : A longer description of this location.}'; @@ -49,8 +49,6 @@ class AddLocation extends Command /** * Create a new command instance. - * - * @return void */ public function __construct() { diff --git a/app/Console/Commands/AddNode.php b/app/Console/Commands/AddNode.php index 0aac540c0..9f31527ed 100644 --- a/app/Console/Commands/AddNode.php +++ b/app/Console/Commands/AddNode.php @@ -57,8 +57,6 @@ class AddNode extends Command /** * Create a new command instance. - * - * @return void */ public function __construct() { diff --git a/app/Console/Commands/CleanServiceBackup.php b/app/Console/Commands/CleanServiceBackup.php index 0a6a3e272..bb56ab7f0 100644 --- a/app/Console/Commands/CleanServiceBackup.php +++ b/app/Console/Commands/CleanServiceBackup.php @@ -46,8 +46,6 @@ class CleanServiceBackup extends Command /** * Create a new command instance. - * - * @return void */ public function __construct() { diff --git a/app/Console/Commands/ClearServices.php b/app/Console/Commands/ClearServices.php index db62d268c..c0438b360 100644 --- a/app/Console/Commands/ClearServices.php +++ b/app/Console/Commands/ClearServices.php @@ -45,8 +45,6 @@ class ClearServices extends Command /** * Create a new command instance. - * - * @return void */ public function __construct() { diff --git a/app/Console/Commands/ClearTasks.php b/app/Console/Commands/ClearTasks.php index 569caf028..027f9915c 100644 --- a/app/Console/Commands/ClearTasks.php +++ b/app/Console/Commands/ClearTasks.php @@ -49,8 +49,6 @@ class ClearTasks extends Command /** * Create a new command instance. - * - * @return void */ public function __construct() { diff --git a/app/Console/Commands/MakeUser.php b/app/Console/Commands/MakeUser.php index 05803dd3b..b7c7c78d7 100644 --- a/app/Console/Commands/MakeUser.php +++ b/app/Console/Commands/MakeUser.php @@ -26,6 +26,7 @@ namespace Pterodactyl\Console\Commands; use Illuminate\Console\Command; use Pterodactyl\Repositories\UserRepository; +use Pterodactyl\Services\Users\UserCreationService; class MakeUser extends Command { @@ -50,13 +51,19 @@ class MakeUser extends Command protected $description = 'Create a user within the panel.'; /** - * Create a new command instance. - * - * @return void + * @var \Pterodactyl\Services\Users\UserCreationService */ - public function __construct() + protected $creationService; + + /** + * Create a new command instance. + */ + public function __construct( + UserCreationService $creationService + ) { parent::__construct(); + $this->creationService = $creationService; } /** @@ -80,8 +87,8 @@ class MakeUser extends Command $data['root_admin'] = is_null($this->option('admin')) ? $this->confirm('Is this user a root administrator?') : $this->option('admin'); try { - $user = new UserRepository; - $user->create($data); + + $this->creationService->handle($data); return $this->info('User successfully created.'); } catch (\Exception $ex) { diff --git a/app/Console/Commands/RebuildServer.php b/app/Console/Commands/RebuildServer.php index 2177b112a..c0e36535b 100644 --- a/app/Console/Commands/RebuildServer.php +++ b/app/Console/Commands/RebuildServer.php @@ -49,8 +49,6 @@ class RebuildServer extends Command /** * Create a new command instance. - * - * @return void */ public function __construct() { diff --git a/app/Console/Commands/RunTasks.php b/app/Console/Commands/RunTasks.php index 6c3ccc6c9..9b1bff25f 100644 --- a/app/Console/Commands/RunTasks.php +++ b/app/Console/Commands/RunTasks.php @@ -50,8 +50,6 @@ class RunTasks extends Command /** * Create a new command instance. - * - * @return void */ public function __construct() { diff --git a/app/Console/Commands/ShowVersion.php b/app/Console/Commands/ShowVersion.php index 199a905b1..a5a91bfa7 100644 --- a/app/Console/Commands/ShowVersion.php +++ b/app/Console/Commands/ShowVersion.php @@ -45,8 +45,6 @@ class ShowVersion extends Command /** * Create a new command instance. - * - * @return void */ public function __construct() { diff --git a/app/Console/Commands/UpdateEmailSettings.php b/app/Console/Commands/UpdateEmailSettings.php index 1e316d6cb..960742b99 100644 --- a/app/Console/Commands/UpdateEmailSettings.php +++ b/app/Console/Commands/UpdateEmailSettings.php @@ -51,8 +51,6 @@ class UpdateEmailSettings extends Command /** * Create a new command instance. - * - * @return void */ public function __construct() { @@ -163,7 +161,7 @@ class UpdateEmailSettings extends Command file_put_contents($file, $envContents); $bar->finish(); - $this->line('Updating evironment configuration cache file.'); + $this->line('Updating environment configuration cache file.'); $this->call('config:cache'); echo "\n"; } diff --git a/app/Console/Commands/UpdateEnvironment.php b/app/Console/Commands/UpdateEnvironment.php index 6252a2824..5ee663908 100644 --- a/app/Console/Commands/UpdateEnvironment.php +++ b/app/Console/Commands/UpdateEnvironment.php @@ -55,8 +55,6 @@ class UpdateEnvironment extends Command /** * Create a new command instance. - * - * @return void */ public function __construct() { diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index bba5bb66e..e5fa22ee3 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -30,8 +30,7 @@ class Kernel extends ConsoleKernel /** * Define the application's command schedule. * - * @param \Illuminate\Console\Scheduling\Schedule $schedule - * @return void + * @param \Illuminate\Console\Scheduling\Schedule $schedule */ protected function schedule(Schedule $schedule) { diff --git a/app/Contracts/Criteria/CriteriaInterface.php b/app/Contracts/Criteria/CriteriaInterface.php new file mode 100644 index 000000000..8dc3599f8 --- /dev/null +++ b/app/Contracts/Criteria/CriteriaInterface.php @@ -0,0 +1,39 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Contracts\Criteria; + +use Pterodactyl\Repositories\Repository; + +interface CriteriaInterface +{ + /** + * Apply selected criteria to a repository call. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @param \Pterodactyl\Repositories\Repository $repository + * @return mixed + */ + public function apply($model, Repository $repository); +} diff --git a/app/Contracts/Extensions/HashidsInterface.php b/app/Contracts/Extensions/HashidsInterface.php new file mode 100644 index 000000000..8630718f2 --- /dev/null +++ b/app/Contracts/Extensions/HashidsInterface.php @@ -0,0 +1,41 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Contracts\Extensions; + +use Hashids\HashidsInterface as VendorHashidsInterface; + +interface HashidsInterface extends VendorHashidsInterface +{ + /** + * Decode an encoded hashid and return the first result. + * + * @param string $encoded + * @param null $default + * @return mixed + * + * @throws \InvalidArgumentException + */ + public function decodeFirst($encoded, $default = null); +} diff --git a/app/Contracts/Repository/AllocationRepositoryInterface.php b/app/Contracts/Repository/AllocationRepositoryInterface.php new file mode 100644 index 000000000..ec54de974 --- /dev/null +++ b/app/Contracts/Repository/AllocationRepositoryInterface.php @@ -0,0 +1,45 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Contracts\Repository; + +interface AllocationRepositoryInterface extends RepositoryInterface +{ + /** + * Set an array of allocation IDs to be assigned to a specific server. + * + * @param int|null $server + * @param array $ids + * @return int + */ + public function assignAllocationsToServer($server, array $ids); + + /** + * Return all of the allocations for a specific node. + * + * @param int $node + * @return \Illuminate\Database\Eloquent\Collection + */ + public function getAllocationsForNode($node); +} diff --git a/app/Contracts/Repository/ApiKeyRepositoryInterface.php b/app/Contracts/Repository/ApiKeyRepositoryInterface.php new file mode 100644 index 000000000..613a629e2 --- /dev/null +++ b/app/Contracts/Repository/ApiKeyRepositoryInterface.php @@ -0,0 +1,29 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Contracts\Repository; + +interface ApiKeyRepositoryInterface extends RepositoryInterface +{ +} diff --git a/app/Contracts/Repository/ApiPermissionRepositoryInterface.php b/app/Contracts/Repository/ApiPermissionRepositoryInterface.php new file mode 100644 index 000000000..4e5492972 --- /dev/null +++ b/app/Contracts/Repository/ApiPermissionRepositoryInterface.php @@ -0,0 +1,29 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Contracts\Repository; + +interface ApiPermissionRepositoryInterface extends RepositoryInterface +{ +} diff --git a/app/Facades/Version.php b/app/Contracts/Repository/Attributes/SearchableInterface.php similarity index 80% rename from app/Facades/Version.php rename to app/Contracts/Repository/Attributes/SearchableInterface.php index e9475d2a6..d33c1e41d 100644 --- a/app/Facades/Version.php +++ b/app/Contracts/Repository/Attributes/SearchableInterface.php @@ -1,5 +1,5 @@ . * @@ -22,19 +22,15 @@ * SOFTWARE. */ -namespace Pterodactyl\Facades; +namespace Pterodactyl\Contracts\Repository\Attributes; -use Illuminate\Support\Facades\Facade; - -class Version extends Facade +interface SearchableInterface { /** - * Returns the facade accessor class. + * Filter results by search term. * - * @return strig + * @param string $term + * @return $this */ - protected static function getFacadeAccessor() - { - return '\Pterodactyl\Services\VersionService'; - } + public function search($term); } diff --git a/app/Contracts/Repository/Daemon/BaseRepositoryInterface.php b/app/Contracts/Repository/Daemon/BaseRepositoryInterface.php new file mode 100644 index 000000000..234fe42bc --- /dev/null +++ b/app/Contracts/Repository/Daemon/BaseRepositoryInterface.php @@ -0,0 +1,83 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Contracts\Repository\Daemon; + +interface BaseRepositoryInterface +{ + /** + * Set the node model to be used for this daemon connection. + * + * @param int $id + * @return $this + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function setNode($id); + + /** + * Return the node model being used. + * + * @return \Pterodactyl\Models\Node + */ + public function getNode(); + + /** + * Set the UUID for the server to be used in the X-Access-Server header for daemon requests. + * + * @param null|string $server + * @return $this + */ + public function setAccessServer($server = null); + + /** + * Return the UUID of the server being used in requests. + * + * @return string + */ + public function getAccessServer(); + + /** + * Set the token to be used in the X-Access-Token header for requests to the daemon. + * + * @param null|string $token + * @return $this + */ + public function setAccessToken($token = null); + + /** + * Return the access token being used for requests. + * + * @return string + */ + public function getAccessToken(); + + /** + * Return an instance of the Guzzle HTTP Client to be used for requests. + * + * @param array $headers + * @return \GuzzleHttp\Client + */ + public function getHttpClient(array $headers = []); +} diff --git a/app/Contracts/Repository/Daemon/CommandRepositoryInterface.php b/app/Contracts/Repository/Daemon/CommandRepositoryInterface.php new file mode 100644 index 000000000..f55113a9c --- /dev/null +++ b/app/Contracts/Repository/Daemon/CommandRepositoryInterface.php @@ -0,0 +1,36 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Contracts\Repository\Daemon; + +interface CommandRepositoryInterface extends BaseRepositoryInterface +{ + /** + * Send a command to a server. + * + * @param string $command + * @return \Psr\Http\Message\ResponseInterface + */ + public function send($command); +} diff --git a/app/Contracts/Repository/Daemon/ConfigurationRepositoryInterface.php b/app/Contracts/Repository/Daemon/ConfigurationRepositoryInterface.php new file mode 100644 index 000000000..0d4421f80 --- /dev/null +++ b/app/Contracts/Repository/Daemon/ConfigurationRepositoryInterface.php @@ -0,0 +1,36 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Contracts\Repository\Daemon; + +interface ConfigurationRepositoryInterface extends BaseRepositoryInterface +{ + /** + * Update the configuration details for the specified node using data from the database. + * + * @param array $overrides + * @return \Psr\Http\Message\ResponseInterface + */ + public function update(array $overrides = []); +} diff --git a/app/Contracts/Repository/Daemon/FileRepositoryInterface.php b/app/Contracts/Repository/Daemon/FileRepositoryInterface.php new file mode 100644 index 000000000..d395794f3 --- /dev/null +++ b/app/Contracts/Repository/Daemon/FileRepositoryInterface.php @@ -0,0 +1,69 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Contracts\Repository\Daemon; + +interface FileRepositoryInterface extends BaseRepositoryInterface +{ + /** + * Return stat information for a given file. + * + * @param string $path + * @return object + * + * @throws \GuzzleHttp\Exception\RequestException + */ + public function getFileStat($path); + + /** + * Return the contents of a given file if it can be edited in the Panel. + * + * @param string $path + * @return object + * + * @throws \GuzzleHttp\Exception\RequestException + */ + public function getContent($path); + + /** + * Save new contents to a given file. + * + * @param string $path + * @param string $content + * @return \Psr\Http\Message\ResponseInterface + * + * @throws \GuzzleHttp\Exception\RequestException + */ + public function putContent($path, $content); + + /** + * Return a directory listing for a given path. + * + * @param string $path + * @return array + * + * @throws \GuzzleHttp\Exception\RequestException + */ + public function getDirectory($path); +} diff --git a/app/Contracts/Repository/Daemon/PowerRepositoryInterface.php b/app/Contracts/Repository/Daemon/PowerRepositoryInterface.php new file mode 100644 index 000000000..11c0c7e5c --- /dev/null +++ b/app/Contracts/Repository/Daemon/PowerRepositoryInterface.php @@ -0,0 +1,43 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Contracts\Repository\Daemon; + +interface PowerRepositoryInterface extends BaseRepositoryInterface +{ + const SIGNAL_START = 'start'; + const SIGNAL_STOP = 'stop'; + const SIGNAL_RESTART = 'restart'; + const SIGNAL_KILL = 'kill'; + + /** + * Send a power signal to a server. + * + * @param string $signal + * @return \Psr\Http\Message\ResponseInterface + * + * @throws \Pterodactyl\Exceptions\Repository\Daemon\InvalidPowerSignalException + */ + public function sendSignal($signal); +} diff --git a/app/Contracts/Repository/Daemon/ServerRepositoryInterface.php b/app/Contracts/Repository/Daemon/ServerRepositoryInterface.php new file mode 100644 index 000000000..703736547 --- /dev/null +++ b/app/Contracts/Repository/Daemon/ServerRepositoryInterface.php @@ -0,0 +1,98 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Contracts\Repository\Daemon; + +interface ServerRepositoryInterface extends BaseRepositoryInterface +{ + /** + * Create a new server on the daemon for the panel. + * + * @param int $id + * @param array $overrides + * @param bool $start + * @return \Psr\Http\Message\ResponseInterface + */ + public function create($id, array $overrides = [], $start = false); + + /** + * Set an access token and associated permissions for a server. + * + * @param string $key + * @param array $permissions + * @return \Psr\Http\Message\ResponseInterface + */ + public function setSubuserKey($key, array $permissions); + + /** + * Update server details on the daemon. + * + * @param array $data + * @return \Psr\Http\Message\ResponseInterface + */ + public function update(array $data); + + /** + * Mark a server to be reinstalled on the system. + * + * @param array|null $data + * @return \Psr\Http\Message\ResponseInterface + */ + public function reinstall($data = null); + + /** + * Mark a server as needing a container rebuild the next time the server is booted. + * + * @return \Psr\Http\Message\ResponseInterface + */ + public function rebuild(); + + /** + * Suspend a server on the daemon. + * + * @return \Psr\Http\Message\ResponseInterface + */ + public function suspend(); + + /** + * Un-suspend a server on the daemon. + * + * @return \Psr\Http\Message\ResponseInterface + */ + public function unsuspend(); + + /** + * Delete a server on the daemon. + * + * @return \Psr\Http\Message\ResponseInterface + */ + public function delete(); + + /** + * Return detials on a specific server. + * + * @return \Psr\Http\Message\ResponseInterface + */ + public function details(); +} diff --git a/app/Contracts/Repository/DatabaseHostRepositoryInterface.php b/app/Contracts/Repository/DatabaseHostRepositoryInterface.php new file mode 100644 index 000000000..3a677fcbb --- /dev/null +++ b/app/Contracts/Repository/DatabaseHostRepositoryInterface.php @@ -0,0 +1,45 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Contracts\Repository; + +interface DatabaseHostRepositoryInterface extends RepositoryInterface +{ + /** + * Return database hosts with a count of databases and the node information for which it is attached. + * + * @return \Illuminate\Support\Collection + */ + public function getWithViewDetails(); + + /** + * Return a database host with the databases and associated servers that are attached to said databases. + * + * @param int $id + * @return mixed + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getWithServers($id); +} diff --git a/app/Contracts/Repository/DatabaseRepositoryInterface.php b/app/Contracts/Repository/DatabaseRepositoryInterface.php new file mode 100644 index 000000000..97a2c7d8a --- /dev/null +++ b/app/Contracts/Repository/DatabaseRepositoryInterface.php @@ -0,0 +1,98 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Contracts\Repository; + +interface DatabaseRepositoryInterface extends RepositoryInterface +{ + /** + * Create a new database if it does not already exist on the host with + * the provided details. + * + * @param array $data + * @return mixed + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function createIfNotExists(array $data); + + /** + * Create a new database on a given connection. + * + * @param string $database + * @param null|string $connection + * @return bool + */ + public function createDatabase($database, $connection = null); + + /** + * Create a new database user on a given connection. + * + * @param string $username + * @param string $remote + * @param string $password + * @param null|string $connection + * @return bool + */ + public function createUser($username, $remote, $password, $connection = null); + + /** + * Give a specific user access to a given database. + * + * @param string $database + * @param string $username + * @param string $remote + * @param null|string $connection + * @return bool + */ + public function assignUserToDatabase($database, $username, $remote, $connection = null); + + /** + * Flush the privileges for a given connection. + * + * @param null|string $connection + * @return mixed + */ + public function flush($connection = null); + + /** + * Drop a given database on a specific connection. + * + * @param string $database + * @param null|string $connection + * @return bool + */ + public function dropDatabase($database, $connection = null); + + /** + * Drop a given user on a specific connection. + * + * @param string $username + * @param string $remote + * @param null|string $connection + * @return mixed + */ + public function dropUser($username, $remote, $connection = null); +} diff --git a/app/Contracts/Repository/LocationRepositoryInterface.php b/app/Contracts/Repository/LocationRepositoryInterface.php new file mode 100644 index 000000000..600c41c14 --- /dev/null +++ b/app/Contracts/Repository/LocationRepositoryInterface.php @@ -0,0 +1,65 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Contracts\Repository; + +use Pterodactyl\Contracts\Repository\Attributes\SearchableInterface; + +interface LocationRepositoryInterface extends RepositoryInterface, SearchableInterface +{ + /** + * Delete a location only if there are no nodes attached to it. + * + * @param $id + * @return bool|mixed|null + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function deleteIfNoNodes($id); + + /** + * Return locations with a count of nodes and servers attached to it. + * + * @return mixed + */ + public function getAllWithDetails(); + + /** + * Return all of the available locations with the nodes as a relationship. + * + * @return \Illuminate\Support\Collection + */ + public function getAllWithNodes(); + + /** + * Return all of the nodes and their respective count of servers for a location. + * + * @param int $id + * @return mixed + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getWithNodes($id); +} diff --git a/app/Contracts/Repository/NodeRepositoryInterface.php b/app/Contracts/Repository/NodeRepositoryInterface.php new file mode 100644 index 000000000..fe0449a38 --- /dev/null +++ b/app/Contracts/Repository/NodeRepositoryInterface.php @@ -0,0 +1,84 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Contracts\Repository; + +use Pterodactyl\Contracts\Repository\Attributes\SearchableInterface; + +interface NodeRepositoryInterface extends RepositoryInterface, SearchableInterface +{ + /** + * Return the usage stats for a single node. + * + * @param int $id + * @return array + */ + public function getUsageStats($id); + + /** + * Return all available nodes with a searchable interface. + * + * @param int $count + * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator + */ + public function getNodeListingData($count = 25); + + /** + * Return a single node with location and server information. + * + * @param int $id + * @return mixed + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getSingleNode($id); + + /** + * Return a node with all of the associated allocations and servers that are attached to said allocations. + * + * @param int $id + * @return mixed + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getNodeAllocations($id); + + /** + * Return a node with all of the servers attached to that node. + * + * @param int $id + * @return mixed + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getNodeServers($id); + + /** + * Return a collection of nodes beloning to a specific location for use on frontend display. + * + * @param int $location + * @return mixed + */ + public function getNodesForLocation($location); +} diff --git a/app/Contracts/Repository/OptionVariableRepositoryInterface.php b/app/Contracts/Repository/OptionVariableRepositoryInterface.php new file mode 100644 index 000000000..7a8f055d9 --- /dev/null +++ b/app/Contracts/Repository/OptionVariableRepositoryInterface.php @@ -0,0 +1,29 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Contracts\Repository; + +interface OptionVariableRepositoryInterface extends RepositoryInterface +{ +} diff --git a/app/Contracts/Repository/PackRepositoryInterface.php b/app/Contracts/Repository/PackRepositoryInterface.php new file mode 100644 index 000000000..e644e0399 --- /dev/null +++ b/app/Contracts/Repository/PackRepositoryInterface.php @@ -0,0 +1,59 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Contracts\Repository; + +use Pterodactyl\Contracts\Repository\Attributes\SearchableInterface; + +interface PackRepositoryInterface extends RepositoryInterface, SearchableInterface +{ + /** + * Return a paginated listing of packs with their associated option and server count. + * + * @param int $paginate + * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator + */ + public function paginateWithOptionAndServerCount($paginate = 50); + + /** + * Return a pack with the associated server models attached to it. + * + * @param int $id + * @return \Illuminate\Database\Eloquent\Collection + * + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException + */ + public function getWithServers($id); + + /** + * Return all of the file archives for a given pack. + * + * @param int $id + * @param bool $collection + * @return object|\Illuminate\Support\Collection + * + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException + */ + public function getFileArchives($id, $collection = false); +} diff --git a/app/Contracts/Repository/PermissionRepositoryInterface.php b/app/Contracts/Repository/PermissionRepositoryInterface.php new file mode 100644 index 000000000..f84c16f7f --- /dev/null +++ b/app/Contracts/Repository/PermissionRepositoryInterface.php @@ -0,0 +1,29 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Contracts\Repository; + +interface PermissionRepositoryInterface extends RepositoryInterface +{ +} diff --git a/app/Contracts/Repository/RepositoryInterface.php b/app/Contracts/Repository/RepositoryInterface.php new file mode 100644 index 000000000..aa8154fbd --- /dev/null +++ b/app/Contracts/Repository/RepositoryInterface.php @@ -0,0 +1,204 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Contracts\Repository; + +interface RepositoryInterface +{ + /** + * Return an identifier or Model object to be used by the repository. + * + * @return string|\Closure|object + */ + public function model(); + + /** + * Return the model being used for this repository instance. + * + * @return mixed + */ + public function getModel(); + + /** + * Returns an instance of a query builder. + * + * @return mixed + */ + public function getBuilder(); + + /** + * Returns the colummns to be selected or returned by the query. + * + * @return mixed + */ + public function getColumns(); + + /** + * An array of columns to filter the response by. + * + * @param array $columns + * @return $this + */ + public function withColumns($columns = ['*']); + + /** + * Disable returning a fresh model when data is inserted or updated. + * + * @return $this + */ + public function withoutFresh(); + + /** + * Create a new model instance and persist it to the database. + * + * @param array $fields + * @param bool $validate + * @param bool $force + * @return mixed + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function create(array $fields, $validate = true, $force = false); + + /** + * Delete a given record from the database. + * + * @param int $id + * @return int + */ + public function delete($id); + + /** + * Delete records matching the given attributes. + * + * @param array $attributes + * @return int + */ + public function deleteWhere(array $attributes); + + /** + * Find a model that has the specific ID passed. + * + * @param int $id + * @return mixed + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function find($id); + + /** + * Find a model matching an array of where clauses. + * + * @param array $fields + * @return mixed + */ + public function findWhere(array $fields); + + /** + * Find and return the first matching instance for the given fields. + * + * @param array $fields + * @return mixed + */ + public function findFirstWhere(array $fields); + + /** + * Return a count of records matching the passed arguments. + * + * @param array $fields + * @return int + */ + public function findCountWhere(array $fields); + + /** + * Update a given ID with the passed array of fields. + * + * @param int $id + * @param array $fields + * @param bool $validate + * @param bool $force + * @return mixed + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function update($id, array $fields, $validate = true, $force = false); + + /** + * Perform a mass update where matching records are updated using whereIn. + * This does not perform any model data validation. + * + * @param string $column + * @param array $values + * @param array $fields + * @return int + */ + public function updateWhereIn($column, array $values, array $fields); + + /** + * Update a record if it exists in the database, otherwise create it. + * + * @param array $where + * @param array $fields + * @param bool $validate + * @param bool $force + * @return mixed + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function updateOrCreate(array $where, array $fields, $validate = true, $force = false); + + /** + * Update multiple records matching the passed clauses. + * + * @param array $where + * @param array $fields + * @return mixed + */ + public function massUpdate(array $where, array $fields); + + /** + * Return all records from the model. + * + * @return mixed + */ + public function all(); + + /** + * Insert a single or multiple records into the database at once skipping + * validation and mass assignment checking. + * + * @param array $data + * @return bool + */ + public function insert(array $data); + + /** + * Insert multiple records into the database and ignore duplicates. + * + * @param array $values + * @return bool + */ + public function insertIgnore(array $values); +} diff --git a/app/Contracts/Repository/ServerRepositoryInterface.php b/app/Contracts/Repository/ServerRepositoryInterface.php new file mode 100644 index 000000000..9413b160f --- /dev/null +++ b/app/Contracts/Repository/ServerRepositoryInterface.php @@ -0,0 +1,119 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Contracts\Repository; + +use Pterodactyl\Contracts\Repository\Attributes\SearchableInterface; + +interface ServerRepositoryInterface extends RepositoryInterface, SearchableInterface +{ + /** + * Returns a listing of all servers that exist including relationships. + * + * @param int $paginate + * @return mixed + */ + public function getAllServers($paginate); + + /** + * Return a server model and all variables associated with the server. + * + * @param int $id + * @return mixed + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function findWithVariables($id); + + /** + * Return all of the server variables possible and default to the variable + * default if there is no value defined for the specific server requested. + * + * @param int $id + * @param bool $returnAsObject + * @return array|object + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getVariablesWithValues($id, $returnAsObject = false); + + /** + * Return enough data to be used for the creation of a server via the daemon. + * + * @param int $id + * @return \Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Eloquent\Model + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getDataForCreation($id); + + /** + * Return a server as well as associated databases and their hosts. + * + * @param int $id + * @return mixed + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getWithDatabases($id); + + /** + * Return data about the daemon service in a consumable format. + * + * @param int $id + * @return array + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getDaemonServiceData($id); + + /** + * Return an array of server IDs that a given user can access based on owner and subuser permissions. + * + * @param int $user + * @return array + */ + public function getUserAccessServers($user); + + /** + * Return a paginated list of servers that a user can access at a given level. + * + * @param int $user + * @param string $level + * @param bool $admin + * @param array $relations + * @return \Illuminate\Pagination\LengthAwarePaginator + */ + public function filterUserAccessServers($user, $admin = false, $level = 'all', array $relations = []); + + /** + * Return a server by UUID. + * + * @param string $uuid + * @return \Illuminate\Database\Eloquent\Collection + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getByUuid($uuid); +} diff --git a/app/Contracts/Repository/ServerVariableRepositoryInterface.php b/app/Contracts/Repository/ServerVariableRepositoryInterface.php new file mode 100644 index 000000000..dc5b761cd --- /dev/null +++ b/app/Contracts/Repository/ServerVariableRepositoryInterface.php @@ -0,0 +1,29 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Contracts\Repository; + +interface ServerVariableRepositoryInterface extends RepositoryInterface +{ +} diff --git a/app/Contracts/Repository/ServiceOptionRepositoryInterface.php b/app/Contracts/Repository/ServiceOptionRepositoryInterface.php new file mode 100644 index 000000000..84ce0bea0 --- /dev/null +++ b/app/Contracts/Repository/ServiceOptionRepositoryInterface.php @@ -0,0 +1,53 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Contracts\Repository; + +interface ServiceOptionRepositoryInterface extends RepositoryInterface +{ + /** + * Return a service option with the variables relation attached. + * + * @param int $id + * @return mixed + */ + public function getWithVariables($id); + + /** + * Return a service option with the copyFrom relation loaded onto the model. + * + * @param int $id + * @return mixed + */ + public function getWithCopyFrom($id); + + /** + * Confirm a copy script belongs to the same service as the item trying to use it. + * + * @param int $copyFromId + * @param int $service + * @return bool + */ + public function isCopiableScript($copyFromId, $service); +} diff --git a/app/Contracts/Repository/ServiceRepositoryInterface.php b/app/Contracts/Repository/ServiceRepositoryInterface.php new file mode 100644 index 000000000..da8543a6a --- /dev/null +++ b/app/Contracts/Repository/ServiceRepositoryInterface.php @@ -0,0 +1,44 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Contracts\Repository; + +interface ServiceRepositoryInterface extends RepositoryInterface +{ + /** + * Return a service or all services with their associated options, variables, and packs. + * + * @param int $id + * @return \Illuminate\Support\Collection + */ + public function getWithOptions($id = null); + + /** + * Return a service along with its associated options and the servers relation on those options. + * + * @param int $id + * @return mixed + */ + public function getWithOptionServers($id); +} diff --git a/app/Contracts/Repository/ServiceVariableRepositoryInterface.php b/app/Contracts/Repository/ServiceVariableRepositoryInterface.php new file mode 100644 index 000000000..3c975a046 --- /dev/null +++ b/app/Contracts/Repository/ServiceVariableRepositoryInterface.php @@ -0,0 +1,29 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Contracts\Repository; + +interface ServiceVariableRepositoryInterface extends RepositoryInterface +{ +} diff --git a/app/Contracts/Repository/SessionRepositoryInterface.php b/app/Contracts/Repository/SessionRepositoryInterface.php new file mode 100644 index 000000000..a4f0f3e9b --- /dev/null +++ b/app/Contracts/Repository/SessionRepositoryInterface.php @@ -0,0 +1,45 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Contracts\Repository; + +interface SessionRepositoryInterface extends RepositoryInterface +{ + /** + * Delete a session for a given user. + * + * @param int $user + * @param int $session + * @return null|int + */ + public function deleteUserSession($user, $session); + + /** + * Return all of the active sessions for a user. + * + * @param int $user + * @return \Illuminate\Support\Collection + */ + public function getUserSessions($user); +} diff --git a/app/Contracts/Repository/SubuserRepositoryInterface.php b/app/Contracts/Repository/SubuserRepositoryInterface.php new file mode 100644 index 000000000..6d6889fe9 --- /dev/null +++ b/app/Contracts/Repository/SubuserRepositoryInterface.php @@ -0,0 +1,58 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Contracts\Repository; + +interface SubuserRepositoryInterface extends RepositoryInterface +{ + /** + * Return a subuser with the associated server relationship. + * + * @param int $id + * @return \Illuminate\Database\Eloquent\Collection + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getWithServer($id); + + /** + * Return a subuser with the associated permissions relationship. + * + * @param int $id + * @return \Illuminate\Database\Eloquent\Collection + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getWithPermissions($id); + + /** + * Find a subuser and return with server and permissions relationships. + * + * @param int $id + * @return \Illuminate\Database\Eloquent\Collection + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getWithServerAndPermissions($id); +} diff --git a/app/Contracts/Repository/TaskRepositoryInterface.php b/app/Contracts/Repository/TaskRepositoryInterface.php new file mode 100644 index 000000000..8d8b32c33 --- /dev/null +++ b/app/Contracts/Repository/TaskRepositoryInterface.php @@ -0,0 +1,47 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Contracts\Repository; + +interface TaskRepositoryInterface extends RepositoryInterface +{ + /** + * Return the parent tasks and the count of children attached to that task. + * + * @param int $server + * @return mixed + */ + public function getParentTasksWithChainCount($server); + + /** + * Return a single task for a given server including all of the chained tasks. + * + * @param int $task + * @param int $server + * @return mixed + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getTaskForServer($task, $server); +} diff --git a/app/Contracts/Repository/UserRepositoryInterface.php b/app/Contracts/Repository/UserRepositoryInterface.php new file mode 100644 index 000000000..1638c7ddd --- /dev/null +++ b/app/Contracts/Repository/UserRepositoryInterface.php @@ -0,0 +1,45 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Contracts\Repository; + +use Pterodactyl\Contracts\Repository\Attributes\SearchableInterface; + +interface UserRepositoryInterface extends RepositoryInterface, SearchableInterface +{ + /** + * Return all users with counts of servers and subusers of servers. + * + * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator + */ + public function getAllUsersWithCounts(); + + /** + * Return all matching models for a user in a format that can be used for dropdowns. + * + * @param string $query + * @return \Illuminate\Database\Eloquent\Collection + */ + public function filterUsersByQuery($query); +} diff --git a/app/Events/Auth/FailedCaptcha.php b/app/Events/Auth/FailedCaptcha.php index ba741cf6d..4915f4c43 100644 --- a/app/Events/Auth/FailedCaptcha.php +++ b/app/Events/Auth/FailedCaptcha.php @@ -1,26 +1,26 @@ . -* -* Permission is hereby granted, free of charge, to any person obtaining a copy -* of this software and associated documentation files (the "Software"), to deal -* in the Software without restriction, including without limitation the rights -* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the Software is -* furnished to do so, subject to the following conditions: -* -* The above copyright notice and this permission notice shall be included in all -* copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -* SOFTWARE. -*/ + * Pterodactyl - Panel + * Copyright (c) 2015 - 2017 Dane Everitt . + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ namespace Pterodactyl\Events\Auth; @@ -47,9 +47,8 @@ class FailedCaptcha /** * Create a new event instance. * - * @param string $ip - * @param string $domain - * @return void + * @param string $ip + * @param string $domain */ public function __construct($ip, $domain) { diff --git a/app/Events/Auth/FailedPasswordReset.php b/app/Events/Auth/FailedPasswordReset.php index ec2de3b47..8a06261e4 100644 --- a/app/Events/Auth/FailedPasswordReset.php +++ b/app/Events/Auth/FailedPasswordReset.php @@ -47,9 +47,8 @@ class FailedPasswordReset /** * Create a new event instance. * - * @param string $ip - * @param string $email - * @return void + * @param string $ip + * @param string $email */ public function __construct($ip, $email) { diff --git a/app/Events/Event.php b/app/Events/Event.php index af6056df3..2db145df6 100644 --- a/app/Events/Event.php +++ b/app/Events/Event.php @@ -4,5 +4,4 @@ namespace Pterodactyl\Events; abstract class Event { - // } diff --git a/app/Events/Server/Created.php b/app/Events/Server/Created.php index 2591cd5af..46b25e0d9 100644 --- a/app/Events/Server/Created.php +++ b/app/Events/Server/Created.php @@ -41,8 +41,7 @@ class Created /** * Create a new event instance. * - * @param \Pterodactyl\Models\Server $server - * @return void + * @param \Pterodactyl\Models\Server $server */ public function __construct(Server $server) { diff --git a/app/Events/Server/Creating.php b/app/Events/Server/Creating.php index 46e4898c1..79b358c54 100644 --- a/app/Events/Server/Creating.php +++ b/app/Events/Server/Creating.php @@ -41,8 +41,7 @@ class Creating /** * Create a new event instance. * - * @param \Pterodactyl\Models\Server $server - * @return void + * @param \Pterodactyl\Models\Server $server */ public function __construct(Server $server) { diff --git a/app/Events/Server/Deleted.php b/app/Events/Server/Deleted.php index 6f8709b85..18fb674da 100644 --- a/app/Events/Server/Deleted.php +++ b/app/Events/Server/Deleted.php @@ -41,8 +41,7 @@ class Deleted /** * Create a new event instance. * - * @param \Pterodactyl\Models\Server $server - * @return void + * @param \Pterodactyl\Models\Server $server */ public function __construct(Server $server) { diff --git a/app/Events/Server/Deleting.php b/app/Events/Server/Deleting.php index 3152ed96e..f75c91849 100644 --- a/app/Events/Server/Deleting.php +++ b/app/Events/Server/Deleting.php @@ -41,8 +41,7 @@ class Deleting /** * Create a new event instance. * - * @param \Pterodactyl\Models\Server $server - * @return void + * @param \Pterodactyl\Models\Server $server */ public function __construct(Server $server) { diff --git a/app/Events/Server/Saved.php b/app/Events/Server/Saved.php index d19659045..91d704b75 100644 --- a/app/Events/Server/Saved.php +++ b/app/Events/Server/Saved.php @@ -41,8 +41,7 @@ class Saved /** * Create a new event instance. * - * @param \Pterodactyl\Models\Server $server - * @return void + * @param \Pterodactyl\Models\Server $server */ public function __construct(Server $server) { diff --git a/app/Events/Server/Saving.php b/app/Events/Server/Saving.php index 1ae31da32..0ce98adc4 100644 --- a/app/Events/Server/Saving.php +++ b/app/Events/Server/Saving.php @@ -41,8 +41,7 @@ class Saving /** * Create a new event instance. * - * @param \Pterodactyl\Models\Server $server - * @return void + * @param \Pterodactyl\Models\Server $server */ public function __construct(Server $server) { diff --git a/app/Events/Server/Updated.php b/app/Events/Server/Updated.php index 1284ea504..b0f76820a 100644 --- a/app/Events/Server/Updated.php +++ b/app/Events/Server/Updated.php @@ -41,8 +41,7 @@ class Updated /** * Create a new event instance. * - * @param \Pterodactyl\Models\Server $server - * @return void + * @param \Pterodactyl\Models\Server $server */ public function __construct(Server $server) { diff --git a/app/Events/Server/Updating.php b/app/Events/Server/Updating.php index b2fc255c8..c0ce6ad9e 100644 --- a/app/Events/Server/Updating.php +++ b/app/Events/Server/Updating.php @@ -41,8 +41,7 @@ class Updating /** * Create a new event instance. * - * @param \Pterodactyl\Models\Server $server - * @return void + * @param \Pterodactyl\Models\Server $server */ public function __construct(Server $server) { diff --git a/app/Events/Subuser/Created.php b/app/Events/Subuser/Created.php index 9a2d28536..4e661da66 100644 --- a/app/Events/Subuser/Created.php +++ b/app/Events/Subuser/Created.php @@ -41,8 +41,7 @@ class Created /** * Create a new event instance. * - * @param \Pterodactyl\Models\Subuser $subuser - * @return void + * @param \Pterodactyl\Models\Subuser $subuser */ public function __construct(Subuser $subuser) { diff --git a/app/Events/Subuser/Creating.php b/app/Events/Subuser/Creating.php index 5083c497c..17ae1d9e5 100644 --- a/app/Events/Subuser/Creating.php +++ b/app/Events/Subuser/Creating.php @@ -41,8 +41,7 @@ class Creating /** * Create a new event instance. * - * @param \Pterodactyl\Models\Subuser $subuser - * @return void + * @param \Pterodactyl\Models\Subuser $subuser */ public function __construct(Subuser $subuser) { diff --git a/app/Events/Subuser/Deleted.php b/app/Events/Subuser/Deleted.php index 1e419ab81..837bfc509 100644 --- a/app/Events/Subuser/Deleted.php +++ b/app/Events/Subuser/Deleted.php @@ -41,8 +41,7 @@ class Deleted /** * Create a new event instance. * - * @param \Pterodactyl\Models\Subuser $subuser - * @return void + * @param \Pterodactyl\Models\Subuser $subuser */ public function __construct(Subuser $subuser) { diff --git a/app/Events/Subuser/Deleting.php b/app/Events/Subuser/Deleting.php index a06ebe2dc..7f7ef9444 100644 --- a/app/Events/Subuser/Deleting.php +++ b/app/Events/Subuser/Deleting.php @@ -41,8 +41,7 @@ class Deleting /** * Create a new event instance. * - * @param \Pterodactyl\Models\Subuser $subuser - * @return void + * @param \Pterodactyl\Models\Subuser $subuser */ public function __construct(Subuser $subuser) { diff --git a/app/Events/User/Created.php b/app/Events/User/Created.php index 4e1fd9e75..2b614a90c 100644 --- a/app/Events/User/Created.php +++ b/app/Events/User/Created.php @@ -41,8 +41,7 @@ class Created /** * Create a new event instance. * - * @param \Pterodactyl\Models\User $user - * @return void + * @param \Pterodactyl\Models\User $user */ public function __construct(User $user) { diff --git a/app/Events/User/Creating.php b/app/Events/User/Creating.php index cc544af9e..ca4c517bf 100644 --- a/app/Events/User/Creating.php +++ b/app/Events/User/Creating.php @@ -41,8 +41,7 @@ class Creating /** * Create a new event instance. * - * @param \Pterodactyl\Models\User $user - * @return void + * @param \Pterodactyl\Models\User $user */ public function __construct(User $user) { diff --git a/app/Events/User/Deleted.php b/app/Events/User/Deleted.php index a3cc3cffe..ce6ca1c91 100644 --- a/app/Events/User/Deleted.php +++ b/app/Events/User/Deleted.php @@ -41,8 +41,7 @@ class Deleted /** * Create a new event instance. * - * @param \Pterodactyl\Models\User $user - * @return void + * @param \Pterodactyl\Models\User $user */ public function __construct(User $user) { diff --git a/app/Events/User/Deleting.php b/app/Events/User/Deleting.php index ad9397e45..581d4e2f6 100644 --- a/app/Events/User/Deleting.php +++ b/app/Events/User/Deleting.php @@ -41,8 +41,7 @@ class Deleting /** * Create a new event instance. * - * @param \Pterodactyl\Models\User $user - * @return void + * @param \Pterodactyl\Models\User $user */ public function __construct(User $user) { diff --git a/app/Exceptions/AccountNotFoundException.php b/app/Exceptions/AccountNotFoundException.php index b35bd2fc0..f8e36ed49 100644 --- a/app/Exceptions/AccountNotFoundException.php +++ b/app/Exceptions/AccountNotFoundException.php @@ -26,5 +26,4 @@ namespace Pterodactyl\Exceptions; class AccountNotFoundException extends \Exception { - // } diff --git a/app/Exceptions/AutoDeploymentException.php b/app/Exceptions/AutoDeploymentException.php index 109fe1096..4e56a0081 100644 --- a/app/Exceptions/AutoDeploymentException.php +++ b/app/Exceptions/AutoDeploymentException.php @@ -26,5 +26,4 @@ namespace Pterodactyl\Exceptions; class AutoDeploymentException extends \Exception { - // } diff --git a/app/Exceptions/DisplayException.php b/app/Exceptions/DisplayException.php index f3f8fd719..ba88486a0 100644 --- a/app/Exceptions/DisplayException.php +++ b/app/Exceptions/DisplayException.php @@ -26,14 +26,13 @@ namespace Pterodactyl\Exceptions; use Log; -class DisplayException extends \Exception +class DisplayException extends PterodactylException { /** * Exception constructor. * - * @param string $message - * @param mixed $log - * @return void + * @param string $message + * @param mixed $log */ public function __construct($message, $log = null) { diff --git a/app/Exceptions/DisplayValidationException.php b/app/Exceptions/DisplayValidationException.php index 3d8a4fda2..2c4e586a3 100644 --- a/app/Exceptions/DisplayValidationException.php +++ b/app/Exceptions/DisplayValidationException.php @@ -24,7 +24,6 @@ namespace Pterodactyl\Exceptions; -class DisplayValidationException extends \Exception +class DisplayValidationException extends PterodactylException { - // } diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index a801cdceb..31fb975f0 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -2,9 +2,15 @@ namespace Pterodactyl\Exceptions; -use Log; use Exception; +use Prologue\Alerts\Facades\Alert; use Illuminate\Auth\AuthenticationException; +use Illuminate\Session\TokenMismatchException; +use Illuminate\Validation\ValidationException; +use Illuminate\Auth\Access\AuthorizationException; +use Illuminate\Database\Eloquent\ModelNotFoundException; +use Pterodactyl\Exceptions\Model\DataValidationException; +use Symfony\Component\HttpKernel\Exception\HttpException; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; class Handler extends ExceptionHandler @@ -15,12 +21,15 @@ class Handler extends ExceptionHandler * @var array */ protected $dontReport = [ - \Illuminate\Auth\AuthenticationException::class, - \Illuminate\Auth\Access\AuthorizationException::class, - \Symfony\Component\HttpKernel\Exception\HttpException::class, - \Illuminate\Database\Eloquent\ModelNotFoundException::class, - \Illuminate\Session\TokenMismatchException::class, - \Illuminate\Validation\ValidationException::class, + AuthenticationException::class, + AuthorizationException::class, + DisplayException::class, + DataValidationException::class, + DisplayValidationException::class, + HttpException::class, + ModelNotFoundException::class, + TokenMismatchException::class, + ValidationException::class, ]; /** @@ -28,27 +37,30 @@ class Handler extends ExceptionHandler * * This is a great spot to send exceptions to Sentry, Bugsnag, etc. * - * @param \Exception $exception - * @return void + * @param \Exception $exception + * + * @throws \Exception */ public function report(Exception $exception) { - return parent::report($exception); + parent::report($exception); } /** * Render an exception into an HTTP response. * - * @param \Illuminate\Http\Request $request - * @param \Exception $exception - * @return \Illuminate\Http\Response + * @param \Illuminate\Http\Request $request + * @param \Exception $exception + * @return \Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\Response + * + * @throws \Exception */ public function render($request, Exception $exception) { if ($request->expectsJson() || $request->isJson() || $request->is(...config('pterodactyl.json_routes'))) { $exception = $this->prepareException($exception); - if (config('app.debug') || $this->isHttpException($exception)) { + if (config('app.debug') || $this->isHttpException($exception) || $exception instanceof DisplayException) { $displayError = $exception->getMessage(); } else { $displayError = 'An unhandled exception was encountered with this request.'; @@ -61,6 +73,10 @@ class Handler extends ExceptionHandler ], ($this->isHttpException($exception)) ? $exception->getStatusCode() : 500, [], JSON_UNESCAPED_SLASHES); parent::report($exception); + } elseif ($exception instanceof DisplayException) { + Alert::danger($exception->getMessage())->flash(); + + return redirect()->back()->withInput(); } return (isset($response)) ? $response : parent::render($request, $exception); @@ -69,8 +85,8 @@ class Handler extends ExceptionHandler /** * Convert an authentication exception into an unauthenticated response. * - * @param \Illuminate\Http\Request $request - * @param \Illuminate\Auth\AuthenticationException $exception + * @param \Illuminate\Http\Request $request + * @param \Illuminate\Auth\AuthenticationException $exception * @return \Illuminate\Http\Response */ protected function unauthenticated($request, AuthenticationException $exception) diff --git a/app/Exceptions/Http/Base/InvalidPasswordProvidedException.php b/app/Exceptions/Http/Base/InvalidPasswordProvidedException.php new file mode 100644 index 000000000..3b4fce107 --- /dev/null +++ b/app/Exceptions/Http/Base/InvalidPasswordProvidedException.php @@ -0,0 +1,31 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Exceptions\Http\Base; + +use Pterodactyl\Exceptions\DisplayException; + +class InvalidPasswordProvidedException extends DisplayException +{ +} diff --git a/app/Exceptions/Http/Server/FileSizeTooLargeException.php b/app/Exceptions/Http/Server/FileSizeTooLargeException.php new file mode 100644 index 000000000..b2da45b69 --- /dev/null +++ b/app/Exceptions/Http/Server/FileSizeTooLargeException.php @@ -0,0 +1,31 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Exceptions\Http\Server; + +use Pterodactyl\Exceptions\DisplayException; + +class FileSizeTooLargeException extends DisplayException +{ +} diff --git a/app/Exceptions/Http/Server/FileTypeNotEditableException.php b/app/Exceptions/Http/Server/FileTypeNotEditableException.php new file mode 100644 index 000000000..96f9f4d4f --- /dev/null +++ b/app/Exceptions/Http/Server/FileTypeNotEditableException.php @@ -0,0 +1,31 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Exceptions\Http\Server; + +use Pterodactyl\Exceptions\DisplayException; + +class FileTypeNotEditableException extends DisplayException +{ +} diff --git a/app/Exceptions/Model/DataValidationException.php b/app/Exceptions/Model/DataValidationException.php new file mode 100644 index 000000000..187a6b028 --- /dev/null +++ b/app/Exceptions/Model/DataValidationException.php @@ -0,0 +1,50 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Exceptions\Model; + +use Illuminate\Contracts\Validation\Validator; +use Illuminate\Validation\ValidationException; +use Illuminate\Contracts\Support\MessageProvider; + +class DataValidationException extends ValidationException implements MessageProvider +{ + /** + * DataValidationException constructor. + * + * @param \Illuminate\Contracts\Validation\Validator $validator + */ + public function __construct(Validator $validator) + { + parent::__construct($validator); + } + + /** + * @return \Illuminate\Support\MessageBag + */ + public function getMessageBag() + { + return $this->validator->errors(); + } +} diff --git a/app/Exceptions/PterodactylException.php b/app/Exceptions/PterodactylException.php new file mode 100644 index 000000000..4766deb30 --- /dev/null +++ b/app/Exceptions/PterodactylException.php @@ -0,0 +1,29 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Exceptions; + +class PterodactylException extends \Exception +{ +} diff --git a/app/Exceptions/Repository/Daemon/InvalidPowerSignalException.php b/app/Exceptions/Repository/Daemon/InvalidPowerSignalException.php new file mode 100644 index 000000000..21579d20a --- /dev/null +++ b/app/Exceptions/Repository/Daemon/InvalidPowerSignalException.php @@ -0,0 +1,29 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Exceptions\Repository\Daemon; + +class InvalidPowerSignalException extends \Exception +{ +} diff --git a/app/Exceptions/Repository/DuplicateDatabaseNameException.php b/app/Exceptions/Repository/DuplicateDatabaseNameException.php new file mode 100644 index 000000000..2308f407e --- /dev/null +++ b/app/Exceptions/Repository/DuplicateDatabaseNameException.php @@ -0,0 +1,31 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Exceptions\Repository; + +use Pterodactyl\Exceptions\DisplayException; + +class DuplicateDatabaseNameException extends DisplayException +{ +} diff --git a/app/Exceptions/Repository/RecordNotFoundException.php b/app/Exceptions/Repository/RecordNotFoundException.php new file mode 100644 index 000000000..796b4c083 --- /dev/null +++ b/app/Exceptions/Repository/RecordNotFoundException.php @@ -0,0 +1,29 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Exceptions\Repository; + +class RecordNotFoundException extends \Exception +{ +} diff --git a/app/Exceptions/Repository/RepositoryException.php b/app/Exceptions/Repository/RepositoryException.php new file mode 100644 index 000000000..07ce6b22b --- /dev/null +++ b/app/Exceptions/Repository/RepositoryException.php @@ -0,0 +1,29 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Exceptions\Repository; + +class RepositoryException extends \Exception +{ +} diff --git a/app/Exceptions/Service/HasActiveServersException.php b/app/Exceptions/Service/HasActiveServersException.php new file mode 100644 index 000000000..fe24a0c03 --- /dev/null +++ b/app/Exceptions/Service/HasActiveServersException.php @@ -0,0 +1,31 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Exceptions\Service; + +use Pterodactyl\Exceptions\DisplayException; + +class HasActiveServersException extends DisplayException +{ +} diff --git a/app/Exceptions/Service/Helper/CdnVersionFetchingException.php b/app/Exceptions/Service/Helper/CdnVersionFetchingException.php new file mode 100644 index 000000000..d96fe25de --- /dev/null +++ b/app/Exceptions/Service/Helper/CdnVersionFetchingException.php @@ -0,0 +1,29 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Exceptions\Service\Helper; + +class CdnVersionFetchingException extends \Exception +{ +} diff --git a/app/Exceptions/Service/Pack/InvalidFileMimeTypeException.php b/app/Exceptions/Service/Pack/InvalidFileMimeTypeException.php new file mode 100644 index 000000000..dd3b89450 --- /dev/null +++ b/app/Exceptions/Service/Pack/InvalidFileMimeTypeException.php @@ -0,0 +1,31 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Exceptions\Service\Pack; + +use Pterodactyl\Exceptions\DisplayException; + +class InvalidFileMimeTypeException extends DisplayException +{ +} diff --git a/app/Exceptions/Service/Pack/InvalidFileUploadException.php b/app/Exceptions/Service/Pack/InvalidFileUploadException.php new file mode 100644 index 000000000..719f02e8c --- /dev/null +++ b/app/Exceptions/Service/Pack/InvalidFileUploadException.php @@ -0,0 +1,31 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Exceptions\Service\Pack; + +use Pterodactyl\Exceptions\DisplayException; + +class InvalidFileUploadException extends DisplayException +{ +} diff --git a/app/Exceptions/Service/Pack/InvalidPackArchiveFormatException.php b/app/Exceptions/Service/Pack/InvalidPackArchiveFormatException.php new file mode 100644 index 000000000..eb57fd4cd --- /dev/null +++ b/app/Exceptions/Service/Pack/InvalidPackArchiveFormatException.php @@ -0,0 +1,31 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Exceptions\Service\Pack; + +use Pterodactyl\Exceptions\DisplayException; + +class InvalidPackArchiveFormatException extends DisplayException +{ +} diff --git a/app/Exceptions/Service/Pack/UnreadableZipArchiveException.php b/app/Exceptions/Service/Pack/UnreadableZipArchiveException.php new file mode 100644 index 000000000..013b15e2e --- /dev/null +++ b/app/Exceptions/Service/Pack/UnreadableZipArchiveException.php @@ -0,0 +1,31 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Exceptions\Service\Pack; + +use Pterodactyl\Exceptions\DisplayException; + +class UnreadableZipArchiveException extends DisplayException +{ +} diff --git a/app/Exceptions/Service/Pack/ZipArchiveCreationException.php b/app/Exceptions/Service/Pack/ZipArchiveCreationException.php new file mode 100644 index 000000000..30043ee9c --- /dev/null +++ b/app/Exceptions/Service/Pack/ZipArchiveCreationException.php @@ -0,0 +1,29 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Exceptions\Service\Pack; + +class ZipArchiveCreationException extends \Exception +{ +} diff --git a/app/Exceptions/Service/Pack/ZipExtractionException.php b/app/Exceptions/Service/Pack/ZipExtractionException.php new file mode 100644 index 000000000..b465075e3 --- /dev/null +++ b/app/Exceptions/Service/Pack/ZipExtractionException.php @@ -0,0 +1,31 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Exceptions\Service\Pack; + +use Pterodactyl\Exceptions\DisplayException; + +class ZipExtractionException extends DisplayException +{ +} diff --git a/app/Exceptions/Service/Server/RequiredVariableMissingException.php b/app/Exceptions/Service/Server/RequiredVariableMissingException.php new file mode 100644 index 000000000..ea22dd08b --- /dev/null +++ b/app/Exceptions/Service/Server/RequiredVariableMissingException.php @@ -0,0 +1,31 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Exceptions\Service\Server; + +use Pterodactyl\Exceptions\PterodactylException; + +class RequiredVariableMissingException extends PterodactylException +{ +} diff --git a/app/Exceptions/Service/Server/UserNotLinkedToServerException.php b/app/Exceptions/Service/Server/UserNotLinkedToServerException.php new file mode 100644 index 000000000..346b41fe7 --- /dev/null +++ b/app/Exceptions/Service/Server/UserNotLinkedToServerException.php @@ -0,0 +1,31 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Exceptions\Service\Server; + +use Pterodactyl\Exceptions\PterodactylException; + +class UserNotLinkedToServerException extends PterodactylException +{ +} diff --git a/app/Exceptions/Service/ServiceOption/InvalidCopyFromException.php b/app/Exceptions/Service/ServiceOption/InvalidCopyFromException.php new file mode 100644 index 000000000..c7ba77909 --- /dev/null +++ b/app/Exceptions/Service/ServiceOption/InvalidCopyFromException.php @@ -0,0 +1,29 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Exceptions\Service\ServiceOption; + +class InvalidCopyFromException extends \Exception +{ +} diff --git a/app/Exceptions/Service/ServiceOption/NoParentConfigurationFoundException.php b/app/Exceptions/Service/ServiceOption/NoParentConfigurationFoundException.php new file mode 100644 index 000000000..6a9ff9bc8 --- /dev/null +++ b/app/Exceptions/Service/ServiceOption/NoParentConfigurationFoundException.php @@ -0,0 +1,29 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Exceptions\Service\ServiceOption; + +class NoParentConfigurationFoundException extends \Exception +{ +} diff --git a/app/Exceptions/Service/ServiceVariable/ReservedVariableNameException.php b/app/Exceptions/Service/ServiceVariable/ReservedVariableNameException.php new file mode 100644 index 000000000..07769f4ce --- /dev/null +++ b/app/Exceptions/Service/ServiceVariable/ReservedVariableNameException.php @@ -0,0 +1,31 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Exceptions\Service\ServiceVariable; + +use Exception; + +class ReservedVariableNameException extends Exception +{ +} diff --git a/app/Exceptions/Service/Subuser/ServerSubuserExistsException.php b/app/Exceptions/Service/Subuser/ServerSubuserExistsException.php new file mode 100644 index 000000000..0c14f8034 --- /dev/null +++ b/app/Exceptions/Service/Subuser/ServerSubuserExistsException.php @@ -0,0 +1,31 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Exceptions\Service\Subuser; + +use Pterodactyl\Exceptions\DisplayException; + +class ServerSubuserExistsException extends DisplayException +{ +} diff --git a/app/Exceptions/Service/Subuser/UserIsServerOwnerException.php b/app/Exceptions/Service/Subuser/UserIsServerOwnerException.php new file mode 100644 index 000000000..ad551b19f --- /dev/null +++ b/app/Exceptions/Service/Subuser/UserIsServerOwnerException.php @@ -0,0 +1,31 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Exceptions\Service\Subuser; + +use Pterodactyl\Exceptions\DisplayException; + +class UserIsServerOwnerException extends DisplayException +{ +} diff --git a/app/Exceptions/Service/User/TwoFactorAuthenticationTokenInvalid.php b/app/Exceptions/Service/User/TwoFactorAuthenticationTokenInvalid.php new file mode 100644 index 000000000..1e7c6483b --- /dev/null +++ b/app/Exceptions/Service/User/TwoFactorAuthenticationTokenInvalid.php @@ -0,0 +1,29 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Exceptions\Service\User; + +class TwoFactorAuthenticationTokenInvalid extends \Exception +{ +} diff --git a/app/Extensions/DynamicDatabaseConnection.php b/app/Extensions/DynamicDatabaseConnection.php new file mode 100644 index 000000000..15328d05b --- /dev/null +++ b/app/Extensions/DynamicDatabaseConnection.php @@ -0,0 +1,96 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Extensions; + +use Pterodactyl\Models\DatabaseHost; +use Illuminate\Contracts\Encryption\Encrypter; +use Illuminate\Config\Repository as ConfigRepository; +use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface; + +class DynamicDatabaseConnection +{ + const DB_CHARSET = 'utf8'; + const DB_COLLATION = 'utf8_unicode_ci'; + const DB_DRIVER = 'mysql'; + + /** + * @var \Illuminate\Config\Repository + */ + protected $config; + + /** + * @var \Illuminate\Contracts\Encryption\Encrypter + */ + protected $encrypter; + + /** + * @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface + */ + protected $repository; + + /** + * DynamicDatabaseConnection constructor. + * + * @param \Illuminate\Config\Repository $config + * @param \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface $repository + * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter + */ + public function __construct( + ConfigRepository $config, + DatabaseHostRepositoryInterface $repository, + Encrypter $encrypter + ) { + $this->config = $config; + $this->encrypter = $encrypter; + $this->repository = $repository; + } + + /** + * Adds a dynamic database connection entry to the runtime config. + * + * @param string $connection + * @param \Pterodactyl\Models\DatabaseHost|int $host + * @param string $database + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function set($connection, $host, $database = 'mysql') + { + if (! $host instanceof DatabaseHost) { + $host = $this->repository->find($host); + } + + $this->config->set('database.connections.' . $connection, [ + 'driver' => self::DB_DRIVER, + 'host' => $host->host, + 'port' => $host->port, + 'database' => $database, + 'username' => $host->username, + 'password' => $this->encrypter->decrypt($host->password), + 'charset' => self::DB_CHARSET, + 'collation' => self::DB_COLLATION, + ]); + } +} diff --git a/app/Extensions/Hashids.php b/app/Extensions/Hashids.php new file mode 100644 index 000000000..49107a5e4 --- /dev/null +++ b/app/Extensions/Hashids.php @@ -0,0 +1,44 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Extensions; + +use Hashids\Hashids as VendorHashids; +use Pterodactyl\Contracts\Extensions\HashidsInterface; + +class Hashids extends VendorHashids implements HashidsInterface +{ + /** + * {@inheritdoc} + */ + public function decodeFirst($encoded, $default = null) + { + $result = $this->decode($encoded); + if (! is_array($result)) { + return $default; + } + + return array_first($result, null, $default); + } +} diff --git a/app/Extensions/PhraseAppTranslator.php b/app/Extensions/PhraseAppTranslator.php index f79cf2820..b61344cf7 100644 --- a/app/Extensions/PhraseAppTranslator.php +++ b/app/Extensions/PhraseAppTranslator.php @@ -31,10 +31,10 @@ class PhraseAppTranslator extends LaravelTranslator /** * Get the translation for the given key. * - * @param string $key - * @param array $replace - * @param string|null $locale - * @param bool $fallback + * @param string $key + * @param array $replace + * @param string|null $locale + * @param bool $fallback * @return string */ public function get($key, array $replace = [], $locale = null, $fallback = true) diff --git a/app/Http/Controllers/API/Admin/LocationController.php b/app/Http/Controllers/API/Admin/LocationController.php index 41405e91e..34230ea76 100644 --- a/app/Http/Controllers/API/Admin/LocationController.php +++ b/app/Http/Controllers/API/Admin/LocationController.php @@ -35,7 +35,7 @@ class LocationController extends Controller /** * Controller to handle returning all locations on the system. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return array */ public function index(Request $request) diff --git a/app/Http/Controllers/API/Admin/NodeController.php b/app/Http/Controllers/API/Admin/NodeController.php index 74784fdb6..3c6586543 100644 --- a/app/Http/Controllers/API/Admin/NodeController.php +++ b/app/Http/Controllers/API/Admin/NodeController.php @@ -40,7 +40,7 @@ class NodeController extends Controller /** * Controller to handle returning all nodes on the system. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return array */ public function index(Request $request) @@ -63,8 +63,8 @@ class NodeController extends Controller /** * Display information about a single node on the system. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param int $id * @return array */ public function view(Request $request, $id) @@ -84,8 +84,8 @@ class NodeController extends Controller /** * Display information about a single node on the system. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param int $id * @return \Illuminate\Http\JsonResponse */ public function viewConfig(Request $request, $id) @@ -100,7 +100,7 @@ class NodeController extends Controller /** * Create a new node on the system. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\JsonResponse|array */ public function store(Request $request) @@ -146,8 +146,8 @@ class NodeController extends Controller /** * Delete a node from the system. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param int $id * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse */ public function delete(Request $request, $id) diff --git a/app/Http/Controllers/API/Admin/ServerController.php b/app/Http/Controllers/API/Admin/ServerController.php index 28cb40641..409cf6d67 100644 --- a/app/Http/Controllers/API/Admin/ServerController.php +++ b/app/Http/Controllers/API/Admin/ServerController.php @@ -41,7 +41,7 @@ class ServerController extends Controller /** * Controller to handle returning all servers on the system. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return array */ public function index(Request $request) @@ -64,8 +64,8 @@ class ServerController extends Controller /** * Controller to handle returning information on a single server. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param int $id * @return array */ public function view(Request $request, $id) @@ -87,7 +87,7 @@ class ServerController extends Controller /** * Create a new server on the system. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\JsonResponse|array */ public function store(Request $request) @@ -130,8 +130,8 @@ class ServerController extends Controller /** * Delete a server from the system. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param int $id * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse */ public function delete(Request $request, $id) @@ -165,8 +165,8 @@ class ServerController extends Controller /** * Update the details for a server. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param int $id * @return \Illuminate\Http\JsonResponse|array */ public function details(Request $request, $id) @@ -205,8 +205,8 @@ class ServerController extends Controller /** * Set the new docker container for a server. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param int $id * @return \Illuminate\Http\RedirectResponse|array */ public function container(Request $request, $id) @@ -245,8 +245,8 @@ class ServerController extends Controller /** * Toggles the install status for a server. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param int $id * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse */ public function install(Request $request, $id) @@ -274,8 +274,8 @@ class ServerController extends Controller /** * Setup a server to have a container rebuild. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param int $id * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse */ public function rebuild(Request $request, $id) @@ -302,8 +302,8 @@ class ServerController extends Controller /** * Manage the suspension status for a server. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param int $id * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse */ public function suspend(Request $request, $id) @@ -344,8 +344,8 @@ class ServerController extends Controller /** * Update the build configuration for a server. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param int $id * @return \Illuminate\Http\JsonResponse|array */ public function build(Request $request, $id) @@ -391,8 +391,8 @@ class ServerController extends Controller /** * Update the startup command as well as variables. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param int $id * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse */ public function startup(Request $request, $id) diff --git a/app/Http/Controllers/API/Admin/ServiceController.php b/app/Http/Controllers/API/Admin/ServiceController.php index fbe911f14..a7b03b0ca 100644 --- a/app/Http/Controllers/API/Admin/ServiceController.php +++ b/app/Http/Controllers/API/Admin/ServiceController.php @@ -35,7 +35,7 @@ class ServiceController extends Controller /** * Controller to handle returning all locations on the system. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return array */ public function index(Request $request) @@ -52,8 +52,8 @@ class ServiceController extends Controller /** * Controller to handle returning information on a single server. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param int $id * @return array */ public function view(Request $request, $id) diff --git a/app/Http/Controllers/API/Admin/UserController.php b/app/Http/Controllers/API/Admin/UserController.php index c94fe8090..d2b70f7d8 100644 --- a/app/Http/Controllers/API/Admin/UserController.php +++ b/app/Http/Controllers/API/Admin/UserController.php @@ -30,7 +30,7 @@ use Illuminate\Http\Request; use Pterodactyl\Models\User; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\UserRepository; +use Pterodactyl\Repositories\oldUserRepository; use Pterodactyl\Transformers\Admin\UserTransformer; use Pterodactyl\Exceptions\DisplayValidationException; use League\Fractal\Pagination\IlluminatePaginatorAdapter; @@ -40,7 +40,7 @@ class UserController extends Controller /** * Controller to handle returning all users on the system. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return array */ public function index(Request $request) @@ -63,8 +63,8 @@ class UserController extends Controller /** * Display information about a single user on the system. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param int $id * @return array */ public function view(Request $request, $id) @@ -84,14 +84,14 @@ class UserController extends Controller /** * Create a new user on the system. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\JsonResponse|array */ public function store(Request $request) { $this->authorize('user-create', $request->apiKey()); - $repo = new UserRepository; + $repo = new oldUserRepository; try { $user = $repo->create($request->only([ 'custom_id', 'email', 'password', 'name_first', @@ -120,15 +120,15 @@ class UserController extends Controller /** * Update a user. * - * @param \Illuminate\Http\Request $request - * @param int $user + * @param \Illuminate\Http\Request $request + * @param int $user * @return \Illuminate\Http\RedirectResponse */ public function update(Request $request, $user) { $this->authorize('user-edit', $request->apiKey()); - $repo = new UserRepository; + $repo = new oldUserRepository; try { $user = $repo->update($user, $request->intersect([ 'email', 'password', 'name_first', @@ -157,15 +157,15 @@ class UserController extends Controller /** * Delete a user from the system. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param int $id * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse */ public function delete(Request $request, $id) { $this->authorize('user-delete', $request->apiKey()); - $repo = new UserRepository; + $repo = new oldUserRepository; try { $repo->delete($id); diff --git a/app/Http/Controllers/API/User/CoreController.php b/app/Http/Controllers/API/User/CoreController.php index 5c1d07406..851ff6b60 100644 --- a/app/Http/Controllers/API/User/CoreController.php +++ b/app/Http/Controllers/API/User/CoreController.php @@ -34,7 +34,7 @@ class CoreController extends Controller /** * Controller to handle base user request for all of their servers. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return array */ public function index(Request $request) diff --git a/app/Http/Controllers/API/User/ServerController.php b/app/Http/Controllers/API/User/ServerController.php index 904935186..dfb625be5 100644 --- a/app/Http/Controllers/API/User/ServerController.php +++ b/app/Http/Controllers/API/User/ServerController.php @@ -28,17 +28,17 @@ use Fractal; use Illuminate\Http\Request; use Pterodactyl\Models\Server; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\Daemon\PowerRepository; use Pterodactyl\Transformers\User\ServerTransformer; -use Pterodactyl\Repositories\Daemon\CommandRepository; +use Pterodactyl\Repositories\old_Daemon\PowerRepository; +use Pterodactyl\Repositories\old_Daemon\CommandRepository; class ServerController extends Controller { /** * Controller to handle base request for individual server information. * - * @param \Illuminate\Http\Request $request - * @param string $uuid + * @param \Illuminate\Http\Request $request + * @param string $uuid * @return array */ public function index(Request $request, $uuid) @@ -60,8 +60,8 @@ class ServerController extends Controller /** * Controller to handle request for server power toggle. * - * @param \Illuminate\Http\Request $request - * @param string $uuid + * @param \Illuminate\Http\Request $request + * @param string $uuid * @return \Illuminate\Http\Response */ public function power(Request $request, $uuid) @@ -80,8 +80,8 @@ class ServerController extends Controller /** * Controller to handle base request for individual server information. * - * @param \Illuminate\Http\Request $request - * @param string $uuid + * @param \Illuminate\Http\Request $request + * @param string $uuid * @return \Illuminate\Http\Response */ public function command(Request $request, $uuid) diff --git a/app/Http/Controllers/Admin/BaseController.php b/app/Http/Controllers/Admin/BaseController.php index 8c5719827..2858cdaf3 100644 --- a/app/Http/Controllers/Admin/BaseController.php +++ b/app/Http/Controllers/Admin/BaseController.php @@ -24,32 +24,62 @@ namespace Pterodactyl\Http\Controllers\Admin; -use Alert; -use Settings; -use Validator; -use Illuminate\Http\Request; +use Krucas\Settings\Settings; +use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Http\Requests\Admin\BaseFormRequest; +use Pterodactyl\Services\Helpers\SoftwareVersionService; class BaseController extends Controller { + /** + * @var \Prologue\Alerts\AlertsMessageBag + */ + protected $alert; + + /** + * @var \Krucas\Settings\Settings + */ + protected $settings; + + /** + * @var \Pterodactyl\Services\Helpers\SoftwareVersionService + */ + protected $version; + + /** + * BaseController constructor. + * + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Krucas\Settings\Settings $settings + * @param \Pterodactyl\Services\Helpers\SoftwareVersionService $version + */ + public function __construct( + AlertsMessageBag $alert, + Settings $settings, + SoftwareVersionService $version + ) { + $this->alert = $alert; + $this->settings = $settings; + $this->version = $version; + } + /** * Return the admin index view. * - * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ - public function getIndex(Request $request) + public function getIndex() { - return view('admin.index'); + return view('admin.index', ['version' => $this->version]); } /** * Return the admin settings view. * - * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ - public function getSettings(Request $request) + public function getSettings() { return view('admin.settings'); } @@ -57,24 +87,14 @@ class BaseController extends Controller /** * Handle settings post request. * - * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Http\Requests\Admin\BaseFormRequest $request * @return \Illuminate\Http\RedirectResponse */ - public function postSettings(Request $request) + public function postSettings(BaseFormRequest $request) { - $validator = Validator::make($request->all(), [ - 'company' => 'required|between:1,256', - // 'default_language' => 'required|alpha_dash|min:2|max:5', - ]); + $this->settings->set('company', $request->input('company')); - if ($validator->fails()) { - return redirect()->route('admin.settings')->withErrors($validator->errors())->withInput(); - } - - Settings::set('company', $request->input('company')); - // Settings::set('default_language', $request->input('default_language')); - - Alert::success('Settings have been successfully updated.')->flash(); + $this->alert->success('Settings have been successfully updated.')->flash(); return redirect()->route('admin.settings'); } diff --git a/app/Http/Controllers/Admin/DatabaseController.php b/app/Http/Controllers/Admin/DatabaseController.php index 6b4e12a1d..0a9dbad88 100644 --- a/app/Http/Controllers/Admin/DatabaseController.php +++ b/app/Http/Controllers/Admin/DatabaseController.php @@ -24,112 +24,146 @@ namespace Pterodactyl\Http\Controllers\Admin; -use Log; -use Alert; -use Illuminate\Http\Request; -use Pterodactyl\Models\Database; -use Pterodactyl\Models\Location; use Pterodactyl\Models\DatabaseHost; -use Pterodactyl\Exceptions\DisplayException; +use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\DatabaseRepository; -use Pterodactyl\Exceptions\DisplayValidationException; +use Pterodactyl\Services\Database\DatabaseHostService; +use Pterodactyl\Http\Requests\Admin\DatabaseHostFormRequest; +use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; +use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface; class DatabaseController extends Controller { + /** + * @var \Prologue\Alerts\AlertsMessageBag + */ + protected $alert; + + /** + * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface + */ + protected $locationRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Database\DatabaseHostService + */ + protected $service; + + /** + * DatabaseController constructor. + * + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface $repository + * @param \Pterodactyl\Services\Database\DatabaseHostService $service + * @param \Pterodactyl\Contracts\Repository\LocationRepositoryInterface $locationRepository + */ + public function __construct( + AlertsMessageBag $alert, + DatabaseHostRepositoryInterface $repository, + DatabaseHostService $service, + LocationRepositoryInterface $locationRepository + ) { + $this->alert = $alert; + $this->repository = $repository; + $this->service = $service; + $this->locationRepository = $locationRepository; + } + /** * Display database host index. * - * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ - public function index(Request $request) + public function index() { return view('admin.databases.index', [ - 'locations' => Location::with('nodes')->get(), - 'hosts' => DatabaseHost::withCount('databases')->with('node')->get(), + 'locations' => $this->locationRepository->getAllWithNodes(), + 'hosts' => $this->repository->getWithViewDetails(), ]); } /** * Display database host to user. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param int $host * @return \Illuminate\View\View + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function view(Request $request, $id) + public function view($host) { return view('admin.databases.view', [ - 'locations' => Location::with('nodes')->get(), - 'host' => DatabaseHost::with('databases.server')->findOrFail($id), + 'locations' => $this->locationRepository->getAllWithNodes(), + 'host' => $this->repository->getWithServers($host), ]); } /** - * Handle post request to create database host. + * Handle request to create a new database host. * - * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Http\Requests\Admin\DatabaseHostFormRequest $request * @return \Illuminate\Http\RedirectResponse + * + * @throws \Throwable */ - public function create(Request $request) + public function create(DatabaseHostFormRequest $request) { - $repo = new DatabaseRepository; - try { - $host = $repo->add($request->intersect([ - 'name', 'username', 'password', - 'host', 'port', 'node_id', - ])); - Alert::success('Successfully created new database host on the system.')->flash(); + $host = $this->service->create($request->normalize()); + $this->alert->success('Successfully created a new database host on the system.')->flash(); return redirect()->route('admin.databases.view', $host->id); } catch (\PDOException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.databases')->withErrors(json_decode($ex->getMessage())); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An error was encountered while trying to process this request. This error has been logged.')->flash(); + $this->alert->danger($ex->getMessage())->flash(); } return redirect()->route('admin.databases'); } /** - * Handle post request to update a database host. + * Handle updating database host. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Http\Requests\Admin\DatabaseHostFormRequest $request + * @param \Pterodactyl\Models\DatabaseHost $host * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function update(Request $request, $id) + public function update(DatabaseHostFormRequest $request, DatabaseHost $host) { - $repo = new DatabaseRepository; - - try { - if ($request->input('action') !== 'delete') { - $host = $repo->update($id, $request->intersect([ - 'name', 'username', 'password', - 'host', 'port', 'node_id', - ])); - Alert::success('Database host was updated successfully.')->flash(); - } else { - $repo->delete($id); - - return redirect()->route('admin.databases'); - } - } catch (\PDOException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.databases.view', $id)->withErrors(json_decode($ex->getMessage())); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An error was encountered while trying to process this request. This error has been logged.')->flash(); + if ($request->input('action') === 'delete') { + return $this->delete($host); } - return redirect()->route('admin.databases.view', $id); + try { + $host = $this->service->update($host->id, $request->normalize()); + $this->alert->success('Database host was updated successfully.')->flash(); + } catch (\PDOException $ex) { + $this->alert->danger($ex->getMessage())->flash(); + } + + return redirect()->route('admin.databases.view', $host->id); + } + + /** + * Handle request to delete a database host. + * + * @param \Pterodactyl\Models\DatabaseHost $host + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\DisplayException + */ + public function delete(DatabaseHost $host) + { + $this->service->delete($host->id); + $this->alert->success('The requested database host has been deleted from the system.')->flash(); + + return redirect()->route('admin.databases'); } } diff --git a/app/Http/Controllers/Admin/LocationController.php b/app/Http/Controllers/Admin/LocationController.php index 4e602f7ca..c7a78baf0 100644 --- a/app/Http/Controllers/Admin/LocationController.php +++ b/app/Http/Controllers/Admin/LocationController.php @@ -24,96 +24,133 @@ namespace Pterodactyl\Http\Controllers\Admin; -use Log; -use Alert; -use Illuminate\Http\Request; use Pterodactyl\Models\Location; +use Prologue\Alerts\AlertsMessageBag; +use Pterodactyl\Services\LocationService; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\LocationRepository; -use Pterodactyl\Exceptions\DisplayValidationException; +use Pterodactyl\Http\Requests\Admin\LocationFormRequest; +use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; class LocationController extends Controller { + /** + * @var \Prologue\Alerts\AlertsMessageBag + */ + protected $alert; + + /** + * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\\LocationService + */ + protected $service; + + /** + * LocationController constructor. + * + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Pterodactyl\Contracts\Repository\LocationRepositoryInterface $repository + * @param \Pterodactyl\Services\LocationService $service + */ + public function __construct( + AlertsMessageBag $alert, + LocationRepositoryInterface $repository, + LocationService $service + ) { + $this->alert = $alert; + $this->repository = $repository; + $this->service = $service; + } + /** * Return the location overview page. * - * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ - public function index(Request $request) + public function index() { return view('admin.locations.index', [ - 'locations' => Location::withCount('nodes', 'servers')->get(), + 'locations' => $this->repository->getAllWithDetails(), ]); } /** * Return the location view page. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param int $id * @return \Illuminate\View\View + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function view(Request $request, $id) + public function view($id) { - return view('admin.locations.view', ['location' => Location::with('nodes.servers')->findOrFail($id)]); + return view('admin.locations.view', [ + 'location' => $this->repository->getWithNodes($id), + ]); } /** * Handle request to create new location. * - * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Http\Requests\Admin\LocationFormRequest $request * @return \Illuminate\Http\RedirectResponse + * + * @throws \Throwable + * @throws \Watson\Validating\ValidationException */ - public function create(Request $request) + public function create(LocationFormRequest $request) { - $repo = new LocationRepository; + $location = $this->service->create($request->normalize()); + $this->alert->success('Location was created successfully.')->flash(); - try { - $location = $repo->create($request->intersect(['short', 'long'])); - Alert::success('Location was created successfully.')->flash(); - - return redirect()->route('admin.locations.view', $location->id); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.locations')->withErrors(json_decode($ex->getMessage())); - } catch (\Exception $ex) { - Log::error($ex); - Alert::error('An unhandled exception occurred while processing this request. This error has been logged.')->flash(); - } - - return redirect()->route('admin.locations'); + return redirect()->route('admin.locations.view', $location->id); } /** * Handle request to update or delete location. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Http\Requests\Admin\LocationFormRequest $request + * @param \Pterodactyl\Models\Location $location * @return \Illuminate\Http\RedirectResponse + * + * @throws \Throwable + * @throws \Watson\Validating\ValidationException */ - public function update(Request $request, $id) + public function update(LocationFormRequest $request, Location $location) { - $repo = new LocationRepository; - - try { - if ($request->input('action') !== 'delete') { - $location = $repo->update($id, $request->intersect(['short', 'long'])); - Alert::success('Location was updated successfully.')->flash(); - } else { - $repo->delete($id); - - return redirect()->route('admin.locations'); - } - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.locations.view', $id)->withErrors(json_decode($ex->getMessage())); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::error('An unhandled exception occurred while processing this request. This error has been logged.')->flash(); + if ($request->input('action') === 'delete') { + return $this->delete($location); } - return redirect()->route('admin.locations.view', $id); + $this->service->update($location->id, $request->normalize()); + $this->alert->success('Location was updated successfully.')->flash(); + + return redirect()->route('admin.locations.view', $location->id); + } + + /** + * Delete a location from the system. + * + * @param \Pterodactyl\Models\Location $location + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Exception + * @throws \Pterodactyl\Exceptions\DisplayException + */ + public function delete(Location $location) + { + try { + $this->service->delete($location->id); + + return redirect()->route('admin.locations'); + } catch (DisplayException $ex) { + $this->alert->danger($ex->getMessage())->flash(); + } + + return redirect()->route('admin.locations.view', $location->id); } } diff --git a/app/Http/Controllers/Admin/NodesController.php b/app/Http/Controllers/Admin/NodesController.php index ce02febac..0262f4e60 100644 --- a/app/Http/Controllers/Admin/NodesController.php +++ b/app/Http/Controllers/Admin/NodesController.php @@ -24,48 +24,137 @@ namespace Pterodactyl\Http\Controllers\Admin; -use DB; -use Log; -use Alert; -use Cache; use Javascript; -use Pterodactyl\Models; use Illuminate\Http\Request; -use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Models\Node; +use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\NodeRepository; -use Pterodactyl\Exceptions\DisplayValidationException; +use Pterodactyl\Services\Nodes\NodeUpdateService; +use Illuminate\Cache\Repository as CacheRepository; +use Pterodactyl\Services\Nodes\NodeCreationService; +use Pterodactyl\Services\Nodes\NodeDeletionService; +use Pterodactyl\Services\Allocations\AssignmentService; +use Pterodactyl\Services\Helpers\SoftwareVersionService; +use Pterodactyl\Http\Requests\Admin\Node\NodeFormRequest; +use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; +use Pterodactyl\Http\Requests\Admin\Node\AllocationFormRequest; +use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; +use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; +use Pterodactyl\Http\Requests\Admin\Node\AllocationAliasFormRequest; class NodesController extends Controller { + /** + * @var \Prologue\Alerts\AlertsMessageBag + */ + protected $alert; + + /** + * @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface + */ + protected $allocationRepository; + + /** + * @var \Pterodactyl\Services\Allocations\AssignmentService + */ + protected $assignmentService; + + /** + * @var \Illuminate\Cache\Repository + */ + protected $cache; + + /** + * @var \Pterodactyl\Services\Nodes\NodeCreationService + */ + protected $creationService; + + /** + * @var \Pterodactyl\Services\Nodes\NodeDeletionService + */ + protected $deletionService; + + /** + * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface + */ + protected $locationRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Nodes\NodeUpdateService + */ + protected $updateService; + + /** + * @var \Pterodactyl\Services\Helpers\SoftwareVersionService + */ + protected $versionService; + + /** + * NodesController constructor. + * + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $allocationRepository + * @param \Pterodactyl\Services\Allocations\AssignmentService $assignmentService + * @param \Illuminate\Cache\Repository $cache + * @param \Pterodactyl\Services\Nodes\NodeCreationService $creationService + * @param \Pterodactyl\Services\Nodes\NodeDeletionService $deletionService + * @param \Pterodactyl\Contracts\Repository\LocationRepositoryInterface $locationRepository + * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $repository + * @param \Pterodactyl\Services\Nodes\NodeUpdateService $updateService + * @param \Pterodactyl\Services\Helpers\SoftwareVersionService $versionService + */ + public function __construct( + AlertsMessageBag $alert, + AllocationRepositoryInterface $allocationRepository, + AssignmentService $assignmentService, + CacheRepository $cache, + NodeCreationService $creationService, + NodeDeletionService $deletionService, + LocationRepositoryInterface $locationRepository, + NodeRepositoryInterface $repository, + NodeUpdateService $updateService, + SoftwareVersionService $versionService + ) { + $this->alert = $alert; + $this->allocationRepository = $allocationRepository; + $this->assignmentService = $assignmentService; + $this->cache = $cache; + $this->creationService = $creationService; + $this->deletionService = $deletionService; + $this->locationRepository = $locationRepository; + $this->repository = $repository; + $this->updateService = $updateService; + $this->versionService = $versionService; + } + /** * Displays the index page listing all nodes on the panel. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ public function index(Request $request) { - $nodes = Models\Node::with('location')->withCount('servers'); - - if (! is_null($request->input('query'))) { - $nodes->search($request->input('query')); - } - - return view('admin.nodes.index', ['nodes' => $nodes->paginate(25)]); + return view('admin.nodes.index', [ + 'nodes' => $this->repository->search($request->input('query'))->getNodeListingData(), + ]); } /** * Displays create new node page. * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\View\View|\Illuminate\Http\RedirectResponse + * @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View */ - public function create(Request $request) + public function create() { - $locations = Models\Location::all(); - if (! $locations->count()) { - Alert::warning('You must add a location before you can add a new node.')->flash(); + $locations = $this->locationRepository->all(); + if (count($locations) < 1) { + $this->alert->warning(trans('admin/node.notices.location_required'))->flash(); return redirect()->route('admin.locations'); } @@ -76,117 +165,73 @@ class NodesController extends Controller /** * Post controller to create a new node on the system. * - * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Http\Requests\Admin\Node\NodeFormRequest $request * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function store(Request $request) + public function store(NodeFormRequest $request) { - try { - $repo = new NodeRepository; - $node = $repo->create(array_merge( - $request->only([ - 'public', 'disk_overallocate', - 'memory_overallocate', 'behind_proxy', - ]), - $request->intersect([ - 'name', 'location_id', 'fqdn', - 'scheme', 'memory', 'disk', - 'daemonBase', 'daemonSFTP', 'daemonListen', - ]) - )); - Alert::success('Successfully created new node that can be configured automatically on your remote machine by visiting the configuration tab. Before you can add any servers you need to first assign some IP addresses and ports by adding an allocation.')->flash(); + $node = $this->creationService->handle($request->normalize()); + $this->alert->info(trans('admin/node.notices.node_created'))->flash(); - return redirect()->route('admin.nodes.view.allocation', $node->id); - } catch (DisplayValidationException $e) { - return redirect()->route('admin.nodes.new')->withErrors(json_decode($e->getMessage()))->withInput(); - } catch (DisplayException $e) { - Alert::danger($e->getMessage())->flash(); - } catch (\Exception $e) { - Log::error($e); - Alert::danger('An unhandled exception occured while attempting to add this node. Please try again.')->flash(); - } - - return redirect()->route('admin.nodes.new')->withInput(); + return redirect()->route('admin.nodes.view.allocation', $node->id); } /** * Shows the index overview page for a specific node. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param int $node * @return \Illuminate\View\View + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function viewIndex(Request $request, $id) + public function viewIndex($node) { - $node = Models\Node::with('location')->withCount('servers')->findOrFail($id); - $stats = collect( - Models\Server::select( - DB::raw('SUM(memory) as memory, SUM(disk) as disk') - )->where('node_id', $node->id)->first() - )->mapWithKeys(function ($item, $key) use ($node) { - if ($node->{$key . '_overallocate'} > 0) { - $withover = $node->{$key} * (1 + ($node->{$key . '_overallocate'} / 100)); - } else { - $withover = $node->{$key}; - } - - $percent = ($item / $withover) * 100; - - return [$key => [ - 'value' => number_format($item), - 'max' => number_format($withover), - 'percent' => $percent, - 'css' => ($percent <= 75) ? 'green' : (($percent > 90) ? 'red' : 'yellow'), - ]]; - })->toArray(); - - return view('admin.nodes.view.index', ['node' => $node, 'stats' => $stats]); + return view('admin.nodes.view.index', [ + 'node' => $this->repository->getSingleNode($node), + 'stats' => $this->repository->getUsageStats($node), + 'version' => $this->versionService, + ]); } /** * Shows the settings page for a specific node. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Models\Node $node * @return \Illuminate\View\View */ - public function viewSettings(Request $request, $id) + public function viewSettings(Node $node) { return view('admin.nodes.view.settings', [ - 'node' => Models\Node::findOrFail($id), - 'locations' => Models\Location::all(), + 'node' => $node, + 'locations' => $this->locationRepository->all(), ]); } /** * Shows the configuration page for a specific node. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Models\Node $node * @return \Illuminate\View\View */ - public function viewConfiguration(Request $request, $id) + public function viewConfiguration(Node $node) { - return view('admin.nodes.view.configuration', [ - 'node' => Models\Node::findOrFail($id), - ]); + return view('admin.nodes.view.configuration', ['node' => $node]); } /** * Shows the allocation page for a specific node. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param int $node * @return \Illuminate\View\View + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function viewAllocation(Request $request, $id) + public function viewAllocation($node) { - $node = Models\Node::findOrFail($id); - $node->setRelation('allocations', $node->allocations()->orderBy('ip', 'asc')->orderBy('port', 'asc')->with('server')->paginate(50)); - - Javascript::put([ - 'node' => collect($node)->only(['id']), - ]); + $node = $this->repository->getNodeAllocations($node); + Javascript::put(['node' => collect($node)->only(['id'])]); return view('admin.nodes.view.allocation', ['node' => $node]); } @@ -194,74 +239,53 @@ class NodesController extends Controller /** * Shows the server listing page for a specific node. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param int $node * @return \Illuminate\View\View + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function viewServers(Request $request, $id) + public function viewServers($node) { - $node = Models\Node::with('servers.user', 'servers.service', 'servers.option')->findOrFail($id); + $node = $this->repository->getNodeServers($node); Javascript::put([ 'node' => collect($node->makeVisible('daemonSecret'))->only(['scheme', 'fqdn', 'daemonListen', 'daemonSecret']), ]); - return view('admin.nodes.view.servers', [ - 'node' => $node, - ]); + return view('admin.nodes.view.servers', ['node' => $node]); } /** * Updates settings for a node. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Http\Requests\Admin\Node\NodeFormRequest $request + * @param \Pterodactyl\Models\Node $node * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function updateSettings(Request $request, $id) + public function updateSettings(NodeFormRequest $request, Node $node) { - $repo = new NodeRepository; + $this->updateService->handle($node, $request->normalize()); + $this->alert->success(trans('admin/node.notices.node_updated'))->flash(); - try { - $node = $repo->update($id, array_merge( - $request->only([ - 'public', 'disk_overallocate', - 'memory_overallocate', 'behind_proxy', - ]), - $request->intersect([ - 'name', 'location_id', 'fqdn', - 'scheme', 'memory', 'disk', 'upload_size', - 'reset_secret', 'daemonSFTP', 'daemonListen', - ]) - )); - Alert::success('Successfully updated this node\'s information. If you changed any daemon settings you will need to restart it now.')->flash(); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.nodes.view.settings', $id)->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attempting to edit this node. Please try again.')->flash(); - } - - return redirect()->route('admin.nodes.view.settings', $id)->withInput(); + return redirect()->route('admin.nodes.view.settings', $node->id)->withInput(); } /** * Removes a single allocation from a node. * - * @param \Illuminate\Http\Request $request - * @param int $node - * @param int $allocation + * @param int $node + * @param int $allocation * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse */ - public function allocationRemoveSingle(Request $request, $node, $allocation) + public function allocationRemoveSingle($node, $allocation) { - $query = Models\Allocation::where('node_id', $node)->whereNull('server_id')->where('id', $allocation)->delete(); - if ($query < 1) { - return response()->json([ - 'error' => 'Unable to find an allocation matching those details to delete.', - ], 400); - } + $this->allocationRepository->deleteWhere([ + ['id', '=', $allocation], + ['node_id', '=', $node], + ['server_id', '=', null], + ]); return response('', 204); } @@ -269,18 +293,20 @@ class NodesController extends Controller /** * Remove all allocations for a specific IP at once on a node. * - * @param \Illuminate\Http\Request $request - * @param int $node + * @param \Illuminate\Http\Request $request + * @param int $node * @return \Illuminate\Http\RedirectResponse */ public function allocationRemoveBlock(Request $request, $node) { - $query = Models\Allocation::where('node_id', $node)->whereNull('server_id')->where('ip', $request->input('ip'))->delete(); - if ($query < 1) { - Alert::danger('There was an error while attempting to delete allocations on that IP.')->flash(); - } else { - Alert::success('Deleted all unallocated ports for ' . $request->input('ip') . '.')->flash(); - } + $this->allocationRepository->deleteWhere([ + ['node_id', '=', $node], + ['server_id', '=', null], + ['ip', '=', $request->input('ip')], + ]); + + $this->alert->success(trans('admin/node.notices.unallocated_deleted', ['ip' => $request->input('ip')])) + ->flash(); return redirect()->route('admin.nodes.view.allocation', $node); } @@ -288,92 +314,64 @@ class NodesController extends Controller /** * Sets an alias for a specific allocation on a node. * - * @param \Illuminate\Http\Request $request - * @param int $node - * @return \Illuminate\Http\Response + * @param \Pterodactyl\Http\Requests\Admin\Node\AllocationAliasFormRequest $request + * @return \Symfony\Component\HttpFoundation\Response + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function allocationSetAlias(Request $request, $node) + public function allocationSetAlias(AllocationAliasFormRequest $request) { - if (! $request->input('allocation_id')) { - return response('Missing required parameters.', 422); - } + $this->allocationRepository->update($request->input('allocation_id'), [ + 'ip_alias' => (empty($request->input('alias'))) ? null : $request->input('alias'), + ]); - try { - $update = Models\Allocation::findOrFail($request->input('allocation_id')); - $update->ip_alias = (empty($request->input('alias'))) ? null : $request->input('alias'); - $update->save(); - - return response('', 204); - } catch (\Exception $ex) { - throw $ex; - } + return response('', 204); } /** * Creates new allocations on a node. * - * @param \Illuminate\Http\Request $request - * @param int $node + * @param \Pterodactyl\Http\Requests\Admin\Node\AllocationFormRequest $request + * @param int|\Pterodactyl\Models\Node $node * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\DisplayException */ - public function createAllocation(Request $request, $node) + public function createAllocation(AllocationFormRequest $request, Node $node) { - $repo = new NodeRepository; + $this->assignmentService->handle($node, $request->normalize()); + $this->alert->success(trans('admin/node.notices.allocations_added'))->flash(); - try { - $repo->addAllocations($node, $request->intersect(['allocation_ip', 'allocation_alias', 'allocation_ports'])); - Alert::success('Successfully added new allocations!')->flash(); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.nodes.view.allocation', $node)->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attempting to add allocations this node. This error has been logged.')->flash(); - } - - return redirect()->route('admin.nodes.view.allocation', $node); + return redirect()->route('admin.nodes.view.allocation', $node->id); } /** * Deletes a node from the system. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param $node * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\DisplayException */ - public function delete(Request $request, $id) + public function delete($node) { - $repo = new NodeRepository; + $this->deletionService->handle($node); + $this->alert->success(trans('admin/node.notices.node_deleted'))->flash(); - try { - $repo->delete($id); - Alert::success('Successfully deleted the requested node from the panel.')->flash(); - - return redirect()->route('admin.nodes'); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attempting to delete this node. Please try again.')->flash(); - } - - return redirect()->route('admin.nodes.view', $id); + return redirect()->route('admin.nodes'); } /** * Returns the configuration token to auto-deploy a node. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Models\Node $node * @return \Illuminate\Http\JsonResponse */ - public function setToken(Request $request, $id) + public function setToken(Node $node) { - $node = Models\Node::findOrFail($id); - - $token = str_random(32); - Cache::tags(['Node:Configuration'])->put($token, $node->id, 5); + $token = bin2hex(random_bytes(16)); + $this->cache->tags(['Node:Configuration'])->put($token, $node->id, 5); return response()->json(['token' => $token]); } diff --git a/app/Http/Controllers/Admin/OptionController.php b/app/Http/Controllers/Admin/OptionController.php index 41c4544a1..22b3ea4ce 100644 --- a/app/Http/Controllers/Admin/OptionController.php +++ b/app/Http/Controllers/Admin/OptionController.php @@ -24,136 +24,171 @@ namespace Pterodactyl\Http\Controllers\Admin; -use Log; -use Alert; use Javascript; use Illuminate\Http\Request; -use Pterodactyl\Models\Service; +use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Models\ServiceOption; -use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\OptionRepository; -use Pterodactyl\Repositories\VariableRepository; -use Pterodactyl\Exceptions\DisplayValidationException; +use Pterodactyl\Http\Requests\Admin\Service\EditOptionScript; +use Pterodactyl\Services\Services\Options\OptionUpdateService; +use Pterodactyl\Contracts\Repository\ServiceRepositoryInterface; +use Pterodactyl\Services\Services\Options\OptionCreationService; +use Pterodactyl\Services\Services\Options\OptionDeletionService; +use Pterodactyl\Http\Requests\Admin\Service\ServiceOptionFormRequest; +use Pterodactyl\Services\Services\Options\InstallScriptUpdateService; +use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface; +use Pterodactyl\Exceptions\Service\ServiceOption\InvalidCopyFromException; +use Pterodactyl\Exceptions\Service\ServiceOption\NoParentConfigurationFoundException; class OptionController extends Controller { + /** + * @var \Prologue\Alerts\AlertsMessageBag + */ + protected $alert; + + /** + * @var \Pterodactyl\Services\Services\Options\InstallScriptUpdateService + */ + protected $installScriptUpdateService; + + /** + * @var \Pterodactyl\Services\Services\Options\OptionCreationService + */ + protected $optionCreationService; + + /** + * @var \Pterodactyl\Services\Services\Options\OptionDeletionService + */ + protected $optionDeletionService; + + /** + * @var \Pterodactyl\Services\Services\Options\OptionUpdateService + */ + protected $optionUpdateService; + + /** + * @var \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface + */ + protected $serviceRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface + */ + protected $serviceOptionRepository; + + /** + * OptionController constructor. + * + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Pterodactyl\Services\Services\Options\InstallScriptUpdateService $installScriptUpdateService + * @param \Pterodactyl\Services\Services\Options\OptionCreationService $optionCreationService + * @param \Pterodactyl\Services\Services\Options\OptionDeletionService $optionDeletionService + * @param \Pterodactyl\Services\Services\Options\OptionUpdateService $optionUpdateService + * @param \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface $serviceRepository + * @param \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface $serviceOptionRepository + */ + public function __construct( + AlertsMessageBag $alert, + InstallScriptUpdateService $installScriptUpdateService, + OptionCreationService $optionCreationService, + OptionDeletionService $optionDeletionService, + OptionUpdateService $optionUpdateService, + ServiceRepositoryInterface $serviceRepository, + ServiceOptionRepositoryInterface $serviceOptionRepository + ) { + $this->alert = $alert; + $this->installScriptUpdateService = $installScriptUpdateService; + $this->optionCreationService = $optionCreationService; + $this->optionDeletionService = $optionDeletionService; + $this->optionUpdateService = $optionUpdateService; + $this->serviceRepository = $serviceRepository; + $this->serviceOptionRepository = $serviceOptionRepository; + } + /** * Handles request to view page for adding new option. * - * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ - public function create(Request $request) + public function create() { - $services = Service::with('options')->get(); + $services = $this->serviceRepository->getWithOptions(); Javascript::put(['services' => $services->keyBy('id')]); return view('admin.services.options.new', ['services' => $services]); } /** - * Handles POST request to create a new option. + * Handle adding a new service option. * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Response\RedirectResponse + * @param \Pterodactyl\Http\Requests\Admin\Service\ServiceOptionFormRequest $request + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function store(Request $request) + public function store(ServiceOptionFormRequest $request) { - $repo = new OptionRepository; - try { - $option = $repo->create($request->intersect([ - 'service_id', 'name', 'description', 'tag', - 'docker_image', 'startup', 'config_from', 'config_startup', - 'config_logs', 'config_files', 'config_stop', - ])); - Alert::success('Successfully created new service option.')->flash(); + $option = $this->optionCreationService->handle($request->normalize()); + $this->alert->success(trans('admin/services.options.notices.option_created'))->flash(); + } catch (NoParentConfigurationFoundException $exception) { + $this->alert->danger($exception->getMessage())->flash(); - return redirect()->route('admin.services.option.view', $option->id); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.services.option.new')->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occurred while attempting to create this service. This error has been logged.')->flash(); + return redirect()->back()->withInput(); } - return redirect()->route('admin.services.option.new')->withInput(); + return redirect()->route('admin.services.option.view', $option->id); } /** - * Handles POST request to create a new option variable. + * Delete a given option from the database. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Models\ServiceOption $option * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException */ - public function createVariable(Request $request, $id) + public function destroy(ServiceOption $option) { - $repo = new VariableRepository; + $this->optionDeletionService->handle($option->id); + $this->alert->success(trans('admin/services.options.notices.option_deleted'))->flash(); - try { - $variable = $repo->create($id, $request->intersect([ - 'name', 'description', 'env_variable', - 'default_value', 'options', 'rules', - ])); - - Alert::success('New variable successfully assigned to this service option.')->flash(); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.services.option.variables', $id)->withErrors(json_decode($ex->getMessage())); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception was encountered while attempting to process that request. This error has been logged.')->flash(); - } - - return redirect()->route('admin.services.option.variables', $id); + return redirect()->route('admin.services.view', $option->service_id); } /** * Display option overview page. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Models\ServiceOption $option * @return \Illuminate\View\View */ - public function viewConfiguration(Request $request, $id) + public function viewConfiguration(ServiceOption $option) { - return view('admin.services.options.view', ['option' => ServiceOption::findOrFail($id)]); - } - - /** - * Display variable overview page for a service option. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\View\View - */ - public function viewVariables(Request $request, $id) - { - return view('admin.services.options.variables', ['option' => ServiceOption::with('variables')->findOrFail($id)]); + return view('admin.services.options.view', ['option' => $option]); } /** * Display script management page for an option. * - * @param Request $request - * @param int $id + * @param int $option * @return \Illuminate\View\View + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function viewScripts(Request $request, $id) + public function viewScripts($option) { - $option = ServiceOption::with('copyFrom')->findOrFail($id); + $option = $this->serviceOptionRepository->getWithCopyFrom($option); + $copyOptions = $this->serviceOptionRepository->findWhere([ + ['copy_script_from', '=', null], + ['service_id', '=', $option->service_id], + ['id', '!=', $option], + ]); + $relyScript = $this->serviceOptionRepository->findWhere([['copy_script_from', '=', $option]]); return view('admin.services.options.scripts', [ - 'copyFromOptions' => ServiceOption::whereNull('copy_script_from')->where([ - ['service_id', $option->service_id], - ['id', '!=', $option->id], - ])->get(), - 'relyOnScript' => ServiceOption::where('copy_script_from', $option->id)->get(), + 'copyFromOptions' => $copyOptions, + 'relyOnScript' => $relyScript, 'option' => $option, ]); } @@ -161,101 +196,45 @@ class OptionController extends Controller /** * Handles POST when editing a configration for a service option. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\ServiceOption $option * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function editConfiguration(Request $request, $id) + public function editConfiguration(Request $request, ServiceOption $option) { - $repo = new OptionRepository; - try { - if ($request->input('action') !== 'delete') { - $repo->update($id, $request->intersect([ - 'name', 'description', 'tag', 'docker_image', 'startup', - 'config_from', 'config_stop', 'config_logs', 'config_files', 'config_startup', - ])); - Alert::success('Service option configuration has been successfully updated.')->flash(); - } else { - $option = ServiceOption::with('service')->where('id', $id)->first(); - $repo->delete($id); - Alert::success('Successfully deleted service option from the system.')->flash(); - - return redirect()->route('admin.services.view', $option->service_id); - } - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.services.option.view', $id)->withErrors(json_decode($ex->getMessage())); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occurred while attempting to perform that action. This error has been logged.')->flash(); + $this->optionUpdateService->handle($option, $request->all()); + $this->alert->success(trans('admin/services.options.notices.option_updated'))->flash(); + } catch (NoParentConfigurationFoundException $exception) { + dd('hodor'); + $this->alert->danger($exception->getMessage())->flash(); } - return redirect()->route('admin.services.option.view', $id); + return redirect()->route('admin.services.option.view', $option->id); } /** - * Handles POST when editing a configration for a service option. + * Handles POST when updating script for a service option. * - * @param \Illuminate\Http\Request $request - * @param int $option - * @param int $variable + * @param \Pterodactyl\Http\Requests\Admin\Service\EditOptionScript $request + * @param \Pterodactyl\Models\ServiceOption $option * @return \Illuminate\Http\RedirectResponse - */ - public function editVariable(Request $request, $option, $variable) - { - $repo = new VariableRepository; - - try { - if ($request->input('action') !== 'delete') { - $variable = $repo->update($variable, $request->only([ - 'name', 'description', 'env_variable', - 'default_value', 'options', 'rules', - ])); - Alert::success("The service variable '{$variable->name}' has been updated.")->flash(); - } else { - $repo->delete($variable); - Alert::success('That service variable has been deleted.')->flash(); - } - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.services.option.variables', $option)->withErrors(json_decode($ex->getMessage())); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception was encountered while attempting to process that request. This error has been logged.')->flash(); - } - - return redirect()->route('admin.services.option.variables', $option); - } - - /** - * Handles POST when updating scripts for a service option. * - * @param Request $request - * @param int $id - * @return \Illuminate\Response\RedirectResponse + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function updateScripts(Request $request, $id) + public function updateScripts(EditOptionScript $request, ServiceOption $option) { - $repo = new OptionRepository; - try { - $repo->scripts($id, $request->only([ - 'script_install', 'script_entry', - 'script_container', 'copy_script_from', - ])); - Alert::success('Successfully updated option scripts to be run when servers are installed.')->flash(); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.services.option.scripts', $id)->withErrors(json_decode($ex->getMessage())); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception was encountered while attempting to process that request. This error has been logged.')->flash(); + $this->installScriptUpdateService->handle($option, $request->normalize()); + $this->alert->success(trans('admin/services.options.notices.script_updated'))->flash(); + } catch (InvalidCopyFromException $exception) { + $this->alert->danger($exception->getMessage())->flash(); } - return redirect()->route('admin.services.option.scripts', $id); + return redirect()->route('admin.services.option.scripts', $option->id); } } diff --git a/app/Http/Controllers/Admin/PackController.php b/app/Http/Controllers/Admin/PackController.php index d02273672..ae06aff0b 100644 --- a/app/Http/Controllers/Admin/PackController.php +++ b/app/Http/Controllers/Admin/PackController.php @@ -24,191 +24,239 @@ namespace Pterodactyl\Http\Controllers\Admin; -use Log; -use Alert; -use Storage; use Illuminate\Http\Request; use Pterodactyl\Models\Pack; -use Pterodactyl\Models\Service; -use Pterodactyl\Exceptions\DisplayException; +use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\PackRepository; -use Pterodactyl\Exceptions\DisplayValidationException; +use Pterodactyl\Services\Packs\ExportPackService; +use Pterodactyl\Services\Packs\PackUpdateService; +use Pterodactyl\Services\Packs\PackCreationService; +use Pterodactyl\Services\Packs\PackDeletionService; +use Pterodactyl\Http\Requests\Admin\PackFormRequest; +use Pterodactyl\Services\Packs\TemplateUploadService; +use Pterodactyl\Contracts\Repository\PackRepositoryInterface; +use Illuminate\Contracts\Config\Repository as ConfigRepository; +use Pterodactyl\Contracts\Repository\ServiceRepositoryInterface; class PackController extends Controller { + /** + * @var \Prologue\Alerts\AlertsMessageBag + */ + protected $alert; + + /** + * @var \Illuminate\Contracts\Config\Repository + */ + protected $config; + + /** + * @var \Pterodactyl\Services\Packs\PackCreationService + */ + protected $creationService; + + /** + * @var \Pterodactyl\Services\Packs\PackDeletionService + */ + protected $deletionService; + + /** + * @var \Pterodactyl\Services\Packs\ExportPackService + */ + protected $exportService; + + /** + * @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Packs\PackUpdateService + */ + protected $updateService; + + /** + * @var \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface + */ + protected $serviceRepository; + + /** + * @var \Pterodactyl\Services\Packs\TemplateUploadService + */ + protected $templateUploadService; + + /** + * PackController constructor. + * + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Illuminate\Contracts\Config\Repository $config + * @param \Pterodactyl\Services\Packs\ExportPackService $exportService + * @param \Pterodactyl\Services\Packs\PackCreationService $creationService + * @param \Pterodactyl\Services\Packs\PackDeletionService $deletionService + * @param \Pterodactyl\Contracts\Repository\PackRepositoryInterface $repository + * @param \Pterodactyl\Services\Packs\PackUpdateService $updateService + * @param \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface $serviceRepository + * @param \Pterodactyl\Services\Packs\TemplateUploadService $templateUploadService + */ + public function __construct( + AlertsMessageBag $alert, + ConfigRepository $config, + ExportPackService $exportService, + PackCreationService $creationService, + PackDeletionService $deletionService, + PackRepositoryInterface $repository, + PackUpdateService $updateService, + ServiceRepositoryInterface $serviceRepository, + TemplateUploadService $templateUploadService + ) { + $this->alert = $alert; + $this->config = $config; + $this->creationService = $creationService; + $this->deletionService = $deletionService; + $this->exportService = $exportService; + $this->repository = $repository; + $this->updateService = $updateService; + $this->serviceRepository = $serviceRepository; + $this->templateUploadService = $templateUploadService; + } + /** * Display listing of all packs on the system. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ public function index(Request $request) { - $packs = Pack::with('option')->withCount('servers'); - - if (! is_null($request->input('query'))) { - $packs->search($request->input('query')); - } - - return view('admin.packs.index', ['packs' => $packs->paginate(50)]); + return view('admin.packs.index', [ + 'packs' => $this->repository->search($request->input('query'))->paginateWithOptionAndServerCount( + $this->config->get('pterodactyl.paginate.admin.packs') + ), + ]); } /** * Display new pack creation form. * - * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ - public function create(Request $request) + public function create() { return view('admin.packs.new', [ - 'services' => Service::with('options')->get(), + 'services' => $this->serviceRepository->getWithOptions(), ]); } /** * Display new pack creation modal for use with template upload. * - * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ - public function newTemplate(Request $request) + public function newTemplate() { return view('admin.packs.modal', [ - 'services' => Service::with('options')->get(), + 'services' => $this->serviceRepository->getWithOptions(), ]); } /** * Handle create pack request and route user to location. * - * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Http\Requests\Admin\PackFormRequest $request * @return \Illuminate\View\View + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException + * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileUploadException + * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException + * @throws \Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException + * @throws \Pterodactyl\Exceptions\Service\Pack\ZipExtractionException */ - public function store(Request $request) + public function store(PackFormRequest $request) { - $repo = new PackRepository; - - try { - if ($request->input('action') === 'from_template') { - $pack = $repo->createWithTemplate($request->intersect(['option_id', 'file_upload'])); - } else { - $pack = $repo->create($request->intersect([ - 'name', 'description', 'version', 'option_id', - 'selectable', 'visible', 'locked', 'file_upload', - ])); - } - Alert::success('Pack successfully created on the system.')->flash(); - - return redirect()->route('admin.packs.view', $pack->id); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.packs.new')->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An error occured while attempting to add a new service pack. This error has been logged.')->flash(); + if ($request->has('from_template')) { + $pack = $this->templateUploadService->handle($request->input('option_id'), $request->file('file_upload')); + } else { + $pack = $this->creationService->handle($request->normalize(), $request->file('file_upload')); } - return redirect()->route('admin.packs.new')->withInput(); + $this->alert->success(trans('admin/pack.notices.pack_created'))->flash(); + + return redirect()->route('admin.packs.view', $pack->id); } /** * Display pack view template to user. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param int $pack * @return \Illuminate\View\View */ - public function view(Request $request, $id) + public function view($pack) { return view('admin.packs.view', [ - 'pack' => Pack::with('servers.node', 'servers.user')->findOrFail($id), - 'services' => Service::with('options')->get(), + 'pack' => $this->repository->getWithServers($pack), + 'services' => $this->serviceRepository->getWithOptions(), ]); } /** * Handle updating or deleting pack information. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Http\Requests\Admin\PackFormRequest $request + * @param \Pterodactyl\Models\Pack $pack * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException */ - public function update(Request $request, $id) + public function update(PackFormRequest $request, Pack $pack) { - $repo = new PackRepository; + $this->updateService->handle($pack, $request->normalize()); + $this->alert->success(trans('admin/pack.notices.pack_updated'))->flash(); - try { - if ($request->input('action') !== 'delete') { - $pack = $repo->update($id, $request->intersect([ - 'name', 'description', 'version', - 'option_id', 'selectable', 'visible', 'locked', - ])); - Alert::success('Pack successfully updated.')->flash(); - } else { - $repo->delete($id); - Alert::success('Pack was successfully deleted from the system.')->flash(); + return redirect()->route('admin.packs.view', $pack->id); + } - return redirect()->route('admin.packs'); - } - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.packs.view', $id)->withErrors(json_decode($ex->getMessage())); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An error occured while attempting to edit this service pack. This error has been logged.')->flash(); - } + /** + * Delete a pack if no servers are attached to it currently. + * + * @param \Pterodactyl\Models\Pack $pack + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException + */ + public function destroy(Pack $pack) + { + $this->deletionService->handle($pack->id); + $this->alert->success(trans('admin/pack.notices.pack_deleted', [ + 'name' => $pack->name, + ]))->flash(); - return redirect()->route('admin.packs.view', $id); + return redirect()->route('admin.packs'); } /** * Creates an archive of the pack and downloads it to the browser. * - * @param \Illuminate\Http\Request $request - * @param int $id - * @param bool $files - * @return \Symfony\Component\HttpFoundation\BinaryFileResponse + * @param \Pterodactyl\Models\Pack $pack + * @param bool|string $files + * @return \Symfony\Component\HttpFoundation\Response + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Pack\ZipArchiveCreationException */ - public function export(Request $request, $id, $files = false) + public function export(Pack $pack, $files = false) { - $pack = Pack::findOrFail($id); - $json = [ - 'name' => $pack->name, - 'version' => $pack->version, - 'description' => $pack->description, - 'selectable' => $pack->selectable, - 'visible' => $pack->visible, - 'locked' => $pack->locked, - ]; - - $filename = tempnam(sys_get_temp_dir(), 'pterodactyl_'); - if ($files === 'with-files') { - $zip = new \ZipArchive; - if (! $zip->open($filename, \ZipArchive::CREATE)) { - abort(503, 'Unable to open file for writing.'); - } - - $files = Storage::files('packs/' . $pack->uuid); - foreach ($files as $file) { - $zip->addFile(storage_path('app/' . $file), basename(storage_path('app/' . $file))); - } - - $zip->addFromString('import.json', json_encode($json, JSON_PRETTY_PRINT)); - $zip->close(); + $filename = $this->exportService->handle($pack, is_string($files)); + if (is_string($files)) { return response()->download($filename, 'pack-' . $pack->name . '.zip')->deleteFileAfterSend(true); - } else { - $fp = fopen($filename, 'a+'); - fwrite($fp, json_encode($json, JSON_PRETTY_PRINT)); - fclose($fp); - - return response()->download($filename, 'pack-' . $pack->name . '.json', [ - 'Content-Type' => 'application/json', - ])->deleteFileAfterSend(true); } + + return response()->download($filename, 'pack-' . $pack->name . '.json', [ + 'Content-Type' => 'application/json', + ])->deleteFileAfterSend(true); } } diff --git a/app/Http/Controllers/Admin/ServersController.php b/app/Http/Controllers/Admin/ServersController.php index 76715c84c..db84291be 100644 --- a/app/Http/Controllers/Admin/ServersController.php +++ b/app/Http/Controllers/Admin/ServersController.php @@ -24,49 +24,210 @@ namespace Pterodactyl\Http\Controllers\Admin; -use Log; -use Alert; use Javascript; -use Pterodactyl\Models; use Illuminate\Http\Request; -use GuzzleHttp\Exception\TransferException; +use Pterodactyl\Models\Server; +use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\ServerRepository; -use Pterodactyl\Repositories\DatabaseRepository; -use Pterodactyl\Exceptions\AutoDeploymentException; -use Pterodactyl\Exceptions\DisplayValidationException; +use Pterodactyl\Services\Servers\SuspensionService; +use Pterodactyl\Http\Requests\Admin\ServerFormRequest; +use Pterodactyl\Services\Servers\ServerCreationService; +use Pterodactyl\Services\Servers\ServerDeletionService; +use Pterodactyl\Services\Servers\ReinstallServerService; +use Pterodactyl\Services\Servers\ContainerRebuildService; +use Pterodactyl\Services\Servers\BuildModificationService; +use Pterodactyl\Services\Database\DatabaseManagementService; +use Pterodactyl\Services\Servers\DetailsModificationService; +use Pterodactyl\Services\Servers\StartupModificationService; +use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; +use Pterodactyl\Repositories\Eloquent\DatabaseHostRepository; +use Illuminate\Contracts\Config\Repository as ConfigRepository; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Contracts\Repository\ServiceRepositoryInterface; +use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; +use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; +use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; class ServersController extends Controller { + /** + * @var \Prologue\Alerts\AlertsMessageBag + */ + protected $alert; + + /** + * @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface + */ + protected $allocationRepository; + + /** + * @var \Pterodactyl\Services\Servers\BuildModificationService + */ + protected $buildModificationService; + + /** + * @var \Illuminate\Contracts\Config\Repository + */ + protected $config; + + /** + * @var \Pterodactyl\Services\Servers\ContainerRebuildService + */ + protected $containerRebuildService; + + /** + * @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface + */ + protected $databaseRepository; + + /** + * @var \Pterodactyl\Services\Database\DatabaseManagementService + */ + protected $databaseManagementService; + + /** + * @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface + */ + protected $databaseHostRepository; + + /** + * @var \Pterodactyl\Services\Servers\ServerDeletionService + */ + protected $deletionService; + + /** + * @var \Pterodactyl\Services\Servers\DetailsModificationService + */ + protected $detailsModificationService; + + /** + * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface + */ + protected $locationRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface + */ + protected $nodeRepository; + + /** + * @var \Pterodactyl\Services\Servers\ReinstallServerService + */ + protected $reinstallService; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Servers\ServerCreationService + */ + protected $service; + + /** + * @var \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface + */ + protected $serviceRepository; + + /** + * @var \Pterodactyl\Services\Servers\StartupModificationService + */ + private $startupModificationService; + + /** + * @var \Pterodactyl\Services\Servers\SuspensionService + */ + protected $suspensionService; + + /** + * ServersController constructor. + * + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $allocationRepository + * @param \Pterodactyl\Services\Servers\BuildModificationService $buildModificationService + * @param \Illuminate\Contracts\Config\Repository $config + * @param \Pterodactyl\Services\Servers\ContainerRebuildService $containerRebuildService + * @param \Pterodactyl\Services\Servers\ServerCreationService $service + * @param \Pterodactyl\Services\Database\DatabaseManagementService $databaseManagementService + * @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $databaseRepository + * @param \Pterodactyl\Repositories\Eloquent\DatabaseHostRepository $databaseHostRepository + * @param \Pterodactyl\Services\Servers\ServerDeletionService $deletionService + * @param \Pterodactyl\Services\Servers\DetailsModificationService $detailsModificationService + * @param \Pterodactyl\Contracts\Repository\LocationRepositoryInterface $locationRepository + * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $nodeRepository + * @param \Pterodactyl\Services\Servers\ReinstallServerService $reinstallService + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository + * @param \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface $serviceRepository + * @param \Pterodactyl\Services\Servers\StartupModificationService $startupModificationService + * @param \Pterodactyl\Services\Servers\SuspensionService $suspensionService + */ + public function __construct( + AlertsMessageBag $alert, + AllocationRepositoryInterface $allocationRepository, + BuildModificationService $buildModificationService, + ConfigRepository $config, + ContainerRebuildService $containerRebuildService, + ServerCreationService $service, + DatabaseManagementService $databaseManagementService, + DatabaseRepositoryInterface $databaseRepository, + DatabaseHostRepository $databaseHostRepository, + ServerDeletionService $deletionService, + DetailsModificationService $detailsModificationService, + LocationRepositoryInterface $locationRepository, + NodeRepositoryInterface $nodeRepository, + ReinstallServerService $reinstallService, + ServerRepositoryInterface $repository, + ServiceRepositoryInterface $serviceRepository, + StartupModificationService $startupModificationService, + SuspensionService $suspensionService + ) { + $this->alert = $alert; + $this->allocationRepository = $allocationRepository; + $this->buildModificationService = $buildModificationService; + $this->config = $config; + $this->containerRebuildService = $containerRebuildService; + $this->databaseManagementService = $databaseManagementService; + $this->databaseRepository = $databaseRepository; + $this->databaseHostRepository = $databaseHostRepository; + $this->detailsModificationService = $detailsModificationService; + $this->deletionService = $deletionService; + $this->locationRepository = $locationRepository; + $this->nodeRepository = $nodeRepository; + $this->reinstallService = $reinstallService; + $this->repository = $repository; + $this->service = $service; + $this->serviceRepository = $serviceRepository; + $this->startupModificationService = $startupModificationService; + $this->suspensionService = $suspensionService; + } + /** * Display the index page with all servers currently on the system. * - * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ - public function index(Request $request) + public function index() { - $servers = Models\Server::with('node', 'user', 'allocation'); - - if (! is_null($request->input('query'))) { - $servers->search($request->input('query')); - } - return view('admin.servers.index', [ - 'servers' => $servers->paginate(25), + 'servers' => $this->repository->getAllServers( + $this->config->get('pterodactyl.paginate.admin.servers') + ), ]); } /** * Display create new server page. * - * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View + * + * @throws \Exception */ - public function create(Request $request) + public function create() { - $services = Models\Service::with('options.packs', 'options.variables')->get(); + $services = $this->serviceRepository->getWithOptions(); + Javascript::put([ 'services' => $services->map(function ($item) { return array_merge($item->toArray(), [ @@ -76,147 +237,114 @@ class ServersController extends Controller ]); return view('admin.servers.new', [ - 'locations' => Models\Location::all(), + 'locations' => $this->locationRepository->all(), 'services' => $services, ]); } /** - * Create server controller method. + * Handle POST of server creation form. * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Response\RedirectResponse + * @param \Pterodactyl\Http\Requests\Admin\ServerFormRequest $request + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function store(Request $request) + public function store(ServerFormRequest $request) { - try { - $repo = new ServerRepository; - $server = $repo->create($request->except('_token')); + $server = $this->service->create($request->except('_token')); + $this->alert->success(trans('admin/server.alerts.server_created'))->flash(); - return redirect()->route('admin.servers.view', $server->id); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.servers.new')->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (AutoDeploymentException $ex) { - Alert::danger('Auto-Deployment Exception: ' . $ex->getMessage())->flash(); - } catch (TransferException $ex) { - Log::warning($ex); - Alert::danger('A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.')->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attemping to add this server. Please try again.')->flash(); - } - - return redirect()->route('admin.servers.new')->withInput(); + return redirect()->route('admin.servers.view', $server->id); } /** * Returns a tree of all avaliable nodes in a given location. * - * @param \Illuminate\Http\Request $request - * @return array + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Support\Collection */ public function nodes(Request $request) { - $nodes = Models\Node::with('allocations')->where('location_id', $request->input('location'))->get(); - - return $nodes->map(function ($item) { - $filtered = $item->allocations->where('server_id', null)->map(function ($map) { - return collect($map)->only(['id', 'ip', 'port']); - }); - - $item->ports = $filtered->map(function ($map) use ($item) { - return [ - 'id' => $map['id'], - 'text' => $map['ip'] . ':' . $map['port'], - ]; - })->values(); - - return [ - 'id' => $item->id, - 'text' => $item->name, - 'allocations' => $item->ports, - ]; - })->values(); + return $this->nodeRepository->getNodesForLocation($request->input('location')); } /** * Display the index when viewing a specific server. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\View\View */ - public function viewIndex(Request $request, $id) + public function viewIndex(Server $server) { - return view('admin.servers.view.index', ['server' => Models\Server::findOrFail($id)]); + return view('admin.servers.view.index', ['server' => $server]); } /** * Display the details page when viewing a specific server. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param int $server * @return \Illuminate\View\View */ - public function viewDetails(Request $request, $id) + public function viewDetails($server) { - $server = Models\Server::where('installed', 1)->findOrFail($id); - - return view('admin.servers.view.details', ['server' => $server]); + return view('admin.servers.view.details', [ + 'server' => $this->repository->findFirstWhere([ + ['id', '=', $server], + ['installed', '=', 1], + ]), + ]); } /** * Display the build details page when viewing a specific server. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param int $server * @return \Illuminate\View\View */ - public function viewBuild(Request $request, $id) + public function viewBuild($server) { - $server = Models\Server::where('installed', 1)->with('node.allocations')->findOrFail($id); + $server = $this->repository->findFirstWhere([ + ['id', '=', $server], + ['installed', '=', 1], + ]); + + $allocations = $this->allocationRepository->getAllocationsForNode($server->node_id); return view('admin.servers.view.build', [ 'server' => $server, - 'assigned' => $server->node->allocations->where('server_id', $server->id)->sortBy('port')->sortBy('ip'), - 'unassigned' => $server->node->allocations->where('server_id', null)->sortBy('port')->sortBy('ip'), + 'assigned' => $allocations->where('server_id', $server->id)->sortBy('port')->sortBy('ip'), + 'unassigned' => $allocations->where('server_id', null)->sortBy('port')->sortBy('ip'), ]); } /** * Display startup configuration page for a server. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param int $server * @return \Illuminate\View\View */ - public function viewStartup(Request $request, $id) + public function viewStartup($server) { - $server = Models\Server::where('installed', 1)->with('option.variables', 'variables')->findOrFail($id); - $server->option->variables->transform(function ($item, $key) use ($server) { - $item->server_value = $server->variables->where('variable_id', $item->id)->pluck('variable_value')->first(); + $parameters = $this->repository->getVariablesWithValues($server, true); + if (! $parameters->server->installed) { + abort(404); + } - return $item; - }); + $services = $this->serviceRepository->getWithOptions(); - $services = Models\Service::with('options.packs', 'options.variables')->get(); Javascript::put([ 'services' => $services->map(function ($item) { return array_merge($item->toArray(), [ 'options' => $item->options->keyBy('id')->toArray(), ]); })->keyBy('id'), - 'server_variables' => $server->variables->mapWithKeys(function ($item) { - return ['env_' . $item->variable_id => [ - 'value' => $item->variable_value, - ]]; - })->toArray(), + 'server_variables' => $parameters->data, ]); return view('admin.servers.view.startup', [ - 'server' => $server, + 'server' => $parameters->server, 'services' => $services, ]); } @@ -224,16 +352,15 @@ class ServersController extends Controller /** * Display the database management page for a specific server. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param int $server * @return \Illuminate\View\View */ - public function viewDatabase(Request $request, $id) + public function viewDatabase($server) { - $server = Models\Server::where('installed', 1)->with('databases.host')->findOrFail($id); + $server = $this->repository->getWithDatabases($server); return view('admin.servers.view.database', [ - 'hosts' => Models\DatabaseHost::all(), + 'hosts' => $this->databaseHostRepository->all(), 'server' => $server, ]); } @@ -241,360 +368,268 @@ class ServersController extends Controller /** * Display the management page when viewing a specific server. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\View\View */ - public function viewManage(Request $request, $id) + public function viewManage(Server $server) { - return view('admin.servers.view.manage', ['server' => Models\Server::findOrFail($id)]); + return view('admin.servers.view.manage', ['server' => $server]); } /** * Display the deletion page for a server. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\View\View */ - public function viewDelete(Request $request, $id) + public function viewDelete(Server $server) { - return view('admin.servers.view.delete', ['server' => Models\Server::findOrFail($id)]); + return view('admin.servers.view.delete', ['server' => $server]); } /** * Update the details for a server. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function setDetails(Request $request, $id) + public function setDetails(Request $request, Server $server) { - $repo = new ServerRepository; - try { - $repo->updateDetails($id, array_merge( - $request->only('description'), - $request->intersect([ - 'owner_id', 'name', 'reset_token', - ]) - )); + $this->detailsModificationService->edit($server, $request->only([ + 'owner_id', 'name', 'description', 'reset_token', + ])); - Alert::success('Server details were successfully updated.')->flash(); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.servers.view.details', $id)->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attemping to update this server. This error has been logged.')->flash(); - } + $this->alert->success(trans('admin/server.alerts.details_updated'))->flash(); - return redirect()->route('admin.servers.view.details', $id)->withInput(); + return redirect()->route('admin.servers.view.details', $server->id); } /** * Set the new docker container for a server. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function setContainer(Request $request, $id) + public function setContainer(Request $request, Server $server) { - $repo = new ServerRepository; + $this->detailsModificationService->setDockerImage($server, $request->input('docker_image')); + $this->alert->success(trans('admin/server.alerts.docker_image_updated'))->flash(); - try { - $repo->updateContainer($id, $request->intersect('docker_image')); - - Alert::success('Successfully updated this server\'s docker image.')->flash(); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.servers.view.details', $id)->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (TransferException $ex) { - Log::warning($ex); - Alert::danger('A TransferException occured while attempting to update the container image. Is the daemon online? This error has been logged.'); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attemping to update this server\'s docker image. This error has been logged.')->flash(); - } - - return redirect()->route('admin.servers.view.details', $id); + return redirect()->route('admin.servers.view.details', $server->id); } /** * Toggles the install status for a server. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function toggleInstall(Request $request, $id) + public function toggleInstall(Server $server) { - $repo = new ServerRepository; - try { - $repo->toggleInstall($id); - - Alert::success('Server install status was successfully toggled.')->flash(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attemping to toggle this servers status. This error has been logged.')->flash(); + if ($server->installed > 1) { + throw new DisplayException(trans('admin/server.exceptions.marked_as_failed')); } - return redirect()->route('admin.servers.view.manage', $id); + $this->repository->update($server->id, [ + 'installed' => ! $server->installed, + ]); + + $this->alert->success(trans('admin/server.alerts.install_toggled'))->flash(); + + return redirect()->route('admin.servers.view.manage', $server->id); } /** * Reinstalls the server with the currently assigned pack and service. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function reinstallServer(Request $request, $id) + public function reinstallServer(Server $server) { - $repo = new ServerRepository; - try { - $repo->reinstall($id); + $this->reinstallService->reinstall($server); + $this->alert->success(trans('admin/server.alerts.server_reinstalled'))->flash(); - Alert::success('Server successfully marked for reinstallation.')->flash(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attemping to perform this reinstallation. This error has been logged.')->flash(); - } - - return redirect()->route('admin.servers.view.manage', $id); + return redirect()->route('admin.servers.view.manage', $server->id); } /** * Setup a server to have a container rebuild. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\DisplayException */ - public function rebuildContainer(Request $request, $id) + public function rebuildContainer(Server $server) { - $server = Models\Server::with('node')->findOrFail($id); + $this->containerRebuildService->rebuild($server); + $this->alert->success(trans('admin/server.alerts.rebuild_on_boot'))->flash(); - try { - $server->node->guzzleClient([ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $server->node->daemonSecret, - ])->request('POST', '/server/rebuild'); - - Alert::success('A rebuild has been queued successfully. It will run the next time this server is booted.')->flash(); - } catch (TransferException $ex) { - Log::warning($ex); - Alert::danger('A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.')->flash(); - } - - return redirect()->route('admin.servers.view.manage', $id); + return redirect()->route('admin.servers.view.manage', $server->id); } /** * Manage the suspension status for a server. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function manageSuspension(Request $request, $id) + public function manageSuspension(Request $request, Server $server) { - $repo = new ServerRepository; - $action = $request->input('action'); + $this->suspensionService->toggle($server, $request->input('action')); + $this->alert->success(trans('admin/server.alerts.suspension_toggled', [ + 'status' => $request->input('action') . 'ed', + ]))->flash(); - if (! in_array($action, ['suspend', 'unsuspend'])) { - Alert::danger('Invalid action was passed to function.')->flash(); - - return redirect()->route('admin.servers.view.manage', $id); - } - - try { - $repo->toggleAccess($id, ($action === 'unsuspend')); - - Alert::success('Server has been ' . $action . 'ed.'); - } catch (TransferException $ex) { - Log::warning($ex); - Alert::danger('A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.')->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attemping to ' . $action . ' this server. This error has been logged.')->flash(); - } - - return redirect()->route('admin.servers.view.manage', $id); + return redirect()->route('admin.servers.view.manage', $server->id); } /** * Update the build configuration for a server. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\RedirectResponse + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @internal param int $id */ - public function updateBuild(Request $request, $id) + public function updateBuild(Request $request, Server $server) { - $repo = new ServerRepository; + $this->buildModificationService->handle($server, $request->only([ + 'allocation_id', 'add_allocations', 'remove_allocations', + 'memory', 'swap', 'io', 'cpu', 'disk', + ])); + $this->alert->success(trans('admin/server.alerts.build_updated'))->flash(); - try { - $repo->changeBuild($id, $request->intersect([ - 'allocation_id', 'add_allocations', 'remove_allocations', - 'memory', 'swap', 'io', 'cpu', 'disk', - ])); - - Alert::success('Server details were successfully updated.')->flash(); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.servers.view.build', $id)->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (TransferException $ex) { - Log::warning($ex); - Alert::danger('A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.')->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attemping to add this server. This error has been logged.')->flash(); - } - - return redirect()->route('admin.servers.view.build', $id); + return redirect()->route('admin.servers.view.build', $server->id); } /** * Start the server deletion process. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function delete(Request $request, $id) + public function delete(Request $request, Server $server) { - $repo = new ServerRepository; + $this->deletionService->withForce($request->has('force_delete'))->handle($server); + $this->alert->success(trans('admin/server.alerts.server_deleted'))->flash(); - try { - $repo->delete($id, $request->has('force_delete')); - Alert::success('Server was successfully deleted from the system.')->flash(); - - return redirect()->route('admin.servers'); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (TransferException $ex) { - Log::warning($ex); - Alert::danger('A TransferException occurred while attempting to delete this server from the daemon, please ensure it is running. This error has been logged.')->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attemping to delete this server. This error has been logged.')->flash(); - } - - return redirect()->route('admin.servers.view.delete', $id); + return redirect()->route('admin.servers'); } /** * Update the startup command as well as variables. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function saveStartup(Request $request, $id) + public function saveStartup(Request $request, Server $server) { - $repo = new ServerRepository; + $this->startupModificationService->isAdmin()->handle( + $server, + $request->except('_token') + ); + $this->alert->success(trans('admin/server.alerts.startup_changed'))->flash(); - try { - if ($repo->updateStartup($id, $request->except('_token'), true)) { - Alert::success('Service configuration successfully modfied for this server, reinstalling now.')->flash(); - - return redirect()->route('admin.servers.view', $id); - } else { - Alert::success('Startup variables were successfully modified and assigned for this server.')->flash(); - } - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.servers.view.startup', $id)->withErrors(json_decode($ex->getMessage())); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (TransferException $ex) { - Log::warning($ex); - Alert::danger('A TransferException occurred while attempting to update the startup for this server, please ensure the daemon is running. This error has been logged.')->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attemping to update startup variables for this server. This error has been logged.')->flash(); - } - - return redirect()->route('admin.servers.view.startup', $id); + return redirect()->route('admin.servers.view.startup', $server->id); } /** * Creates a new database assigned to a specific server. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param int $server * @return \Illuminate\Http\RedirectResponse + * + * @throws \Exception + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function newDatabase(Request $request, $id) + public function newDatabase(Request $request, $server) { - $repo = new DatabaseRepository; + $this->databaseManagementService->create($server, [ + 'database' => $request->input('database'), + 'remote' => $request->input('remote'), + 'database_host_id' => $request->input('database_host_id'), + ]); - try { - $repo->create($id, $request->only(['host', 'database', 'connection'])); - - Alert::success('A new database was assigned to this server successfully.')->flash(); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.servers.view.database', $id)->withInput()->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An exception occured while attempting to add a new database for this server. This error has been logged.')->flash(); - } - - return redirect()->route('admin.servers.view.database', $id)->withInput(); + return redirect()->route('admin.servers.view.database', $server)->withInput(); } /** * Resets the database password for a specific database on this server. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param int $server * @return \Illuminate\Http\RedirectResponse + * + * @throws \Exception + * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function resetDatabasePassword(Request $request, $id) + public function resetDatabasePassword(Request $request, $server) { - $database = Models\Database::where('server_id', $id)->findOrFail($request->input('database')); - $repo = new DatabaseRepository; + $database = $this->databaseRepository->findFirstWhere([ + ['server_id', '=', $server], + ['id', '=', $request->input('database')], + ]); - try { - $repo->password($database->id, str_random(20)); + $this->databaseManagementService->changePassword($database->id, str_random(20)); - return response('', 204); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json(['error' => 'A unhandled exception occurred while attempting to reset this password. This error has been logged.'], 503); - } + return response('', 204); } /** * Deletes a database from a server. * - * @param \Illuminate\Http\Request $request - * @param int $id - * @param int $database + * @param int $server + * @param int $database * @return \Illuminate\Http\RedirectResponse + * + * @throws \Exception + * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function deleteDatabase(Request $request, $id, $database) + public function deleteDatabase($server, $database) { - $database = Models\Database::where('server_id', $id)->findOrFail($database); - $repo = new DatabaseRepository; + $database = $this->databaseRepository->findFirstWhere([ + ['server_id', '=', $server], + ['id', '=', $database], + ]); - try { - $repo->drop($database->id); + $this->databaseManagementService->delete($database->id); - return response('', 204); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json(['error' => 'A unhandled exception occurred while attempting to drop this database. This error has been logged.'], 503); - } + return response('', 204); } } diff --git a/app/Http/Controllers/Admin/ServiceController.php b/app/Http/Controllers/Admin/ServiceController.php index beaac5340..817f082f7 100644 --- a/app/Http/Controllers/Admin/ServiceController.php +++ b/app/Http/Controllers/Admin/ServiceController.php @@ -24,37 +24,75 @@ namespace Pterodactyl\Http\Controllers\Admin; -use Log; -use Alert; -use Pterodactyl\Models; -use Illuminate\Http\Request; -use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Models\Service; +use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\ServiceRepository; -use Pterodactyl\Exceptions\DisplayValidationException; +use Pterodactyl\Services\Services\ServiceUpdateService; +use Pterodactyl\Services\Services\ServiceCreationService; +use Pterodactyl\Services\Services\ServiceDeletionService; +use Pterodactyl\Http\Requests\Admin\Service\ServiceFormRequest; +use Pterodactyl\Contracts\Repository\ServiceRepositoryInterface; +use Pterodactyl\Http\Requests\Admin\Service\ServiceFunctionsFormRequest; class ServiceController extends Controller { + /** + * @var \Prologue\Alerts\AlertsMessageBag + */ + protected $alert; + + /** + * @var \Pterodactyl\Services\Services\ServiceCreationService + */ + protected $creationService; + + /** + * @var \Pterodactyl\Services\Services\ServiceDeletionService + */ + protected $deletionService; + + /** + * @var \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Services\ServiceUpdateService + */ + protected $updateService; + + public function __construct( + AlertsMessageBag $alert, + ServiceCreationService $creationService, + ServiceDeletionService $deletionService, + ServiceRepositoryInterface $repository, + ServiceUpdateService $updateService + ) { + $this->alert = $alert; + $this->creationService = $creationService; + $this->deletionService = $deletionService; + $this->repository = $repository; + $this->updateService = $updateService; + } + /** * Display service overview page. * - * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ - public function index(Request $request) + public function index() { return view('admin.services.index', [ - 'services' => Models\Service::withCount('servers', 'options', 'packs')->get(), + 'services' => $this->repository->getWithOptions(), ]); } /** * Display create service page. * - * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ - public function create(Request $request) + public function create() { return view('admin.services.new'); } @@ -62,91 +100,92 @@ class ServiceController extends Controller /** * Return base view for a service. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param int $service * @return \Illuminate\View\View */ - public function view(Request $request, $id) + public function view($service) { return view('admin.services.view', [ - 'service' => Models\Service::with('options', 'options.servers')->findOrFail($id), + 'service' => $this->repository->getWithOptionServers($service), ]); } /** * Return function editing view for a service. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Models\Service $service * @return \Illuminate\View\View */ - public function viewFunctions(Request $request, $id) + public function viewFunctions(Service $service) { - return view('admin.services.functions', ['service' => Models\Service::findOrFail($id)]); + return view('admin.services.functions', ['service' => $service]); } /** * Handle post action for new service. * - * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Http\Requests\Admin\Service\ServiceFormRequest $request * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function store(Request $request) + public function store(ServiceFormRequest $request) { - $repo = new ServiceRepository; + $service = $this->creationService->handle($request->normalize()); + $this->alert->success(trans('admin/services.notices.service_created', ['name' => $service->name]))->flash(); - try { - $service = $repo->create($request->intersect([ - 'name', 'description', 'folder', 'startup', - ])); - Alert::success('Successfully created new service!')->flash(); - - return redirect()->route('admin.services.view', $service->id); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.services.new')->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An error occured while attempting to add a new service. This error has been logged.')->flash(); - } - - return redirect()->route('admin.services.new')->withInput(); + return redirect()->route('admin.services.view', $service->id); } /** * Edits configuration for a specific service. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Http\Requests\Admin\Service\ServiceFormRequest $request + * @param \Pterodactyl\Models\Service $service * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function edit(Request $request, $id) + public function update(ServiceFormRequest $request, Service $service) { - $repo = new ServiceRepository; - $redirectTo = ($request->input('redirect_to')) ? 'admin.services.view.functions' : 'admin.services.view'; + $this->updateService->handle($service->id, $request->normalize()); + $this->alert->success(trans('admin/services.notices.service_updated'))->flash(); - try { - if ($request->input('action') !== 'delete') { - $repo->update($id, $request->intersect([ - 'name', 'description', 'folder', 'startup', 'index_file', - ])); - Alert::success('Service has been updated successfully.')->flash(); - } else { - $repo->delete($id); - Alert::success('Successfully deleted service from the system.')->flash(); + return redirect()->route('admin.services.view', $service); + } - return redirect()->route('admin.services'); - } - } catch (DisplayValidationException $ex) { - return redirect()->route($redirectTo, $id)->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An error occurred while attempting to update this service. This error has been logged.')->flash(); - } + /** + * Update the functions file for a service. + * + * @param \Pterodactyl\Http\Requests\Admin\Service\ServiceFunctionsFormRequest $request + * @param \Pterodactyl\Models\Service $service + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function updateFunctions(ServiceFunctionsFormRequest $request, Service $service) + { + $this->updateService->handle($service->id, $request->normalize()); + $this->alert->success(trans('admin/services.notices.functions_updated'))->flash(); - return redirect()->route($redirectTo, $id); + return redirect()->route('admin.services.view.functions', $service->id); + } + + /** + * Delete a service from the panel. + * + * @param \Pterodactyl\Models\Service $service + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException + */ + public function destroy(Service $service) + { + $this->deletionService->handle($service->id); + $this->alert->success(trans('admin/services.notices.service_deleted'))->flash(); + + return redirect()->route('admin.services'); } } diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 0e943eb46..6907eaaae 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -1,8 +1,7 @@ - * Some Modifications (c) 2015 Dylan Seidt . + * Copyright (c) 2015 - 2017 Dane Everitt . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -25,43 +24,95 @@ namespace Pterodactyl\Http\Controllers\Admin; -use Log; -use Alert; use Illuminate\Http\Request; use Pterodactyl\Models\User; +use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\UserRepository; -use Pterodactyl\Exceptions\DisplayValidationException; +use Illuminate\Contracts\Translation\Translator; +use Pterodactyl\Services\Users\UserUpdateService; +use Pterodactyl\Services\Users\UserCreationService; +use Pterodactyl\Services\Users\UserDeletionService; +use Pterodactyl\Http\Requests\Admin\UserFormRequest; +use Pterodactyl\Contracts\Repository\UserRepositoryInterface; class UserController extends Controller { + /** + * @var \Prologue\Alerts\AlertsMessageBag + */ + protected $alert; + + /** + * @var \Pterodactyl\Services\Users\UserCreationService + */ + protected $creationService; + + /** + * @var \Pterodactyl\Services\Users\UserDeletionService + */ + protected $deletionService; + + /** + * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface + */ + protected $repository; + + /** + * @var \Illuminate\Contracts\Translation\Translator + */ + protected $translator; + + /** + * @var \Pterodactyl\Services\Users\UserUpdateService + */ + protected $updateService; + + /** + * UserController constructor. + * + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Pterodactyl\Services\Users\UserCreationService $creationService + * @param \Pterodactyl\Services\Users\UserDeletionService $deletionService + * @param \Illuminate\Contracts\Translation\Translator $translator + * @param \Pterodactyl\Services\Users\UserUpdateService $updateService + * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository + */ + public function __construct( + AlertsMessageBag $alert, + UserCreationService $creationService, + UserDeletionService $deletionService, + Translator $translator, + UserUpdateService $updateService, + UserRepositoryInterface $repository + ) { + $this->alert = $alert; + $this->creationService = $creationService; + $this->deletionService = $deletionService; + $this->repository = $repository; + $this->translator = $translator; + $this->updateService = $updateService; + } + /** * Display user index page. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ public function index(Request $request) { - $users = User::withCount('servers', 'subuserOf'); + $users = $this->repository->search($request->input('query'))->getAllUsersWithCounts(); - if (! is_null($request->input('query'))) { - $users->search($request->input('query')); - } - - return view('admin.users.index', [ - 'users' => $users->paginate(25), - ]); + return view('admin.users.index', ['users' => $users]); } /** * Display new user page. * - * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ - public function create(Request $request) + public function create() { return view('admin.users.new'); } @@ -69,112 +120,77 @@ class UserController extends Controller /** * Display user view page. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Models\User $user * @return \Illuminate\View\View */ - public function view(Request $request, $id) + public function view(User $user) { - return view('admin.users.view', [ - 'user' => User::with('servers.node')->findOrFail($id), - ]); + return view('admin.users.view', ['user' => $user]); } /** - * Delete a user. + * Delete a user from the system. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\User $user * @return \Illuminate\Http\RedirectResponse + * + * @throws \Exception + * @throws \Pterodactyl\Exceptions\DisplayException */ - public function delete(Request $request, $id) + public function delete(Request $request, User $user) { - try { - $repo = new UserRepository; - $repo->delete($id); - Alert::success('Successfully deleted user from system.')->flash(); - - return redirect()->route('admin.users'); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An exception was encountered while attempting to delete this user.')->flash(); + if ($request->user()->id === $user->id) { + throw new DisplayException($this->translator->trans('admin/user.exceptions.user_has_servers')); } - return redirect()->route('admin.users.view', $id); + $this->deletionService->handle($user); + + return redirect()->route('admin.users.view', $user->id); } /** * Create a user. * - * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Http\Requests\Admin\UserFormRequest $request * @return \Illuminate\Http\RedirectResponse + * + * @throws \Exception + * @throws \Throwable */ - public function store(Request $request) + public function store(UserFormRequest $request) { - try { - $user = new UserRepository; - $userid = $user->create($request->only([ - 'email', 'password', 'name_first', - 'name_last', 'username', 'root_admin', - ])); - Alert::success('Account has been successfully created.')->flash(); + $user = $this->creationService->handle($request->normalize()); + $this->alert->success($this->translator->trans('admin/user.notices.account_created'))->flash(); - return redirect()->route('admin.users.view', $userid); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.users.new')->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An error occured while attempting to add a new user.')->flash(); - - return redirect()->route('admin.users.new'); - } + return redirect()->route('admin.users.view', $user->id); } /** - * Update a user. + * Update a user on the system. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Pterodactyl\Http\Requests\Admin\UserFormRequest $request + * @param \Pterodactyl\Models\User $user * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function update(Request $request, $id) + public function update(UserFormRequest $request, User $user) { - try { - $repo = new UserRepository; - $user = $repo->update($id, array_merge( - $request->only('root_admin'), - $request->intersect([ - 'email', 'password', 'name_first', - 'name_last', 'username', - ]) - )); - Alert::success('User account was successfully updated.')->flash(); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.users.view', $id)->withErrors(json_decode($ex->getMessage())); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An error occured while attempting to update this user.')->flash(); - } + $this->updateService->handle($user->id, $request->normalize()); + $this->alert->success($this->translator->trans('admin/user.notices.account_updated'))->flash(); - return redirect()->route('admin.users.view', $id); + return redirect()->route('admin.users.view', $user->id); } /** * Get a JSON response of users on the system. * - * @param \Illuminate\Http\Request $request - * @return \Pterodactyl\Models\User + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Database\Eloquent\Collection */ public function json(Request $request) { - return User::select('id', 'email', 'username', 'name_first', 'name_last') - ->search($request->input('q')) - ->get()->transform(function ($item) { - $item->md5 = md5(strtolower($item->email)); - - return $item; - }); + return $this->repository->filterUsersByQuery($request->input('q')); } } diff --git a/app/Http/Controllers/Admin/VariableController.php b/app/Http/Controllers/Admin/VariableController.php new file mode 100644 index 000000000..d634052aa --- /dev/null +++ b/app/Http/Controllers/Admin/VariableController.php @@ -0,0 +1,148 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Controllers\Admin; + +use Prologue\Alerts\AlertsMessageBag; +use Pterodactyl\Models\ServiceOption; +use Pterodactyl\Models\ServiceVariable; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Http\Requests\Admin\OptionVariableFormRequest; +use Pterodactyl\Repositories\Eloquent\ServiceVariableRepository; +use Pterodactyl\Services\Services\Variables\VariableUpdateService; +use Pterodactyl\Services\Services\Variables\VariableCreationService; +use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface; + +class VariableController extends Controller +{ + /** + * @var \Prologue\Alerts\AlertsMessageBag + */ + protected $alert; + + /** + * @var \Pterodactyl\Services\Services\Variables\VariableCreationService + */ + protected $creationService; + + /** + * @var \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface + */ + protected $serviceOptionRepository; + + /** + * @var \Pterodactyl\Repositories\Eloquent\ServiceVariableRepository + */ + protected $serviceVariableRepository; + + /** + * @var \Pterodactyl\Services\Services\Variables\VariableUpdateService + */ + protected $updateService; + + public function __construct( + AlertsMessageBag $alert, + ServiceOptionRepositoryInterface $serviceOptionRepository, + ServiceVariableRepository $serviceVariableRepository, + VariableCreationService $creationService, + VariableUpdateService $updateService + ) { + $this->alert = $alert; + $this->creationService = $creationService; + $this->serviceOptionRepository = $serviceOptionRepository; + $this->serviceVariableRepository = $serviceVariableRepository; + $this->updateService = $updateService; + } + + /** + * Handles POST request to create a new option variable. + * + * @param \Pterodactyl\Http\Requests\Admin\OptionVariableFormRequest $request + * @param \Pterodactyl\Models\ServiceOption $option + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Service\ServiceVariable\ReservedVariableNameException + */ + public function store(OptionVariableFormRequest $request, ServiceOption $option) + { + $this->creationService->handle($option->id, $request->normalize()); + $this->alert->success(trans('admin/services.variables.notices.variable_created'))->flash(); + + return redirect()->route('admin.services.option.variables', $option->id); + } + + /** + * Display variable overview page for a service option. + * + * @param int $option + * @return \Illuminate\View\View + */ + public function view($option) + { + $option = $this->serviceOptionRepository->getWithVariables($option); + + return view('admin.services.options.variables', ['option' => $option]); + } + + /** + * Handles POST when editing a configration for a service variable. + * + * @param \Pterodactyl\Http\Requests\Admin\OptionVariableFormRequest $request + * @param \Pterodactyl\Models\ServiceOption $option + * @param \Pterodactyl\Models\ServiceVariable $variable + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\ServiceVariable\ReservedVariableNameException + */ + public function update(OptionVariableFormRequest $request, ServiceOption $option, ServiceVariable $variable) + { + $this->updateService->handle($variable, $request->normalize()); + $this->alert->success(trans('admin/services.variables.notices.variable_updated', [ + 'variable' => $variable->name, + ]))->flash(); + + return redirect()->route('admin.services.option.variables', $option->id); + } + + /** + * Delete a service variable from the system. + * + * @param \Pterodactyl\Models\ServiceOption $option + * @param \Pterodactyl\Models\ServiceVariable $variable + * @return \Illuminate\Http\RedirectResponse + */ + public function delete(ServiceOption $option, ServiceVariable $variable) + { + $this->serviceVariableRepository->delete($variable->id); + $this->alert->success(trans('admin/services.variables.notices.variable_deleted', [ + 'variable' => $variable->name, + ]))->flash(); + + return redirect()->route('admin.services.option.variables', $option->id); + } +} diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php index 9dd80824a..fd3e6147e 100644 --- a/app/Http/Controllers/Auth/ForgotPasswordController.php +++ b/app/Http/Controllers/Auth/ForgotPasswordController.php @@ -47,8 +47,6 @@ class ForgotPasswordController extends Controller /** * Create a new controller instance. - * - * @return void */ public function __construct() { @@ -59,7 +57,7 @@ class ForgotPasswordController extends Controller * Get the response for a failed password reset link. * * @param \Illuminate\Http\Request - * @param string $response + * @param string $response * @return \Illuminate\Http\RedirectResponse */ protected function sendResetLinkFailedResponse(Request $request, $response) diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index e4ca0d2ca..12f3df533 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -71,8 +71,6 @@ class LoginController extends Controller /** * Create a new controller instance. - * - * @return void */ public function __construct() { @@ -82,7 +80,7 @@ class LoginController extends Controller /** * Get the failed login response instance. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\RedirectResponse */ protected function sendFailedLoginResponse(Request $request) @@ -103,7 +101,7 @@ class LoginController extends Controller /** * Handle a login request to the application. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response|\Illuminate\Response\RedirectResponse */ public function login(Request $request) @@ -156,7 +154,7 @@ class LoginController extends Controller /** * Handle a TOTP implementation page. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View */ public function totp(Request $request) @@ -176,7 +174,7 @@ class LoginController extends Controller /** * Handle a TOTP input. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\RedirectResponse */ public function totpCheckpoint(Request $request) diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index d28692293..c0621270a 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -31,8 +31,6 @@ class RegisterController extends Controller /** * Create a new controller instance. - * - * @return void */ public function __construct() { @@ -42,7 +40,7 @@ class RegisterController extends Controller /** * Get a validator for an incoming registration request. * - * @param array $data + * @param array $data * @return \Illuminate\Contracts\Validation\Validator */ protected function validator(array $data) @@ -57,7 +55,7 @@ class RegisterController extends Controller /** * Create a new user instance after a valid registration. * - * @param array $data + * @param array $data * @return User */ protected function create(array $data) diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php index 628f55f42..67d9b8f33 100644 --- a/app/Http/Controllers/Auth/ResetPasswordController.php +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -30,8 +30,6 @@ class ResetPasswordController extends Controller /** * Create a new controller instance. - * - * @return void */ public function __construct() { diff --git a/app/Http/Controllers/Base/APIController.php b/app/Http/Controllers/Base/APIController.php index 9c3816db3..4bf89012f 100644 --- a/app/Http/Controllers/Base/APIController.php +++ b/app/Http/Controllers/Base/APIController.php @@ -25,43 +25,75 @@ namespace Pterodactyl\Http\Controllers\Base; -use Log; -use Alert; use Illuminate\Http\Request; -use Pterodactyl\Models\APIKey; +use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Models\APIPermission; -use Pterodactyl\Repositories\APIRepository; -use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Exceptions\DisplayValidationException; +use Pterodactyl\Services\Api\KeyCreationService; +use Pterodactyl\Http\Requests\Base\ApiKeyFormRequest; +use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface; class APIController extends Controller { + /** + * @var \Prologue\Alerts\AlertsMessageBag + */ + protected $alert; + + /** + * @var \Pterodactyl\Services\Api\KeyCreationService + */ + protected $keyService; + + /** + * @var \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface + */ + protected $repository; + + /** + * APIController constructor. + * + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface $repository + * @param \Pterodactyl\Services\Api\KeyCreationService $keyService + */ + public function __construct( + AlertsMessageBag $alert, + ApiKeyRepositoryInterface $repository, + KeyCreationService $keyService + ) { + $this->alert = $alert; + $this->keyService = $keyService; + $this->repository = $repository; + } + /** * Display base API index page. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function index(Request $request) { return view('base.api.index', [ - 'keys' => APIKey::where('user_id', $request->user()->id)->get(), + 'keys' => $this->repository->findWhere([['user_id', '=', $request->user()->id]]), ]); } /** * Display API key creation page. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ public function create(Request $request) { return view('base.api.new', [ 'permissions' => [ - 'user' => collect(APIPermission::permissions())->pull('_user'), - 'admin' => collect(APIPermission::permissions())->except('_user')->toArray(), + 'user' => collect(APIPermission::CONST_PERMISSIONS)->pull('_user'), + 'admin' => ! $request->user()->root_admin ? null : collect(APIPermission::CONST_PERMISSIONS)->except('_user')->toArray(), ], ]); } @@ -69,52 +101,44 @@ class APIController extends Controller /** * Handle saving new API key. * - * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Http\Requests\Base\ApiKeyFormRequest $request * @return \Illuminate\Http\RedirectResponse + * + * @throws \Exception + * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function store(Request $request) + public function store(ApiKeyFormRequest $request) { - try { - $repo = new APIRepository($request->user()); - $secret = $repo->create($request->intersect([ - 'memo', 'allowed_ips', - 'admin_permissions', 'permissions', - ])); - Alert::success('An API Key-Pair has successfully been generated. The API secret for this public key is shown below and will not be shown again.

' . $secret . '')->flash(); - - return redirect()->route('account.api'); - } catch (DisplayValidationException $ex) { - return redirect()->route('account.api.new')->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attempting to add this API key.')->flash(); + $adminPermissions = []; + if ($request->user()->root_admin) { + $adminPermissions = $request->input('admin_permissions', []); } - return redirect()->route('account.api.new')->withInput(); + $secret = $this->keyService->handle([ + 'user_id' => $request->user()->id, + 'allowed_ips' => $request->input('allowed_ips'), + 'memo' => $request->input('memo'), + ], $request->input('permissions', []), $adminPermissions); + + $this->alert->success(trans('base.api.index.keypair_created', ['token' => $secret]))->flash(); + + return redirect()->route('account.api'); } /** - * Handle revoking API key. + * @param \Illuminate\Http\Request $request + * @param string $key + * @return \Illuminate\Http\Response * - * @param \Illuminate\Http\Request $request - * @param string $key - * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response + * @throws \Exception */ public function revoke(Request $request, $key) { - try { - $repo = new APIRepository($request->user()); - $repo->revoke($key); + $this->repository->deleteWhere([ + ['user_id', '=', $request->user()->id], + ['public', '=', $key], + ]); - return response('', 204); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'An error occured while attempting to remove this key.', - ], 503); - } + return response('', 204); } } diff --git a/app/Http/Controllers/Base/AccountController.php b/app/Http/Controllers/Base/AccountController.php index 10c33e380..fea7f09dd 100644 --- a/app/Http/Controllers/Base/AccountController.php +++ b/app/Http/Controllers/Base/AccountController.php @@ -25,80 +25,69 @@ namespace Pterodactyl\Http\Controllers\Base; -use Log; -use Alert; -use Illuminate\Http\Request; -use Pterodactyl\Models\User; +use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\UserRepository; -use Pterodactyl\Exceptions\DisplayValidationException; +use Pterodactyl\Services\Users\UserUpdateService; +use Pterodactyl\Http\Requests\Base\AccountDataFormRequest; class AccountController extends Controller { + /** + * @var \Prologue\Alerts\AlertsMessageBag + */ + protected $alert; + + /** + * @var \Pterodactyl\Services\Users\UserUpdateService + */ + protected $updateService; + + /** + * AccountController constructor. + * + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Pterodactyl\Services\Users\UserUpdateService $updateService + */ + public function __construct( + AlertsMessageBag $alert, + UserUpdateService $updateService + ) { + $this->alert = $alert; + $this->updateService = $updateService; + } + /** * Display base account information page. * - * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ - public function index(Request $request) + public function index() { return view('base.account'); } /** - * Update details for a users account. + * Update details for a user's account. * - * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Http\Requests\Base\AccountDataFormRequest $request * @return \Illuminate\Http\RedirectResponse - * @throws \Symfony\Component\HttpKernel\Exception\HttpException + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function update(Request $request) + public function update(AccountDataFormRequest $request) { $data = []; - - // Request to update account Password if ($request->input('do_action') === 'password') { - $this->validate($request, [ - 'current_password' => 'required', - 'new_password' => 'required|confirmed|' . User::PASSWORD_RULES, - 'new_password_confirmation' => 'required', - ]); - $data['password'] = $request->input('new_password'); - - // Request to update account Email } elseif ($request->input('do_action') === 'email') { $data['email'] = $request->input('new_email'); - - // Request to update account Identity } elseif ($request->input('do_action') === 'identity') { $data = $request->only(['name_first', 'name_last', 'username']); - - // Unknown, hit em with a 404 - } else { - return abort(404); } - if ( - in_array($request->input('do_action'), ['email', 'password']) - && ! password_verify($request->input('current_password'), $request->user()->password) - ) { - Alert::danger(trans('base.account.invalid_pass'))->flash(); - - return redirect()->route('account'); - } - - try { - $repo = new UserRepository; - $repo->update($request->user()->id, $data); - Alert::success('Your account details were successfully updated.')->flash(); - } catch (DisplayValidationException $ex) { - return redirect()->route('account')->withErrors(json_decode($ex->getMessage())); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger(trans('base.account.exception'))->flash(); - } + $this->updateService->handle($request->user()->id, $data); + $this->alert->success(trans('base.account.details_updated'))->flash(); return redirect()->route('account'); } diff --git a/app/Http/Controllers/Base/IndexController.php b/app/Http/Controllers/Base/IndexController.php index 556ea157c..3c52a84fd 100644 --- a/app/Http/Controllers/Base/IndexController.php +++ b/app/Http/Controllers/Base/IndexController.php @@ -26,85 +26,83 @@ namespace Pterodactyl\Http\Controllers\Base; use Illuminate\Http\Request; -use Pterodactyl\Models\Server; use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Services\Servers\ServerAccessHelperService; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; class IndexController extends Controller { + /** + * @var \Pterodactyl\Services\Servers\ServerAccessHelperService + */ + protected $access; + + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface + */ + protected $daemonRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $repository; + + /** + * IndexController constructor. + * + * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonRepository + * @param \Pterodactyl\Services\Servers\ServerAccessHelperService $access + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository + */ + public function __construct( + DaemonServerRepositoryInterface $daemonRepository, + ServerAccessHelperService $access, + ServerRepositoryInterface $repository + ) { + $this->access = $access; + $this->daemonRepository = $daemonRepository; + $this->repository = $repository; + } + /** * Returns listing of user's servers. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ public function getIndex(Request $request) { - $servers = $request->user()->access()->with('user'); + $servers = $this->repository->search($request->input('query'))->filterUserAccessServers( + $request->user()->id, $request->user()->root_admin, 'all', ['user'] + ); - if (! is_null($request->input('query'))) { - $servers->search($request->input('query')); - } - - return view('base.index', [ - 'servers' => $servers->paginate(config('pterodactyl.paginate.frontend.servers')), - ]); - } - - /** - * Generate a random string. - * - * @param \Illuminate\Http\Request $request - * @param int $length - * @return string - * @deprecated - */ - public function getPassword(Request $request, $length = 16) - { - $length = ($length < 8) ? 8 : $length; - - $returnable = false; - while (! $returnable) { - $generated = str_random($length); - if (preg_match('/[A-Z]+[a-z]+[0-9]+/', $generated)) { - $returnable = true; - } - } - - return $generated; + return view('base.index', ['servers' => $servers]); } /** * Returns status of the server in a JSON response used for populating active status list. * - * @param \Illuminate\Http\Request $request - * @param string $uuid + * @param \Illuminate\Http\Request $request + * @param string $uuid * @return \Illuminate\Http\JsonResponse + * @throws \Exception */ public function status(Request $request, $uuid) { - $server = Server::byUuid($uuid); - - if (! $server) { - return response()->json([], 404); - } + $server = $this->access->handle($uuid, $request->user()); if (! $server->installed) { return response()->json(['status' => 20]); - } - - if ($server->suspended) { + } elseif ($server->suspended) { return response()->json(['status' => 30]); } - try { - $res = $server->guzzleClient()->request('GET', '/server'); - if ($res->getStatusCode() === 200) { - return response()->json(json_decode($res->getBody())); - } - } catch (\Exception $e) { - // - } + $response = $this->daemonRepository->setNode($server->node_id) + ->setAccessServer($server->uuid) + ->setAccessToken($server->daemonSecret) + ->details(); - return response()->json([]); + return response()->json(json_decode($response->getBody())); } } diff --git a/app/Http/Controllers/Base/SecurityController.php b/app/Http/Controllers/Base/SecurityController.php index 052a7a527..d22c0ddb9 100644 --- a/app/Http/Controllers/Base/SecurityController.php +++ b/app/Http/Controllers/Base/SecurityController.php @@ -25,24 +25,88 @@ namespace Pterodactyl\Http\Controllers\Base; -use Alert; -use Google2FA; use Illuminate\Http\Request; -use Pterodactyl\Models\Session; +use Prologue\Alerts\AlertsMessageBag; +use Illuminate\Contracts\Session\Session; use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Services\Users\TwoFactorSetupService; +use Pterodactyl\Services\Users\ToggleTwoFactorService; +use Illuminate\Contracts\Config\Repository as ConfigRepository; +use Pterodactyl\Contracts\Repository\SessionRepositoryInterface; +use Pterodactyl\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid; class SecurityController extends Controller { + /** + * @var \Prologue\Alerts\AlertsMessageBag + */ + protected $alert; + + /** + * @var \Illuminate\Contracts\Config\Repository + */ + protected $config; + + /** + * @var \Pterodactyl\Contracts\Repository\SessionRepositoryInterface + */ + protected $repository; + + /** + * @var \Illuminate\Contracts\Session\Session + */ + protected $session; + + /** + * @var \Pterodactyl\Services\Users\ToggleTwoFactorService + */ + protected $toggleTwoFactorService; + + /** + * @var \Pterodactyl\Services\Users\TwoFactorSetupService + */ + protected $twoFactorSetupService; + + /** + * SecurityController constructor. + * + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Illuminate\Contracts\Config\Repository $config + * @param \Illuminate\Contracts\Session\Session $session + * @param \Pterodactyl\Contracts\Repository\SessionRepositoryInterface $repository + * @param \Pterodactyl\Services\Users\ToggleTwoFactorService $toggleTwoFactorService + * @param \Pterodactyl\Services\Users\TwoFactorSetupService $twoFactorSetupService + */ + public function __construct( + AlertsMessageBag $alert, + ConfigRepository $config, + Session $session, + SessionRepositoryInterface $repository, + ToggleTwoFactorService $toggleTwoFactorService, + TwoFactorSetupService $twoFactorSetupService + ) { + $this->alert = $alert; + $this->config = $config; + $this->repository = $repository; + $this->session = $session; + $this->toggleTwoFactorService = $toggleTwoFactorService; + $this->twoFactorSetupService = $twoFactorSetupService; + } + /** * Returns Security Management Page. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ public function index(Request $request) { + if ($this->config->get('session.driver') === 'database') { + $activeSessions = $this->repository->getUserSessions($request->user()->id); + } + return view('base.security', [ - 'sessions' => Session::where('user_id', $request->user()->id)->get(), + 'sessions' => $activeSessions ?? null, ]); } @@ -50,82 +114,67 @@ class SecurityController extends Controller * Generates TOTP Secret and returns popup data for user to verify * that they can generate a valid response. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\JsonResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function generateTotp(Request $request) { - $user = $request->user(); - - $user->totp_secret = Google2FA::generateSecretKey(); - $user->save(); - - return response()->json([ - 'qrImage' => Google2FA::getQRCodeGoogleUrl( - 'Pterodactyl', - $user->email, - $user->totp_secret - ), - 'secret' => $user->totp_secret, - ]); + return response()->json($this->twoFactorSetupService->handle($request->user())); } /** * Verifies that 2FA token recieved is valid and will work on the account. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function setTotp(Request $request) { - if (! $request->has('token')) { - return response()->json([ - 'error' => 'Request is missing token parameter.', - ], 500); - } + try { + $this->toggleTwoFactorService->handle($request->user(), $request->input('token')); - $user = $request->user(); - if ($user->toggleTotp($request->input('token'))) { return response('true'); + } catch (TwoFactorAuthenticationTokenInvalid $exception) { + return response('false'); } - - return response('false'); } /** * Disables TOTP on an account. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function disableTotp(Request $request) { - if (! $request->has('token')) { - Alert::danger('Missing required `token` field in request.')->flash(); - - return redirect()->route('account.security'); + try { + $this->toggleTwoFactorService->handle($request->user(), $request->input('token'), false); + } catch (TwoFactorAuthenticationTokenInvalid $exception) { + $this->alert->danger(trans('base.security.2fa_disable_error'))->flash(); } - $user = $request->user(); - if ($user->toggleTotp($request->input('token'))) { - return redirect()->route('account.security'); - } - - Alert::danger('The TOTP token provided was invalid.')->flash(); - return redirect()->route('account.security'); } /** * Revokes a user session. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param int $id * @return \Illuminate\Http\RedirectResponse */ public function revoke(Request $request, $id) { - Session::where('user_id', $request->user()->id)->findOrFail($id)->delete(); + $this->repository->deleteUserSession($request->user()->id, $id); return redirect()->route('account.security'); } diff --git a/app/Http/Controllers/Daemon/ActionController.php b/app/Http/Controllers/Daemon/ActionController.php index 0a054218c..f0e84ed0d 100644 --- a/app/Http/Controllers/Daemon/ActionController.php +++ b/app/Http/Controllers/Daemon/ActionController.php @@ -35,7 +35,7 @@ class ActionController extends Controller /** * Handles download request from daemon. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\JsonResponse */ public function authenticateDownload(Request $request) @@ -57,7 +57,7 @@ class ActionController extends Controller /** * Handles install toggle request from daemon. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\JsonResponse */ public function markInstall(Request $request) @@ -87,8 +87,8 @@ class ActionController extends Controller /** * Handles configuration data request from daemon. * - * @param \Illuminate\Http\Request $request - * @param string $token + * @param \Illuminate\Http\Request $request + * @param string $token * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response */ public function configuration(Request $request, $token) diff --git a/app/Http/Controllers/Daemon/PackController.php b/app/Http/Controllers/Daemon/PackController.php index 419ae7c9c..21866ed59 100644 --- a/app/Http/Controllers/Daemon/PackController.php +++ b/app/Http/Controllers/Daemon/PackController.php @@ -34,8 +34,8 @@ class PackController extends Controller /** * Pulls an install pack archive from the system. * - * @param \Illuminate\Http\Request $request - * @param string $uuid + * @param \Illuminate\Http\Request $request + * @param string $uuid * @return \Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\BinaryFileResponse */ public function pull(Request $request, $uuid) @@ -56,8 +56,8 @@ class PackController extends Controller /** * Returns the hash information for a pack. * - * @param \Illuminate\Http\Request $request - * @param string $uuid + * @param \Illuminate\Http\Request $request + * @param string $uuid * @return \Illuminate\Http\JsonResponse */ public function hash(Request $request, $uuid) @@ -80,11 +80,9 @@ class PackController extends Controller /** * Pulls an update pack archive from the system. * - * @param \Illuminate\Http\Request $request - * @return void + * @param \Illuminate\Http\Request $request */ public function pullUpdate(Request $request) { - // } } diff --git a/app/Http/Controllers/Daemon/ServiceController.php b/app/Http/Controllers/Daemon/ServiceController.php index d461786f0..a9de34d97 100644 --- a/app/Http/Controllers/Daemon/ServiceController.php +++ b/app/Http/Controllers/Daemon/ServiceController.php @@ -36,7 +36,7 @@ class ServiceController extends Controller * as well as the associated files and the file hashes for * caching purposes. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\JsonResponse */ public function listServices(Request $request) @@ -55,9 +55,9 @@ class ServiceController extends Controller /** * Returns the contents of the requested file for the given service. * - * @param \Illuminate\Http\Request $request - * @param string $folder - * @param string $file + * @param \Illuminate\Http\Request $request + * @param string $folder + * @param string $file * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\FileResponse */ public function pull(Request $request, $folder, $file) @@ -77,7 +77,7 @@ class ServiceController extends Controller * Returns a `main.json` file based on the configuration * of each service option. * - * @param int $id + * @param int $id * @return \Illuminate\Support\Collection */ protected function getConfiguration($id) diff --git a/app/Http/Controllers/Server/AjaxController.php b/app/Http/Controllers/Server/AjaxController.php index c22b0c202..f4b54ca86 100644 --- a/app/Http/Controllers/Server/AjaxController.php +++ b/app/Http/Controllers/Server/AjaxController.php @@ -30,7 +30,6 @@ use Illuminate\Http\Request; use Pterodactyl\Repositories; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Exceptions\DisplayValidationException; class AjaxController extends Controller { @@ -49,139 +48,11 @@ class AjaxController extends Controller */ protected $directory; - /** - * Returns a listing of files in a given directory for a server. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\View\View|\Illuminate\Http\Response - */ - public function postDirectoryList(Request $request, $uuid) - { - $server = Models\Server::byUuid($uuid); - $this->authorize('list-files', $server); - - $this->directory = '/' . trim(urldecode($request->input('directory', '/')), '/'); - $prevDir = [ - 'header' => ($this->directory !== '/') ? $this->directory : '', - ]; - if ($this->directory !== '/') { - $prevDir['first'] = true; - } - - // Determine if we should show back links in the file browser. - // This code is strange, and could probably be rewritten much better. - $goBack = explode('/', trim($this->directory, '/')); - if (! empty(array_filter($goBack)) && count($goBack) >= 2) { - $prevDir['show'] = true; - array_pop($goBack); - $prevDir['link'] = '/' . implode('/', $goBack); - $prevDir['link_show'] = implode('/', $goBack) . '/'; - } - - $controller = new Repositories\Daemon\FileRepository($uuid); - - try { - $directoryContents = $controller->returnDirectoryListing($this->directory); - } catch (DisplayException $ex) { - return response($ex->getMessage(), 500); - } catch (\Exception $ex) { - Log::error($ex); - - return response('An error occured while attempting to load the requested directory, please try again.', 500); - } - - return view('server.files.list', [ - 'server' => $server, - 'files' => $directoryContents->files, - 'folders' => $directoryContents->folders, - 'editableMime' => Repositories\HelperRepository::editableFiles(), - 'directory' => $prevDir, - ]); - } - - /** - * Handles a POST request to save a file. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\Http\Response - */ - public function postSaveFile(Request $request, $uuid) - { - $server = Models\Server::byUuid($uuid); - $this->authorize('save-files', $server); - - $controller = new Repositories\Daemon\FileRepository($uuid); - - try { - $controller->saveFileContents($request->input('file'), $request->input('contents')); - - return response(null, 204); - } catch (DisplayException $ex) { - return response($ex->getMessage(), 500); - } catch (\Exception $ex) { - Log::error($ex); - - return response('An error occured while attempting to save this file, please try again.', 500); - } - } - - /** - * Sets the primary allocation for a server. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\Http\JsonResponse - * @deprecated - */ - public function postSetPrimary(Request $request, $uuid) - { - $server = Models\Server::byUuid($uuid)->load('allocations'); - $this->authorize('set-connection', $server); - - if ((int) $request->input('allocation') === $server->allocation_id) { - return response()->json([ - 'error' => 'You are already using this as your default connection.', - ], 409); - } - - try { - $allocation = $server->allocations->where('id', $request->input('allocation'))->where('server_id', $server->id)->first(); - if (! $allocation) { - return response()->json([ - 'error' => 'No allocation matching your request was found in the system.', - ], 422); - } - - $repo = new Repositories\ServerRepository; - $repo->changeBuild($server->id, [ - 'default' => $allocation->ip . ':' . $allocation->port, - ]); - - return response('The default connection for this server has been updated. Please be aware that you will need to restart your server for this change to go into effect.'); - } catch (DisplayValidationException $ex) { - return response()->json([ - 'error' => json_decode($ex->getMessage(), true), - ], 422); - } catch (DisplayException $ex) { - return response()->json([ - 'error' => $ex->getMessage(), - ], 503); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'An unhandled exception occured while attemping to modify the default connection for this server.', - ], 503); - } - } - /** * Resets a database password for a server. * - * @param \Illuminate\Http\Request $request - * @param string $uuid + * @param \Illuminate\Http\Request $request + * @param string $uuid * @return \Illuminate\Http\JsonResponse * @deprecated */ diff --git a/app/Http/Controllers/Server/ConsoleController.php b/app/Http/Controllers/Server/ConsoleController.php new file mode 100644 index 000000000..ca20852e9 --- /dev/null +++ b/app/Http/Controllers/Server/ConsoleController.php @@ -0,0 +1,97 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Controllers\Server; + +use Illuminate\Contracts\Session\Session; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Traits\Controllers\JavascriptInjection; +use Illuminate\Contracts\Config\Repository as ConfigRepository; + +class ConsoleController extends Controller +{ + use JavascriptInjection; + + /** + * @var \Illuminate\Contracts\Config\Repository + */ + protected $config; + + /** + * @var \Illuminate\Contracts\Session\Session + */ + protected $session; + + /** + * ConsoleController constructor. + * + * @param \Illuminate\Contracts\Config\Repository $config + * @param \Illuminate\Contracts\Session\Session $session + */ + public function __construct( + ConfigRepository $config, + Session $session + ) { + $this->config = $config; + $this->session = $session; + } + + /** + * Render server index page with the console and power options. + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function index() + { + $server = $this->session->get('server_data.model'); + + $this->injectJavascript([ + 'meta' => [ + 'saveFile' => route('server.files.save', $server->uuidShort), + 'csrfToken' => csrf_token(), + ], + 'config' => [ + 'console_count' => $this->config->get('pterodactyl.console.count'), + 'console_freq' => $this->config->get('pterodactyl.console.frequency'), + ], + ]); + + return view('server.index'); + } + + /** + * Render a stand-alone console in the browser. + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function console() + { + $this->injectJavascript(['config' => [ + 'console_count' => $this->config->get('pterodactyl.console.count'), + 'console_freq' => $this->config->get('pterodactyl.console.frequency'), + ]]); + + return view('server.console'); + } +} diff --git a/app/Http/Controllers/Server/Files/DownloadController.php b/app/Http/Controllers/Server/Files/DownloadController.php new file mode 100644 index 000000000..32d4cb5f9 --- /dev/null +++ b/app/Http/Controllers/Server/Files/DownloadController.php @@ -0,0 +1,76 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Controllers\Server\Files; + +use Illuminate\Cache\Repository; +use Illuminate\Contracts\Session\Session; +use Pterodactyl\Http\Controllers\Controller; + +class DownloadController extends Controller +{ + /** + * @var \Illuminate\Cache\Repository + */ + protected $cache; + + /** + * @var \Illuminate\Contracts\Session\Session + */ + protected $session; + + /** + * DownloadController constructor. + * + * @param \Illuminate\Cache\Repository $cache + * @param \Illuminate\Contracts\Session\Session $session + */ + public function __construct(Repository $cache, Session $session) + { + $this->cache = $cache; + $this->session = $session; + } + + /** + * Setup a unique download link for a user to download a file from. + * + * @param string $uuid + * @param string $file + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + * + * @throws \Illuminate\Auth\Access\AuthorizationException + */ + public function index($uuid, $file) + { + $server = $this->session->get('server_data.model'); + $this->authorize('download-files', $server); + + $token = str_random(40); + $this->cache->tags(['Server:Downloads'])->put($token, ['server' => $server->uuid, 'path' => $file], 5); + + return redirect(sprintf( + '%s://%s:%s/server/file/download/%s', $server->node->scheme, $server->node->fqdn, $server->node->daemonListen, $token + )); + } +} diff --git a/app/Http/Controllers/Server/Files/FileActionsController.php b/app/Http/Controllers/Server/Files/FileActionsController.php new file mode 100644 index 000000000..878332a14 --- /dev/null +++ b/app/Http/Controllers/Server/Files/FileActionsController.php @@ -0,0 +1,161 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Controllers\Server\Files; + +use Illuminate\Log\Writer; +use Illuminate\Http\Request; +use Illuminate\Contracts\Session\Session; +use GuzzleHttp\Exception\RequestException; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Traits\Controllers\JavascriptInjection; +use Pterodactyl\Http\Requests\Server\UpdateFileContentsFormRequest; +use Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface; + +class FileActionsController extends Controller +{ + use JavascriptInjection; + + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface + */ + protected $fileRepository; + + /** + * @var \Illuminate\Contracts\Session\Session + */ + protected $session; + + /** + * @var \Illuminate\Log\Writer + */ + protected $writer; + + /** + * FileActionsController constructor. + * + * @param \Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface $fileRepository + * @param \Illuminate\Contracts\Session\Session $session + * @param \Illuminate\Log\Writer $writer + */ + public function __construct(FileRepositoryInterface $fileRepository, Session $session, Writer $writer) + { + $this->fileRepository = $fileRepository; + $this->session = $session; + $this->writer = $writer; + } + + /** + * Display server file index list. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + * + * @throws \Illuminate\Auth\Access\AuthorizationException + */ + public function index(Request $request) + { + $server = $this->session->get('server_data.model'); + $this->authorize('list-files', $server); + + $this->injectJavascript([ + 'meta' => [ + 'directoryList' => route('server.files.directory-list', $server->uuidShort), + 'csrftoken' => csrf_token(), + ], + 'permissions' => [ + 'moveFiles' => $request->user()->can('move-files', $server), + 'copyFiles' => $request->user()->can('copy-files', $server), + 'compressFiles' => $request->user()->can('compress-files', $server), + 'decompressFiles' => $request->user()->can('decompress-files', $server), + 'createFiles' => $request->user()->can('create-files', $server), + 'downloadFiles' => $request->user()->can('download-files', $server), + 'deleteFiles' => $request->user()->can('delete-files', $server), + ], + ]); + + return view('server.files.index'); + } + + /** + * Render page to manually create a file in the panel. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + * + * @throws \Illuminate\Auth\Access\AuthorizationException + */ + public function create(Request $request) + { + $this->authorize('create-files', $this->session->get('server_data.model')); + $this->injectJavascript(); + + return view('server.files.add', [ + 'directory' => (in_array($request->get('dir'), [null, '/', ''])) ? '' : trim($request->get('dir'), '/') . '/', + ]); + } + + /** + * Display a form to allow for editing of a file. + * + * @param \Pterodactyl\Http\Requests\Server\UpdateFileContentsFormRequest $request + * @param string $uuid + * @param string $file + * @return \Illuminate\View\View + * + * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function update(UpdateFileContentsFormRequest $request, $uuid, $file) + { + $server = $this->session->get('server_data.model'); + $this->authorize('edit-files', $server); + + $dirname = pathinfo($file, PATHINFO_DIRNAME); + try { + $content = $this->fileRepository->setNode($server->node_id) + ->setAccessServer($server->uuid) + ->setAccessToken($this->session->get('server_data.token')) + ->getContent($file); + } catch (RequestException $exception) { + $response = $exception->getResponse(); + $this->writer->warning($exception); + + throw new DisplayException(trans('exceptions.daemon_connection_failed', [ + 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), + ])); + } + + $this->injectJavascript(['stat' => $request->getStats()]); + + return view('server.files.edit', [ + 'file' => $file, + 'stat' => $request->getStats(), + 'contents' => $content, + 'directory' => (in_array($dirname, ['.', './', '/'])) ? '/' : trim($dirname, '/') . '/', + ]); + } +} diff --git a/app/Http/Controllers/Server/Files/RemoteRequestController.php b/app/Http/Controllers/Server/Files/RemoteRequestController.php new file mode 100644 index 000000000..5995d4717 --- /dev/null +++ b/app/Http/Controllers/Server/Files/RemoteRequestController.php @@ -0,0 +1,159 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Controllers\Server\Files; + +use Illuminate\Log\Writer; +use Illuminate\Http\Request; +use Illuminate\Contracts\Session\Session; +use GuzzleHttp\Exception\RequestException; +use Pterodactyl\Http\Controllers\Controller; +use Illuminate\Contracts\Config\Repository as ConfigRepository; +use Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface; + +class RemoteRequestController extends Controller +{ + /** + * @var \Illuminate\Contracts\Config\Repository + */ + protected $config; + + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface + */ + protected $fileRepository; + + /** + * @var \Illuminate\Contracts\Session\Session + */ + protected $session; + + /** + * @var \Illuminate\Log\Writer + */ + protected $writer; + + /** + * RemoteRequestController constructor. + * + * @param \Illuminate\Contracts\Config\Repository $config + * @param \Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface $fileRepository + * @param \Illuminate\Contracts\Session\Session $session + * @param \Illuminate\Log\Writer $writer + */ + public function __construct( + ConfigRepository $config, + FileRepositoryInterface $fileRepository, + Session $session, + Writer $writer + ) { + $this->config = $config; + $this->fileRepository = $fileRepository; + $this->session = $session; + $this->writer = $writer; + } + + /** + * Return a listing of a servers file directory. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse|\Illuminate\View\View + * + * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function directory(Request $request) + { + $server = $this->session->get('server_data.model'); + $this->authorize('list-files', $server); + + $requestDirectory = '/' . trim(urldecode($request->input('directory', '/')), '/'); + $directory = [ + 'header' => $requestDirectory !== '/' ? $requestDirectory : '', + 'first' => $requestDirectory !== '/', + ]; + + $goBack = explode('/', trim($requestDirectory, '/')); + if (! empty(array_filter($goBack)) && count($goBack) >= 2) { + array_pop($goBack); + + $directory['show'] = true; + $directory['link'] = '/' . implode('/', $goBack); + $directory['link_show'] = implode('/', $goBack) . '/'; + } + + try { + $listing = $this->fileRepository->setNode($server->node_id) + ->setAccessServer($server->uuid) + ->setAccessToken($this->session->get('server_data.token')) + ->getDirectory($requestDirectory); + } catch (RequestException $exception) { + $this->writer->warning($exception); + $response = $exception->getResponse(); + + return response()->json(['error' => trans('exceptions.daemon_connection_failed', [ + 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), + ])], 500); + } + + return view('server.files.list', [ + 'files' => $listing['files'], + 'folders' => $listing['folders'], + 'editableMime' => $this->config->get('pterodactyl.files.editable'), + 'directory' => $directory, + ]); + } + + /** + * Put the contents of a file onto the daemon. + * + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @return \Illuminate\Http\JsonResponse + * + * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function store(Request $request, $uuid) + { + $server = $this->session->get('server_data.model'); + $this->authorize('save-files', $server); + + try { + $this->fileRepository->setNode($server->node_id) + ->setAccessServer($server->uuid) + ->setAccessToken($this->session->get('server_data.token')) + ->putContent($request->input('file'), $request->input('contents')); + + return response('', 204); + } catch (RequestException $exception) { + $this->writer->warning($exception); + $response = $exception->getResponse(); + + return response()->json(['error' => trans('exceptions.daemon_connection_failed', [ + 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), + ])], 500); + } + } +} diff --git a/app/Http/Controllers/Server/ServerController.php b/app/Http/Controllers/Server/ServerController.php index ce6f59595..e5bad57af 100644 --- a/app/Http/Controllers/Server/ServerController.php +++ b/app/Http/Controllers/Server/ServerController.php @@ -26,195 +26,19 @@ namespace Pterodactyl\Http\Controllers\Server; use Log; use Alert; -use Cache; use Pterodactyl\Models; use Illuminate\Http\Request; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\ServerRepository; -use Pterodactyl\Repositories\Daemon\FileRepository; use Pterodactyl\Exceptions\DisplayValidationException; class ServerController extends Controller { - /** - * Renders server index page for specified server. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\View\View - */ - public function getIndex(Request $request, $uuid) - { - $server = Models\Server::byUuid($uuid); - - $server->js([ - 'meta' => [ - 'saveFile' => route('server.files.save', $server->uuidShort), - 'csrfToken' => csrf_token(), - ], - 'config' => [ - 'console_count' => config('pterodactyl.console.count'), - 'console_freq' => config('pterodactyl.console.frequency'), - ], - ]); - - return view('server.index', [ - 'server' => $server, - 'node' => $server->node, - ]); - } - - /** - * Renders server console as an individual item. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\View\View - */ - public function getConsole(Request $request, $uuid) - { - \Debugbar::disable(); - $server = Models\Server::byUuid($uuid); - - $server->js([ - 'config' => [ - 'console_count' => config('pterodactyl.console.count'), - 'console_freq' => config('pterodactyl.console.frequency'), - ], - ]); - - return view('server.console', [ - 'server' => $server, - 'node' => $server->node, - ]); - } - - /** - * Renders file overview page. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\View\View - */ - public function getFiles(Request $request, $uuid) - { - $server = Models\Server::byUuid($uuid); - $this->authorize('list-files', $server); - - $server->js([ - 'meta' => [ - 'directoryList' => route('server.files.directory-list', $server->uuidShort), - 'csrftoken' => csrf_token(), - ], - 'permissions' => [ - 'moveFiles' => $request->user()->can('move-files', $server), - 'copyFiles' => $request->user()->can('copy-files', $server), - 'compressFiles' => $request->user()->can('compress-files', $server), - 'decompressFiles' => $request->user()->can('decompress-files', $server), - 'createFiles' => $request->user()->can('create-files', $server), - 'downloadFiles' => $request->user()->can('download-files', $server), - 'deleteFiles' => $request->user()->can('delete-files', $server), - ], - ]); - - return view('server.files.index', [ - 'server' => $server, - 'node' => $server->node, - ]); - } - - /** - * Renders add file page. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\View\View - */ - public function getAddFile(Request $request, $uuid) - { - $server = Models\Server::byUuid($uuid); - $this->authorize('create-files', $server); - - $server->js(); - - return view('server.files.add', [ - 'server' => $server, - 'node' => $server->node, - 'directory' => (in_array($request->get('dir'), [null, '/', ''])) ? '' : trim($request->get('dir'), '/') . '/', - ]); - } - - /** - * Renders edit file page for a given file. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @param string $file - * @return \Illuminate\View\View - */ - public function getEditFile(Request $request, $uuid, $file) - { - $server = Models\Server::byUuid($uuid); - $this->authorize('edit-files', $server); - - $fileInfo = (object) pathinfo($file); - $controller = new FileRepository($uuid); - - try { - $fileContent = $controller->returnFileContents($file); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - - return redirect()->route('server.files.index', $uuid); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An error occured while attempting to load the requested file for editing, please try again.')->flash(); - - return redirect()->route('server.files.index', $uuid); - } - - $server->js([ - 'stat' => $fileContent['stat'], - ]); - - return view('server.files.edit', [ - 'server' => $server, - 'node' => $server->node, - 'file' => $file, - 'stat' => $fileContent['stat'], - 'contents' => $fileContent['file']->content, - 'directory' => (in_array($fileInfo->dirname, ['.', './', '/'])) ? '/' : trim($fileInfo->dirname, '/') . '/', - ]); - } - - /** - * Handles downloading a file for the user. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @param string $file - * @return \Illuminate\View\View - */ - public function getDownloadFile(Request $request, $uuid, $file) - { - $server = Models\Server::byUuid($uuid); - $this->authorize('download-files', $server); - - $token = str_random(40); - Cache::tags(['Server:Downloads'])->put($token, [ - 'server' => $server->uuid, - 'path' => $file, - ], 5); - - return redirect($server->node->scheme . '://' . $server->node->fqdn . ':' . $server->node->daemonListen . '/server/file/download/' . $token); - } - /** * Returns the allocation overview for a server. * - * @param \Illuminate\Http\Request $request - * @param string $uuid + * @param \Illuminate\Http\Request $request + * @param string $uuid * @return \Illuminate\View\View */ public function getAllocation(Request $request, $uuid) @@ -235,8 +59,8 @@ class ServerController extends Controller /** * Returns the startup overview for a server. * - * @param \Illuminate\Http\Request $request - * @param string $uuid + * @param \Illuminate\Http\Request $request + * @param string $uuid * @return \Illuminate\View\View */ public function getStartup(Request $request, $uuid) @@ -280,8 +104,8 @@ class ServerController extends Controller /** * Returns the database overview for a server. * - * @param \Illuminate\Http\Request $request - * @param string $uuid + * @param \Illuminate\Http\Request $request + * @param string $uuid * @return \Illuminate\View\View */ public function getDatabases(Request $request, $uuid) @@ -302,8 +126,8 @@ class ServerController extends Controller /** * Returns the SFTP overview for a server. * - * @param \Illuminate\Http\Request $request - * @param string $uuid + * @param \Illuminate\Http\Request $request + * @param string $uuid * @return \Illuminate\View\View */ public function getSFTP(Request $request, $uuid) @@ -321,8 +145,8 @@ class ServerController extends Controller /** * Handles changing the SFTP password for a server. * - * @param \Illuminate\Http\Request $request - * @param string $uuid + * @param \Illuminate\Http\Request $request + * @param string $uuid * @return \Illuminate\Http\RedirectResponse */ public function postSettingsSFTP(Request $request, $uuid) @@ -349,8 +173,8 @@ class ServerController extends Controller /** * Handles changing the startup settings for a server. * - * @param \Illuminate\Http\Request $request - * @param string $uuid + * @param \Illuminate\Http\Request $request + * @param string $uuid * @return \Illuminate\Http\RedirectResponse */ public function postSettingsStartup(Request $request, $uuid) diff --git a/app/Http/Controllers/Server/SubuserController.php b/app/Http/Controllers/Server/SubuserController.php index 9a8b35075..3d287fd9e 100644 --- a/app/Http/Controllers/Server/SubuserController.php +++ b/app/Http/Controllers/Server/SubuserController.php @@ -24,62 +24,118 @@ namespace Pterodactyl\Http\Controllers\Server; -use Log; -use Auth; -use Alert; -use Pterodactyl\Models; use Illuminate\Http\Request; -use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Models\Permission; +use Prologue\Alerts\AlertsMessageBag; +use Illuminate\Contracts\Session\Session; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\SubuserRepository; -use Pterodactyl\Exceptions\DisplayValidationException; +use Pterodactyl\Services\Subusers\SubuserUpdateService; +use Pterodactyl\Traits\Controllers\JavascriptInjection; +use Pterodactyl\Services\Subusers\SubuserCreationService; +use Pterodactyl\Services\Subusers\SubuserDeletionService; +use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; class SubuserController extends Controller { + use JavascriptInjection; + + /** + * @var \Prologue\Alerts\AlertsMessageBag + */ + protected $alert; + + /** + * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface + */ + protected $repository; + + /** + * @var \Illuminate\Contracts\Session\Session + */ + protected $session; + + /** + * @var \Pterodactyl\Services\Subusers\SubuserCreationService + */ + protected $subuserCreationService; + + /** + * @var \Pterodactyl\Services\Subusers\SubuserDeletionService + */ + protected $subuserDeletionService; + + /** + * @var \Pterodactyl\Services\Subusers\SubuserUpdateService + */ + protected $subuserUpdateService; + + /** + * SubuserController constructor. + * + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Illuminate\Contracts\Session\Session $session + * @param \Pterodactyl\Services\Subusers\SubuserCreationService $subuserCreationService + * @param \Pterodactyl\Services\Subusers\SubuserDeletionService $subuserDeletionService + * @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $repository + * @param \Pterodactyl\Services\Subusers\SubuserUpdateService $subuserUpdateService + */ + public function __construct( + AlertsMessageBag $alert, + Session $session, + SubuserCreationService $subuserCreationService, + SubuserDeletionService $subuserDeletionService, + SubuserRepositoryInterface $repository, + SubuserUpdateService $subuserUpdateService + ) { + $this->alert = $alert; + $this->repository = $repository; + $this->session = $session; + $this->subuserCreationService = $subuserCreationService; + $this->subuserDeletionService = $subuserDeletionService; + $this->subuserUpdateService = $subuserUpdateService; + } + /** * Displays the subuser overview index. * - * @param \Illuminate\Http\Request $request - * @param string $uuid * @return \Illuminate\View\View + * + * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function index(Request $request, $uuid) + public function index() { - $server = Models\Server::byUuid($uuid)->load('subusers.user'); + $server = $this->session->get('server_data.model'); $this->authorize('list-subusers', $server); - $server->js(); + $this->injectJavascript(); return view('server.users.index', [ - 'server' => $server, - 'node' => $server->node, - 'subusers' => $server->subusers, + 'subusers' => $this->repository->findWhere([['server_id', '=', $server->id]]), ]); } /** * Displays the a single subuser overview. * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @param int $id + * @param string $uuid + * @param int $id * @return \Illuminate\View\View + * + * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function view(Request $request, $uuid, $id) + public function view($uuid, $id) { - $server = Models\Server::byUuid($uuid)->load('node'); + $server = $this->session->get('server_data.model'); $this->authorize('view-subuser', $server); - $subuser = Models\Subuser::with('permissions', 'user') - ->where('server_id', $server->id)->findOrFail($id); - - $server->js(); + $subuser = $this->repository->getWithPermissions($id); + $this->injectJavascript(); return view('server.users.view', [ - 'server' => $server, - 'node' => $server->node, 'subuser' => $subuser, - 'permlist' => Models\Permission::listPermissions(), + 'permlist' => Permission::getPermissions(), 'permissions' => $subuser->permissions->mapWithKeys(function ($item, $key) { return [$item->permission => true]; }), @@ -89,133 +145,91 @@ class SubuserController extends Controller /** * Handles editing a subuser. * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @param int $id + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @param int $id * @return \Illuminate\Http\RedirectResponse + * + * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function update(Request $request, $uuid, $id) { - $server = Models\Server::byUuid($uuid); + $server = $this->session->get('server_data.model'); $this->authorize('edit-subuser', $server); - $subuser = Models\Subuser::where('server_id', $server->id)->findOrFail($id); + $this->subuserUpdateService->handle($id, $request->input('permissions', [])); + $this->alert->success(trans('server.users.user_updated'))->flash(); - try { - if ($subuser->user_id === Auth::user()->id) { - throw new DisplayException('You are not authorized to edit you own account.'); - } - - $repo = new SubuserRepository; - $repo->update($subuser->id, [ - 'permissions' => $request->input('permissions'), - 'server' => $server->id, - 'user' => $subuser->user_id, - ]); - - Alert::success('Subuser permissions have successfully been updated.')->flash(); - } catch (DisplayValidationException $ex) { - return redirect()->route('server.subusers.view', [ - 'uuid' => $uuid, - 'id' => $id, - ])->withErrors(json_decode($ex->getMessage())); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unknown error occured while attempting to update this subuser.')->flash(); - } - - return redirect()->route('server.subusers.view', [ - 'uuid' => $uuid, - 'id' => $id, - ]); + return redirect()->route('server.subusers.view', ['uuid' => $uuid, 'id' => $id]); } /** * Display new subuser creation page. * - * @param \Illuminate\Http\Request $request - * @param string $uuid * @return \Illuminate\View\View + * + * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function create(Request $request, $uuid) + public function create() { - $server = Models\Server::byUuid($uuid); + $server = $this->session->get('server_data.model'); $this->authorize('create-subuser', $server); - $server->js(); - return view('server.users.new', [ - 'server' => $server, - 'permissions' => Models\Permission::listPermissions(), - 'node' => $server->node, - ]); + $this->injectJavascript(); + + return view('server.users.new', ['permissions' => Permission::getPermissions()]); } /** * Handles creating a new subuser. * - * @param \Illuminate\Http\Request $request - * @param string $uuid + * @param \Illuminate\Http\Request $request + * @param string $uuid * @return \Illuminate\Http\RedirectResponse + * + * @throws \Exception + * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException + * @throws \Pterodactyl\Exceptions\Service\Subuser\UserIsServerOwnerException */ public function store(Request $request, $uuid) { - $server = Models\Server::byUuid($uuid); + $server = $this->session->get('server_data.model'); $this->authorize('create-subuser', $server); - try { - $repo = new SubuserRepository; - $subuser = $repo->create($server->id, $request->only([ - 'permissions', 'email', - ])); - Alert::success('Successfully created new subuser.')->flash(); + $subuser = $this->subuserCreationService->handle($server, $request->input('email'), $request->input('permissions', [])); + $this->alert->success(trans('server.users.user_assigned'))->flash(); - return redirect()->route('server.subusers.view', [ - 'uuid' => $uuid, - 'id' => $subuser->id, - ]); - } catch (DisplayValidationException $ex) { - return redirect()->route('server.subusers.new', $uuid)->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unknown error occured while attempting to add a new subuser.')->flash(); - } - - return redirect()->route('server.subusers.new', $uuid)->withInput(); + return redirect()->route('server.subusers.view', [ + 'uuid' => $uuid, + 'id' => $subuser->id, + ]); } /** * Handles deleting a subuser. * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @param int $id - * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response + * @param string $uuid + * @param int $id + * @return \Illuminate\Http\Response + * + * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function delete(Request $request, $uuid, $id) + public function delete($uuid, $id) { - $server = Models\Server::byUuid($uuid); + $server = $this->session->get('server_data.model'); $this->authorize('delete-subuser', $server); - try { - $subuser = Models\Subuser::where('server_id', $server->id)->findOrFail($id); + $this->subuserDeletionService->handle($id); - $repo = new SubuserRepository; - $repo->delete($subuser->id); - - return response('', 204); - } catch (DisplayException $ex) { - response()->json([ - 'error' => $ex->getMessage(), - ], 422); - } catch (\Exception $ex) { - Log::error($ex); - response()->json([ - 'error' => 'An unknown error occured while attempting to delete this subuser.', - ], 503); - } + return response('', 204); } } diff --git a/app/Http/Controllers/Server/TaskController.php b/app/Http/Controllers/Server/TaskController.php index a3908943a..618a14902 100644 --- a/app/Http/Controllers/Server/TaskController.php +++ b/app/Http/Controllers/Server/TaskController.php @@ -38,8 +38,8 @@ class TaskController extends Controller /** * Display task index page. * - * @param \Illuminate\Http\Request $request - * @param string $uuid + * @param \Illuminate\Http\Request $request + * @param string $uuid * @return \Illuminate\View\View */ public function index(Request $request, $uuid) @@ -62,8 +62,8 @@ class TaskController extends Controller /** * Display new task page. * - * @param \Illuminate\Http\Request $request - * @param string $uuid + * @param \Illuminate\Http\Request $request + * @param string $uuid * @return \Illuminate\View\View */ public function create(Request $request, $uuid) @@ -81,8 +81,8 @@ class TaskController extends Controller /** * Handle creation of new task. * - * @param \Illuminate\Http\Request $request - * @param string $uuid + * @param \Illuminate\Http\Request $request + * @param string $uuid * @return \Illuminate\Http\RedirectResponse */ public function store(Request $request, $uuid) @@ -112,9 +112,9 @@ class TaskController extends Controller /** * Handle deletion of a task. * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @param int $id + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @param int $id * @return \Illuminate\Http\JsonResponse */ public function delete(Request $request, $uuid, $id) @@ -146,9 +146,9 @@ class TaskController extends Controller /** * Toggle the status of a task. * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @param int $id + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @param int $id * @return \Illuminate\Http\JsonResponse */ public function toggle(Request $request, $uuid, $id) diff --git a/app/Http/Controllers/Server/Tasks/TaskManagementController.php b/app/Http/Controllers/Server/Tasks/TaskManagementController.php new file mode 100644 index 000000000..80c2e6679 --- /dev/null +++ b/app/Http/Controllers/Server/Tasks/TaskManagementController.php @@ -0,0 +1,171 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Controllers\Server\Tasks; + +use Illuminate\Contracts\Session\Session; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Services\Tasks\TaskCreationService; +use Pterodactyl\Contracts\Extensions\HashidsInterface; +use Pterodactyl\Traits\Controllers\JavascriptInjection; +use Pterodactyl\Contracts\Repository\TaskRepositoryInterface; +use Pterodactyl\Http\Requests\Server\TaskCreationFormRequest; + +class TaskManagementController extends Controller +{ + use JavascriptInjection; + + /** + * @var \Pterodactyl\Services\Tasks\TaskCreationService + */ + protected $creationService; + + /** + * @var \Pterodactyl\Contracts\Extensions\HashidsInterface + */ + protected $hashids; + + /** + * @var \Pterodactyl\Contracts\Repository\TaskRepositoryInterface + */ + protected $repository; + + /** + * @var \Illuminate\Contracts\Session\Session + */ + protected $session; + + /** + * TaskManagementController constructor. + * + * @param \Pterodactyl\Contracts\Extensions\HashidsInterface $hashids + * @param \Illuminate\Contracts\Session\Session $session + * @param \Pterodactyl\Services\Tasks\TaskCreationService $creationService + * @param \Pterodactyl\Contracts\Repository\TaskRepositoryInterface $repository + */ + public function __construct( + HashidsInterface $hashids, + Session $session, + TaskCreationService $creationService, + TaskRepositoryInterface $repository + ) { + $this->creationService = $creationService; + $this->hashids = $hashids; + $this->repository = $repository; + $this->session = $session; + } + + /** + * Display the task page listing. + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + * + * @throws \Illuminate\Auth\Access\AuthorizationException + */ + public function index() + { + $server = $this->session->get('server_data.model'); + $this->authorize('list-tasks', $server); + $this->injectJavascript(); + + return view('server.tasks.index', [ + 'tasks' => $this->repository->getParentTasksWithChainCount($server->id), + 'actions' => [ + 'command' => trans('server.tasks.actions.command'), + 'power' => trans('server.tasks.actions.power'), + ], + ]); + } + + /** + * Display the task creation page. + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + * + * @throws \Illuminate\Auth\Access\AuthorizationException + */ + public function create() + { + $server = $this->session->get('server_data.model'); + $this->authorize('create-task', $server); + $this->injectJavascript(); + + return view('server.tasks.new'); + } + + /** + * @param \Pterodactyl\Http\Requests\Server\TaskCreationFormRequest $request + * + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Exception + * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function store(TaskCreationFormRequest $request) + { + $server = $this->session->get('server_data.model'); + $this->authorize('create-task', $server); + + $task = $this->creationService->handle($server, $request->normalize(), $request->getChainedTasks()); + + return redirect()->route('server.tasks.view', [ + 'server' => $server->uuidShort, + 'task' => $task->id, + ]); + } + + /** + * Return a view to modify task settings. + * + * @param string $uuid + * @param string $task + * @return \Illuminate\View\View + * + * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function view($uuid, $task) + { + $server = $this->session->get('server_data.model'); + $this->authorize('edit-task', $server); + $task = $this->repository->getTaskForServer($this->hashids->decodeFirst($task, 0), $server->id); + + $this->injectJavascript([ + 'chained' => $task->chained->map(function ($chain) { + return collect($chain->toArray())->only('action', 'chain_delay', 'data')->all(); + }), + ]); + + return view('server.tasks.view', ['task' => $task]); + } + + public function update(TaskCreationFormRequest $request, $uuid, $task) + { + $server = $this->session->get('server_data.model'); + $this->authorize('edit-task', $server); + $task = $this->repository->getTaskForServer($this->hashids->decodeFirst($task, 0), $server->id); + } +} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index a70895a3e..1e98b9cfd 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -54,7 +54,8 @@ class Kernel extends HttpKernel 'auth' => \Illuminate\Auth\Middleware\Authenticate::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'guest' => \Pterodactyl\Http\Middleware\RedirectIfAuthenticated::class, - 'server' => \Pterodactyl\Http\Middleware\CheckServer::class, + 'server' => \Pterodactyl\Http\Middleware\ServerAuthenticate::class, + 'subuser' => \Pterodactyl\Http\Middleware\SubuserAccessAuthenticate::class, 'admin' => \Pterodactyl\Http\Middleware\AdminAuthenticate::class, 'daemon' => \Pterodactyl\Http\Middleware\DaemonAuthenticate::class, 'csrf' => \Pterodactyl\Http\Middleware\VerifyCsrfToken::class, diff --git a/app/Http/Middleware/AdminAuthenticate.php b/app/Http/Middleware/AdminAuthenticate.php index 175210929..2360a5210 100644 --- a/app/Http/Middleware/AdminAuthenticate.php +++ b/app/Http/Middleware/AdminAuthenticate.php @@ -25,46 +25,27 @@ namespace Pterodactyl\Http\Middleware; use Closure; -use Illuminate\Contracts\Auth\Guard; class AdminAuthenticate { - /** - * The Guard implementation. - * - * @var \Illuminate\Contracts\Auth\Guard - */ - protected $auth; - - /** - * Create a new filter instance. - * - * @param \Illuminate\Contracts\Auth\Guard $auth - * @return void - */ - public function __construct(Guard $auth) - { - $this->auth = $auth; - } - /** * Handle an incoming request. * - * @param \Illuminate\Http\Request $request - * @param \Closure $next + * @param \Illuminate\Http\Request $request + * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { - if ($this->auth->guest()) { - if ($request->ajax()) { + if (! $request->user()) { + if ($request->expectsJson() || $request->json()) { return response('Unauthorized.', 401); } else { return redirect()->guest('auth/login'); } } - if ($this->auth->user()->root_admin !== 1) { + if (! $request->user()->root_admin) { return abort(403); } diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index b6db005ef..06e2d6b70 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -17,8 +17,7 @@ class Authenticate /** * Create a new filter instance. * - * @param \Illuminate\Contracts\Auth\Guard $auth - * @return void + * @param \Illuminate\Contracts\Auth\Guard $auth */ public function __construct(Guard $auth) { @@ -28,8 +27,8 @@ class Authenticate /** * Handle an incoming request. * - * @param \Illuminate\Http\Request $request - * @param \Closure $next + * @param \Illuminate\Http\Request $request + * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) diff --git a/app/Http/Middleware/DaemonAuthenticate.php b/app/Http/Middleware/DaemonAuthenticate.php index b924b6fb5..9e190ba9f 100644 --- a/app/Http/Middleware/DaemonAuthenticate.php +++ b/app/Http/Middleware/DaemonAuthenticate.php @@ -49,8 +49,7 @@ class DaemonAuthenticate /** * Create a new filter instance. * - * @param \Illuminate\Contracts\Auth\Guard $auth - * @return void + * @param \Illuminate\Contracts\Auth\Guard $auth */ public function __construct(Guard $auth) { @@ -60,8 +59,8 @@ class DaemonAuthenticate /** * Handle an incoming request. * - * @param \Illuminate\Http\Request $request - * @param \Closure $next + * @param \Illuminate\Http\Request $request + * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) diff --git a/app/Http/Middleware/EncryptCookies.php b/app/Http/Middleware/EncryptCookies.php index 8e8559f23..eefb3359f 100644 --- a/app/Http/Middleware/EncryptCookies.php +++ b/app/Http/Middleware/EncryptCookies.php @@ -12,6 +12,5 @@ class EncryptCookies extends BaseEncrypter * @var array */ protected $except = [ - // ]; } diff --git a/app/Http/Middleware/HMACAuthorization.php b/app/Http/Middleware/HMACAuthorization.php index 4f5c86f2b..12b6ac40f 100644 --- a/app/Http/Middleware/HMACAuthorization.php +++ b/app/Http/Middleware/HMACAuthorization.php @@ -68,8 +68,6 @@ class HMACAuthorization /** * Construct class instance. - * - * @return void */ public function __construct() { @@ -80,8 +78,8 @@ class HMACAuthorization /** * Handle an incoming request for the API. * - * @param \Illuminate\Http\Request $request - * @param \Closure $next + * @param \Illuminate\Http\Request $request + * @param \Closure $next * @return mixed */ public function handle(Request $request, Closure $next) @@ -101,7 +99,6 @@ class HMACAuthorization /** * Checks that the Bearer token is provided and in a valid format. * - * @return void * * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException */ @@ -126,7 +123,6 @@ class HMACAuthorization * Determine if the request contains a valid public API key * as well as permissions for the resource. * - * @return void * * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException */ @@ -164,7 +160,6 @@ class HMACAuthorization * Determine if the HMAC sent in the request matches the one generated * on the panel side. * - * @return void * * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException */ diff --git a/app/Http/Middleware/LanguageMiddleware.php b/app/Http/Middleware/LanguageMiddleware.php index 44553ebef..c83f6aa15 100644 --- a/app/Http/Middleware/LanguageMiddleware.php +++ b/app/Http/Middleware/LanguageMiddleware.php @@ -35,8 +35,8 @@ class LanguageMiddleware /** * Handle an incoming request. * - * @param \Illuminate\Http\Request $request - * @param \Closure $next + * @param \Illuminate\Http\Request $request + * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) diff --git a/app/Http/Middleware/RedirectIfAuthenticated.php b/app/Http/Middleware/RedirectIfAuthenticated.php index 731a1767f..a25e05fb2 100644 --- a/app/Http/Middleware/RedirectIfAuthenticated.php +++ b/app/Http/Middleware/RedirectIfAuthenticated.php @@ -10,9 +10,9 @@ class RedirectIfAuthenticated /** * Handle an incoming request. * - * @param \Illuminate\Http\Request $request - * @param \Closure $next - * @param string|null $guard + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @param string|null $guard * @return mixed */ public function handle($request, Closure $next, $guard = null) diff --git a/app/Http/Middleware/Server/SubuserAccess.php b/app/Http/Middleware/Server/SubuserAccess.php new file mode 100644 index 000000000..97e08af5a --- /dev/null +++ b/app/Http/Middleware/Server/SubuserAccess.php @@ -0,0 +1,84 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Middleware\Server; + +use Closure; +use Illuminate\Contracts\Session\Session; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; + +class SubuserAccess +{ + /** + * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface + */ + protected $repository; + + /** + * @var \Illuminate\Contracts\Session\Session + */ + protected $session; + + /** + * SubuserAccess constructor. + * + * @param \Illuminate\Contracts\Session\Session $session + * @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $repository + */ + public function __construct(Session $session, SubuserRepositoryInterface $repository) + { + $this->repository = $repository; + $this->session = $session; + } + + /** + * Determine if a user has permission to access a subuser. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle($request, Closure $next) + { + $server = $this->session->get('server_data.model'); + + $subuser = $this->repository->find($request->route()->parameter('subuser', 0)); + if ($subuser->server_id !== $server->id) { + throw new NotFoundHttpException; + } + + if ($request->method() === 'PATCH') { + if ($subuser->user_id === $request->user()->id) { + throw new DisplayException(trans('exceptions.subusers.editing_self')); + } + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/CheckServer.php b/app/Http/Middleware/ServerAuthenticate.php similarity index 54% rename from app/Http/Middleware/CheckServer.php rename to app/Http/Middleware/ServerAuthenticate.php index 4cfe08191..b5d3fd1c2 100644 --- a/app/Http/Middleware/CheckServer.php +++ b/app/Http/Middleware/ServerAuthenticate.php @@ -24,106 +24,102 @@ namespace Pterodactyl\Http\Middleware; -use Auth; use Closure; use Illuminate\Http\Request; use Pterodactyl\Models\Server; +use Illuminate\Contracts\Session\Session; use Illuminate\Auth\AuthenticationException; +use Illuminate\Contracts\Config\Repository as ConfigRepository; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; -class CheckServer +class ServerAuthenticate { /** - * The elquent model for the server. - * + * @var \Illuminate\Contracts\Config\Repository + */ + protected $config; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $repository; + + /** * @var \Pterodactyl\Models\Server */ protected $server; /** - * The request object. - * - * @var \Illuminate\Http\Request + * @var \Illuminate\Contracts\Session\Session */ - protected $request; + protected $session; /** - * Handle an incoming request. + * ServerAuthenticate constructor. * - * @param \Illuminate\Http\Request $request - * @param \Closure $next + * @param \Illuminate\Contracts\Config\Repository $config + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository + * @param \Illuminate\Contracts\Session\Session $session + */ + public function __construct( + ConfigRepository $config, + ServerRepositoryInterface $repository, + Session $session + ) { + $this->config = $config; + $this->repository = $repository; + $this->session = $session; + } + + /** + * Determine if a given user has permission to access a server. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next * @return mixed + * + * @throws \Illuminate\Auth\AuthenticationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function handle(Request $request, Closure $next) { - if (! Auth::user()) { - throw new AuthenticationException(); + if (! $request->user()) { + throw new AuthenticationException; } - $this->request = $request; - $this->server = Server::byUuid($request->route()->server); + $attributes = $request->route()->parameter('server'); + $isApiRequest = $request->expectsJson() || $request->is(...$this->config->get('pterodactyl.json_routes', [])); + $server = $this->repository->getByUuid($attributes instanceof Server ? $attributes->uuid : $attributes); + + if (! $server) { + if ($isApiRequest) { + throw new NotFoundHttpException('The requested server was not found on the system.'); + } - if (! $this->exists()) { return response()->view('errors.404', [], 404); } - if ($this->suspended()) { + if ($server->suspended) { + if ($isApiRequest) { + throw new AccessDeniedHttpException('Server is suspended.'); + } + return response()->view('errors.suspended', [], 403); } - if (! $this->installed()) { + if ($server->installed !== 1) { + if ($isApiRequest) { + throw new AccessDeniedHttpException('Server is completing install process.'); + } + return response()->view('errors.installing', [], 403); } + // Store the server in the session. + $this->session->now('server_data.model', $server); + return $next($request); } - - /** - * Determine if the server was found on the system. - * - * @return bool - */ - protected function exists() - { - if (! $this->server) { - if ($this->request->expectsJson() || $this->request->is(...config('pterodactyl.json_routes'))) { - throw new NotFoundHttpException('The requested server was not found on the system.'); - } - } - - return (! $this->server) ? false : true; - } - - /** - * Determine if the server is suspended. - * - * @return bool - */ - protected function suspended() - { - if ($this->server->suspended) { - if ($this->request->expectsJson() || $this->request->is(...config('pterodactyl.json_routes'))) { - throw new AccessDeniedHttpException('Server is suspended.'); - } - } - - return $this->server->suspended; - } - - /** - * Determine if the server is installed. - * - * @return bool - */ - protected function installed() - { - if ($this->server->installed !== 1) { - if ($this->request->expectsJson() || $this->request->is(...config('pterodactyl.json_routes'))) { - throw new AccessDeniedHttpException('Server is completing install process.'); - } - } - - return $this->server->installed === 1; - } } diff --git a/app/Http/Middleware/SubuserAccessAuthenticate.php b/app/Http/Middleware/SubuserAccessAuthenticate.php new file mode 100644 index 000000000..b3b54fb0b --- /dev/null +++ b/app/Http/Middleware/SubuserAccessAuthenticate.php @@ -0,0 +1,81 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Middleware; + +use Closure; +use Illuminate\Http\Request; +use Illuminate\Contracts\Session\Session; +use Illuminate\Auth\AuthenticationException; +use Pterodactyl\Services\Servers\ServerAccessHelperService; +use Pterodactyl\Exceptions\Service\Server\UserNotLinkedToServerException; + +class SubuserAccessAuthenticate +{ + /** + * @var \Pterodactyl\Services\Servers\ServerAccessHelperService + */ + protected $accessHelperService; + + /** + * @var \Illuminate\Contracts\Session\Session + */ + protected $session; + + /** + * SubuserAccessAuthenticate constructor. + * + * @param \Pterodactyl\Services\Servers\ServerAccessHelperService $accessHelperService + * @param \Illuminate\Contracts\Session\Session $session + */ + public function __construct( + ServerAccessHelperService $accessHelperService, + Session $session + ) { + $this->accessHelperService = $accessHelperService; + $this->session = $session; + } + + /** + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + * + * @throws \Illuminate\Auth\AuthenticationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle(Request $request, Closure $next) + { + $server = $this->session->get('server_data.model'); + + try { + $token = $this->accessHelperService->handle($server, $request->user()); + $this->session->now('server_data.token', $token); + } catch (UserNotLinkedToServerException $exception) { + throw new AuthenticationException('This account does not have permission to access this server.'); + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/VerifyReCaptcha.php b/app/Http/Middleware/VerifyReCaptcha.php index 58c0597aa..07a0783c7 100644 --- a/app/Http/Middleware/VerifyReCaptcha.php +++ b/app/Http/Middleware/VerifyReCaptcha.php @@ -10,8 +10,8 @@ class VerifyReCaptcha /** * Handle an incoming request. * - * @param \Illuminate\Http\Request $request - * @param \Closure $next + * @param \Illuminate\Http\Request $request + * @param \Closure $next * @return \Illuminate\Http\RediectResponse */ public function handle($request, Closure $next) diff --git a/app/Http/Controllers/Base/LanguageController.php b/app/Http/Requests/Admin/AdminFormRequest.php similarity index 52% rename from app/Http/Controllers/Base/LanguageController.php rename to app/Http/Requests/Admin/AdminFormRequest.php index 67d04f66c..a3442a568 100644 --- a/app/Http/Controllers/Base/LanguageController.php +++ b/app/Http/Requests/Admin/AdminFormRequest.php @@ -1,5 +1,5 @@ . * @@ -22,50 +22,41 @@ * SOFTWARE. */ -namespace Pterodactyl\Http\Controllers\Base; +namespace Pterodactyl\Http\Requests\Admin; -use Auth; -use Session; -use Illuminate\Http\Request; -use Pterodactyl\Models\User; -use Pterodactyl\Http\Controllers\Controller; +use Illuminate\Foundation\Http\FormRequest; -class LanguageController extends Controller +abstract class AdminFormRequest extends FormRequest { - /** - * A list of supported languages on the panel. - * - * @var array - */ - protected $languages = [ - 'de' => 'German', - 'en' => 'English', - 'et' => 'Estonian', - 'nb' => 'Norwegian', - 'nl' => 'Dutch', - 'pt' => 'Portuguese', - 'ro' => 'Romanian', - 'ru' => 'Russian', - ]; + abstract public function rules(); /** - * Sets the language for a user. + * Determine if the user is an admin and has permission to access this + * form controller in the first place. * - * @param \Illuminate\Http\Request $request - * @param string $language - * @return \Illuminate\Http\RedirectResponse + * @return bool */ - public function setLanguage(Request $request, $language) + public function authorize() { - if (array_key_exists($language, $this->languages)) { - if (Auth::check()) { - $user = User::findOrFail(Auth::user()->id); - $user->language = $language; - $user->save(); - } - Session::put('applocale', $language); + if (is_null($this->user())) { + return false; } - return redirect()->back(); + return (bool) $this->user()->root_admin; + } + + /** + * Return only the fields that we are interested in from the request. + * This will include empty fields as a null value. + * + * @param array $only + * @return array + */ + public function normalize($only = []) + { + return array_merge( + $this->only($only), + $this->intersect(array_keys($this->rules())) + ); } } diff --git a/app/Http/Requests/Admin/BaseFormRequest.php b/app/Http/Requests/Admin/BaseFormRequest.php new file mode 100644 index 000000000..0d9884254 --- /dev/null +++ b/app/Http/Requests/Admin/BaseFormRequest.php @@ -0,0 +1,35 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Requests\Admin; + +class BaseFormRequest extends AdminFormRequest +{ + public function rules() + { + return [ + 'company' => 'required|between:1,256', + ]; + } +} diff --git a/app/Http/Requests/Admin/DatabaseHostFormRequest.php b/app/Http/Requests/Admin/DatabaseHostFormRequest.php new file mode 100644 index 000000000..66c95703f --- /dev/null +++ b/app/Http/Requests/Admin/DatabaseHostFormRequest.php @@ -0,0 +1,42 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Requests\Admin; + +use Pterodactyl\Models\DatabaseHost; + +class DatabaseHostFormRequest extends AdminFormRequest +{ + /** + * @return mixed + */ + public function rules() + { + if ($this->method() !== 'POST') { + return DatabaseHost::getUpdateRulesForId($this->route()->parameter('host')->id); + } + + return DatabaseHost::getCreateRules(); + } +} diff --git a/app/Http/Requests/Admin/LocationFormRequest.php b/app/Http/Requests/Admin/LocationFormRequest.php new file mode 100644 index 000000000..a4c8ca8d3 --- /dev/null +++ b/app/Http/Requests/Admin/LocationFormRequest.php @@ -0,0 +1,44 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Requests\Admin; + +use Pterodactyl\Models\Location; + +class LocationFormRequest extends AdminFormRequest +{ + /** + * Setup the validation rules to use for these requests. + * + * @return array + */ + public function rules() + { + if ($this->method() === 'PATCH') { + return Location::getUpdateRulesForId($this->route()->parameter('location')->id); + } + + return Location::getCreateRules(); + } +} diff --git a/app/Http/Requests/Admin/Node/AllocationAliasFormRequest.php b/app/Http/Requests/Admin/Node/AllocationAliasFormRequest.php new file mode 100644 index 000000000..a2832567f --- /dev/null +++ b/app/Http/Requests/Admin/Node/AllocationAliasFormRequest.php @@ -0,0 +1,41 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Requests\Admin\Node; + +use Pterodactyl\Http\Requests\Admin\AdminFormRequest; + +class AllocationAliasFormRequest extends AdminFormRequest +{ + /** + * @return array + */ + public function rules() + { + return [ + 'alias' => 'required|nullable|string', + 'allocation_id' => 'required|numeric|exists:allocations,id', + ]; + } +} diff --git a/app/Http/Requests/Admin/Node/AllocationFormRequest.php b/app/Http/Requests/Admin/Node/AllocationFormRequest.php new file mode 100644 index 000000000..162896b5e --- /dev/null +++ b/app/Http/Requests/Admin/Node/AllocationFormRequest.php @@ -0,0 +1,42 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Requests\Admin\Node; + +use Pterodactyl\Http\Requests\Admin\AdminFormRequest; + +class AllocationFormRequest extends AdminFormRequest +{ + /** + * @return array + */ + public function rules() + { + return [ + 'allocation_ip' => 'required|string', + 'allocation_alias' => 'sometimes|string|max:255', + 'allocation_ports' => 'required|array', + ]; + } +} diff --git a/app/Http/Requests/Admin/Node/NodeFormRequest.php b/app/Http/Requests/Admin/Node/NodeFormRequest.php new file mode 100644 index 000000000..4ab23aad2 --- /dev/null +++ b/app/Http/Requests/Admin/Node/NodeFormRequest.php @@ -0,0 +1,63 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Requests\Admin\Node; + +use Pterodactyl\Models\Node; +use Pterodactyl\Http\Requests\Admin\AdminFormRequest; + +class NodeFormRequest extends AdminFormRequest +{ + /** + * Get rules to apply to data in this request. + */ + public function rules() + { + if ($this->method() === 'PATCH') { + return Node::getUpdateRulesForId($this->route()->parameter('node')->id); + } + + return Node::getCreateRules(); + } + + /** + * Run validation after the rules above have been applied. + * + * @param \Illuminate\Validation\Validator $validator + */ + public function withValidator($validator) + { + $validator->after(function ($validator) { + // Check that the FQDN is a valid IP address. + if (! filter_var(gethostbyname($this->input('fqdn')), FILTER_VALIDATE_IP)) { + $validator->errors()->add('fqdn', trans('admin/node.validation.fqdn_not_resolvable')); + } + + // Check that if using HTTPS the FQDN is not an IP address. + if (filter_var($this->input('fqdn'), FILTER_VALIDATE_IP) && $this->input('scheme') === 'https') { + $validator->errors()->add('fqdn', trans('admin/node.validation.fqdn_required_for_ssl')); + } + }); + } +} diff --git a/app/Repositories/HelperRepository.php b/app/Http/Requests/Admin/PackFormRequest.php similarity index 56% rename from app/Repositories/HelperRepository.php rename to app/Http/Requests/Admin/PackFormRequest.php index 204480ec2..75fd9063e 100644 --- a/app/Repositories/HelperRepository.php +++ b/app/Http/Requests/Admin/PackFormRequest.php @@ -1,5 +1,5 @@ . * @@ -22,52 +22,43 @@ * SOFTWARE. */ -namespace Pterodactyl\Repositories; +namespace Pterodactyl\Http\Requests\Admin; -class HelperRepository +use Pterodactyl\Models\Pack; +use Pterodactyl\Services\Packs\PackCreationService; + +class PackFormRequest extends AdminFormRequest { /** - * Listing of editable files in the control panel. - * - * @var array - */ - protected static $editable = [ - 'application/json', - 'application/javascript', - 'application/xml', - 'application/xhtml+xml', - 'text/xml', - 'text/css', - 'text/html', - 'text/plain', - 'text/x-perl', - 'text/x-shellscript', - 'inode/x-empty', - ]; - - /** - * Converts from bytes to the largest possible size that is still readable. - * - * @param int $bytes - * @param int $decimals - * @return string - * @deprecated - */ - public static function bytesToHuman($bytes, $decimals = 2) - { - $sz = explode(',', 'B,KB,MB,GB'); - $factor = floor((strlen($bytes) - 1) / 3); - - return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . ' ' . $sz[$factor]; - } - - /** - * Returns array of editable files. - * * @return array */ - public static function editableFiles() + public function rules() { - return self::$editable; + if ($this->method() === 'PATCH') { + return Pack::getUpdateRulesForId($this->route()->parameter('pack')->id); + } + + return Pack::getCreateRules(); + } + + /** + * Run validation after the rules above have been applied. + * + * @param \Illuminate\Validation\Validator $validator + */ + public function withValidator($validator) + { + if ($this->method() !== 'POST') { + return; + } + + $validator->after(function ($validator) { + $mimetypes = implode(',', PackCreationService::VALID_UPLOAD_TYPES); + + /* @var $validator \Illuminate\Validation\Validator */ + $validator->sometimes('file_upload', 'sometimes|required|file|mimetypes:' . $mimetypes, function () { + return true; + }); + }); } } diff --git a/app/Http/Requests/Admin/ServerFormRequest.php b/app/Http/Requests/Admin/ServerFormRequest.php new file mode 100644 index 000000000..c18d8c163 --- /dev/null +++ b/app/Http/Requests/Admin/ServerFormRequest.php @@ -0,0 +1,88 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Requests\Admin; + +use Pterodactyl\Models\Server; +use Illuminate\Validation\Rule; + +class ServerFormRequest extends AdminFormRequest +{ + /** + * Rules to be applied to this request. + * + * @return array + */ + public function rules() + { + return Server::getCreateRules(); + } + + /** + * Run validation after the rules above have been applied. + * + * @param \Illuminate\Validation\Validator $validator + */ + public function withValidator($validator) + { + $validator->after(function ($validator) { + $validator->sometimes('node_id', 'required|numeric|bail|exists:nodes,id', function ($input) { + return ! ($input->auto_deploy); + }); + + $validator->sometimes('allocation_id', [ + 'required', + 'numeric', + 'bail', + Rule::exists('allocations', 'id')->where(function ($query) { + $query->where('node_id', $this->input('node_id')); + $query->whereNull('server_id'); + }), + ], function ($input) { + return ! ($input->auto_deploy); + }); + + $validator->sometimes('allocation_additional.*', [ + 'sometimes', + 'required', + 'numeric', + Rule::exists('allocations', 'id')->where(function ($query) { + $query->where('node_id', $this->input('node_id')); + $query->whereNull('server_id'); + }), + ], function ($input) { + return ! ($input->auto_deploy); + }); + + $validator->sometimes('pack_id', [ + Rule::exists('packs', 'id')->where(function ($query) { + $query->where('selectable', 1); + $query->where('option_id', $this->input('option_id')); + }), + ], function ($input) { + return $input->pack_id !== 0 && $input->pack_id !== null; + }); + }); + } +} diff --git a/app/Http/Requests/Admin/Service/EditOptionScript.php b/app/Http/Requests/Admin/Service/EditOptionScript.php new file mode 100644 index 000000000..52cfff4e0 --- /dev/null +++ b/app/Http/Requests/Admin/Service/EditOptionScript.php @@ -0,0 +1,46 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Requests\Admin\Service; + +use Pterodactyl\Http\Requests\Admin\AdminFormRequest; + +class EditOptionScript extends AdminFormRequest +{ + /** + * Return the rules to be used when validating the sent data in the request. + * + * @return array + */ + public function rules() + { + return [ + 'script_install' => 'sometimes|nullable|string', + 'script_is_privileged' => 'sometimes|required|boolean', + 'script_entry' => 'sometimes|required|string', + 'script_container' => 'sometimes|required|string', + 'copy_script_from' => 'sometimes|nullable|numeric', + ]; + } +} diff --git a/app/Http/Requests/Admin/Service/OptionVariableFormRequest.php b/app/Http/Requests/Admin/Service/OptionVariableFormRequest.php new file mode 100644 index 000000000..477e33532 --- /dev/null +++ b/app/Http/Requests/Admin/Service/OptionVariableFormRequest.php @@ -0,0 +1,62 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Requests\Admin; + +use Pterodactyl\Models\ServiceVariable; + +class OptionVariableFormRequest extends AdminFormRequest +{ + /** + * @return array + */ + public function rules() + { + return [ + 'name' => 'required|string|min:1|max:255', + 'description' => 'sometimes|nullable|string', + 'env_variable' => 'required|regex:/^[\w]{1,255}$/|notIn:' . ServiceVariable::RESERVED_ENV_NAMES, + 'default_value' => 'string', + 'options' => 'sometimes|required|array', + 'rules' => 'bail|required|string', + ]; + } + + /** + * Run validation after the rules above have been applied. + * + * @param \Illuminate\Validation\Validator $validator + */ + public function withValidator($validator) + { + $rules = $this->input('rules'); + if ($this->method() === 'PATCH') { + $rules = $this->input('rules', $this->route()->parameter('variable')->rules); + } + + $validator->sometimes('default_value', $rules, function ($input) { + return $input->default_value; + }); + } +} diff --git a/app/Http/Requests/Admin/Service/ServiceFormRequest.php b/app/Http/Requests/Admin/Service/ServiceFormRequest.php new file mode 100644 index 000000000..6d6be5e1e --- /dev/null +++ b/app/Http/Requests/Admin/Service/ServiceFormRequest.php @@ -0,0 +1,52 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Requests\Admin\Service; + +use Pterodactyl\Http\Requests\Admin\AdminFormRequest; + +class ServiceFormRequest extends AdminFormRequest +{ + /** + * @return array + */ + public function rules() + { + $rules = [ + 'name' => 'required|string|min:1|max:255', + 'description' => 'required|nullable|string', + 'folder' => 'required|regex:/^[\w.-]{1,50}$/|unique:services,folder', + 'startup' => 'required|nullable|string', + ]; + + if ($this->method() === 'PATCH') { + $service = $this->route()->parameter('service'); + $rules['folder'] = $rules['folder'] . ',' . $service->id; + + return $rules; + } + + return $rules; + } +} diff --git a/app/Http/Requests/Admin/Service/ServiceFunctionsFormRequest.php b/app/Http/Requests/Admin/Service/ServiceFunctionsFormRequest.php new file mode 100644 index 000000000..d5836c234 --- /dev/null +++ b/app/Http/Requests/Admin/Service/ServiceFunctionsFormRequest.php @@ -0,0 +1,40 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Requests\Admin\Service; + +use Pterodactyl\Http\Requests\Admin\AdminFormRequest; + +class ServiceFunctionsFormRequest extends AdminFormRequest +{ + /** + * @return array + */ + public function rules() + { + return [ + 'index_file' => 'required|nullable|string', + ]; + } +} diff --git a/app/Http/Requests/Admin/Service/ServiceOptionFormRequest.php b/app/Http/Requests/Admin/Service/ServiceOptionFormRequest.php new file mode 100644 index 000000000..8867a27f5 --- /dev/null +++ b/app/Http/Requests/Admin/Service/ServiceOptionFormRequest.php @@ -0,0 +1,39 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Requests\Admin\Service; + +use Pterodactyl\Models\ServiceOption; +use Pterodactyl\Http\Requests\Admin\AdminFormRequest; + +class ServiceOptionFormRequest extends AdminFormRequest +{ + /** + * {@inheritdoc} + */ + public function rules() + { + return ServiceOption::getCreateRules(); + } +} diff --git a/app/Http/Requests/Admin/UserFormRequest.php b/app/Http/Requests/Admin/UserFormRequest.php new file mode 100644 index 000000000..c067e14d0 --- /dev/null +++ b/app/Http/Requests/Admin/UserFormRequest.php @@ -0,0 +1,54 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Requests\Admin; + +use Pterodactyl\Models\User; + +class UserFormRequest extends AdminFormRequest +{ + /** + * {@inheritdoc} + */ + public function rules() + { + if ($this->method() === 'PATCH') { + return User::getUpdateRulesForId($this->route()->parameter('user')->id); + } + + return User::getCreateRules(); + } + + public function normalize($only = []) + { + if ($this->method === 'PATCH') { + return array_merge( + $this->intersect('password'), + $this->only(['email', 'username', 'name_first', 'name_last', 'root_admin']) + ); + } + + return parent::normalize(); + } +} diff --git a/app/Http/Requests/Base/AccountDataFormRequest.php b/app/Http/Requests/Base/AccountDataFormRequest.php new file mode 100644 index 000000000..63cacacf9 --- /dev/null +++ b/app/Http/Requests/Base/AccountDataFormRequest.php @@ -0,0 +1,85 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Requests\Base; + +use Pterodactyl\Models\User; +use Pterodactyl\Http\Requests\FrontendUserFormRequest; +use Pterodactyl\Exceptions\Http\Base\InvalidPasswordProvidedException; + +class AccountDataFormRequest extends FrontendUserFormRequest +{ + /** + * @return bool + * @throws \Pterodactyl\Exceptions\Http\Base\InvalidPasswordProvidedException + */ + public function authorize() + { + if (! parent::authorize()) { + return false; + } + + // Verify password matches when changing password or email. + if (in_array($this->input('do_action'), ['password', 'email'])) { + if (! password_verify($this->input('current_password'), $this->user()->password)) { + throw new InvalidPasswordProvidedException(trans('base.account.invalid_password')); + } + } + + return true; + } + + /** + * @return array + */ + public function rules() + { + $modelRules = User::getUpdateRulesForId($this->user()->id); + + switch ($this->input('do_action')) { + case 'email': + $rules = [ + 'new_email' => array_get($modelRules, 'email'), + ]; + break; + case 'password': + $rules = [ + 'new_password' => 'required|confirmed|string|min:8', + 'new_password_confirmation' => 'required', + ]; + break; + case 'identity': + $rules = [ + 'name_first' => array_get($modelRules, 'name_first'), + 'name_last' => array_get($modelRules, 'name_last'), + 'username' => array_get($modelRules, 'username'), + ]; + break; + default: + abort(422); + } + + return $rules; + } +} diff --git a/app/Http/Requests/Base/ApiKeyFormRequest.php b/app/Http/Requests/Base/ApiKeyFormRequest.php new file mode 100644 index 000000000..33b2541cd --- /dev/null +++ b/app/Http/Requests/Base/ApiKeyFormRequest.php @@ -0,0 +1,89 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Requests\Base; + +use IPTools\Network; +use Pterodactyl\Http\Requests\FrontendUserFormRequest; + +class ApiKeyFormRequest extends FrontendUserFormRequest +{ + /** + * Rules applied to data passed in this request. + * + * @return array + */ + public function rules() + { + $this->parseAllowedIntoArray(); + + return [ + 'memo' => 'required|nullable|string|max:500', + 'permissions' => 'sometimes|present|array', + 'admin_permissions' => 'sometimes|present|array', + 'allowed_ips' => 'present', + 'allowed_ips.*' => 'sometimes|string', + ]; + } + + /** + * Parse the string of allowed IPs into an array. + */ + protected function parseAllowedIntoArray() + { + $loop = []; + if (! empty($this->input('allowed_ips'))) { + foreach (explode(PHP_EOL, $this->input('allowed_ips')) as $ip) { + $loop[] = trim($ip); + } + } + + $this->merge(['allowed_ips' => $loop]); + } + + /** + * Run additional validation rules on the request to ensure all of the data is good. + * + * @param \Illuminate\Validation\Validator $validator + */ + public function withValidator($validator) + { + $validator->after(function ($validator) { + /* @var \Illuminate\Validation\Validator $validator */ + if (empty($this->input('permissions')) && empty($this->input('admin_permissions'))) { + $validator->errors()->add('permissions', 'At least one permission must be selected.'); + } + + foreach ($this->input('allowed_ips') as $ip) { + $ip = trim($ip); + + try { + Network::parse($ip); + } catch (\Exception $ex) { + $validator->errors()->add('allowed_ips', 'Could not parse IP ' . $ip . ' because it is in an invalid format.'); + } + } + }); + } +} diff --git a/app/Http/Requests/FrontendUserFormRequest.php b/app/Http/Requests/FrontendUserFormRequest.php new file mode 100644 index 000000000..404003c31 --- /dev/null +++ b/app/Http/Requests/FrontendUserFormRequest.php @@ -0,0 +1,55 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Requests; + +use Illuminate\Foundation\Http\FormRequest; + +abstract class FrontendUserFormRequest extends FormRequest +{ + abstract public function rules(); + + /** + * Determine if a user is authorized to access this endpoint. + * + * @return bool + */ + public function authorize() + { + return ! is_null($this->user()); + } + + /** + * Return only the fields that we are interested in from the request. + * This will include empty fields as a null value. + * + * @return array + */ + public function normalize() + { + return $this->only( + array_keys($this->rules()) + ); + } +} diff --git a/app/Http/Requests/Request.php b/app/Http/Requests/Request.php index 5e3ae0bc5..a9771726f 100644 --- a/app/Http/Requests/Request.php +++ b/app/Http/Requests/Request.php @@ -6,5 +6,4 @@ use Illuminate\Foundation\Http\FormRequest; abstract class Request extends FormRequest { - // } diff --git a/app/Http/Requests/Server/TaskCreationFormRequest.php b/app/Http/Requests/Server/TaskCreationFormRequest.php new file mode 100644 index 000000000..486e04b36 --- /dev/null +++ b/app/Http/Requests/Server/TaskCreationFormRequest.php @@ -0,0 +1,84 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Requests\Server; + +use Pterodactyl\Http\Requests\FrontendUserFormRequest; + +class TaskCreationFormRequest extends FrontendUserFormRequest +{ + /** + * Validation rules to apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'name' => 'string|max:255', + 'day_of_week' => 'required|string', + 'day_of_month' => 'required|string', + 'hour' => 'required|string', + 'minute' => 'required|string', + 'action' => 'required|string|in:power,command', + 'data' => 'required|string', + 'chain' => 'sometimes|array|size:4', + 'chain.time_value' => 'required_with:chain|max:5', + 'chain.time_interval' => 'required_with:chain|max:5', + 'chain.action' => 'required_with:chain|max:5', + 'chain.payload' => 'required_with:chain|max:5', + 'chain.time_value.*' => 'numeric|between:1,60', + 'chain.time_interval.*' => 'string|in:s,m', + 'chain.action.*' => 'string|in:power,command', + 'chain.payload.*' => 'string', + ]; + } + + /** + * Normalize the request into a format that can be used by the application. + * + * @return array + */ + public function normalize() + { + return $this->only('name', 'day_of_week', 'day_of_month', 'hour', 'minute', 'action', 'data'); + } + + /** + * Return the chained tasks provided in the request. + * + * @return array|null + */ + public function getChainedTasks() + { + $restructured = []; + foreach (array_get($this->all(), 'chain', []) as $key => $values) { + for ($i = 0; $i < count($values); ++$i) { + $restructured[$i][$key] = $values[$i]; + } + } + + return empty($restructured) ? null : $restructured; + } +} diff --git a/app/Http/Requests/Server/UpdateFileContentsFormRequest.php b/app/Http/Requests/Server/UpdateFileContentsFormRequest.php new file mode 100644 index 000000000..1f469aa8f --- /dev/null +++ b/app/Http/Requests/Server/UpdateFileContentsFormRequest.php @@ -0,0 +1,127 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Requests\Server; + +use Illuminate\Log\Writer; +use Illuminate\Contracts\Session\Session; +use GuzzleHttp\Exception\RequestException; +use Illuminate\Contracts\Config\Repository; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Http\Requests\FrontendUserFormRequest; +use Pterodactyl\Exceptions\Http\Server\FileSizeTooLargeException; +use Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface; +use Pterodactyl\Exceptions\Http\Server\FileTypeNotEditableException; + +class UpdateFileContentsFormRequest extends FrontendUserFormRequest +{ + /** + * @var object + */ + protected $stats; + + /** + * Authorize a request to edit a file. + * + * @return bool + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Http\Server\FileSizeTooLargeException + * @throws \Pterodactyl\Exceptions\Http\Server\FileTypeNotEditableException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function authorize() + { + parent::authorize(); + + $session = app()->make(Session::class); + $server = $session->get('server_data.model'); + $token = $session->get('server_data.token'); + + $permission = $this->user()->can('edit-files', $server); + if (! $permission) { + return false; + } + + return $this->checkFileCanBeEdited($server, $token); + } + + /** + * @return array + */ + public function rules() + { + return []; + } + + /** + * Return the file stats from the Daemon. + * + * @return object + */ + public function getStats() + { + return $this->stats; + } + + /** + * @param \Pterodactyl\Models\Server $server + * @param string $token + * @return bool + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Http\Server\FileSizeTooLargeException + * @throws \Pterodactyl\Exceptions\Http\Server\FileTypeNotEditableException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + protected function checkFileCanBeEdited($server, $token) + { + $config = app()->make(Repository::class); + $repository = app()->make(FileRepositoryInterface::class); + + try { + $this->stats = $repository->setNode($server->node_id) + ->setAccessServer($server->uuid) + ->setAccessToken($token) + ->getFileStat($this->route()->parameter('file')); + } catch (RequestException $exception) { + $response = $exception->getResponse(); + app()->make(Writer::class)->warning($exception); + + throw new DisplayException(trans('exceptions.daemon_connection_failed', [ + 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), + ])); + } + + if (! $this->stats->file || ! in_array($this->stats->mime, $config->get('pterodactyl.files.editable'))) { + throw new FileTypeNotEditableException(trans('server.files.exceptions.invalid_mime')); + } + + if ($this->stats->size > $config->get('pterodactyl.files.max_edit_size')) { + throw new FileSizeTooLargeException(trans('server.files.exceptions.max_size')); + } + + return true; + } +} diff --git a/app/Http/ViewComposers/Server/ServerDataComposer.php b/app/Http/ViewComposers/Server/ServerDataComposer.php new file mode 100644 index 000000000..93bdb1bf3 --- /dev/null +++ b/app/Http/ViewComposers/Server/ServerDataComposer.php @@ -0,0 +1,60 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\ViewComposers\Server; + +use Illuminate\View\View; +use Illuminate\Contracts\Session\Session; + +class ServerDataComposer +{ + /** + * @var \Illuminate\Contracts\Session\Session + */ + protected $session; + + /** + * ServerDataComposer constructor. + * + * @param \Illuminate\Contracts\Session\Session $session + */ + public function __construct(Session $session) + { + $this->session = $session; + } + + /** + * Attach server data to a view automatically. + * + * @param \Illuminate\View\View $view + */ + public function compose(View $view) + { + $data = $this->session->get('server_data'); + + $view->with('server', array_get($data, 'model')); + $view->with('node', object_get($data['model'], 'node')); + $view->with('daemon_token', array_get($data, 'token')); + } +} diff --git a/app/Jobs/SendScheduledTask.php b/app/Jobs/SendScheduledTask.php index 525b670df..8e44e3e9b 100644 --- a/app/Jobs/SendScheduledTask.php +++ b/app/Jobs/SendScheduledTask.php @@ -31,8 +31,8 @@ use Pterodactyl\Models\TaskLog; use Illuminate\Queue\SerializesModels; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; -use Pterodactyl\Repositories\Daemon\PowerRepository; -use Pterodactyl\Repositories\Daemon\CommandRepository; +use Pterodactyl\Repositories\old_Daemon\PowerRepository; +use Pterodactyl\Repositories\old_Daemon\CommandRepository; class SendScheduledTask extends Job implements ShouldQueue { @@ -45,8 +45,6 @@ class SendScheduledTask extends Job implements ShouldQueue /** * Create a new job instance. - * - * @return void */ public function __construct(Task $task) { @@ -58,8 +56,6 @@ class SendScheduledTask extends Job implements ShouldQueue /** * Execute the job. - * - * @return void */ public function handle() { @@ -96,7 +92,8 @@ class SendScheduledTask extends Job implements ShouldQueue 'response' => $ex->getMessage(), ]); } finally { - $cron = Cron::factory(sprintf('%s %s %s %s %s %s', + $cron = Cron::factory(sprintf( + '%s %s %s %s %s %s', $this->task->minute, $this->task->hour, $this->task->day_of_month, diff --git a/app/Models/APIKey.php b/app/Models/APIKey.php index 6ed73b7c2..1df3f6aa6 100644 --- a/app/Models/APIKey.php +++ b/app/Models/APIKey.php @@ -24,16 +24,15 @@ namespace Pterodactyl\Models; +use Sofa\Eloquence\Eloquence; +use Sofa\Eloquence\Validable; use Illuminate\Database\Eloquent\Model; +use Sofa\Eloquence\Contracts\CleansAttributes; +use Sofa\Eloquence\Contracts\Validable as ValidableContract; -class APIKey extends Model +class APIKey extends Model implements CleansAttributes, ValidableContract { - /** - * Public key defined length used in verification methods. - * - * @var int - */ - const PUBLIC_KEY_LEN = 16; + use Eloquence, Validable; /** * The table associated with the model. @@ -65,6 +64,32 @@ class APIKey extends Model */ protected $guarded = ['id', 'created_at', 'updated_at']; + /** + * Rules defining what fields must be passed when making a model. + * + * @var array + */ + protected static $applicationRules = [ + 'memo' => 'required', + 'user_id' => 'required', + 'secret' => 'required', + 'public' => 'required', + ]; + + /** + * Rules to protect aganist invalid data entry to DB. + * + * @var array + */ + protected static $dataIntegrityRules = [ + 'user_id' => 'exists:users,id', + 'public' => 'string|size:16', + 'secret' => 'string', + 'memo' => 'nullable|string|max:500', + 'allowed_ips' => 'nullable|json', + 'expires_at' => 'nullable|datetime', + ]; + /** * Gets the permissions associated with a key. * diff --git a/app/Models/APIPermission.php b/app/Models/APIPermission.php index bfe2fc908..4072cd56f 100644 --- a/app/Models/APIPermission.php +++ b/app/Models/APIPermission.php @@ -24,46 +24,20 @@ namespace Pterodactyl\Models; +use Sofa\Eloquence\Eloquence; +use Sofa\Eloquence\Validable; use Illuminate\Database\Eloquent\Model; +use Sofa\Eloquence\Contracts\CleansAttributes; +use Sofa\Eloquence\Contracts\Validable as ValidableContract; -class APIPermission extends Model +class APIPermission extends Model implements CleansAttributes, ValidableContract { - /** - * The table associated with the model. - * - * @var string - */ - protected $table = 'api_permissions'; - - /** - * Fields that are not mass assignable. - * - * @var array - */ - protected $guarded = ['id']; - - /** - * Cast values to correct type. - * - * @var array - */ - protected $casts = [ - 'key_id' => 'integer', - ]; - - /** - * Disable timestamps for this table. - * - * @var bool - */ - public $timestamps = false; + use Eloquence, Validable; /** * List of permissions available for the API. - * - * @var array */ - protected static $permissions = [ + const CONST_PERMISSIONS = [ // Items within this block are available to non-adminitrative users. '_user' => [ 'server' => [ @@ -119,13 +93,49 @@ class APIPermission extends Model ], ]; + /** + * The table associated with the model. + * + * @var string + */ + protected $table = 'api_permissions'; + + /** + * Fields that are not mass assignable. + * + * @var array + */ + protected $guarded = ['id']; + + /** + * Cast values to correct type. + * + * @var array + */ + protected $casts = [ + 'key_id' => 'integer', + ]; + + protected static $dataIntegrityRules = [ + 'key_id' => 'required|numeric', + 'permission' => 'required|string|max:200', + ]; + + /** + * Disable timestamps for this table. + * + * @var bool + */ + public $timestamps = false; + /** * Return permissions for API. * * @return array + * @deprecated */ public static function permissions() { - return self::$permissions; + return []; } } diff --git a/app/Models/Allocation.php b/app/Models/Allocation.php index d37e52a0e..e5d862bfe 100644 --- a/app/Models/Allocation.php +++ b/app/Models/Allocation.php @@ -24,10 +24,16 @@ namespace Pterodactyl\Models; +use Sofa\Eloquence\Eloquence; +use Sofa\Eloquence\Validable; use Illuminate\Database\Eloquent\Model; +use Sofa\Eloquence\Contracts\CleansAttributes; +use Sofa\Eloquence\Contracts\Validable as ValidableContract; -class Allocation extends Model +class Allocation extends Model implements CleansAttributes, ValidableContract { + use Eloquence, Validable; + /** * The table associated with the model. * @@ -42,46 +48,66 @@ class Allocation extends Model */ protected $guarded = ['id', 'created_at', 'updated_at']; - /** - * Cast values to correct type. - * - * @var array - */ - protected $casts = [ - 'node_id' => 'integer', - 'port' => 'integer', - 'server_id' => 'integer', - ]; + /** + * Cast values to correct type. + * + * @var array + */ + protected $casts = [ + 'node_id' => 'integer', + 'port' => 'integer', + 'server_id' => 'integer', + ]; - /** - * Accessor to automatically provide the IP alias if defined. - * - * @param null|string $value - * @return string - */ - public function getAliasAttribute($value) - { - return (is_null($this->ip_alias)) ? $this->ip : $this->ip_alias; - } + /** + * @var array + */ + protected static $applicationRules = [ + 'node_id' => 'required', + 'ip' => 'required', + 'port' => 'required', + ]; - /** - * Accessor to quickly determine if this allocation has an alias. - * - * @param null|string $value - * @return bool - */ - public function getHasAliasAttribute($value) - { - return ! is_null($this->ip_alias); - } + /** + * @var array + */ + protected static $dataIntegrityRules = [ + 'node_id' => 'exists:nodes,id', + 'ip' => 'ip', + 'port' => 'numeric|between:1024,65553', + 'alias' => 'string', + 'server_id' => 'nullable|exists:servers,id', + ]; - /** - * Gets information for the server associated with this allocation. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function server() - { - return $this->belongsTo(Server::class); - } + /** + * Accessor to automatically provide the IP alias if defined. + * + * @param null|string $value + * @return string + */ + public function getAliasAttribute($value) + { + return (is_null($this->ip_alias)) ? $this->ip : $this->ip_alias; + } + + /** + * Accessor to quickly determine if this allocation has an alias. + * + * @param null|string $value + * @return bool + */ + public function getHasAliasAttribute($value) + { + return ! is_null($this->ip_alias); + } + + /** + * Gets information for the server associated with this allocation. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function server() + { + return $this->belongsTo(Server::class); + } } diff --git a/app/Models/Database.php b/app/Models/Database.php index 20ad3c1b0..4453c790f 100644 --- a/app/Models/Database.php +++ b/app/Models/Database.php @@ -24,10 +24,16 @@ namespace Pterodactyl\Models; +use Sofa\Eloquence\Eloquence; +use Sofa\Eloquence\Validable; use Illuminate\Database\Eloquent\Model; +use Sofa\Eloquence\Contracts\CleansAttributes; +use Sofa\Eloquence\Contracts\Validable as ValidableContract; -class Database extends Model +class Database extends Model implements CleansAttributes, ValidableContract { + use Eloquence, Validable; + /** * The table associated with the model. * @@ -48,7 +54,7 @@ class Database extends Model * @var array */ protected $fillable = [ - 'server_id', 'database_host_id', 'database', 'username', 'remote', + 'server_id', 'database_host_id', 'database', 'username', 'password', 'remote', ]; /** @@ -61,6 +67,22 @@ class Database extends Model 'database_host_id' => 'integer', ]; + protected static $applicationRules = [ + 'server_id' => 'required', + 'database_host_id' => 'required', + 'database' => 'required', + 'remote' => 'required', + ]; + + protected static $dataIntegrityRules = [ + 'server_id' => 'numeric|exists:servers,id', + 'database_host_id' => 'exists:database_hosts,id', + 'database' => 'string|alpha_dash|between:3,100', + 'username' => 'string|alpha_dash|between:3,100', + 'remote' => 'string|regex:/^[0-9%.]{1,15}$/', + 'password' => 'string', + ]; + /** * Gets the host database server associated with a database. * diff --git a/app/Models/DatabaseHost.php b/app/Models/DatabaseHost.php index 165a99c5a..9533c0a5e 100644 --- a/app/Models/DatabaseHost.php +++ b/app/Models/DatabaseHost.php @@ -24,12 +24,16 @@ namespace Pterodactyl\Models; -use Crypt; -use Config; +use Sofa\Eloquence\Eloquence; +use Sofa\Eloquence\Validable; use Illuminate\Database\Eloquent\Model; +use Sofa\Eloquence\Contracts\CleansAttributes; +use Sofa\Eloquence\Contracts\Validable as ValidableContract; -class DatabaseHost extends Model +class DatabaseHost extends Model implements CleansAttributes, ValidableContract { + use Eloquence, Validable; + /** * The table associated with the model. * @@ -50,7 +54,7 @@ class DatabaseHost extends Model * @var array */ protected $fillable = [ - 'name', 'host', 'port', 'username', 'max_databases', 'node_id', + 'name', 'host', 'port', 'username', 'password', 'max_databases', 'node_id', ]; /** @@ -65,24 +69,32 @@ class DatabaseHost extends Model ]; /** - * Sets the database connection name with the details of the host. + * Application validation rules. * - * @param string $connection - * @return void + * @var array */ - public function setDynamicConnection($connection = 'dynamic') - { - Config::set('database.connections.' . $connection, [ - 'driver' => 'mysql', - 'host' => $this->host, - 'port' => $this->port, - 'database' => 'mysql', - 'username' => $this->username, - 'password' => Crypt::decrypt($this->password), - 'charset' => 'utf8', - 'collation' => 'utf8_unicode_ci', - ]); - } + protected static $applicationRules = [ + 'name' => 'required', + 'host' => 'required', + 'port' => 'required', + 'username' => 'required', + 'node_id' => 'sometimes|required', + ]; + + /** + * Validation rules to assign to this model. + * + * @var array + * @todo the node_id field doesn't validate correctly if no node is provided in request + */ + protected static $dataIntegrityRules = [ + 'name' => 'string|max:255', + 'host' => 'ip|unique:database_hosts,host', + 'port' => 'numeric|between:1,65535', + 'username' => 'string|max:32', + 'password' => 'nullable|string', + 'node_id' => 'nullable|exists:nodes,id', + ]; /** * Gets the node associated with a database host. diff --git a/app/Models/Location.php b/app/Models/Location.php index f9ceec767..0337bcc3f 100644 --- a/app/Models/Location.php +++ b/app/Models/Location.php @@ -24,10 +24,16 @@ namespace Pterodactyl\Models; +use Sofa\Eloquence\Eloquence; +use Sofa\Eloquence\Validable; use Illuminate\Database\Eloquent\Model; +use Sofa\Eloquence\Contracts\CleansAttributes; +use Sofa\Eloquence\Contracts\Validable as ValidableContract; -class Location extends Model +class Location extends Model implements CleansAttributes, ValidableContract { + use Eloquence, Validable; + /** * The table associated with the model. * @@ -42,6 +48,26 @@ class Location extends Model */ protected $guarded = ['id', 'created_at', 'updated_at']; + /** + * Validation rules to apply to this model. + * + * @var array + */ + protected static $applicationRules = [ + 'short' => 'required', + 'long' => 'required', + ]; + + /** + * Rules ensuring that the raw data stored in the database meets expectations. + * + * @var array + */ + protected static $dataIntegrityRules = [ + 'short' => 'string|between:1,60|unique:locations,short', + 'long' => 'string|between:1,255', + ]; + /** * Gets the nodes in a specificed location. * diff --git a/app/Models/Node.php b/app/Models/Node.php index 9c454cfcb..76f04e9c9 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -25,13 +25,16 @@ namespace Pterodactyl\Models; use GuzzleHttp\Client; +use Sofa\Eloquence\Eloquence; +use Sofa\Eloquence\Validable; use Illuminate\Database\Eloquent\Model; use Illuminate\Notifications\Notifiable; -use Nicolaslopezj\Searchable\SearchableTrait; +use Sofa\Eloquence\Contracts\CleansAttributes; +use Sofa\Eloquence\Contracts\Validable as ValidableContract; -class Node extends Model +class Node extends Model implements CleansAttributes, ValidableContract { - use Notifiable, SearchableTrait; + use Eloquence, Notifiable, Validable; /** * The table associated with the model. @@ -47,20 +50,20 @@ class Node extends Model */ protected $hidden = ['daemonSecret']; - /** - * Cast values to correct type. - * - * @var array - */ - protected $casts = [ - 'public' => 'integer', - 'location_id' => 'integer', - 'memory' => 'integer', - 'disk' => 'integer', - 'daemonListen' => 'integer', - 'daemonSFTP' => 'integer', - 'behind_proxy' => 'boolean', - ]; + /** + * Cast values to correct type. + * + * @var array + */ + protected $casts = [ + 'public' => 'integer', + 'location_id' => 'integer', + 'memory' => 'integer', + 'disk' => 'integer', + 'daemonListen' => 'integer', + 'daemonSFTP' => 'integer', + 'behind_proxy' => 'boolean', + ]; /** * Fields that are mass assignable. @@ -81,22 +84,67 @@ class Node extends Model * * @var array */ - protected $searchable = [ - 'columns' => [ - 'nodes.name' => 10, - 'nodes.fqdn' => 8, - 'locations.short' => 4, - 'locations.long' => 4, - ], - 'joins' => [ - 'locations' => ['locations.id', 'nodes.location_id'], - ], - ]; + protected $searchableColumns = [ + 'name' => 10, + 'fqdn' => 8, + 'location.short' => 4, + 'location.long' => 4, + ]; + + /** + * @var array + */ + protected static $applicationRules = [ + 'name' => 'required', + 'location_id' => 'required', + 'fqdn' => 'required', + 'scheme' => 'required', + 'memory' => 'required', + 'memory_overallocate' => 'required', + 'disk' => 'required', + 'disk_overallocate' => 'required', + 'daemonBase' => 'sometimes|required', + 'daemonSFTP' => 'required', + 'daemonListen' => 'required', + ]; + + /** + * @var array + */ + protected static $dataIntegrityRules = [ + 'name' => 'regex:/^([\w .-]{1,100})$/', + 'location_id' => 'exists:locations,id', + 'public' => 'boolean', + 'fqdn' => 'string', + 'behind_proxy' => 'boolean', + 'memory' => 'numeric|min:1', + 'memory_overallocate' => 'numeric|min:-1', + 'disk' => 'numeric|min:1', + 'disk_overallocate' => 'numeric|min:-1', + 'daemonBase' => 'regex:/^([\/][\d\w.\-\/]+)$/', + 'daemonSFTP' => 'numeric|between:1024,65535', + 'daemonListen' => 'numeric|between:1024,65535', + ]; + + /** + * Default values for specific columns that are generally not changed on base installs. + * + * @var array + */ + protected $attributes = [ + 'public' => true, + 'behind_proxy' => false, + 'memory_overallocate' => 0, + 'disk_overallocate' => 0, + 'daemonBase' => '/srv/daemon-data', + 'daemonSFTP' => 2022, + 'daemonListen' => 8080, + ]; /** * Return an instance of the Guzzle client for this specific node. * - * @param array $headers + * @param array $headers * @return \GuzzleHttp\Client */ public function guzzleClient($headers = []) @@ -112,7 +160,7 @@ class Node extends Model /** * Returns the configuration in JSON format. * - * @param bool $pretty + * @param bool $pretty * @return string */ public function getConfigurationAsJson($pretty = false) diff --git a/app/Models/Pack.php b/app/Models/Pack.php index 9cda19790..48382a425 100644 --- a/app/Models/Pack.php +++ b/app/Models/Pack.php @@ -26,12 +26,15 @@ namespace Pterodactyl\Models; use File; use Storage; +use Sofa\Eloquence\Eloquence; +use Sofa\Eloquence\Validable; use Illuminate\Database\Eloquent\Model; -use Nicolaslopezj\Searchable\SearchableTrait; +use Sofa\Eloquence\Contracts\CleansAttributes; +use Sofa\Eloquence\Contracts\Validable as ValidableContract; -class Pack extends Model +class Pack extends Model implements CleansAttributes, ValidableContract { - use SearchableTrait; + use Eloquence, Validable; /** * The table associated with the model. @@ -46,7 +49,33 @@ class Pack extends Model * @var array */ protected $fillable = [ - 'option_id', 'name', 'version', 'description', 'selectable', 'visible', 'locked', + 'option_id', 'uuid', 'name', 'version', 'description', 'selectable', 'visible', 'locked', + ]; + + /** + * @var array + */ + protected static $applicationRules = [ + 'name' => 'required', + 'version' => 'required', + 'description' => 'sometimes', + 'selectable' => 'sometimes|required', + 'visible' => 'sometimes|required', + 'locked' => 'sometimes|required', + 'option_id' => 'required', + ]; + + /** + * @var array + */ + protected static $dataIntegrityRules = [ + 'name' => 'string', + 'version' => 'string', + 'description' => 'nullable|string', + 'selectable' => 'boolean', + 'visible' => 'boolean', + 'locked' => 'boolean', + 'option_id' => 'exists:service_options,id', ]; /** @@ -66,25 +95,21 @@ class Pack extends Model * * @var array */ - protected $searchable = [ - 'columns' => [ - 'packs.name' => 10, - 'packs.uuid' => 8, - 'service_options.name' => 6, - 'service_options.tag' => 5, - 'service_options.docker_image' => 5, - 'packs.version' => 2, - ], - 'joins' => [ - 'service_options' => ['packs.option_id', 'service_options.id'], - ], + protected $searchableColumns = [ + 'name' => 10, + 'uuid' => 8, + 'option.name' => 6, + 'option.tag' => 5, + 'option.docker_image' => 5, + 'version' => 2, ]; /** * Returns all of the archived files for a given pack. * - * @param bool $collection + * @param bool $collection * @return \Illuminate\Support\Collection|object + * @deprecated */ public function files($collection = false) { diff --git a/app/Models/Permission.php b/app/Models/Permission.php index c126967ec..7d4619c64 100644 --- a/app/Models/Permission.php +++ b/app/Models/Permission.php @@ -24,10 +24,14 @@ namespace Pterodactyl\Models; +use Sofa\Eloquence\Eloquence; use Illuminate\Database\Eloquent\Model; +use Sofa\Eloquence\Contracts\CleansAttributes; -class Permission extends Model +class Permission extends Model implements CleansAttributes { + use Eloquence; + /** * Should timestamps be used on this model. * @@ -81,7 +85,7 @@ class Permission extends Model 'server' => [ 'set-connection' => null, 'view-startup' => null, - 'edit-startup' => null, + 'edit-startup' => null, ], 'sftp' => [ 'view-sftp' => null, @@ -118,12 +122,12 @@ class Permission extends Model /** * Return a collection of permissions available. * - * @param array $single - * @return \Illuminate\Support\Collection|array + * @param bool $array + * @return array|\Illuminate\Support\Collection */ - public static function listPermissions($single = false) + public static function getPermissions($array = false) { - if ($single) { + if ($array) { return collect(self::$permissions)->mapWithKeys(function ($item) { return $item; })->all(); @@ -135,8 +139,8 @@ class Permission extends Model /** * Find permission by permission node. * - * @param \Illuminate\Database\Query\Builder $query - * @param string $permission + * @param \Illuminate\Database\Query\Builder $query + * @param string $permission * @return \Illuminate\Database\Query\Builder */ public function scopePermission($query, $permission) @@ -147,8 +151,8 @@ class Permission extends Model /** * Filter permission by server. * - * @param \Illuminate\Database\Query\Builder $query - * @param \Pterodactyl\Models\Server $server + * @param \Illuminate\Database\Query\Builder $query + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Database\Query\Builder */ public function scopeServer($query, Server $server) diff --git a/app/Models/Server.php b/app/Models/Server.php index 1a26243e9..b31a89a7c 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -29,13 +29,16 @@ use Cache; use Carbon; use Schema; use Javascript; +use Sofa\Eloquence\Eloquence; +use Sofa\Eloquence\Validable; use Illuminate\Database\Eloquent\Model; use Illuminate\Notifications\Notifiable; -use Nicolaslopezj\Searchable\SearchableTrait; +use Sofa\Eloquence\Contracts\CleansAttributes; +use Sofa\Eloquence\Contracts\Validable as ValidableContract; -class Server extends Model +class Server extends Model implements CleansAttributes, ValidableContract { - use Notifiable, SearchableTrait; + use Eloquence, Notifiable, Validable; /** * The table associated with the model. @@ -65,6 +68,49 @@ class Server extends Model */ protected $guarded = ['id', 'installed', 'created_at', 'updated_at', 'deleted_at']; + /** + * @var array + */ + protected static $applicationRules = [ + 'owner_id' => 'required', + 'name' => 'required', + 'memory' => 'required', + 'swap' => 'required', + 'io' => 'required', + 'cpu' => 'required', + 'disk' => 'required', + 'service_id' => 'required', + 'option_id' => 'required', + 'pack_id' => 'sometimes', + 'auto_deploy' => 'sometimes', + 'custom_id' => 'sometimes', + 'skip_scripts' => 'sometimes', + ]; + + /** + * @var array + */ + protected static $dataIntegrityRules = [ + 'owner_id' => 'exists:users,id', + 'name' => 'regex:/^([\w .-]{1,200})$/', + 'node_id' => 'exists:nodes,id', + 'description' => 'nullable|string', + 'memory' => 'numeric|min:0', + 'swap' => 'numeric|min:-1', + 'io' => 'numeric|between:10,1000', + 'cpu' => 'numeric|min:0', + 'disk' => 'numeric|min:0', + 'allocation_id' => 'exists:allocations,id', + 'service_id' => 'exists:services,id', + 'option_id' => 'exists:service_options,id', + 'pack_id' => 'nullable|numeric|min:0', + 'custom_container' => 'nullable|string', + 'startup' => 'nullable|string', + 'auto_deploy' => 'accepted', + 'custom_id' => 'numeric|unique:servers,id', + 'skip_scripts' => 'boolean', + ]; + /** * Cast values to correct type. * @@ -93,22 +139,15 @@ class Server extends Model * * @var array */ - protected $searchable = [ - 'columns' => [ - 'servers.name' => 10, - 'servers.username' => 10, - 'servers.uuidShort' => 9, - 'servers.uuid' => 8, - 'packs.name' => 7, - 'users.email' => 6, - 'users.username' => 6, - 'nodes.name' => 2, - ], - 'joins' => [ - 'packs' => ['packs.id', 'servers.pack_id'], - 'users' => ['users.id', 'servers.owner_id'], - 'nodes' => ['nodes.id', 'servers.node_id'], - ], + protected $searchableColumns = [ + 'name' => 10, + 'username' => 10, + 'uuidShort' => 9, + 'uuid' => 8, + 'pack.name' => 7, + 'user.email' => 6, + 'user.username' => 6, + 'node.name' => 2, ]; /** @@ -116,10 +155,11 @@ class Server extends Model * DO NOT USE THIS TO MODIFY SERVER DETAILS OR SAVE THOSE DETAILS. * YOU WILL OVERWRITE THE SECRET KEY AND BREAK THINGS. * - * @param string $uuid - * @param array $with - * @param array $withCount + * @param string $uuid + * @param array $with + * @param array $withCount * @return \Pterodactyl\Models\Server + * @throws \Exception * @todo Remove $with and $withCount due to cache issues, they aren't used anyways. */ public static function byUuid($uuid, array $with = [], array $withCount = []) @@ -151,7 +191,7 @@ class Server extends Model /** * Returns non-administrative headers for accessing a server on the daemon. * - * @param Pterodactyl\Models\User|null $user + * @param Pterodactyl\Models\User|null $user * @return array */ public function guzzleHeaders(User $user = null) @@ -171,7 +211,7 @@ class Server extends Model /** * Return an instance of the Guzzle client for this specific server using defined access token. * - * @param Pterodactyl\Models\User|null $user + * @param Pterodactyl\Models\User|null $user * @return \GuzzleHttp\Client */ public function guzzleClient(User $user = null) @@ -182,8 +222,8 @@ class Server extends Model /** * Returns javascript object to be embedded on server view pages with relevant information. * - * @param array|null $additional - * @param array|null $overwrite + * @param array|null $additional + * @param array|null $overwrite * @return \Laracasts\Utilities\JavaScript\JavaScriptFacade */ public function js($additional = null, $overwrite = null) diff --git a/app/Models/ServerVariable.php b/app/Models/ServerVariable.php index 165d5b3df..fdff100ee 100644 --- a/app/Models/ServerVariable.php +++ b/app/Models/ServerVariable.php @@ -42,53 +42,53 @@ class ServerVariable extends Model */ protected $guarded = ['id', 'created_at', 'updated_at']; - /** - * Cast values to correct type. - * - * @var array - */ - protected $casts = [ + /** + * Cast values to correct type. + * + * @var array + */ + protected $casts = [ 'server_id' => 'integer', 'variable_id' => 'integer', ]; - /** - * Determine if variable is viewable by users. - * - * @return bool - */ - public function getUserCanViewAttribute() - { - return (bool) $this->variable->user_viewable; - } + /** + * Determine if variable is viewable by users. + * + * @return bool + */ + public function getUserCanViewAttribute() + { + return (bool) $this->variable->user_viewable; + } - /** - * Determine if variable is editable by users. - * - * @return bool - */ - public function getUserCanEditAttribute() - { - return (bool) $this->variable->user_editable; - } + /** + * Determine if variable is editable by users. + * + * @return bool + */ + public function getUserCanEditAttribute() + { + return (bool) $this->variable->user_editable; + } - /** - * Determine if variable is required. - * - * @return bool - */ - public function getRequiredAttribute() - { - return $this->variable->required; - } + /** + * Determine if variable is required. + * + * @return bool + */ + public function getRequiredAttribute() + { + return $this->variable->required; + } - /** - * Returns information about a given variables parent. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function variable() - { - return $this->belongsTo(ServiceVariable::class, 'variable_id'); - } + /** + * Returns information about a given variables parent. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function variable() + { + return $this->belongsTo(ServiceVariable::class, 'variable_id'); + } } diff --git a/app/Models/Service.php b/app/Models/Service.php index b05366882..391f4ae1c 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -24,10 +24,16 @@ namespace Pterodactyl\Models; +use Sofa\Eloquence\Eloquence; +use Sofa\Eloquence\Validable; use Illuminate\Database\Eloquent\Model; +use Sofa\Eloquence\Contracts\CleansAttributes; +use Sofa\Eloquence\Contracts\Validable as ValidableContract; -class Service extends Model +class Service extends Model implements CleansAttributes, ValidableContract { + use Eloquence, Validable; + /** * The table associated with the model. * @@ -40,52 +46,31 @@ class Service extends Model * * @var array */ - protected $fillable = [ - 'name', 'description', 'folder', 'startup', 'index_file', + protected $fillable = ['name', 'author', 'description', 'folder', 'startup', 'index_file']; + + /** + * @var array + */ + protected static $applicationRules = [ + 'author' => 'required', + 'name' => 'required', + 'description' => 'sometimes', + 'folder' => 'required', + 'startup' => 'sometimes', + 'index_file' => 'required', ]; /** - * Returns the default contents of the index.js file for a service. - * - * @return string + * @var array */ - public static function defaultIndexFile() - { - return <<<'EOF' -'use strict'; - -/** - * Pterodactyl - Daemon - * Copyright (c) 2015 - 2017 Dane Everitt - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -const rfr = require('rfr'); -const _ = require('lodash'); - -const Core = rfr('src/services/index.js'); - -class Service extends Core {} - -module.exports = Service; -EOF; - } + protected static $dataIntegrityRules = [ + 'author' => 'string|size:36', + 'name' => 'string|max:255', + 'description' => 'nullable|string', + 'folder' => 'string|max:255|regex:/^[\w.-]{1,50}$/|unique:services,folder', + 'startup' => 'nullable|string', + 'index_file' => 'string', + ]; /** * Gets all service options associated with this service. @@ -105,8 +90,10 @@ EOF; public function packs() { return $this->hasManyThrough( - 'Pterodactyl\Models\Pack', 'Pterodactyl\Models\ServiceOption', - 'service_id', 'option_id' + 'Pterodactyl\Models\Pack', + 'Pterodactyl\Models\ServiceOption', + 'service_id', + 'option_id' ); } diff --git a/app/Models/ServiceOption.php b/app/Models/ServiceOption.php index 5f0cd9c9e..2b553f49d 100644 --- a/app/Models/ServiceOption.php +++ b/app/Models/ServiceOption.php @@ -24,10 +24,16 @@ namespace Pterodactyl\Models; +use Sofa\Eloquence\Eloquence; +use Sofa\Eloquence\Validable; use Illuminate\Database\Eloquent\Model; +use Sofa\Eloquence\Contracts\CleansAttributes; +use Sofa\Eloquence\Contracts\Validable as ValidableContract; -class ServiceOption extends Model +class ServiceOption extends Model implements CleansAttributes, ValidableContract { + use Eloquence, Validable; + /** * The table associated with the model. * @@ -42,15 +48,61 @@ class ServiceOption extends Model */ protected $guarded = ['id', 'created_at', 'updated_at']; - /** - * Cast values to correct type. - * - * @var array - */ - protected $casts = [ - 'service_id' => 'integer', - 'script_is_privileged' => 'boolean', - ]; + /** + * Cast values to correct type. + * + * @var array + */ + protected $casts = [ + 'service_id' => 'integer', + 'script_is_privileged' => 'boolean', + ]; + + /** + * @var array + */ + protected static $applicationRules = [ + 'service_id' => 'required', + 'name' => 'required', + 'description' => 'required', + 'tag' => 'required', + 'docker_image' => 'sometimes', + 'startup' => 'sometimes', + 'config_from' => 'sometimes', + 'config_stop' => 'required_without:config_from', + 'config_startup' => 'required_without:config_from', + 'config_logs' => 'required_without:config_from', + 'config_files' => 'required_without:config_from', + ]; + + /** + * @var array + */ + protected static $dataIntegrityRules = [ + 'service_id' => 'numeric|exists:services,id', + 'name' => 'string|max:255', + 'description' => 'string', + 'tag' => 'alpha_num|max:60|unique:service_options,tag', + 'docker_image' => 'string|max:255', + 'startup' => 'nullable|string', + 'config_from' => 'nullable|numeric|exists:service_options,id', + 'config_stop' => 'nullable|string|max:255', + 'config_startup' => 'nullable|json', + 'config_logs' => 'nullable|json', + 'config_files' => 'nullable|json', + ]; + + /** + * @var array + */ + protected $attributes = [ + 'config_stop' => null, + 'config_startup' => null, + 'config_logs' => null, + 'config_files' => null, + 'startup' => null, + 'docker_image' => null, + ]; /** * Returns the display startup string for the option and will use the parent @@ -136,6 +188,11 @@ class ServiceOption extends Model return $this->hasMany(Pack::class, 'option_id'); } + /** + * Get the parent service option from which to copy scripts. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ public function copyFrom() { return $this->belongsTo(self::class, 'copy_script_from'); diff --git a/app/Models/ServiceVariable.php b/app/Models/ServiceVariable.php index 93e93e7e9..a341165e3 100644 --- a/app/Models/ServiceVariable.php +++ b/app/Models/ServiceVariable.php @@ -24,10 +24,23 @@ namespace Pterodactyl\Models; +use Sofa\Eloquence\Eloquence; +use Sofa\Eloquence\Validable; use Illuminate\Database\Eloquent\Model; +use Sofa\Eloquence\Contracts\CleansAttributes; +use Sofa\Eloquence\Contracts\Validable as ValidableContract; -class ServiceVariable extends Model +class ServiceVariable extends Model implements CleansAttributes, ValidableContract { + use Eloquence, Validable; + + /** + * Reserved environment variable names. + * + * @var string + */ + const RESERVED_ENV_NAMES = 'SERVER_MEMORY,SERVER_IP,SERVER_PORT,ENV,HOME,USER,STARTUP,SERVER_UUID,UUID'; + /** * The table associated with the model. * @@ -54,28 +67,35 @@ class ServiceVariable extends Model ]; /** - * Reserved environment variable names. - * * @var array */ - protected static $reservedNames = [ - 'SERVER_MEMORY', - 'SERVER_IP', - 'SERVER_PORT', - 'ENV', - 'HOME', - 'USER', + protected static $applicationRules = [ + 'name' => 'required', + 'env_variable' => 'required', + 'rules' => 'required', ]; /** - * Returns an array of environment variable names that cannot be used. - * - * @return array + * @var array */ - public static function reservedNames() - { - return self::$reservedNames; - } + protected static $dataIntegrityRules = [ + 'option_id' => 'exists:service_options,id', + 'name' => 'string|between:1,255', + 'description' => 'nullable|string', + 'env_variable' => 'regex:/^[\w]{1,255}$/|notIn:' . self::RESERVED_ENV_NAMES, + 'default_value' => 'string', + 'user_viewable' => 'boolean', + 'user_editable' => 'boolean', + 'rules' => 'string', + ]; + + /** + * @var array + */ + protected $attributes = [ + 'user_editable' => 0, + 'user_viewable' => 0, + ]; /** * Returns the display executable for the option and will use the parent diff --git a/app/Models/Subuser.php b/app/Models/Subuser.php index a2eff9c9a..5326da3f4 100644 --- a/app/Models/Subuser.php +++ b/app/Models/Subuser.php @@ -24,12 +24,16 @@ namespace Pterodactyl\Models; +use Sofa\Eloquence\Eloquence; +use Sofa\Eloquence\Validable; use Illuminate\Database\Eloquent\Model; use Illuminate\Notifications\Notifiable; +use Sofa\Eloquence\Contracts\CleansAttributes; +use Sofa\Eloquence\Contracts\Validable as ValidableContract; -class Subuser extends Model +class Subuser extends Model implements CleansAttributes, ValidableContract { - use Notifiable; + use Eloquence, Notifiable, Validable; /** * The table associated with the model. @@ -62,6 +66,24 @@ class Subuser extends Model 'server_id' => 'integer', ]; + /** + * @var array + */ + protected static $applicationRules = [ + 'user_id' => 'required', + 'server_id' => 'required', + 'daemonSecret' => 'required', + ]; + + /** + * @var array + */ + protected static $dataIntegrityRules = [ + 'user_id' => 'numeric|exists:users,id', + 'server_id' => 'numeric|exists:servers,id', + 'daemonSecret' => 'string', + ]; + /** * Gets the server associated with a subuser. * diff --git a/app/Models/Task.php b/app/Models/Task.php index 5e44a8264..71a582510 100644 --- a/app/Models/Task.php +++ b/app/Models/Task.php @@ -24,10 +24,16 @@ namespace Pterodactyl\Models; +use Sofa\Eloquence\Eloquence; +use Sofa\Eloquence\Validable; use Illuminate\Database\Eloquent\Model; +use Sofa\Eloquence\Contracts\CleansAttributes; +use Sofa\Eloquence\Contracts\Validable as ValidableContract; -class Task extends Model +class Task extends Model implements CleansAttributes, ValidableContract { + use Eloquence, Validable; + /** * The table associated with the model. * @@ -55,6 +61,53 @@ class Task extends Model 'active' => 'boolean', ]; + /** + * Default attributes when creating a new model. + * + * @var array + */ + protected $attributes = [ + 'parent_task_id' => null, + 'chain_order' => null, + 'active' => true, + 'day_of_week' => '*', + 'day_of_month' => '*', + 'hour' => '*', + 'minute' => '*', + 'chain_delay' => null, + 'queued' => false, + ]; + + /** + * @var array + */ + protected static $applicationRules = [ + 'server_id' => 'required', + 'action' => 'required', + 'data' => 'required', + ]; + + /** + * @var array + */ + protected static $dataIntegrityRules = [ + 'name' => 'nullable|string|max:255', + 'parent_task_id' => 'nullable|numeric|exists:tasks,id', + 'chain_order' => 'nullable|numeric|min:1', + 'server_id' => 'numeric|exists:servers,id', + 'active' => 'boolean', + 'action' => 'string', + 'data' => 'string', + 'queued' => 'boolean', + 'day_of_month' => 'string', + 'day_of_week' => 'string', + 'hour' => 'string', + 'minute' => 'string', + 'chain_delay' => 'nullable|numeric|between:1,900', + 'last_run' => 'nullable|timestamp', + 'next_run' => 'nullable|timestamp', + ]; + /** * The attributes that should be mutated to dates. * @@ -62,6 +115,16 @@ class Task extends Model */ protected $dates = ['last_run', 'next_run', 'created_at', 'updated_at']; + /** + * Return a hashid encoded string to represent the ID of the task. + * + * @return string + */ + public function getHashidAttribute() + { + return app()->make('hashids')->encode($this->id); + } + /** * Gets the server associated with a task. * @@ -81,4 +144,14 @@ class Task extends Model { return $this->belongsTo(User::class); } + + /** + * Return chained tasks for a parent task. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function chained() + { + return $this->hasMany(self::class, 'parent_task_id')->orderBy('chain_order', 'asc'); + } } diff --git a/app/Models/User.php b/app/Models/User.php index 12504b6b0..29941e090 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -26,35 +26,29 @@ namespace Pterodactyl\Models; use Hash; use Google2FA; +use Sofa\Eloquence\Eloquence; +use Sofa\Eloquence\Validable; use Illuminate\Auth\Authenticatable; use Illuminate\Database\Eloquent\Model; use Illuminate\Notifications\Notifiable; use Pterodactyl\Exceptions\DisplayException; -use Nicolaslopezj\Searchable\SearchableTrait; +use Sofa\Eloquence\Contracts\CleansAttributes; use Illuminate\Auth\Passwords\CanResetPassword; use Illuminate\Foundation\Auth\Access\Authorizable; +use Sofa\Eloquence\Contracts\Validable as ValidableContract; use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract; use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification; -class User extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract +class User extends Model implements + AuthenticatableContract, + AuthorizableContract, + CanResetPasswordContract, + CleansAttributes, + ValidableContract { - use Authenticatable, Authorizable, CanResetPassword, Notifiable, SearchableTrait; - - /** - * The rules for user passwords. - * - * @var string - */ - const PASSWORD_RULES = 'regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})'; - - /** - * The regex rules for usernames. - * - * @var string - */ - const USERNAME_RULES = 'regex:/^([\w\d\.\-]{1,255})$/'; + use Authenticatable, Authorizable, CanResetPassword, Eloquence, Notifiable, Validable; /** * Level of servers to display when using access() on a user. @@ -83,9 +77,9 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac * @var array */ protected $casts = [ - 'root_admin' => 'integer', - 'use_totp' => 'integer', - 'gravatar' => 'integer', + 'root_admin' => 'boolean', + 'use_totp' => 'boolean', + 'gravatar' => 'boolean', ]; /** @@ -100,23 +94,62 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac * * @var array */ - protected $searchable = [ - 'columns' => [ - 'email' => 10, - 'username' => 9, - 'name_first' => 6, - 'name_last' => 6, - 'uuid' => 1, - ], + protected $searchableColumns = [ + 'email' => 10, + 'username' => 9, + 'name_first' => 6, + 'name_last' => 6, + 'uuid' => 1, ]; - protected $query; + /** + * Default values for specific fields in the database. + * + * @var array + */ + protected $attributes = [ + 'root_admin' => false, + 'language' => 'en', + 'use_totp' => false, + 'totp_secret' => null, + ]; + + /** + * Rules verifying that the data passed in forms is valid and meets application logic rules. + * + * @var array + */ + protected static $applicationRules = [ + 'email' => 'required', + 'username' => 'required', + 'name_first' => 'required', + 'name_last' => 'required', + 'password' => 'sometimes', + ]; + + /** + * Rules verifying that the data being stored matches the expectations of the database. + * + * @var array + */ + protected static $dataIntegrityRules = [ + 'email' => 'email|unique:users,email', + 'username' => 'alpha_dash|between:1,255|unique:users,username', + 'name_first' => 'string|between:1,255', + 'name_last' => 'string|between:1,255', + 'password' => 'nullable|string', + 'root_admin' => 'boolean', + 'language' => 'string|between:2,5', + 'use_totp' => 'boolean', + 'totp_secret' => 'nullable|string', + ]; /** * Enables or disables TOTP on an account if the token is valid. * - * @param int $token + * @param int $token * @return bool + * @deprecated */ public function toggleTotp($token) { @@ -136,9 +169,10 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac * - at least one lowercase character * - at least one number. * - * @param string $password - * @param string $regex - * @return void + * @param string $password + * @param string $regex + * @throws \Pterodactyl\Exceptions\DisplayException + * @deprecated */ public function setPassword($password, $regex = '((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})') { @@ -153,8 +187,7 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac /** * Send the password reset notification. * - * @param string $token - * @return void + * @param string $token */ public function sendPasswordResetNotification($token) { @@ -165,16 +198,17 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac * Return true or false depending on wether the user is root admin or not. * * @return bool + * @deprecated */ public function isRootAdmin() { - return $this->root_admin === 1; + return $this->root_admin; } /** * Returns the user's daemon secret for a given server. * - * @param \Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\Server $server * @return null|string */ public function daemonToken(Server $server) @@ -204,8 +238,8 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac /** * Change the access level for a given call to `access()` on the user. * - * @param string $level can be all, admin, subuser, owner - * @return void + * @param string $level can be all, admin, subuser, owner + * @return $this */ public function setAccessLevel($level = 'all') { @@ -221,8 +255,8 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac * Returns an array of all servers a user is able to access. * Note: does not account for user admin status. * - * @param array $load - * @return \Illuiminate\Database\Eloquent\Builder + * @param array $load + * @return \Pterodactyl\Models\Server */ public function access(...$load) { @@ -256,6 +290,16 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac return $query; } + /** + * Store the username as a lowecase string. + * + * @param string $value + */ + public function setUsernameAttribute($value) + { + $this->attributes['username'] = strtolower($value); + } + /** * Returns all permissions that a user has. * diff --git a/app/Notifications/AccountCreated.php b/app/Notifications/AccountCreated.php index f92a7a477..47fbf7551 100644 --- a/app/Notifications/AccountCreated.php +++ b/app/Notifications/AccountCreated.php @@ -43,8 +43,7 @@ class AccountCreated extends Notification implements ShouldQueue /** * Create a new notification instance. * - * @param aray $user - * @return void + * @param aray $user */ public function __construct(array $user) { @@ -54,7 +53,7 @@ class AccountCreated extends Notification implements ShouldQueue /** * Get the notification's delivery channels. * - * @param mixed $notifiable + * @param mixed $notifiable * @return array */ public function via($notifiable) @@ -65,7 +64,7 @@ class AccountCreated extends Notification implements ShouldQueue /** * Get the mail representation of the notification. * - * @param mixed $notifiable + * @param mixed $notifiable * @return \Illuminate\Notifications\Messages\MailMessage */ public function toMail($notifiable) diff --git a/app/Notifications/AddedToServer.php b/app/Notifications/AddedToServer.php index 415b39fb0..4d8bf838e 100644 --- a/app/Notifications/AddedToServer.php +++ b/app/Notifications/AddedToServer.php @@ -41,8 +41,7 @@ class AddedToServer extends Notification implements ShouldQueue /** * Create a new notification instance. * - * @param array $server - * @return void + * @param array $server */ public function __construct(array $server) { @@ -52,7 +51,7 @@ class AddedToServer extends Notification implements ShouldQueue /** * Get the notification's delivery channels. * - * @param mixed $notifiable + * @param mixed $notifiable * @return array */ public function via($notifiable) @@ -63,7 +62,7 @@ class AddedToServer extends Notification implements ShouldQueue /** * Get the mail representation of the notification. * - * @param mixed $notifiable + * @param mixed $notifiable * @return \Illuminate\Notifications\Messages\MailMessage */ public function toMail($notifiable) diff --git a/app/Notifications/RemovedFromServer.php b/app/Notifications/RemovedFromServer.php index d4831dbe4..46b7143a5 100644 --- a/app/Notifications/RemovedFromServer.php +++ b/app/Notifications/RemovedFromServer.php @@ -41,8 +41,7 @@ class RemovedFromServer extends Notification implements ShouldQueue /** * Create a new notification instance. * - * @param array $server - * @return void + * @param array $server */ public function __construct(array $server) { @@ -52,7 +51,7 @@ class RemovedFromServer extends Notification implements ShouldQueue /** * Get the notification's delivery channels. * - * @param mixed $notifiable + * @param mixed $notifiable * @return array */ public function via($notifiable) @@ -63,7 +62,7 @@ class RemovedFromServer extends Notification implements ShouldQueue /** * Get the mail representation of the notification. * - * @param mixed $notifiable + * @param mixed $notifiable * @return \Illuminate\Notifications\Messages\MailMessage */ public function toMail($notifiable) diff --git a/app/Notifications/SendPasswordReset.php b/app/Notifications/SendPasswordReset.php index 367f863ed..1ba616eae 100644 --- a/app/Notifications/SendPasswordReset.php +++ b/app/Notifications/SendPasswordReset.php @@ -43,8 +43,7 @@ class SendPasswordReset extends Notification implements ShouldQueue /** * Create a new notification instance. * - * @param string $token - * @return void + * @param string $token */ public function __construct($token) { @@ -54,7 +53,7 @@ class SendPasswordReset extends Notification implements ShouldQueue /** * Get the notification's delivery channels. * - * @param mixed $notifiable + * @param mixed $notifiable * @return array */ public function via($notifiable) @@ -65,7 +64,7 @@ class SendPasswordReset extends Notification implements ShouldQueue /** * Get the mail representation of the notification. * - * @param mixed $notifiable + * @param mixed $notifiable * @return \Illuminate\Notifications\Messages\MailMessage */ public function toMail($notifiable) diff --git a/app/Notifications/ServerCreated.php b/app/Notifications/ServerCreated.php index 9f881729a..054b4adb1 100644 --- a/app/Notifications/ServerCreated.php +++ b/app/Notifications/ServerCreated.php @@ -41,8 +41,7 @@ class ServerCreated extends Notification implements ShouldQueue /** * Create a new notification instance. * - * @param array $server - * @return void + * @param array $server */ public function __construct(array $server) { @@ -52,7 +51,7 @@ class ServerCreated extends Notification implements ShouldQueue /** * Get the notification's delivery channels. * - * @param mixed $notifiable + * @param mixed $notifiable * @return array */ public function via($notifiable) @@ -63,7 +62,7 @@ class ServerCreated extends Notification implements ShouldQueue /** * Get the mail representation of the notification. * - * @param mixed $notifiable + * @param mixed $notifiable * @return \Illuminate\Notifications\Messages\MailMessage */ public function toMail($notifiable) diff --git a/app/Observers/ServerObserver.php b/app/Observers/ServerObserver.php index cd8c2187a..557da2eb9 100644 --- a/app/Observers/ServerObserver.php +++ b/app/Observers/ServerObserver.php @@ -37,8 +37,7 @@ class ServerObserver /** * Listen to the Server creating event. * - * @param \Pterodactyl\Models\Server $server - * @return void + * @param \Pterodactyl\Models\Server $server */ public function creating(Server $server) { @@ -48,8 +47,7 @@ class ServerObserver /** * Listen to the Server created event. * - * @param \Pterodactyl\Models\Server $server - * @return void + * @param \Pterodactyl\Models\Server $server */ public function created(Server $server) { @@ -69,8 +67,7 @@ class ServerObserver /** * Listen to the Server deleting event. * - * @param \Pterodactyl\Models\Server $server - * @return void + * @param \Pterodactyl\Models\Server $server */ public function deleting(Server $server) { @@ -80,8 +77,7 @@ class ServerObserver /** * Listen to the Server deleted event. * - * @param \Pterodactyl\Models\Server $server - * @return void + * @param \Pterodactyl\Models\Server $server */ public function deleted(Server $server) { @@ -91,8 +87,7 @@ class ServerObserver /** * Listen to the Server saving event. * - * @param \Pterodactyl\Models\Server $server - * @return void + * @param \Pterodactyl\Models\Server $server */ public function saving(Server $server) { @@ -102,8 +97,7 @@ class ServerObserver /** * Listen to the Server saved event. * - * @param \Pterodactyl\Models\Server $server - * @return void + * @param \Pterodactyl\Models\Server $server */ public function saved(Server $server) { @@ -113,8 +107,7 @@ class ServerObserver /** * Listen to the Server updating event. * - * @param \Pterodactyl\Models\Server $server - * @return void + * @param \Pterodactyl\Models\Server $server */ public function updating(Server $server) { @@ -124,8 +117,7 @@ class ServerObserver /** * Listen to the Server saved event. * - * @param \Pterodactyl\Models\Server $server - * @return void + * @param \Pterodactyl\Models\Server $server */ public function updated(Server $server) { diff --git a/app/Observers/SubuserObserver.php b/app/Observers/SubuserObserver.php index 57eab2680..a6bcdaccf 100644 --- a/app/Observers/SubuserObserver.php +++ b/app/Observers/SubuserObserver.php @@ -34,8 +34,7 @@ class SubuserObserver /** * Listen to the Subuser creating event. * - * @param \Pterodactyl\Models\Subuser $subuser - * @return void + * @param \Pterodactyl\Models\Subuser $subuser */ public function creating(Subuser $subuser) { @@ -45,8 +44,7 @@ class SubuserObserver /** * Listen to the Subuser created event. * - * @param \Pterodactyl\Models\Subuser $subuser - * @return void + * @param \Pterodactyl\Models\Subuser $subuser */ public function created(Subuser $subuser) { @@ -62,8 +60,7 @@ class SubuserObserver /** * Listen to the Subuser deleting event. * - * @param \Pterodactyl\Models\Subuser $subuser - * @return void + * @param \Pterodactyl\Models\Subuser $subuser */ public function deleting(Subuser $subuser) { @@ -73,8 +70,7 @@ class SubuserObserver /** * Listen to the Subuser deleted event. * - * @param \Pterodactyl\Models\Subuser $subuser - * @return void + * @param \Pterodactyl\Models\Subuser $subuser */ public function deleted(Subuser $subuser) { diff --git a/app/Observers/UserObserver.php b/app/Observers/UserObserver.php index e7b07851d..5b1ee844c 100644 --- a/app/Observers/UserObserver.php +++ b/app/Observers/UserObserver.php @@ -24,57 +24,45 @@ namespace Pterodactyl\Observers; -use DB; -use Hash; -use Carbon; use Pterodactyl\Events; use Pterodactyl\Models\User; -use Pterodactyl\Notifications\AccountCreated; +use Pterodactyl\Services\Components\UuidService; class UserObserver { + protected $uuid; + + public function __construct(UuidService $uuid) + { + $this->uuid = $uuid; + } + /** * Listen to the User creating event. * - * @param \Pterodactyl\Models\User $user - * @return void + * @param \Pterodactyl\Models\User $user */ public function creating(User $user) { + $user->uuid = $this->uuid->generate('users', 'uuid'); + event(new Events\User\Creating($user)); } /** * Listen to the User created event. * - * @param \Pterodactyl\Models\User $user - * @return void + * @param \Pterodactyl\Models\User $user */ public function created(User $user) { event(new Events\User\Created($user)); - - if ($user->password === 'unset') { - $token = hash_hmac('sha256', str_random(40), config('app.key')); - DB::table('password_resets')->insert([ - 'email' => $user->email, - 'token' => Hash::make($token), - 'created_at' => Carbon::now()->toDateTimeString(), - ]); - } - - $user->notify(new AccountCreated([ - 'name' => $user->name_first, - 'username' => $user->username, - 'token' => (isset($token)) ? $token : null, - ])); } /** * Listen to the User deleting event. * - * @param \Pterodactyl\Models\User $user - * @return void + * @param \Pterodactyl\Models\User $user */ public function deleting(User $user) { @@ -84,8 +72,7 @@ class UserObserver /** * Listen to the User deleted event. * - * @param \Pterodactyl\Models\User $user - * @return void + * @param \Pterodactyl\Models\User $user */ public function deleted(User $user) { diff --git a/app/Policies/APIKeyPolicy.php b/app/Policies/APIKeyPolicy.php index 95846b9e4..f397d4868 100644 --- a/app/Policies/APIKeyPolicy.php +++ b/app/Policies/APIKeyPolicy.php @@ -43,7 +43,7 @@ class APIKeyPolicy protected function checkPermission(User $user, Key $key, $permission) { // Non-administrative users cannot use administrative routes. - if (! starts_with($key, 'user.') && ! $user->isRootAdmin()) { + if (! starts_with($key, 'user.') && ! $user->root_admin) { return false; } @@ -61,9 +61,9 @@ class APIKeyPolicy /** * Determine if a user has permission to perform this action against the system. * - * @param \Pterodactyl\Models\User $user - * @param string $permission - * @param \Pterodactyl\Models\APIKey $key + * @param \Pterodactyl\Models\User $user + * @param string $permission + * @param \Pterodactyl\Models\APIKey $key * @return bool */ public function before(User $user, $permission, Key $key) diff --git a/app/Policies/ServerPolicy.php b/app/Policies/ServerPolicy.php index 56cd359e1..0b9968f9e 100644 --- a/app/Policies/ServerPolicy.php +++ b/app/Policies/ServerPolicy.php @@ -53,14 +53,14 @@ class ServerPolicy /** * Runs before any of the functions are called. Used to determine if user is root admin, if so, ignore permissions. * - * @param \Pterodactyl\Models\User $user - * @param string $ability - * @param \Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\User $user + * @param string $ability + * @param \Pterodactyl\Models\Server $server * @return bool */ public function before(User $user, $ability, Server $server) { - if ($user->isRootAdmin() || $server->owner_id === $user->id) { + if ($user->root_admin || $server->owner_id === $user->id) { return true; } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 03fafc49c..1fc8b7423 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -34,8 +34,6 @@ class AppServiceProvider extends ServiceProvider { /** * Bootstrap any application services. - * - * @return void */ public function boot() { @@ -49,8 +47,6 @@ class AppServiceProvider extends ServiceProvider /** * Register any application services. - * - * @return void */ public function register() { @@ -73,7 +69,10 @@ class AppServiceProvider extends ServiceProvider return Cache::remember('git-version', 5, function () { if (file_exists(base_path('.git/HEAD'))) { $head = explode(' ', file_get_contents(base_path('.git/HEAD'))); - $path = base_path('.git/' . trim($head[1])); + + if (array_key_exists(1, $head)) { + $path = base_path('.git/' . trim($head[1])); + } } if (isset($path) && file_exists($path)) { diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index e1401e844..0cdb82a29 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -19,8 +19,7 @@ class AuthServiceProvider extends ServiceProvider /** * Register any application authentication / authorization services. * - * @param \Illuminate\Contracts\Auth\Access\Gate $gate - * @return void + * @param \Illuminate\Contracts\Auth\Access\Gate $gate */ public function boot() { diff --git a/app/Providers/BroadcastServiceProvider.php b/app/Providers/BroadcastServiceProvider.php index e61e610d5..3f7c84be4 100644 --- a/app/Providers/BroadcastServiceProvider.php +++ b/app/Providers/BroadcastServiceProvider.php @@ -9,8 +9,6 @@ class BroadcastServiceProvider extends ServiceProvider { /** * Bootstrap any application services. - * - * @return void */ public function boot() { diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 9126aa6ae..1f48d33da 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -16,8 +16,6 @@ class EventServiceProvider extends ServiceProvider /** * Register any other events for your application. - * - * @return void */ public function boot() { diff --git a/app/Providers/HashidsServiceProvider.php b/app/Providers/HashidsServiceProvider.php new file mode 100644 index 000000000..ee28104ef --- /dev/null +++ b/app/Providers/HashidsServiceProvider.php @@ -0,0 +1,51 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Providers; + +use Pterodactyl\Extensions\Hashids; +use Illuminate\Support\ServiceProvider; +use Pterodactyl\Contracts\Extensions\HashidsInterface; + +class HashidsServiceProvider extends ServiceProvider +{ + /** + * Register the ability to use Hashids. + */ + public function register() + { + $this->app->singleton(HashidsInterface::class, function () { + /** @var \Illuminate\Contracts\Config\Repository $config */ + $config = $this->app['config']; + + return new Hashids( + $config->get('hashids.salt', ''), + $config->get('hashids.length', 0), + $config->get('hashids.alphabet', 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890') + ); + }); + + $this->app->alias(HashidsInterface::class, 'hashids'); + } +} diff --git a/app/Providers/MacroServiceProvider.php b/app/Providers/MacroServiceProvider.php index 8dd08f73b..50e205151 100644 --- a/app/Providers/MacroServiceProvider.php +++ b/app/Providers/MacroServiceProvider.php @@ -30,13 +30,12 @@ use Carbon; use Request; use Pterodactyl\Models\APIKey; use Illuminate\Support\ServiceProvider; +use Pterodactyl\Services\ApiKeyService; class MacroServiceProvider extends ServiceProvider { /** * Bootstrap the application services. - * - * @return void */ public function boot() { @@ -47,7 +46,7 @@ class MacroServiceProvider extends ServiceProvider $i = 0; while (($size / 1024) > 0.9) { $size = $size / 1024; - $i++; + ++$i; } return round($size, ($i < 2) ? 0 : $precision) . ' ' . $units[$i]; @@ -60,7 +59,7 @@ class MacroServiceProvider extends ServiceProvider $parts = explode('.', Request::bearerToken()); - if (count($parts) === 2 && strlen($parts[0]) === APIKey::PUBLIC_KEY_LEN) { + if (count($parts) === 2 && strlen($parts[0]) === ApiKeyService::PUB_CRYPTO_BYTES * 2) { // Because the key itself isn't changing frequently, we simply cache this for // 15 minutes to speed up the API and keep requests flowing. return Cache::tags([ diff --git a/app/Providers/PhraseAppTranslationProvider.php b/app/Providers/PhraseAppTranslationProvider.php index 840234917..cfc68bb3f 100644 --- a/app/Providers/PhraseAppTranslationProvider.php +++ b/app/Providers/PhraseAppTranslationProvider.php @@ -32,8 +32,6 @@ class PhraseAppTranslationProvider extends TranslationServiceProvider { /** * Register the service provider. - * - * @return void */ public function register() { diff --git a/app/Providers/RepositoryServiceProvider.php b/app/Providers/RepositoryServiceProvider.php new file mode 100644 index 000000000..f44715dfb --- /dev/null +++ b/app/Providers/RepositoryServiceProvider.php @@ -0,0 +1,112 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Providers; + +use Illuminate\Support\ServiceProvider; +use Pterodactyl\Repositories\Daemon\FileRepository; +use Pterodactyl\Repositories\Daemon\PowerRepository; +use Pterodactyl\Repositories\Eloquent\NodeRepository; +use Pterodactyl\Repositories\Eloquent\PackRepository; +use Pterodactyl\Repositories\Eloquent\TaskRepository; +use Pterodactyl\Repositories\Eloquent\UserRepository; +use Pterodactyl\Repositories\Daemon\CommandRepository; +use Pterodactyl\Repositories\Eloquent\ApiKeyRepository; +use Pterodactyl\Repositories\Eloquent\ServerRepository; +use Pterodactyl\Repositories\Eloquent\ServiceRepository; +use Pterodactyl\Repositories\Eloquent\SessionRepository; +use Pterodactyl\Repositories\Eloquent\SubuserRepository; +use Pterodactyl\Repositories\Eloquent\DatabaseRepository; +use Pterodactyl\Repositories\Eloquent\LocationRepository; +use Pterodactyl\Repositories\Eloquent\AllocationRepository; +use Pterodactyl\Repositories\Eloquent\PermissionRepository; +use Pterodactyl\Repositories\Daemon\ConfigurationRepository; +use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; +use Pterodactyl\Contracts\Repository\PackRepositoryInterface; +use Pterodactyl\Contracts\Repository\TaskRepositoryInterface; +use Pterodactyl\Contracts\Repository\UserRepositoryInterface; +use Pterodactyl\Repositories\Eloquent\DatabaseHostRepository; +use Pterodactyl\Repositories\Eloquent\ApiPermissionRepository; +use Pterodactyl\Repositories\Eloquent\ServiceOptionRepository; +use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Repositories\Eloquent\OptionVariableRepository; +use Pterodactyl\Repositories\Eloquent\ServerVariableRepository; +use Pterodactyl\Contracts\Repository\ServiceRepositoryInterface; +use Pterodactyl\Contracts\Repository\SessionRepositoryInterface; +use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; +use Pterodactyl\Repositories\Eloquent\ServiceVariableRepository; +use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; +use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; +use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; +use Pterodactyl\Contracts\Repository\PermissionRepositoryInterface; +use Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface; +use Pterodactyl\Contracts\Repository\Daemon\PowerRepositoryInterface; +use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface; +use Pterodactyl\Contracts\Repository\ApiPermissionRepositoryInterface; +use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface; +use Pterodactyl\Contracts\Repository\Daemon\CommandRepositoryInterface; +use Pterodactyl\Contracts\Repository\OptionVariableRepositoryInterface; +use Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface; +use Pterodactyl\Contracts\Repository\ServiceVariableRepositoryInterface; +use Pterodactyl\Contracts\Repository\Daemon\ConfigurationRepositoryInterface; +use Pterodactyl\Repositories\Daemon\ServerRepository as DaemonServerRepository; +use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; + +class RepositoryServiceProvider extends ServiceProvider +{ + /** + * Register all of the repository bindings. + */ + public function register() + { + // Eloquent Repositories + $this->app->bind(AllocationRepositoryInterface::class, AllocationRepository::class); + $this->app->bind(ApiKeyRepositoryInterface::class, ApiKeyRepository::class); + $this->app->bind(ApiPermissionRepositoryInterface::class, ApiPermissionRepository::class); + $this->app->bind(DatabaseRepositoryInterface::class, DatabaseRepository::class); + $this->app->bind(DatabaseHostRepositoryInterface::class, DatabaseHostRepository::class); + $this->app->bind(LocationRepositoryInterface::class, LocationRepository::class); + $this->app->bind(NodeRepositoryInterface::class, NodeRepository::class); + $this->app->bind(OptionVariableRepositoryInterface::class, OptionVariableRepository::class); + $this->app->bind(PackRepositoryInterface::class, PackRepository::class); + $this->app->bind(PermissionRepositoryInterface::class, PermissionRepository::class); + $this->app->bind(ServerRepositoryInterface::class, ServerRepository::class); + $this->app->bind(ServerVariableRepositoryInterface::class, ServerVariableRepository::class); + $this->app->bind(ServiceRepositoryInterface::class, ServiceRepository::class); + $this->app->bind(ServiceOptionRepositoryInterface::class, ServiceOptionRepository::class); + $this->app->bind(ServiceVariableRepositoryInterface::class, ServiceVariableRepository::class); + $this->app->bind(SessionRepositoryInterface::class, SessionRepository::class); + $this->app->bind(SubuserRepositoryInterface::class, SubuserRepository::class); + $this->app->bind(TaskRepositoryInterface::class, TaskRepository::class); + $this->app->bind(UserRepositoryInterface::class, UserRepository::class); + + // Daemon Repositories + $this->app->bind(ConfigurationRepositoryInterface::class, ConfigurationRepository::class); + $this->app->bind(CommandRepositoryInterface::class, CommandRepository::class); + $this->app->bind(DaemonServerRepositoryInterface::class, DaemonServerRepository::class); + $this->app->bind(FileRepositoryInterface::class, FileRepository::class); + $this->app->bind(PowerRepositoryInterface::class, PowerRepository::class); + } +} diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 00d40bdbd..64b747d0d 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Providers; +use Pterodactyl\Models\User; use Illuminate\Support\Facades\Route; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; @@ -18,8 +19,6 @@ class RouteServiceProvider extends ServiceProvider /** * Define your route model bindings, pattern filters, etc. - * - * @return void */ public function boot() { @@ -28,8 +27,6 @@ class RouteServiceProvider extends ServiceProvider /** * Define the routes for the application. - * - * @return void */ public function map() { @@ -53,7 +50,7 @@ class RouteServiceProvider extends ServiceProvider ->namespace($this->namespace . '\Auth') ->group(base_path('routes/auth.php')); - Route::middleware(['web', 'auth', 'server', 'csrf'])->prefix('/server/{server}') + Route::middleware(['web', 'csrf', 'auth', 'server', 'subuser'])->prefix('/server/{server}') ->namespace($this->namespace . '\Server') ->group(base_path('routes/server.php')); diff --git a/app/Providers/ViewComposerServiceProvider.php b/app/Providers/ViewComposerServiceProvider.php new file mode 100644 index 000000000..df1648f1a --- /dev/null +++ b/app/Providers/ViewComposerServiceProvider.php @@ -0,0 +1,39 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Providers; + +use Illuminate\Support\ServiceProvider; +use Pterodactyl\Http\ViewComposers\Server\ServerDataComposer; + +class ViewComposerServiceProvider extends ServiceProvider +{ + /** + * Register bindings in the container. + */ + public function boot() + { + $this->app->make('view')->composer('server.*', ServerDataComposer::class); + } +} diff --git a/app/Repositories/APIRepository.php b/app/Repositories/APIRepository.php deleted file mode 100644 index 10af25155..000000000 --- a/app/Repositories/APIRepository.php +++ /dev/null @@ -1,207 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Repositories; - -use DB; -use Auth; -use Crypt; -use Validator; -use IPTools\Network; -use Pterodactyl\Models\User; -use Pterodactyl\Models\APIKey as Key; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Models\APIPermission as Permission; -use Pterodactyl\Exceptions\DisplayValidationException; - -class APIRepository -{ - /** - * Holder for listing of allowed IPs when creating a new key. - * - * @var array - */ - protected $allowed = []; - - /** - * The eloquent model for a user. - * - * @var \Pterodactyl\Models\User - */ - protected $user; - - /** - * Constructor for API Repository. - * - * @param null|\Pterodactyl\Models\User $user - * @return void - */ - public function __construct(User $user = null) - { - $this->user = is_null($user) ? Auth::user() : $user; - if (is_null($this->user)) { - throw new \Exception('Unable to initialize user for API repository instance.'); - } - } - - /** - * Create a New API Keypair on the system. - * - * @param array $data - * @return string - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function create(array $data) - { - $validator = Validator::make($data, [ - 'memo' => 'string|max:500', - 'allowed_ips' => 'sometimes|string', - 'permissions' => 'sometimes|required|array', - 'admin_permissions' => 'sometimes|required|array', - ]); - - $validator->after(function ($validator) use ($data) { - if (array_key_exists('allowed_ips', $data) && ! empty($data['allowed_ips'])) { - foreach (explode("\n", $data['allowed_ips']) as $ip) { - $ip = trim($ip); - try { - Network::parse($ip); - array_push($this->allowed, $ip); - } catch (\Exception $ex) { - $validator->errors()->add('allowed_ips', 'Could not parse IP <' . $ip . '> because it is in an invalid format.'); - } - } - } - }); - - // Run validator, throw catchable and displayable exception if it fails. - // Exception includes a JSON result of failed validation rules. - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - DB::beginTransaction(); - try { - $secretKey = str_random(16) . '.' . str_random(7) . '.' . str_random(7); - $key = Key::create([ - 'user_id' => $this->user->id, - 'public' => str_random(16), - 'secret' => Crypt::encrypt($secretKey), - 'allowed_ips' => empty($this->allowed) ? null : json_encode($this->allowed), - 'memo' => $data['memo'], - 'expires_at' => null, - ]); - - $totalPermissions = 0; - $pNodes = Permission::permissions(); - - if (isset($data['permissions'])) { - foreach ($data['permissions'] as $permission) { - $parts = explode('-', $permission); - - if (count($parts) !== 2) { - continue; - } - - list($block, $search) = $parts; - - if (! array_key_exists($block, $pNodes['_user'])) { - continue; - } - - if (! in_array($search, $pNodes['_user'][$block])) { - continue; - } - - $totalPermissions++; - Permission::create([ - 'key_id' => $key->id, - 'permission' => 'user.' . $permission, - ]); - } - } - - if ($this->user->isRootAdmin() && isset($data['admin_permissions'])) { - unset($pNodes['_user']); - - foreach ($data['admin_permissions'] as $permission) { - $parts = explode('-', $permission); - - if (count($parts) !== 2) { - continue; - } - - list($block, $search) = $parts; - - if (! array_key_exists($block, $pNodes)) { - continue; - } - - if (! in_array($search, $pNodes[$block])) { - continue; - } - - $totalPermissions++; - Permission::create([ - 'key_id' => $key->id, - 'permission' => $permission, - ]); - } - } - - if ($totalPermissions < 1) { - throw new DisplayException('No valid permissions were passed.'); - } - - DB::commit(); - - return $secretKey; - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } - } - - /** - * Revokes an API key and associated permissions. - * - * @param string $key - * @return void - * - * @throws \Illuminate\Database\Eloquent\ModelNotFoundException - */ - public function revoke($key) - { - DB::transaction(function () use ($key) { - $model = Key::with('permissions')->where('public', $key)->where('user_id', $this->user->id)->firstOrFail(); - foreach ($model->permissions as &$permission) { - $permission->delete(); - } - - $model->delete(); - }); - } -} diff --git a/app/Repositories/Concerns/Searchable.php b/app/Repositories/Concerns/Searchable.php new file mode 100644 index 000000000..75b95ba5a --- /dev/null +++ b/app/Repositories/Concerns/Searchable.php @@ -0,0 +1,53 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Repositories\Concerns; + +trait Searchable +{ + /** + * The term to search. + * + * @var bool|string + */ + protected $searchTerm = false; + + /** + * Perform a search of the model using the given term. + * + * @param string $term + * @return $this + */ + public function search($term) + { + if (empty($term)) { + return $this; + } + + $clone = clone $this; + $clone->searchTerm = $term; + + return $clone; + } +} diff --git a/app/Repositories/Daemon/BaseRepository.php b/app/Repositories/Daemon/BaseRepository.php new file mode 100644 index 000000000..26fb898c9 --- /dev/null +++ b/app/Repositories/Daemon/BaseRepository.php @@ -0,0 +1,165 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Repositories\Daemon; + +use GuzzleHttp\Client; +use Webmozart\Assert\Assert; +use Illuminate\Foundation\Application; +use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; +use Illuminate\Contracts\Config\Repository as ConfigRepository; +use Pterodactyl\Contracts\Repository\Daemon\BaseRepositoryInterface; + +class BaseRepository implements BaseRepositoryInterface +{ + /** + * @var \Illuminate\Foundation\Application + */ + protected $app; + + /** + * @var + */ + protected $accessServer; + + /** + * @var + */ + protected $accessToken; + + /** + * @var + */ + protected $node; + + /** + * @var \Illuminate\Contracts\Config\Repository + */ + protected $config; + + /** + * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface + */ + protected $nodeRepository; + + /** + * BaseRepository constructor. + * + * @param \Illuminate\Foundation\Application $app + * @param \Illuminate\Contracts\Config\Repository $config + * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $nodeRepository + */ + public function __construct( + Application $app, + ConfigRepository $config, + NodeRepositoryInterface $nodeRepository + ) { + $this->app = $app; + $this->config = $config; + $this->nodeRepository = $nodeRepository; + } + + /** + * {@inheritdoc} + */ + public function setNode($id) + { + Assert::numeric($id, 'The first argument passed to setNode must be numeric, received %s.'); + + $this->node = $this->nodeRepository->find($id); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getNode() + { + return $this->node; + } + + /** + * {@inheritdoc} + */ + public function setAccessServer($server = null) + { + Assert::nullOrString($server, 'The first argument passed to setAccessServer must be null or a string, received %s.'); + + $this->accessServer = $server; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getAccessServer() + { + return $this->accessServer; + } + + /** + * {@inheritdoc} + */ + public function setAccessToken($token = null) + { + Assert::nullOrString($token, 'The first argument passed to setAccessToken must be null or a string, received %s.'); + + $this->accessToken = $token; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getAccessToken() + { + return $this->accessToken; + } + + /** + * {@inheritdoc} + */ + public function getHttpClient(array $headers = []) + { + if (! is_null($this->accessServer)) { + $headers['X-Access-Server'] = $this->getAccessServer(); + } + + if (! is_null($this->accessToken)) { + $headers['X-Access-Token'] = $this->getAccessToken(); + } elseif (! is_null($this->node)) { + $headers['X-Access-Token'] = $this->getNode()->daemonSecret; + } + + return new Client([ + 'base_uri' => sprintf('%s://%s:%s/', $this->getNode()->scheme, $this->getNode()->fqdn, $this->getNode()->daemonListen), + 'timeout' => $this->config->get('pterodactyl.guzzle.timeout'), + 'connect_timeout' => $this->config->get('pterodactyl.guzzle.connect_timeout'), + 'headers' => $headers, + ]); + } +} diff --git a/app/Repositories/Daemon/CommandRepository.php b/app/Repositories/Daemon/CommandRepository.php index beb9e8530..4984b6e46 100644 --- a/app/Repositories/Daemon/CommandRepository.php +++ b/app/Repositories/Daemon/CommandRepository.php @@ -1,5 +1,5 @@ . * @@ -24,68 +24,22 @@ namespace Pterodactyl\Repositories\Daemon; -use Pterodactyl\Models\User; -use Pterodactyl\Models\Server; -use GuzzleHttp\Exception\ConnectException; -use Pterodactyl\Exceptions\DisplayException; +use Webmozart\Assert\Assert; +use Pterodactyl\Contracts\Repository\Daemon\CommandRepositoryInterface; -class CommandRepository +class CommandRepository extends BaseRepository implements CommandRepositoryInterface { /** - * The Eloquent Model associated with the requested server. - * - * @var \Pterodactyl\Models\Server - */ - protected $server; - - /** - * The Eloquent Model associated with the user to run the request as. - * - * @var \Pterodactyl\Models\User|null - */ - protected $user; - - /** - * Constuctor for repository. - * - * @param \Pterodactyl\Models\Server $server - * @param \Pterodactyl\Models\User|null $user - * @return void - */ - public function __construct(Server $server, User $user = null) - { - $this->server = $server; - $this->user = $user; - } - - /** - * Sends a command to the daemon. - * - * @param string $command - * @return string - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \GuzzleHttp\Exception\RequestException + * {@inheritdoc} */ public function send($command) { - // We don't use the user's specific daemon secret here since we - // are assuming that a call to this function has been validated. - try { - $response = $this->server->guzzleClient($this->user)->request('POST', '/server/command', [ - 'http_errors' => false, - 'json' => [ - 'command' => $command, - ], - ]); + Assert::stringNotEmpty($command, 'First argument passed to send must be a non-empty string, received %s.'); - if ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) { - throw new DisplayException('Command sending responded with a non-200 error code (HTTP/' . $response->getStatusCode() . ').'); - } - - return $response->getBody(); - } catch (ConnectException $ex) { - throw $ex; - } + return $this->getHttpClient()->request('POST', '/server/command', [ + 'json' => [ + 'command' => $command, + ], + ]); } } diff --git a/app/Repositories/Daemon/ConfigurationRepository.php b/app/Repositories/Daemon/ConfigurationRepository.php new file mode 100644 index 000000000..14f9436d9 --- /dev/null +++ b/app/Repositories/Daemon/ConfigurationRepository.php @@ -0,0 +1,63 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Repositories\Daemon; + +use Pterodactyl\Contracts\Repository\Daemon\ConfigurationRepositoryInterface; + +class ConfigurationRepository extends BaseRepository implements ConfigurationRepositoryInterface +{ + /** + * {@inheritdoc} + */ + public function update(array $overrides = []) + { + $node = $this->getNode(); + $structure = [ + 'web' => [ + 'listen' => $node->daemonListen, + 'ssl' => [ + 'enabled' => (! $node->behind_proxy && $node->scheme === 'https'), + ], + ], + 'sftp' => [ + 'path' => $node->daemonBase, + 'port' => $node->daemonSFTP, + ], + 'remote' => [ + 'base' => $this->config->get('app.url'), + ], + 'uploads' => [ + 'size_limit' => $node->upload_size, + ], + 'keys' => [ + $node->daemonSecret, + ], + ]; + + return $this->getHttpClient()->request('PATCH', '/config', [ + 'json' => array_merge($structure, $overrides), + ]); + } +} diff --git a/app/Repositories/Daemon/FileRepository.php b/app/Repositories/Daemon/FileRepository.php index e789f7469..ee1733310 100644 --- a/app/Repositories/Daemon/FileRepository.php +++ b/app/Repositories/Daemon/FileRepository.php @@ -1,5 +1,5 @@ . * @@ -24,168 +24,101 @@ namespace Pterodactyl\Repositories\Daemon; -use Exception; -use GuzzleHttp\Client; -use Pterodactyl\Models\Server; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Repositories\HelperRepository; +use Webmozart\Assert\Assert; +use Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface; -class FileRepository +class FileRepository extends BaseRepository implements FileRepositoryInterface { - /** - * The Eloquent Model associated with the requested server. - * - * @var \Pterodactyl\Models\Server - */ - protected $server; - - /** - * Constructor. - * - * @param string $uuid - * @return void - */ - public function __construct($uuid) + public function getFileStat($path) { - $this->server = Server::byUuid($uuid); + Assert::stringNotEmpty($path, 'First argument passed to getStat must be a non-empty string, received %s.'); + + $file = pathinfo($path); + $file['dirname'] = in_array($file['dirname'], ['.', './', '/']) ? null : trim($file['dirname'], '/') . '/'; + + $response = $this->getHttpClient()->request('GET', sprintf( + '/server/file/stat/%s', + rawurlencode($file['dirname'] . $file['basename']) + )); + + return json_decode($response->getBody()); } /** - * Get the contents of a requested file for the server. - * - * @param string $file - * @return array - * - * @throws \GuzzleHttp\Exception\RequestException - * @throws \Pterodactyl\Exceptions\DisplayException + * {@inheritdoc} */ - public function returnFileContents($file) + public function getContent($path) { - if (empty($file)) { - throw new Exception('Not all parameters were properly passed to the function.'); - } + Assert::stringNotEmpty($path, 'First argument passed to getContent must be a non-empty string, received %s.'); - $file = (object) pathinfo($file); - $file->dirname = (in_array($file->dirname, ['.', './', '/'])) ? null : trim($file->dirname, '/') . '/'; + $file = pathinfo($path); + $file['dirname'] = in_array($file['dirname'], ['.', './', '/']) ? null : trim($file['dirname'], '/') . '/'; - $res = $this->server->guzzleClient()->request('GET', '/server/file/stat/' . rawurlencode($file->dirname . $file->basename)); + $response = $this->getHttpClient()->request('GET', sprintf( + '/server/file/f/%s', + rawurlencode($file['dirname'] . $file['basename']) + )); - $stat = json_decode($res->getBody()); - if ($res->getStatusCode() !== 200 || ! isset($stat->size)) { - throw new DisplayException('The daemon provided a non-200 error code on stat lookup: HTTP\\' . $res->getStatusCode()); - } - - if (! in_array($stat->mime, HelperRepository::editableFiles())) { - throw new DisplayException('You cannot edit that type of file (' . $stat->mime . ') through the panel.'); - } - - if ($stat->size > 5000000) { - throw new DisplayException('That file is too large to open in the browser, consider using a SFTP client.'); - } - - $res = $this->server->guzzleClient()->request('GET', '/server/file/f/' . rawurlencode($file->dirname . $file->basename)); - - $json = json_decode($res->getBody()); - if ($res->getStatusCode() !== 200 || ! isset($json->content)) { - throw new DisplayException('The daemon provided a non-200 error code: HTTP\\' . $res->getStatusCode()); - } - - return [ - 'file' => $json, - 'stat' => $stat, - ]; + return object_get(json_decode($response->getBody()), 'content'); } /** - * Save the contents of a requested file on the daemon. - * - * @param string $file - * @param string $content - * @return bool - * - * @throws \GuzzleHttp\Exception\RequestException - * @throws \Pterodactyl\Exceptions\DisplayException + * {@inheritdoc} */ - public function saveFileContents($file, $content) + public function putContent($path, $content) { - if (empty($file)) { - throw new Exception('A valid file and path must be specified to save a file.'); - } + Assert::stringNotEmpty($path, 'First argument passed to putContent must be a non-empty string, received %s.'); + Assert::string($content, 'Second argument passed to putContent must be a string, received %s.'); - $file = (object) pathinfo($file); - $file->dirname = (in_array($file->dirname, ['.', './', '/'])) ? null : trim($file->dirname, '/') . '/'; + $file = pathinfo($path); + $file['dirname'] = in_array($file['dirname'], ['.', './', '/']) ? null : trim($file['dirname'], '/') . '/'; - $res = $this->server->guzzleClient()->request('POST', '/server/file/save', [ + return $this->getHttpClient()->request('POST', '/server/file/save', [ 'json' => [ - 'path' => rawurlencode($file->dirname . $file->basename), + 'path' => rawurlencode($file['dirname'] . $file['basename']), 'content' => $content, ], ]); - - if ($res->getStatusCode() !== 204) { - throw new DisplayException('An error occured while attempting to save this file. ' . $res->getBody()); - } - - return true; } /** - * Returns a listing of all files and folders within a specified directory on the daemon. - * - * @param string $directory - * @return object - * - * @throws \GuzzleHttp\Exception\RequestException - * @throws \Pterodactyl\Exceptions\DisplayException + * {@inheritdoc} */ - public function returnDirectoryListing($directory) + public function getDirectory($path) { - if (empty($directory)) { - throw new Exception('A valid directory must be specified in order to list its contents.'); - } + Assert::string($path, 'First argument passed to getDirectory must be a string, received %s.'); - try { - $res = $this->server->guzzleClient()->request('GET', '/server/directory/' . rawurlencode($directory)); - } catch (\GuzzleHttp\Exception\ClientException $ex) { - $json = json_decode($ex->getResponse()->getBody()); + $response = $this->getHttpClient()->request('GET', sprintf( + '/server/directory/%s', + rawurlencode($path) + )); - throw new DisplayException($json->error); - } catch (\GuzzleHttp\Exception\ServerException $ex) { - throw new DisplayException('A remote server error was encountered while attempting to display this directory.'); - } catch (\GuzzleHttp\Exception\ConnectException $ex) { - throw new DisplayException('A ConnectException was encountered: unable to contact daemon.'); - } catch (\Exception $ex) { - throw $ex; - } - - $json = json_decode($res->getBody()); - - // Iterate through results + $contents = json_decode($response->getBody()); $files = []; $folders = []; - foreach ($json as &$value) { + + foreach ($contents as $value) { if ($value->directory) { - // @TODO Handle Symlinks - $folders[] = [ + array_push($folders, [ 'entry' => $value->name, - 'directory' => trim($directory, '/'), + 'directory' => trim($path, '/'), 'size' => null, 'date' => strtotime($value->modified), 'mime' => $value->mime, - ]; + ]); } elseif ($value->file) { - $files[] = [ + array_push($files, [ 'entry' => $value->name, - 'directory' => trim($directory, '/'), + 'directory' => trim($path, '/'), 'extension' => pathinfo($value->name, PATHINFO_EXTENSION), - 'size' => HelperRepository::bytesToHuman($value->size), + 'size' => human_readable($value->size), 'date' => strtotime($value->modified), 'mime' => $value->mime, - ]; + ]); } } - return (object) [ + return [ 'files' => $files, 'folders' => $folders, ]; diff --git a/app/Repositories/Daemon/PowerRepository.php b/app/Repositories/Daemon/PowerRepository.php index 925379096..660db1e89 100644 --- a/app/Repositories/Daemon/PowerRepository.php +++ b/app/Repositories/Daemon/PowerRepository.php @@ -1,5 +1,5 @@ . * @@ -24,106 +24,32 @@ namespace Pterodactyl\Repositories\Daemon; -use Pterodactyl\Models\User; -use Pterodactyl\Models\Server; -use GuzzleHttp\Exception\ConnectException; -use Pterodactyl\Exceptions\DisplayException; +use Webmozart\Assert\Assert; +use Pterodactyl\Contracts\Repository\Daemon\PowerRepositoryInterface; +use Pterodactyl\Exceptions\Repository\Daemon\InvalidPowerSignalException; -class PowerRepository +class PowerRepository extends BaseRepository implements PowerRepositoryInterface { /** - * The Eloquent Model associated with the requested server. - * - * @var \Pterodactyl\Models\Server + * {@inheritdoc} */ - protected $server; - - /** - * The Eloquent Model associated with the user to run the request as. - * - * @var \Pterodactyl\Models\User|null - */ - protected $user; - - /** - * Constuctor for repository. - * - * @param \Pterodactyl\Models\Server $server - * @param \Pterodactyl\Models\User|null $user - * @return void - */ - public function __construct(Server $server, User $user = null) + public function sendSignal($signal) { - $this->server = $server; - $this->user = $user; - } + Assert::stringNotEmpty($signal, 'The first argument passed to sendSignal must be a non-empty string, received %s.'); - /** - * Sends a power option to the daemon. - * - * @param string $action - * @return string - * - * @throws \GuzzleHttp\Exception\RequestException - * @throws \Pterodactyl\Exceptions\DisplayException - */ - public function do($action) - { - try { - $response = $this->server->guzzleClient($this->user)->request('PUT', '/server/power', [ - 'http_errors' => false, - 'json' => [ - 'action' => $action, - ], - ]); - - if ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) { - throw new DisplayException('Power toggle endpoint responded with a non-200 error code (HTTP/' . $response->getStatusCode() . ').'); - } - - return $response->getBody(); - } catch (ConnectException $ex) { - throw $ex; + switch ($signal) { + case self::SIGNAL_START: + case self::SIGNAL_STOP: + case self::SIGNAL_RESTART: + case self::SIGNAL_KILL: + return $this->getHttpClient()->request('PUT', '/server/power', [ + 'json' => [ + 'action' => $signal, + ], + ]); + break; + default: + throw new InvalidPowerSignalException('The signal ' . $signal . ' is not defined and could not be processed.'); } } - - /** - * Starts a server. - * - * @return void - */ - public function start() - { - $this->do('start'); - } - - /** - * Stops a server. - * - * @return void - */ - public function stop() - { - $this->do('stop'); - } - - /** - * Restarts a server. - * - * @return void - */ - public function restart() - { - $this->do('restart'); - } - - /** - * Kills a server. - * - * @return void - */ - public function kill() - { - $this->do('kill'); - } } diff --git a/app/Repositories/Daemon/ServerRepository.php b/app/Repositories/Daemon/ServerRepository.php new file mode 100644 index 000000000..db2f31e6e --- /dev/null +++ b/app/Repositories/Daemon/ServerRepository.php @@ -0,0 +1,172 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Repositories\Daemon; + +use Webmozart\Assert\Assert; +use Pterodactyl\Services\Servers\EnvironmentService; +use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface as DatabaseServerRepositoryInterface; + +class ServerRepository extends BaseRepository implements ServerRepositoryInterface +{ + const DAEMON_PERMISSIONS = ['s:*']; + + /** + * {@inheritdoc} + */ + public function create($id, array $overrides = [], $start = false) + { + Assert::numeric($id, 'First argument passed to create must be numeric, received %s.'); + Assert::boolean($start, 'Third argument passed to create must be boolean, received %s.'); + + $repository = $this->app->make(DatabaseServerRepositoryInterface::class); + $environment = $this->app->make(EnvironmentService::class); + + $server = $repository->getDataForCreation($id); + + $data = [ + 'uuid' => (string) $server->uuid, + 'user' => $server->username, + 'build' => [ + 'default' => [ + 'ip' => $server->allocation->ip, + 'port' => $server->allocation->port, + ], + 'ports' => $server->allocations->groupBy('ip')->map(function ($item) { + return $item->pluck('port'); + })->toArray(), + 'env' => $environment->process($server), + 'memory' => (int) $server->memory, + 'swap' => (int) $server->swap, + 'io' => (int) $server->io, + 'cpu' => (int) $server->cpu, + 'disk' => (int) $server->disk, + 'image' => $server->image, + ], + 'service' => [ + 'type' => $server->option->service->folder, + 'option' => $server->option->tag, + 'pack' => object_get($server, 'pack.uuid'), + 'skip_scripts' => $server->skip_scripts, + ], + 'rebuild' => false, + 'start_on_completion' => $start, + 'keys' => [ + (string) $server->daemonSecret => self::DAEMON_PERMISSIONS, + ], + ]; + + // Loop through overrides. + foreach ($overrides as $key => $value) { + array_set($data, $key, $value); + } + + return $this->getHttpClient()->request('POST', '/servers', [ + 'json' => $data, + ]); + } + + /** + * {@inheritdoc} + */ + public function setSubuserKey($key, array $permissions) + { + Assert::stringNotEmpty($key, 'First argument passed to setSubuserKey must be a non-empty string, received %s.'); + + return $this->getHttpClient()->request('PATCH', '/server', [ + 'json' => [ + 'keys' => [ + $key => $permissions, + ], + ], + ]); + } + + /** + * {@inheritdoc} + */ + public function update(array $data) + { + return $this->getHttpClient()->request('PATCH', '/server', [ + 'json' => $data, + ]); + } + + /** + * {@inheritdoc} + */ + public function reinstall($data = null) + { + Assert::nullOrIsArray($data, 'First argument passed to reinstall must be null or an array, received %s.'); + + if (is_null($data)) { + return $this->getHttpClient()->request('POST', '/server/reinstall'); + } + + return $this->getHttpClient()->request('POST', '/server/reinstall', [ + 'json' => $data, + ]); + } + + /** + * {@inheritdoc} + */ + public function rebuild() + { + return $this->getHttpClient()->request('POST', '/server/rebuild'); + } + + /** + * {@inheritdoc} + */ + public function suspend() + { + return $this->getHttpClient()->request('POST', '/server/suspend'); + } + + /** + * {@inheritdoc} + */ + public function unsuspend() + { + return $this->getHttpClient()->request('POST', '/server/unsuspend'); + } + + /** + * {@inheritdoc} + */ + public function delete() + { + return $this->getHttpClient()->request('DELETE', '/servers'); + } + + /** + * {@inheritdoc} + */ + public function details() + { + return $this->getHttpClient()->request('GET', '/servers'); + } +} diff --git a/app/Repositories/DatabaseRepository.php b/app/Repositories/DatabaseRepository.php deleted file mode 100644 index f7f428a6f..000000000 --- a/app/Repositories/DatabaseRepository.php +++ /dev/null @@ -1,284 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Repositories; - -use DB; -use Crypt; -use Validator; -use Pterodactyl\Models\Server; -use Pterodactyl\Models\Database; -use Pterodactyl\Models\DatabaseHost; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\DisplayValidationException; - -class DatabaseRepository -{ - /** - * Adds a new database to a specified database host server. - * - * @param int $id - * @param array $data - * @return \Pterodactyl\Models\Database - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function create($id, array $data) - { - $server = Server::findOrFail($id); - - $validator = Validator::make($data, [ - 'host' => 'required|exists:database_hosts,id', - 'database' => 'required|regex:/^\w{1,100}$/', - 'connection' => 'required|regex:/^[0-9%.]{1,15}$/', - ]); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - $host = DatabaseHost::findOrFail($data['host']); - DB::beginTransaction(); - - try { - $database = Database::firstOrNew([ - 'server_id' => $server->id, - 'database_host_id' => $data['host'], - 'database' => sprintf('s%d_%s', $server->id, $data['database']), - ]); - - if ($database->exists) { - throw new DisplayException('A database with those details already exists in the system.'); - } - - $database->username = sprintf('s%d_%s', $server->id, str_random(10)); - $database->remote = $data['connection']; - $database->password = Crypt::encrypt(str_random(20)); - - $database->save(); - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } - - try { - $host->setDynamicConnection(); - - DB::connection('dynamic')->statement(sprintf('CREATE DATABASE IF NOT EXISTS `%s`', $database->database)); - DB::connection('dynamic')->statement(sprintf( - 'CREATE USER `%s`@`%s` IDENTIFIED BY \'%s\'', - $database->username, $database->remote, Crypt::decrypt($database->password) - )); - DB::connection('dynamic')->statement(sprintf( - 'GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, INDEX ON `%s`.* TO `%s`@`%s`', - $database->database, $database->username, $database->remote - )); - - DB::connection('dynamic')->statement('FLUSH PRIVILEGES'); - - // Save Everything - DB::commit(); - - return $database; - } catch (\Exception $ex) { - try { - DB::connection('dynamic')->statement(sprintf('DROP DATABASE IF EXISTS `%s`', $database->database)); - DB::connection('dynamic')->statement(sprintf('DROP USER IF EXISTS `%s`@`%s`', $database->username, $database->remote)); - DB::connection('dynamic')->statement('FLUSH PRIVILEGES'); - } catch (\Exception $ex) { - } - - DB::rollBack(); - throw $ex; - } - } - - /** - * Updates the password for a given database. - * - * @param int $id - * @param string $password - * @return void - * - * @todo Fix logic behind resetting passwords. - */ - public function password($id, $password) - { - $database = Database::with('host')->findOrFail($id); - $database->host->setDynamicConnection(); - - DB::transaction(function () use ($database, $password) { - $database->password = Crypt::encrypt($password); - - // We have to do the whole delete user, create user thing rather than - // SET PASSWORD ... because MariaDB and PHP statements ends up inserting - // a corrupted password. A way around this is strtoupper(sha1(sha1($password, true))) - // but no garuntees that will work correctly with every system. - DB::connection('dynamic')->statement(sprintf('DROP USER IF EXISTS `%s`@`%s`', $database->username, $database->remote)); - DB::connection('dynamic')->statement(sprintf( - 'CREATE USER `%s`@`%s` IDENTIFIED BY \'%s\'', - $database->username, $database->remote, $password - )); - DB::connection('dynamic')->statement(sprintf( - 'GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, INDEX ON `%s`.* TO `%s`@`%s`', - $database->database, $database->username, $database->remote - )); - DB::connection('dynamic')->statement('FLUSH PRIVILEGES'); - - $database->save(); - }); - } - - /** - * Drops a database from the associated database host. - * - * @param int $id - * @return void - */ - public function drop($id) - { - $database = Database::with('host')->findOrFail($id); - $database->host->setDynamicConnection(); - - DB::transaction(function () use ($database) { - DB::connection('dynamic')->statement(sprintf('DROP DATABASE IF EXISTS `%s`', $database->database)); - DB::connection('dynamic')->statement(sprintf('DROP USER IF EXISTS `%s`@`%s`', $database->username, $database->remote)); - DB::connection('dynamic')->statement('FLUSH PRIVILEGES'); - - $database->delete(); - }); - } - - /** - * Deletes a database host from the system if it has no associated databases. - * - * @param int $id - * @return void - * - * @throws \Pterodactyl\Exceptions\DisplayException - */ - public function delete($id) - { - $host = DatabaseHost::withCount('databases')->findOrFail($id); - - if ($host->databases_count > 0) { - throw new DisplayException('You cannot delete a database host that has active databases attached to it.'); - } - - $host->delete(); - } - - /** - * Adds a new Database Host to the system. - * - * @param array $data - * @return \Pterodactyl\Models\DatabaseHost - * - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function add(array $data) - { - if (isset($data['host'])) { - $data['host'] = gethostbyname($data['host']); - } - - $validator = Validator::make($data, [ - 'name' => 'required|string|max:255', - 'host' => 'required|ip|unique:database_hosts,host', - 'port' => 'required|numeric|between:1,65535', - 'username' => 'required|string|max:32', - 'password' => 'required|string', - 'node_id' => 'sometimes|required|exists:nodes,id', - ]); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - return DB::transaction(function () use ($data) { - $host = new DatabaseHost; - $host->password = Crypt::encrypt($data['password']); - - $host->fill([ - 'name' => $data['name'], - 'host' => $data['host'], - 'port' => $data['port'], - 'username' => $data['username'], - 'max_databases' => null, - 'node_id' => (isset($data['node_id'])) ? $data['node_id'] : null, - ])->save(); - - // Allows us to check that we can connect to things. - $host->setDynamicConnection(); - DB::connection('dynamic')->select('SELECT 1 FROM dual'); - - return $host; - }); - } - - /** - * Updates a Database Host on the system. - * - * @param int $id - * @param array $data - * @return \Pterodactyl\Models\DatabaseHost - * - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function update($id, array $data) - { - $host = DatabaseHost::findOrFail($id); - - if (isset($data['host'])) { - $data['host'] = gethostbyname($data['host']); - } - - $validator = Validator::make($data, [ - 'name' => 'sometimes|required|string|max:255', - 'host' => 'sometimes|required|ip|unique:database_hosts,host,' . $host->id, - 'port' => 'sometimes|required|numeric|between:1,65535', - 'username' => 'sometimes|required|string|max:32', - 'password' => 'sometimes|required|string', - 'node_id' => 'sometimes|required|exists:nodes,id', - ]); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - return DB::transaction(function () use ($data, $host) { - if (isset($data['password'])) { - $host->password = Crypt::encrypt($data['password']); - } - $host->fill($data)->save(); - - // Check that we can still connect with these details. - $host->setDynamicConnection(); - DB::connection('dynamic')->select('SELECT 1 FROM dual'); - - return $host; - }); - } -} diff --git a/app/Repositories/Eloquent/AllocationRepository.php b/app/Repositories/Eloquent/AllocationRepository.php new file mode 100644 index 000000000..8903fd9a4 --- /dev/null +++ b/app/Repositories/Eloquent/AllocationRepository.php @@ -0,0 +1,55 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Repositories\Eloquent; + +use Pterodactyl\Models\Allocation; +use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; + +class AllocationRepository extends EloquentRepository implements AllocationRepositoryInterface +{ + /** + * {@inheritdoc} + */ + public function model() + { + return Allocation::class; + } + + /** + * {@inheritdoc} + */ + public function assignAllocationsToServer($server, array $ids) + { + return $this->getBuilder()->whereIn('id', $ids)->update(['server_id' => $server]); + } + + /** + * {@inheritdoc} + */ + public function getAllocationsForNode($node) + { + return $this->getBuilder()->where('node_id', $node)->get(); + } +} diff --git a/app/Repositories/Eloquent/ApiKeyRepository.php b/app/Repositories/Eloquent/ApiKeyRepository.php new file mode 100644 index 000000000..c82088f28 --- /dev/null +++ b/app/Repositories/Eloquent/ApiKeyRepository.php @@ -0,0 +1,39 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Repositories\Eloquent; + +use Pterodactyl\Models\APIKey; +use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface; + +class ApiKeyRepository extends EloquentRepository implements ApiKeyRepositoryInterface +{ + /** + * {@inheritdoc} + */ + public function model() + { + return APIKey::class; + } +} diff --git a/app/Repositories/Eloquent/ApiPermissionRepository.php b/app/Repositories/Eloquent/ApiPermissionRepository.php new file mode 100644 index 000000000..5c87e8131 --- /dev/null +++ b/app/Repositories/Eloquent/ApiPermissionRepository.php @@ -0,0 +1,39 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Repositories\Eloquent; + +use Pterodactyl\Models\APIPermission; +use Pterodactyl\Contracts\Repository\ApiPermissionRepositoryInterface; + +class ApiPermissionRepository extends EloquentRepository implements ApiPermissionRepositoryInterface +{ + /** + * {@inheritdoc} + */ + public function model() + { + return APIPermission::class; + } +} diff --git a/app/Repositories/Eloquent/DatabaseHostRepository.php b/app/Repositories/Eloquent/DatabaseHostRepository.php new file mode 100644 index 000000000..3304c3089 --- /dev/null +++ b/app/Repositories/Eloquent/DatabaseHostRepository.php @@ -0,0 +1,64 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Repositories\Eloquent; + +use Webmozart\Assert\Assert; +use Pterodactyl\Models\DatabaseHost; +use Pterodactyl\Exceptions\Repository\RecordNotFoundException; +use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface; + +class DatabaseHostRepository extends EloquentRepository implements DatabaseHostRepositoryInterface +{ + /** + * {@inheritdoc} + */ + public function model() + { + return DatabaseHost::class; + } + + /** + * {@inheritdoc} + */ + public function getWithViewDetails() + { + return $this->getBuilder()->withCount('databases')->with('node')->get(); + } + + /** + * {@inheritdoc} + */ + public function getWithServers($id) + { + Assert::numeric($id, 'First argument passed to getWithServers must be numeric, recieved %s.'); + + $instance = $this->getBuilder()->with('databases.server')->find($id, $this->getColumns()); + if (! $instance) { + throw new RecordNotFoundException(); + } + + return $instance; + } +} diff --git a/app/Repositories/Eloquent/DatabaseRepository.php b/app/Repositories/Eloquent/DatabaseRepository.php new file mode 100644 index 000000000..995b7db20 --- /dev/null +++ b/app/Repositories/Eloquent/DatabaseRepository.php @@ -0,0 +1,161 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Repositories\Eloquent; + +use Pterodactyl\Models\Database; +use Illuminate\Foundation\Application; +use Illuminate\Database\DatabaseManager; +use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; +use Pterodactyl\Exceptions\Repository\DuplicateDatabaseNameException; + +class DatabaseRepository extends EloquentRepository implements DatabaseRepositoryInterface +{ + /** + * @var \Illuminate\Database\DatabaseManager + */ + protected $database; + + /** + * DatabaseRepository constructor. + * + * @param \Illuminate\Foundation\Application $application + * @param \Illuminate\Database\DatabaseManager $database + */ + public function __construct( + Application $application, + DatabaseManager $database + ) { + parent::__construct($application); + + $this->database = $database; + } + + /** + * {@inheritdoc} + */ + public function model() + { + return Database::class; + } + + /** + * {@inheritdoc} + * @return bool|\Illuminate\Database\Eloquent\Model + */ + public function createIfNotExists(array $data) + { + $instance = $this->getBuilder()->where([ + ['server_id', '=', array_get($data, 'server_id')], + ['database_host_id', '=', array_get($data, 'database_host_id')], + ['database', '=', array_get($data, 'database')], + ])->count(); + + if ($instance > 0) { + throw new DuplicateDatabaseNameException('A database with those details already exists for the specified server.'); + } + + return $this->create($data); + } + + /** + * {@inheritdoc} + */ + public function createDatabase($database, $connection = null) + { + return $this->runStatement( + sprintf('CREATE DATABASE IF NOT EXISTS `%s`', $database), + $connection + ); + } + + /** + * {@inheritdoc} + */ + public function createUser($username, $remote, $password, $connection = null) + { + return $this->runStatement( + sprintf('CREATE USER `%s`@`%s` IDENTIFIED BY \'%s\'', $username, $remote, $password), + $connection + ); + } + + /** + * {@inheritdoc} + */ + public function assignUserToDatabase($database, $username, $remote, $connection = null) + { + return $this->runStatement( + sprintf( + 'GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, INDEX ON `%s`.* TO `%s`@`%s`', + $database, + $username, + $remote + ), + $connection + ); + } + + /** + * {@inheritdoc} + */ + public function flush($connection = null) + { + return $this->runStatement('FLUSH PRIVILEGES', $connection); + } + + /** + * {@inheritdoc} + */ + public function dropDatabase($database, $connection = null) + { + return $this->runStatement( + sprintf('DROP DATABASE IF EXISTS `%s`', $database), + $connection + ); + } + + /** + * {@inheritdoc} + */ + public function dropUser($username, $remote, $connection = null) + { + return $this->runStatement( + sprintf('DROP USER IF EXISTS `%s`@`%s`', $username, $remote), + $connection + ); + } + + /** + * Run the provided statement against the database on a given connection. + * + * @param string $statement + * @param null|string $connection + * @return bool + */ + protected function runStatement($statement, $connection = null) + { + return $this->database->connection($connection)->statement($statement); + } +} diff --git a/app/Repositories/Eloquent/EloquentRepository.php b/app/Repositories/Eloquent/EloquentRepository.php new file mode 100644 index 000000000..f29418d00 --- /dev/null +++ b/app/Repositories/Eloquent/EloquentRepository.php @@ -0,0 +1,269 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Repositories\Eloquent; + +use Webmozart\Assert\Assert; +use Pterodactyl\Repository\Repository; +use Illuminate\Database\Query\Expression; +use Pterodactyl\Contracts\Repository\RepositoryInterface; +use Pterodactyl\Exceptions\Model\DataValidationException; +use Pterodactyl\Exceptions\Repository\RecordNotFoundException; + +abstract class EloquentRepository extends Repository implements RepositoryInterface +{ + /** + * {@inheritdoc} + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getBuilder() + { + return $this->getModel()->newQuery(); + } + + /** + * {@inheritdoc} + * @param bool $force + * @return \Illuminate\Database\Eloquent\Model|bool + */ + public function create(array $fields, $validate = true, $force = false) + { + Assert::boolean($validate, 'Second argument passed to create must be boolean, recieved %s.'); + Assert::boolean($force, 'Third argument passed to create must be boolean, received %s.'); + + $instance = $this->getBuilder()->newModelInstance(); + + if ($force) { + $instance->forceFill($fields); + } else { + $instance->fill($fields); + } + + if (! $validate) { + $saved = $instance->skipValidation()->save(); + } else { + if (! $saved = $instance->save()) { + throw new DataValidationException($instance->getValidator()); + } + } + + return ($this->withFresh) ? $instance->fresh() : $saved; + } + + /** + * {@inheritdoc} + * @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model + */ + public function find($id) + { + Assert::numeric($id, 'First argument passed to find must be numeric, received %s.'); + + $instance = $this->getBuilder()->find($id, $this->getColumns()); + + if (! $instance) { + throw new RecordNotFoundException(); + } + + return $instance; + } + + /** + * {@inheritdoc} + * @return \Illuminate\Database\Eloquent\Collection + */ + public function findWhere(array $fields) + { + return $this->getBuilder()->where($fields)->get($this->getColumns()); + } + + /** + * {@inheritdoc} + * @return \Illuminate\Database\Eloquent\Model + */ + public function findFirstWhere(array $fields) + { + $instance = $this->getBuilder()->where($fields)->first($this->getColumns()); + + if (! $instance) { + throw new RecordNotFoundException(); + } + + return $instance; + } + + /** + * {@inheritdoc}. + */ + public function findCountWhere(array $fields) + { + return $this->getBuilder()->where($fields)->count($this->getColumns()); + } + + /** + * {@inheritdoc} + */ + public function delete($id, $destroy = false) + { + Assert::numeric($id, 'First argument passed to delete must be numeric, received %s.'); + Assert::boolean($destroy, 'Second argument passed to delete must be boolean, received %s.'); + + $instance = $this->getBuilder()->where($this->getModel()->getKeyName(), $id); + + return ($destroy) ? $instance->forceDelete() : $instance->delete(); + } + + /** + * {@inheritdoc} + */ + public function deleteWhere(array $attributes, $force = false) + { + Assert::boolean($force, 'Second argument passed to deleteWhere must be boolean, received %s.'); + + $instance = $this->getBuilder()->where($attributes); + + return ($force) ? $instance->forceDelete() : $instance->delete(); + } + + /** + * {@inheritdoc} + */ + public function update($id, array $fields, $validate = true, $force = false) + { + Assert::numeric($id, 'First argument passed to update must be numeric, received %s.'); + Assert::boolean($validate, 'Third argument passed to update must be boolean, received %s.'); + Assert::boolean($force, 'Fourth argument passed to update must be boolean, received %s.'); + + $instance = $this->getBuilder()->where('id', $id)->first(); + + if (! $instance) { + throw new RecordNotFoundException(); + } + + if ($force) { + $instance->forceFill($fields); + } else { + $instance->fill($fields); + } + + if (! $validate) { + $saved = $instance->skipValidation()->save(); + } else { + if (! $saved = $instance->save()) { + throw new DataValidationException($instance->getValidator()); + } + } + + return ($this->withFresh) ? $instance->fresh() : $saved; + } + + /** + * {@inheritdoc} + */ + public function updateWhereIn($column, array $values, array $fields) + { + Assert::stringNotEmpty($column, 'First argument passed to updateWhereIn must be a non-empty string, received %s.'); + + return $this->getBuilder()->whereIn($column, $values)->update($fields); + } + + /** + * {@inheritdoc} + */ + public function massUpdate(array $where, array $fields) + { + // TODO: Implement massUpdate() method. + } + + /** + * {@inheritdoc} + */ + public function all() + { + return $this->getBuilder()->get($this->getColumns()); + } + + /** + * {@inheritdoc} + */ + public function insert(array $data) + { + return $this->getBuilder()->insert($data); + } + + /** + * Insert multiple records into the database and ignore duplicates. + * + * @param array $values + * @return bool + */ + public function insertIgnore(array $values) + { + if (empty($values)) { + return true; + } + + if (! is_array(reset($values))) { + $values = [$values]; + } else { + foreach ($values as $key => $value) { + ksort($value); + $values[$key] = $value; + } + } + + $bindings = array_values(array_filter(array_flatten($values, 1), function ($binding) { + return ! $binding instanceof Expression; + })); + + $grammar = $this->getBuilder()->toBase()->getGrammar(); + $table = $grammar->wrapTable($this->getModel()->getTable()); + $columns = $grammar->columnize(array_keys(reset($values))); + + $parameters = collect($values)->map(function ($record) use ($grammar) { + return sprintf('(%s)', $grammar->parameterize($record)); + })->implode(', '); + + $statement = "insert ignore into $table ($columns) values $parameters"; + + return $this->getBuilder()->getConnection()->statement($statement, $bindings); + } + + /** + * {@inheritdoc} + * @return bool|\Illuminate\Database\Eloquent\Model + */ + public function updateOrCreate(array $where, array $fields, $validate = true, $force = false) + { + Assert::boolean($validate, 'Third argument passed to updateOrCreate must be boolean, received %s.'); + Assert::boolean($force, 'Fourth argument passed to updateOrCreate must be boolean, received %s.'); + + $instance = $this->withColumns('id')->findWhere($where)->first(); + + if (! $instance) { + return $this->create(array_merge($where, $fields), $validate, $force); + } + + return $this->update($instance->id, $fields, $validate, $force); + } +} diff --git a/app/Repositories/Eloquent/LocationRepository.php b/app/Repositories/Eloquent/LocationRepository.php new file mode 100644 index 000000000..318493a8b --- /dev/null +++ b/app/Repositories/Eloquent/LocationRepository.php @@ -0,0 +1,98 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Repositories\Eloquent; + +use Pterodactyl\Models\Location; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Repositories\Concerns\Searchable; +use Pterodactyl\Exceptions\Repository\RecordNotFoundException; +use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; + +class LocationRepository extends EloquentRepository implements LocationRepositoryInterface +{ + use Searchable; + + /** + * @var string + */ + protected $searchTerm; + + /** + * {@inheritdoc} + */ + public function model() + { + return Location::class; + } + + /** + * {@inheritdoc} + * @todo remove this, do logic in service + */ + public function deleteIfNoNodes($id) + { + $location = $this->getBuilder()->with('nodes')->find($id); + + if (! $location) { + throw new RecordNotFoundException(); + } + + if ($location->nodes_count > 0) { + throw new DisplayException('Cannot delete a location that has nodes assigned to it.'); + } + + return $location->delete(); + } + + /** + * {@inheritdoc} + */ + public function getAllWithDetails() + { + return $this->getBuilder()->withCount('nodes', 'servers')->get($this->getColumns()); + } + + /** + * {@inheritdoc} + */ + public function getAllWithNodes() + { + return $this->getBuilder()->with('nodes')->get($this->getColumns()); + } + + /** + * {@inheritdoc} + */ + public function getWithNodes($id) + { + $instance = $this->getBuilder()->with('nodes.servers')->find($id, $this->getColumns()); + + if (! $instance) { + throw new RecordNotFoundException(); + } + + return $instance; + } +} diff --git a/app/Repositories/Eloquent/NodeRepository.php b/app/Repositories/Eloquent/NodeRepository.php new file mode 100644 index 000000000..240f97918 --- /dev/null +++ b/app/Repositories/Eloquent/NodeRepository.php @@ -0,0 +1,169 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Repositories\Eloquent; + +use Pterodactyl\Models\Node; +use Pterodactyl\Repositories\Concerns\Searchable; +use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; +use Pterodactyl\Exceptions\Repository\RecordNotFoundException; + +class NodeRepository extends EloquentRepository implements NodeRepositoryInterface +{ + use Searchable; + + /** + * {@inheritdoc} + */ + public function model() + { + return Node::class; + } + + /** + * {@inheritdoc} + */ + public function getUsageStats($id) + { + $node = $this->getBuilder()->select( + 'nodes.disk_overallocate', + 'nodes.memory_overallocate', + 'nodes.disk', + 'nodes.memory', + $this->getBuilder()->raw('SUM(servers.memory) as sum_memory, SUM(servers.disk) as sum_disk') + )->join('servers', 'servers.node_id', '=', 'nodes.id') + ->where('nodes.id', $id) + ->first(); + + return collect(['disk' => $node->sum_disk, 'memory' => $node->sum_memory]) + ->mapWithKeys(function ($value, $key) use ($node) { + $maxUsage = $node->{$key}; + if ($node->{$key . '_overallocate'} > 0) { + $maxUsage = $node->{$key} * (1 + ($node->{$key . '_overallocate'} / 100)); + } + + $percent = ($value / $maxUsage) * 100; + + return [ + $key => [ + 'value' => number_format($value), + 'max' => number_format($maxUsage), + 'percent' => $percent, + 'css' => ($percent <= 75) ? 'green' : (($percent > 90) ? 'red' : 'yellow'), + ], + ]; + }) + ->toArray(); + } + + /** + * {@inheritdoc} + */ + public function getNodeListingData($count = 25) + { + $instance = $this->getBuilder()->with('location')->withCount('servers'); + + if ($this->searchTerm) { + $instance->search($this->searchTerm); + } + + return $instance->paginate($count, $this->getColumns()); + } + + /** + * {@inheritdoc} + */ + public function getSingleNode($id) + { + $instance = $this->getBuilder()->with('location')->withCount('servers')->find($id, $this->getColumns()); + + if (! $instance) { + throw new RecordNotFoundException(); + } + + return $instance; + } + + /** + * {@inheritdoc} + */ + public function getNodeAllocations($id) + { + $instance = $this->getBuilder()->find($id, $this->getColumns()); + + if (! $instance) { + throw new RecordNotFoundException(); + } + + $instance->setRelation( + 'allocations', + $instance->allocations()->orderBy('ip', 'asc')->orderBy('port', 'asc') + ->with('server')->paginate(50) + ); + + return $instance; + } + + /** + * {@inheritdoc} + */ + public function getNodeServers($id) + { + $instance = $this->getBuilder()->with('servers.user', 'servers.service', 'servers.option') + ->find($id, $this->getColumns()); + + if (! $instance) { + throw new RecordNotFoundException(); + } + + return $instance; + } + + /** + * {@inheritdoc} + */ + public function getNodesForLocation($location) + { + $instance = $this->getBuilder()->with('allocations')->where('location_id', $location)->get(); + + return $instance->map(function ($item) { + $filtered = $item->allocations->where('server_id', null)->map(function ($map) { + return collect($map)->only(['id', 'ip', 'port']); + }); + + $item->ports = $filtered->map(function ($map) { + return [ + 'id' => $map['id'], + 'text' => sprintf('%s:%s', $map['ip'], $map['port']), + ]; + })->values(); + + return [ + 'id' => $item->id, + 'text' => $item->name, + 'allocations' => $item->ports, + ]; + })->values(); + } +} diff --git a/app/Repositories/Eloquent/OptionVariableRepository.php b/app/Repositories/Eloquent/OptionVariableRepository.php new file mode 100644 index 000000000..45cc9110f --- /dev/null +++ b/app/Repositories/Eloquent/OptionVariableRepository.php @@ -0,0 +1,39 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Repositories\Eloquent; + +use Pterodactyl\Models\ServiceVariable; +use Pterodactyl\Contracts\Repository\OptionVariableRepositoryInterface; + +class OptionVariableRepository extends EloquentRepository implements OptionVariableRepositoryInterface +{ + /** + * {@inheritdoc} + */ + public function model() + { + return ServiceVariable::class; + } +} diff --git a/app/Repositories/Eloquent/PackRepository.php b/app/Repositories/Eloquent/PackRepository.php new file mode 100644 index 000000000..56b04d2eb --- /dev/null +++ b/app/Repositories/Eloquent/PackRepository.php @@ -0,0 +1,101 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Repositories\Eloquent; + +use Pterodactyl\Models\Pack; +use Webmozart\Assert\Assert; +use Pterodactyl\Repositories\Concerns\Searchable; +use Illuminate\Database\Eloquent\ModelNotFoundException; +use Pterodactyl\Contracts\Repository\PackRepositoryInterface; +use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory; + +class PackRepository extends EloquentRepository implements PackRepositoryInterface +{ + use Searchable; + + /** + * {@inheritdoc} + */ + public function model() + { + return Pack::class; + } + + /** + * {@inheritdoc} + */ + public function getFileArchives($id, $collection = false) + { + Assert::numeric($id, 'First argument passed to getFileArchives must be numeric, received %s.'); + Assert::boolean($collection, 'Second argument passed to getFileArchives must be boolean, received %s.'); + + $pack = $this->getBuilder()->find($id, ['id', 'uuid']); + if (! $pack) { + throw new ModelNotFoundException; + } + + $storage = $this->app->make(FilesystemFactory::class); + $files = collect($storage->disk('default')->files('packs/' . $pack->uuid)); + + $files = $files->map(function ($file) { + $path = storage_path('app/' . $file); + + return (object) [ + 'name' => basename($file), + 'hash' => sha1_file($path), + 'size' => human_readable($path), + ]; + }); + + return ($collection) ? $files : (object) $files->all(); + } + + /** + * {@inheritdoc} + */ + public function getWithServers($id) + { + Assert::numeric($id, 'First argument passed to getWithServers must be numeric, received %s.'); + + $instance = $this->getBuilder()->with('servers.node', 'servers.user')->find($id, $this->getColumns()); + if (! $instance) { + throw new ModelNotFoundException; + } + + return $instance; + } + + /** + * {@inheritdoc} + */ + public function paginateWithOptionAndServerCount($paginate = 50) + { + Assert::integer($paginate, 'First argument passed to paginateWithOptionAndServerCount must be integer, received %s.'); + + return $this->getBuilder()->with('option')->withCount('servers') + ->search($this->searchTerm) + ->paginate($paginate, $this->getColumns()); + } +} diff --git a/app/Repositories/Eloquent/PermissionRepository.php b/app/Repositories/Eloquent/PermissionRepository.php new file mode 100644 index 000000000..7fb7b56f6 --- /dev/null +++ b/app/Repositories/Eloquent/PermissionRepository.php @@ -0,0 +1,39 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Repositories\Eloquent; + +use Pterodactyl\Models\Permission; +use Pterodactyl\Contracts\Repository\PermissionRepositoryInterface; + +class PermissionRepository extends EloquentRepository implements PermissionRepositoryInterface +{ + /** + * {@inheritdoc} + */ + public function model() + { + return Permission::class; + } +} diff --git a/app/Repositories/Eloquent/ServerRepository.php b/app/Repositories/Eloquent/ServerRepository.php new file mode 100644 index 000000000..227b7c221 --- /dev/null +++ b/app/Repositories/Eloquent/ServerRepository.php @@ -0,0 +1,222 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Repositories\Eloquent; + +use Webmozart\Assert\Assert; +use Pterodactyl\Models\Server; +use Pterodactyl\Repositories\Concerns\Searchable; +use Pterodactyl\Exceptions\Repository\RecordNotFoundException; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; + +class ServerRepository extends EloquentRepository implements ServerRepositoryInterface +{ + use Searchable; + + /** + * {@inheritdoc} + */ + public function model() + { + return Server::class; + } + + /** + * {@inheritdoc} + */ + public function getAllServers($paginate = 25) + { + $instance = $this->getBuilder()->with('node', 'user', 'allocation'); + + if ($this->searchTerm) { + $instance->search($this->searchTerm); + } + + return $instance->paginate($paginate); + } + + /** + * {@inheritdoc} + * @return \Illuminate\Database\Eloquent\Model + */ + public function findWithVariables($id) + { + $instance = $this->getBuilder()->with('option.variables', 'variables') + ->where($this->getModel()->getKeyName(), '=', $id) + ->first($this->getColumns()); + + if (is_null($instance)) { + throw new RecordNotFoundException(); + } + + return $instance; + } + + /** + * {@inheritdoc} + */ + public function getVariablesWithValues($id, $returnWithObject = false) + { + $instance = $this->getBuilder()->with('variables', 'option.variables') + ->find($id, $this->getColumns()); + + if (! $instance) { + throw new RecordNotFoundException(); + } + + $data = []; + $instance->option->variables->each(function ($item) use (&$data, $instance) { + $display = $instance->variables->where('variable_id', $item->id)->pluck('variable_value')->first(); + + $data[$item->env_variable] = $display ?? $item->default_value; + }); + + if ($returnWithObject) { + return (object) [ + 'data' => $data, + 'server' => $instance, + ]; + } + + return $data; + } + + /** + * {@inheritdoc} + */ + public function getDataForCreation($id) + { + $instance = $this->getBuilder()->with('allocation', 'allocations', 'pack', 'option.service') + ->find($id, $this->getColumns()); + + if (! $instance) { + throw new RecordNotFoundException(); + } + + return $instance; + } + + /** + * {@inheritdoc} + */ + public function getWithDatabases($id) + { + $instance = $this->getBuilder()->with('databases.host') + ->where('installed', 1) + ->find($id, $this->getColumns()); + + if (! $instance) { + throw new RecordNotFoundException(); + } + + return $instance; + } + + /** + * {@inheritdoc} + */ + public function getDaemonServiceData($id) + { + $instance = $this->getBuilder()->with('option.service', 'pack')->find($id, $this->getColumns()); + + if (! $instance) { + throw new RecordNotFoundException(); + } + + return [ + 'type' => $instance->option->service->folder, + 'option' => $instance->option->tag, + 'pack' => (! is_null($instance->pack_id)) ? $instance->pack->uuid : null, + ]; + } + + /** + * {@inheritdoc} + */ + public function getUserAccessServers($user) + { + Assert::numeric($user, 'First argument passed to getUserAccessServers must be numeric, received %s.'); + + $subuser = $this->app->make(SubuserRepository::class); + + return $this->getBuilder()->select('id')->where('owner_id', $user)->union( + $subuser->getBuilder()->select('server_id')->where('user_id', $user) + )->pluck('id')->all(); + } + + /** + * {@inheritdoc} + */ + public function filterUserAccessServers($user, $admin = false, $level = 'all', array $relations = []) + { + Assert::numeric($user, 'First argument passed to filterUserAccessServers must be numeric, received %s.'); + Assert::boolean($admin, 'Second argument passed to filterUserAccessServers must be boolean, received %s.'); + Assert::stringNotEmpty($level, 'Third argument passed to filterUserAccessServers must be a non-empty string, received %s.'); + + $instance = $this->getBuilder()->with($relations); + + // If access level is set to owner, only display servers + // that the user owns. + if ($level === 'owner') { + $instance->where('owner_id', $user); + } + + // If set to all, display all servers they can access, including + // those they access as an admin. + // + // If set to subuser, only return the servers they can access because + // they are owner, or marked as a subuser of the server. + if (($level === 'all' && ! $admin) || $level === 'subuser') { + $instance->whereIn('id', $this->getUserAccessServers($user)); + } + + // If set to admin, only display the servers a user can access + // as an administrator (leaves out owned and subuser of). + if ($level === 'admin' && $admin) { + $instance->whereIn('id', $this->getUserAccessServers($user)); + } + + return $instance->search($this->searchTerm)->paginate( + $this->app->make('config')->get('pterodactyl.paginate.frontend.servers') + ); + } + + /** + * {@inheritdoc} + */ + public function getByUuid($uuid) + { + Assert::stringNotEmpty($uuid, 'First argument passed to getByUuid must be a non-empty string, received %s.'); + + $instance = $this->getBuilder()->with('service', 'node')->where(function ($query) use ($uuid) { + $query->where('uuidShort', $uuid)->orWhere('uuid', $uuid); + })->first($this->getColumns()); + + if (! $instance) { + throw new RecordNotFoundException; + } + + return $instance; + } +} diff --git a/app/Repositories/Eloquent/ServerVariableRepository.php b/app/Repositories/Eloquent/ServerVariableRepository.php new file mode 100644 index 000000000..e309b88d8 --- /dev/null +++ b/app/Repositories/Eloquent/ServerVariableRepository.php @@ -0,0 +1,39 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Repositories\Eloquent; + +use Pterodactyl\Models\ServerVariable; +use Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface; + +class ServerVariableRepository extends EloquentRepository implements ServerVariableRepositoryInterface +{ + /** + * {@inheritdoc} + */ + public function model() + { + return ServerVariable::class; + } +} diff --git a/app/Repositories/Eloquent/ServiceOptionRepository.php b/app/Repositories/Eloquent/ServiceOptionRepository.php new file mode 100644 index 000000000..14869dfb5 --- /dev/null +++ b/app/Repositories/Eloquent/ServiceOptionRepository.php @@ -0,0 +1,66 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Repositories\Eloquent; + +use Pterodactyl\Models\ServiceOption; +use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface; + +class ServiceOptionRepository extends EloquentRepository implements ServiceOptionRepositoryInterface +{ + /** + * {@inheritdoc} + */ + public function model() + { + return ServiceOption::class; + } + + /** + * {@inheritdoc} + */ + public function getWithVariables($id) + { + return $this->getBuilder()->with('variables')->find($id, $this->getColumns()); + } + + /** + * {@inheritdoc} + */ + public function getWithCopyFrom($id) + { + return $this->getBuilder()->with('copyFrom')->find($id, $this->getColumns()); + } + + /** + * {@inheritdoc} + */ + public function isCopiableScript($copyFromId, $service) + { + return $this->getBuilder()->whereNull('copy_script_from') + ->where('id', '=', $copyFromId) + ->where('service_id', '=', $service) + ->exists(); + } +} diff --git a/app/Repositories/Eloquent/ServiceRepository.php b/app/Repositories/Eloquent/ServiceRepository.php new file mode 100644 index 000000000..9e2cd408b --- /dev/null +++ b/app/Repositories/Eloquent/ServiceRepository.php @@ -0,0 +1,77 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Repositories\Eloquent; + +use Webmozart\Assert\Assert; +use Pterodactyl\Models\Service; +use Pterodactyl\Exceptions\Repository\RecordNotFoundException; +use Pterodactyl\Contracts\Repository\ServiceRepositoryInterface; + +class ServiceRepository extends EloquentRepository implements ServiceRepositoryInterface +{ + /** + * {@inheritdoc} + */ + public function model() + { + return Service::class; + } + + /** + * {@inheritdoc} + */ + public function getWithOptions($id = null) + { + Assert::nullOrNumeric($id, 'First argument passed to getWithOptions must be null or numeric, received %s.'); + + $instance = $this->getBuilder()->with('options.packs', 'options.variables'); + + if (! is_null($id)) { + $instance = $instance->find($id, $this->getColumns()); + if (! $instance) { + throw new RecordNotFoundException(); + } + + return $instance; + } + + return $instance->get($this->getColumns()); + } + + /** + * {@inheritdoc} + */ + public function getWithOptionServers($id) + { + Assert::numeric($id, 'First argument passed to getWithOptionServers must be numeric, received %s.'); + + $instance = $this->getBuilder()->with('options.servers')->find($id, $this->getColumns()); + if (! $instance) { + throw new RecordNotFoundException(); + } + + return $instance; + } +} diff --git a/app/Repositories/Eloquent/ServiceVariableRepository.php b/app/Repositories/Eloquent/ServiceVariableRepository.php new file mode 100644 index 000000000..bf7805828 --- /dev/null +++ b/app/Repositories/Eloquent/ServiceVariableRepository.php @@ -0,0 +1,39 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Repositories\Eloquent; + +use Pterodactyl\Models\ServiceVariable; +use Pterodactyl\Contracts\Repository\ServiceVariableRepositoryInterface; + +class ServiceVariableRepository extends EloquentRepository implements ServiceVariableRepositoryInterface +{ + /** + * {@inheritdoc} + */ + public function model() + { + return ServiceVariable::class; + } +} diff --git a/app/Repositories/Eloquent/SessionRepository.php b/app/Repositories/Eloquent/SessionRepository.php new file mode 100644 index 000000000..928feb56a --- /dev/null +++ b/app/Repositories/Eloquent/SessionRepository.php @@ -0,0 +1,55 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Repositories\Eloquent; + +use Pterodactyl\Models\Session; +use Pterodactyl\Contracts\Repository\SessionRepositoryInterface; + +class SessionRepository extends EloquentRepository implements SessionRepositoryInterface +{ + /** + * {@inheritdoc} + */ + public function model() + { + return Session::class; + } + + /** + * {@inheritdoc} + */ + public function getUserSessions($user) + { + return $this->getBuilder()->where('user_id', $user)->get($this->getColumns()); + } + + /** + * {@inheritdoc} + */ + public function deleteUserSession($user, $session) + { + return $this->getBuilder()->where('user_id', $user)->where('id', $session)->delete(); + } +} diff --git a/app/Repositories/Eloquent/SubuserRepository.php b/app/Repositories/Eloquent/SubuserRepository.php new file mode 100644 index 000000000..32d1a172a --- /dev/null +++ b/app/Repositories/Eloquent/SubuserRepository.php @@ -0,0 +1,86 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Repositories\Eloquent; + +use Webmozart\Assert\Assert; +use Pterodactyl\Models\Subuser; +use Pterodactyl\Exceptions\Repository\RecordNotFoundException; +use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; + +class SubuserRepository extends EloquentRepository implements SubuserRepositoryInterface +{ + /** + * {@inheritdoc} + */ + public function model() + { + return Subuser::class; + } + + /** + * {@inheritdoc} + */ + public function getWithServer($id) + { + Assert::numeric($id, 'First argument passed to getWithServer must be numeric, received %s.'); + + $instance = $this->getBuilder()->with('server', 'user')->find($id, $this->getColumns()); + if (! $instance) { + throw new RecordNotFoundException; + } + + return $instance; + } + + /** + * {@inheritdoc} + */ + public function getWithPermissions($id) + { + Assert::numeric($id, 'First argument passed to getWithPermissions must be numeric, received %s.'); + + $instance = $this->getBuilder()->with('permissions', 'user')->find($id, $this->getColumns()); + if (! $instance) { + throw new RecordNotFoundException; + } + + return $instance; + } + + /** + * {@inheritdoc} + */ + public function getWithServerAndPermissions($id) + { + Assert::numeric($id, 'First argument passed to getWithServerAndPermissions must be numeric, received %s.'); + + $instance = $this->getBuilder()->with('server', 'permission', 'user')->find($id, $this->getColumns()); + if (! $instance) { + throw new RecordNotFoundException; + } + + return $instance; + } +} diff --git a/app/Repositories/Eloquent/TaskRepository.php b/app/Repositories/Eloquent/TaskRepository.php new file mode 100644 index 000000000..3cc5b10a9 --- /dev/null +++ b/app/Repositories/Eloquent/TaskRepository.php @@ -0,0 +1,74 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Repositories\Eloquent; + +use Pterodactyl\Models\Task; +use Webmozart\Assert\Assert; +use Pterodactyl\Contracts\Repository\TaskRepositoryInterface; +use Pterodactyl\Exceptions\Repository\RecordNotFoundException; + +class TaskRepository extends EloquentRepository implements TaskRepositoryInterface +{ + /** + * {@inheritdoc} + */ + public function model() + { + return Task::class; + } + + /** + * {@inheritdoc} + */ + public function getParentTasksWithChainCount($server) + { + Assert::numeric($server, 'First argument passed to GetParentTasksWithChainCount must be numeric, received %s.'); + + return $this->getBuilder()->withCount('chained')->where([ + ['server_id', '=', $server], + ['parent_task_id', '=', null], + ])->get($this->getColumns()); + } + + /** + * {@inheritdoc} + */ + public function getTaskForServer($task, $server) + { + Assert::numeric($task, 'First argument passed to getTaskForServer must be numeric, received %s.'); + Assert::numeric($server, 'Second argument passed to getTaskForServer must be numeric, received %s.'); + + $instance = $this->getBuilder()->with('chained')->where([ + ['server_id', '=', $server], + ['parent_task_id', '=', null], + ])->find($task, $this->getColumns()); + + if (! $instance) { + throw new RecordNotFoundException; + } + + return $instance; + } +} diff --git a/app/Repositories/Eloquent/UserRepository.php b/app/Repositories/Eloquent/UserRepository.php new file mode 100644 index 000000000..38f7e355c --- /dev/null +++ b/app/Repositories/Eloquent/UserRepository.php @@ -0,0 +1,97 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Repositories\Eloquent; + +use Pterodactyl\Models\User; +use Illuminate\Foundation\Application; +use Pterodactyl\Repositories\Concerns\Searchable; +use Pterodactyl\Contracts\Repository\UserRepositoryInterface; +use Illuminate\Contracts\Config\Repository as ConfigRepository; + +class UserRepository extends EloquentRepository implements UserRepositoryInterface +{ + use Searchable; + + /** + * @var \Illuminate\Contracts\Config\Repository + */ + protected $config; + + /** + * UserRepository constructor. + * + * @param \Illuminate\Foundation\Application $application + * @param \Illuminate\Contracts\Config\Repository $config + */ + public function __construct(Application $application, ConfigRepository $config) + { + parent::__construct($application); + + $this->config = $config; + } + + /** + * {@inheritdoc} + */ + public function model() + { + return User::class; + } + + /** + * {@inheritdoc} + */ + public function getAllUsersWithCounts() + { + $users = $this->getBuilder()->withCount('servers', 'subuserOf'); + + if ($this->searchTerm) { + $users->search($this->searchTerm); + } + + return $users->paginate( + $this->config->get('pterodactyl.paginate.admin.users'), + $this->getColumns() + ); + } + + /** + * {@inheritdoc} + */ + public function filterUsersByQuery($query) + { + $this->withColumns([ + 'id', 'email', 'username', 'name_first', 'name_last', + ]); + + $instance = $this->getBuilder()->search($query)->get($this->getColumns()); + + return $instance->transform(function ($item) { + $item->md5 = md5(strtolower($item->email)); + + return $item; + }); + } +} diff --git a/app/Repositories/LocationRepository.php b/app/Repositories/LocationRepository.php deleted file mode 100644 index 5f08cfc17..000000000 --- a/app/Repositories/LocationRepository.php +++ /dev/null @@ -1,104 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Repositories; - -use Validator; -use Pterodactyl\Models\Location; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\DisplayValidationException; - -class LocationRepository -{ - /** - * Creates a new location on the system. - * - * @param array $data - * @return \Pterodactyl\Models\Location - * - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function create(array $data) - { - $validator = Validator::make($data, [ - 'short' => 'required|string|between:1,60|unique:locations,short', - 'long' => 'required|string|between:1,255', - ]); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - return Location::create([ - 'long' => $data['long'], - 'short' => $data['short'], - ]); - } - - /** - * Modifies a location. - * - * @param int $id - * @param array $data - * @return \Pterodactyl\Models\Location - * - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function update($id, array $data) - { - $location = Location::findOrFail($id); - - $validator = Validator::make($data, [ - 'short' => 'sometimes|required|string|between:1,60|unique:locations,short,' . $location->id, - 'long' => 'sometimes|required|string|between:1,255', - ]); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - $location->fill($data)->save(); - - return $location; - } - - /** - * Deletes a location from the system. - * - * @param int $id - * @return void - * - * @throws \Pterodactyl\Exceptions\DisplayException - */ - public function delete($id) - { - $location = Location::withCount('nodes')->findOrFail($id); - - if ($location->nodes_count > 0) { - throw new DisplayException('Cannot delete a location that has nodes assigned to it.'); - } - - $location->delete(); - } -} diff --git a/app/Repositories/NodeRepository.php b/app/Repositories/NodeRepository.php deleted file mode 100644 index 2d6fd3c9c..000000000 --- a/app/Repositories/NodeRepository.php +++ /dev/null @@ -1,291 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Repositories; - -use DB; -use Validator; -use IPTools\Network; -use Pterodactyl\Models; -use Pterodactyl\Services\UuidService; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\DisplayValidationException; - -class NodeRepository -{ - /** - * Creates a new node on the system. - * - * @param array $data - * @return \Pterodactyl\Models\Node - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function create(array $data) - { - // Validate Fields - $validator = Validator::make($data, [ - 'name' => 'required|regex:/^([\w .-]{1,100})$/', - 'location_id' => 'required|numeric|min:1|exists:locations,id', - 'public' => 'required|numeric|between:0,1', - 'fqdn' => 'required|string|unique:nodes,fqdn', - 'scheme' => 'required|regex:/^(http(s)?)$/', - 'behind_proxy' => 'required|boolean', - 'memory' => 'required|numeric|min:1', - 'memory_overallocate' => 'required|numeric|min:-1', - 'disk' => 'required|numeric|min:1', - 'disk_overallocate' => 'required|numeric|min:-1', - 'daemonBase' => 'required|regex:/^([\/][\d\w.\-\/]+)$/', - 'daemonSFTP' => 'required|numeric|between:1,65535', - 'daemonListen' => 'required|numeric|between:1,65535', - ]); - - // Run validator, throw catchable and displayable exception if it fails. - // Exception includes a JSON result of failed validation rules. - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - // Verify the FQDN if using SSL - if (filter_var($data['fqdn'], FILTER_VALIDATE_IP) && $data['scheme'] === 'https') { - throw new DisplayException('A fully qualified domain name is required to use a secure comunication method on this node.'); - } - - // Verify FQDN is resolvable, or if not using SSL that the IP is valid. - if (! filter_var(gethostbyname($data['fqdn']), FILTER_VALIDATE_IP)) { - throw new DisplayException('The FQDN (or IP Address) provided does not resolve to a valid IP address.'); - } - - // Should we be nulling the overallocations? - $data['memory_overallocate'] = ($data['memory_overallocate'] < 0) ? null : $data['memory_overallocate']; - $data['disk_overallocate'] = ($data['disk_overallocate'] < 0) ? null : $data['disk_overallocate']; - - // Set the Secret - $uuid = new UuidService; - $data['daemonSecret'] = (string) $uuid->generate('nodes', 'daemonSecret'); - - return Models\Node::create($data); - } - - /** - * Updates a node on the system. - * - * @param int $id - * @param array $data - * @return \Pterodactyl\Models\Node - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function update($id, array $data) - { - $node = Models\Node::findOrFail($id); - - // Validate Fields - $validator = $validator = Validator::make($data, [ - 'name' => 'regex:/^([\w .-]{1,100})$/', - 'location_id' => 'numeric|min:1|exists:locations,id', - 'public' => 'numeric|between:0,1', - 'fqdn' => 'string|unique:nodes,fqdn,' . $id, - 'scheme' => 'regex:/^(http(s)?)$/', - 'behind_proxy' => 'boolean', - 'memory' => 'numeric|min:1', - 'memory_overallocate' => 'numeric|min:-1', - 'disk' => 'numeric|min:1', - 'disk_overallocate' => 'numeric|min:-1', - 'upload_size' => 'numeric|min:0', - 'daemonBase' => 'sometimes|regex:/^([\/][\d\w.\-\/]+)$/', - 'daemonSFTP' => 'numeric|between:1,65535', - 'daemonListen' => 'numeric|between:1,65535', - 'reset_secret' => 'sometimes|nullable|accepted', - ]); - - // Run validator, throw catchable and displayable exception if it fails. - // Exception includes a JSON result of failed validation rules. - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - // Verify the FQDN - if (isset($data['fqdn'])) { - - // Verify the FQDN if using SSL - if ((isset($data['scheme']) && $data['scheme'] === 'https') || (! isset($data['scheme']) && $node->scheme === 'https')) { - if (filter_var($data['fqdn'], FILTER_VALIDATE_IP)) { - throw new DisplayException('A fully qualified domain name is required to use secure comunication on this node.'); - } - } - - // Verify FQDN is resolvable, or if not using SSL that the IP is valid. - if (! filter_var(gethostbyname($data['fqdn']), FILTER_VALIDATE_IP)) { - throw new DisplayException('The FQDN (or IP Address) provided does not resolve to a valid IP address.'); - } - } - - // Should we be nulling the overallocations? - if (isset($data['memory_overallocate'])) { - $data['memory_overallocate'] = ($data['memory_overallocate'] < 0) ? null : $data['memory_overallocate']; - } - - if (isset($data['disk_overallocate'])) { - $data['disk_overallocate'] = ($data['disk_overallocate'] < 0) ? null : $data['disk_overallocate']; - } - - // Set the Secret - if (isset($data['reset_secret']) && ! is_null($data['reset_secret'])) { - $uuid = new UuidService; - $data['daemonSecret'] = (string) $uuid->generate('nodes', 'daemonSecret'); - unset($data['reset_secret']); - } - - $oldDaemonKey = $node->daemonSecret; - $node->update($data); - try { - $node->guzzleClient(['X-Access-Token' => $oldDaemonKey])->request('PATCH', '/config', [ - 'json' => [ - 'web' => [ - 'listen' => $node->daemonListen, - 'ssl' => [ - 'enabled' => (! $node->behind_proxy && $node->scheme === 'https'), - ], - ], - 'sftp' => [ - 'path' => $node->daemonBase, - 'port' => $node->daemonSFTP, - ], - 'remote' => [ - 'base' => config('app.url'), - ], - 'uploads' => [ - 'size_limit' => $node->upload_size, - ], - 'keys' => [ - $node->daemonSecret, - ], - ], - ]); - } catch (\Exception $ex) { - throw new DisplayException('Failed to update the node configuration, however your changes have been saved to the database. You will need to manually update the configuration file for the node to apply these changes.'); - } - } - - /** - * Adds allocations to a provided node. - * - * @param int $id - * @param array $data - * @return void - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function addAllocations($id, array $data) - { - $node = Models\Node::findOrFail($id); - - $validator = Validator::make($data, [ - 'allocation_ip' => 'required|string', - 'allocation_alias' => 'sometimes|required|string|max:255', - 'allocation_ports' => 'required|array', - ]); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - $explode = explode('/', $data['allocation_ip']); - if (count($explode) !== 1) { - if (! ctype_digit($explode[1]) || ($explode[1] > 32 || $explode[1] < 25)) { - throw new DisplayException('CIDR notation only allows masks between /32 and /25.'); - } - } - - DB::transaction(function () use ($data, $node) { - foreach (Network::parse(gethostbyname($data['allocation_ip'])) as $ip) { - foreach ($data['allocation_ports'] as $port) { - // Determine if this is a valid single port, or a valid port range. - if (! ctype_digit($port) && ! preg_match('/^(\d{1,5})-(\d{1,5})$/', $port)) { - throw new DisplayException('The mapping for ' . $port . ' is invalid and cannot be processed.'); - } - - if (preg_match('/^(\d{1,5})-(\d{1,5})$/', $port, $matches)) { - $block = range($matches[1], $matches[2]); - - if (count($block) > 1000) { - throw new DisplayException('Adding more than 1000 ports at once is not supported. Please use a smaller port range.'); - } - - foreach ($block as $unit) { - // Insert into Database - Models\Allocation::firstOrCreate([ - 'node_id' => $node->id, - 'ip' => $ip, - 'port' => $unit, - 'ip_alias' => isset($data['allocation_alias']) ? $data['allocation_alias'] : null, - 'server_id' => null, - ]); - } - } else { - // Insert into Database - Models\Allocation::firstOrCreate([ - 'node_id' => $node->id, - 'ip' => $ip, - 'port' => $port, - 'ip_alias' => isset($data['allocation_alias']) ? $data['allocation_alias'] : null, - 'server_id' => null, - ]); - } - } - } - }); - } - - /** - * Deletes a node on the system. - * - * @param int $id - * @return void - * - * @throws \Pterodactyl\Exceptions\DisplayException - */ - public function delete($id) - { - $node = Models\Node::withCount('servers')->findOrFail($id); - if ($node->servers_count > 0) { - throw new DisplayException('You cannot delete a node with servers currently attached to it.'); - } - - DB::transaction(function () use ($node) { - // Unlink Database Servers - Models\DatabaseHost::where('node_id', $node->id)->update(['node_id' => null]); - - // Delete Allocations - Models\Allocation::where('node_id', $node->id)->delete(); - - // Delete Node - $node->delete(); - }); - } -} diff --git a/app/Repositories/OptionRepository.php b/app/Repositories/OptionRepository.php deleted file mode 100644 index 1a0ce4509..000000000 --- a/app/Repositories/OptionRepository.php +++ /dev/null @@ -1,203 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Repositories; - -use DB; -use Validator; -use Pterodactyl\Models\ServiceOption; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\DisplayValidationException; - -class OptionRepository -{ - /** - * Creates a new service option on the system. - * - * @param array $data - * @return \Pterodactyl\Models\ServiceOption - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function create(array $data) - { - $validator = Validator::make($data, [ - 'service_id' => 'required|numeric|exists:services,id', - 'name' => 'required|string|max:255', - 'description' => 'required|string', - 'tag' => 'required|alpha_num|max:60|unique:service_options,tag', - 'docker_image' => 'sometimes|string|max:255', - 'startup' => 'sometimes|nullable|string', - 'config_from' => 'sometimes|required|numeric|exists:service_options,id', - 'config_startup' => 'required_without:config_from|json', - 'config_stop' => 'required_without:config_from|string|max:255', - 'config_logs' => 'required_without:config_from|json', - 'config_files' => 'required_without:config_from|json', - ]); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - if (isset($data['config_from'])) { - if (! ServiceOption::where('service_id', $data['service_id'])->where('id', $data['config_from'])->first()) { - throw new DisplayException('The `configuration from` directive must be a child of the assigned service.'); - } - } - - return ServiceOption::create($data); - } - - /** - * Deletes a service option from the system. - * - * @param int $id - * @return void - * - * @throws \Pterodactyl\Exceptions\DisplayException - */ - public function delete($id) - { - $option = ServiceOption::with('variables')->withCount('servers')->findOrFail($id); - - if ($option->servers_count > 0) { - throw new DisplayException('You cannot delete a service option that has servers associated with it.'); - } - - DB::transaction(function () use ($option) { - foreach ($option->variables as $variable) { - (new VariableRepository)->delete($variable->id); - } - - $option->delete(); - }); - } - - /** - * Updates a service option in the database which can then be used - * on nodes. - * - * @param int $id - * @param array $data - * @return \Pterodactyl\Models\ServiceOption - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function update($id, array $data) - { - $option = ServiceOption::findOrFail($id); - - // Due to code limitations (at least when I am writing this currently) - // we have to make an assumption that if config_from is not passed - // that we should be telling it that no config is wanted anymore. - // - // This really is only an issue if we open API access to this function, - // in which case users will always need to pass `config_from` in order - // to keep it assigned. - if (! isset($data['config_from']) && ! is_null($option->config_from)) { - $option->config_from = null; - } - - $validator = Validator::make($data, [ - 'name' => 'sometimes|required|string|max:255', - 'description' => 'sometimes|required|string', - 'tag' => 'sometimes|required|string|max:255|unique:service_options,tag,' . $option->id, - 'docker_image' => 'sometimes|required|string|max:255', - 'startup' => 'sometimes|required|string', - 'config_from' => 'sometimes|required|numeric|exists:service_options,id', - ]); - - $validator->sometimes([ - 'config_startup', 'config_logs', 'config_files', - ], 'required_without:config_from|json', function ($input) use ($option) { - return ! (! $input->config_from && ! is_null($option->config_from)); - }); - - $validator->sometimes('config_stop', 'required_without:config_from|string|max:255', function ($input) use ($option) { - return ! (! $input->config_from && ! is_null($option->config_from)); - }); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - if (isset($data['config_from'])) { - if (! ServiceOption::where('service_id', $option->service_id)->where('id', $data['config_from'])->first()) { - throw new DisplayException('The `configuration from` directive must be a child of the assigned service.'); - } - } - - $option->fill($data)->save(); - - return $option; - } - - /** - * Updates a service option's scripts in the database. - * - * @param int $id - * @param array $data - * @return \Pterodactyl\Models\ServiceOption - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function scripts($id, array $data) - { - $option = ServiceOption::findOrFail($id); - - $data['script_install'] = empty($data['script_install']) ? null : $data['script_install']; - - $validator = Validator::make($data, [ - 'script_install' => 'sometimes|nullable|string', - 'script_is_privileged' => 'sometimes|required|boolean', - 'script_entry' => 'sometimes|required|string', - 'script_container' => 'sometimes|required|string', - 'copy_script_from' => 'sometimes|nullable|numeric', - ]); - - if (isset($data['copy_script_from']) && ! empty($data['copy_script_from'])) { - $select = ServiceOption::whereNull('copy_script_from')->where([ - ['id', $data['copy_script_from']], - ['service_id', $option->service_id], - ])->first(); - - if (! $select) { - throw new DisplayException('The service option selected to copy a script from either does not exist, or is copying from a higher level.'); - } - } else { - $data['copy_script_from'] = null; - } - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - $option->fill($data)->save(); - - return $option; - } -} diff --git a/app/Repositories/PackRepository.php b/app/Repositories/PackRepository.php deleted file mode 100644 index 0a8854465..000000000 --- a/app/Repositories/PackRepository.php +++ /dev/null @@ -1,239 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Repositories; - -use DB; -use Uuid; -use Storage; -use Validator; -use Pterodactyl\Models\Pack; -use Pterodactyl\Services\UuidService; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\DisplayValidationException; - -class PackRepository -{ - /** - * Creates a new pack on the system. - * - * @param array $data - * @return \Pterodactyl\Models\Pack - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function create(array $data) - { - $validator = Validator::make($data, [ - 'name' => 'required|string', - 'version' => 'required|string', - 'description' => 'sometimes|nullable|string', - 'selectable' => 'sometimes|required|boolean', - 'visible' => 'sometimes|required|boolean', - 'locked' => 'sometimes|required|boolean', - 'option_id' => 'required|exists:service_options,id', - ]); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - if (isset($data['file_upload'])) { - if (! $data['file_upload']->isValid()) { - throw new DisplayException('The file provided does not appear to be valid.'); - } - - if (! in_array($data['file_upload']->getMimeType(), ['application/gzip', 'application/x-gzip'])) { - throw new DisplayException('The file provided (' . $data['file_upload']->getMimeType() . ') does not meet the required filetype of application/gzip.'); - } - } - - return DB::transaction(function () use ($data) { - $uuid = new UuidService(); - - $pack = new Pack; - $pack->uuid = $uuid->generate('packs', 'uuid'); - $pack->fill([ - 'option_id' => $data['option_id'], - 'name' => $data['name'], - 'version' => $data['version'], - 'description' => (empty($data['description'])) ? null : $data['description'], - 'selectable' => isset($data['selectable']), - 'visible' => isset($data['visible']), - 'locked' => isset($data['locked']), - ])->save(); - - if (! $pack->exists) { - throw new DisplayException('Model does not exist after creation. Did an event prevent it from saving?'); - } - - Storage::makeDirectory('packs/' . $pack->uuid); - if (isset($data['file_upload'])) { - $data['file_upload']->storeAs('packs/' . $pack->uuid, 'archive.tar.gz'); - } - - return $pack; - }); - } - - /** - * Creates a new pack on the system given a template file. - * - * @param array $data - * @return \Pterodactyl\Models\Pack - * - * @throws \Pterodactyl\Exceptions\DisplayException - */ - public function createWithTemplate(array $data) - { - if (! isset($data['file_upload'])) { - throw new DisplayException('No template file was found submitted with this request.'); - } - - if (! $data['file_upload']->isValid()) { - throw new DisplayException('The file provided does not appear to be valid.'); - } - - if (! in_array($data['file_upload']->getMimeType(), [ - 'application/zip', - 'text/plain', - 'application/json', - ])) { - throw new DisplayException('The file provided (' . $data['file_upload']->getMimeType() . ') does not meet the required filetypes of application/zip or application/json.'); - } - - if ($data['file_upload']->getMimeType() === 'application/zip') { - $zip = new \ZipArchive; - if (! $zip->open($data['file_upload']->path())) { - throw new DisplayException('The uploaded archive was unable to be opened.'); - } - - $isTar = $zip->locateName('archive.tar.gz'); - - if (! $zip->locateName('import.json') || ! $isTar) { - throw new DisplayException('This contents of the provided archive were in an invalid format.'); - } - - $json = json_decode($zip->getFromName('import.json')); - $pack = $this->create([ - 'name' => $json->name, - 'version' => $json->version, - 'description' => $json->description, - 'option_id' => $data['option_id'], - 'selectable' => $json->selectable, - 'visible' => $json->visible, - 'locked' => $json->locked, - ]); - - if (! $zip->extractTo(storage_path('app/packs/' . $pack->uuid), 'archive.tar.gz')) { - $pack->delete(); - throw new DisplayException('Unable to extract the archive file to the correct location.'); - } - - $zip->close(); - - return $pack; - } else { - $json = json_decode(file_get_contents($data['file_upload']->path())); - - return $this->create([ - 'name' => $json->name, - 'version' => $json->version, - 'description' => $json->description, - 'option_id' => $data['option_id'], - 'selectable' => $json->selectable, - 'visible' => $json->visible, - 'locked' => $json->locked, - ]); - } - } - - /** - * Updates a pack on the system. - * - * @param int $id - * @param array $data - * @return \Pterodactyl\Models\Pack - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function update($id, array $data) - { - $validator = Validator::make($data, [ - 'name' => 'sometimes|required|string', - 'option_id' => 'sometimes|required|exists:service_options,id', - 'version' => 'sometimes|required|string', - 'description' => 'sometimes|string', - 'selectable' => 'sometimes|required|boolean', - 'visible' => 'sometimes|required|boolean', - 'locked' => 'sometimes|required|boolean', - ]); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - $pack = Pack::withCount('servers')->findOrFail($id); - - if ($pack->servers_count > 0 && (isset($data['option_id']) && (int) $data['option_id'] !== $pack->option_id)) { - throw new DisplayException('You cannot modify the associated option if servers are attached to a pack.'); - } - - $pack->fill([ - 'name' => isset($data['name']) ? $data['name'] : $pack->name, - 'option_id' => isset($data['option_id']) ? $data['option_id'] : $pack->option_id, - 'version' => isset($data['version']) ? $data['version'] : $pack->version, - 'description' => (empty($data['description'])) ? null : $data['description'], - 'selectable' => isset($data['selectable']), - 'visible' => isset($data['visible']), - 'locked' => isset($data['locked']), - ])->save(); - - return $pack; - } - - /** - * Deletes a pack and files from the system. - * - * @param int $id - * @return void - * - * @throws \Pterodactyl\Exceptions\DisplayException - */ - public function delete($id) - { - $pack = Pack::withCount('servers')->findOrFail($id); - - if ($pack->servers_count > 0) { - throw new DisplayException('Cannot delete a pack from the system if servers are assocaited with it.'); - } - - DB::transaction(function () use ($pack) { - $pack->delete(); - Storage::deleteDirectory('packs/' . $pack->uuid); - }); - } -} diff --git a/app/Repositories/Repository.php b/app/Repositories/Repository.php new file mode 100644 index 000000000..375001b01 --- /dev/null +++ b/app/Repositories/Repository.php @@ -0,0 +1,139 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Repository; + +use Illuminate\Foundation\Application; +use Pterodactyl\Contracts\Repository\RepositoryInterface; + +abstract class Repository implements RepositoryInterface +{ + /** + * @var \Illuminate\Foundation\Application + */ + protected $app; + + /** + * @var array + */ + protected $columns = ['*']; + + /** + * @var mixed + */ + protected $model; + + /** + * @var bool + */ + protected $withFresh = true; + + /** + * Repository constructor. + * + * @param \Illuminate\Foundation\Application $application + */ + public function __construct(Application $application) + { + $this->app = $application; + + $this->setModel($this->model()); + } + + /** + * Take the provided model and make it accessible to the rest of the repository. + * + * @param string|array $model + * @return mixed + */ + protected function setModel($model) + { + if (is_array($model)) { + if (count($model) !== 2) { + throw new \InvalidArgumentException( + printf('setModel expected exactly 2 parameters, %d received.', count($model)) + ); + } + + return $this->model = call_user_func( + $model[1], + $this->app->make($model[0]) + ); + } + + return $this->model = $this->app->make($model); + } + + /** + * @return mixed + */ + abstract public function model(); + + /** + * Return the model being used for this repository. + * + * @return mixed + */ + public function getModel() + { + return $this->model; + } + + /** + * Setup column selection functionality. + * + * @param array $columns + * @return $this + */ + public function withColumns($columns = ['*']) + { + $clone = clone $this; + $clone->columns = is_array($columns) ? $columns : func_get_args(); + + return $clone; + } + + /** + * Return the columns to be selected in the repository call. + * + * @return array + */ + public function getColumns() + { + return $this->columns; + } + + /** + * Set repository to not return a fresh record from the DB when completed. + * + * @return $this + */ + public function withoutFresh() + { + $clone = clone $this; + $clone->withFresh = false; + + return $clone; + } +} diff --git a/app/Repositories/ServerRepository.php b/app/Repositories/ServerRepository.php deleted file mode 100644 index 374de1b07..000000000 --- a/app/Repositories/ServerRepository.php +++ /dev/null @@ -1,1036 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Repositories; - -use DB; -use Crypt; -use Validator; -use Pterodactyl\Models\Node; -use Pterodactyl\Models\Pack; -use Pterodactyl\Models\User; -use Pterodactyl\Models\Server; -use Pterodactyl\Models\Service; -use Pterodactyl\Models\Allocation; -use Pterodactyl\Models\ServiceOption; -use Pterodactyl\Services\UuidService; -use Pterodactyl\Models\ServerVariable; -use Pterodactyl\Models\ServiceVariable; -use GuzzleHttp\Exception\ClientException; -use GuzzleHttp\Exception\TransferException; -use Pterodactyl\Services\DeploymentService; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\DisplayValidationException; - -class ServerRepository -{ - /** - * An array of daemon permission to assign to this server. - * - * @var array - */ - protected $daemonPermissions = [ - 's:*', - ]; - - /** - * Generates a SFTP username for a server given a server name. - * format: mumble_67c7a4b0. - * - * @param string $name - * @param null|string $identifier - * @return string - */ - protected function generateSFTPUsername($name, $identifier = null) - { - if (is_null($identifier) || ! ctype_alnum($identifier)) { - $unique = str_random(8); - } else { - if (strlen($identifier) < 8) { - $unique = $identifier . str_random((8 - strlen($identifier))); - } else { - $unique = substr($identifier, 0, 8); - } - } - - // Filter the Server Name - $name = trim(preg_replace('/[^\w]+/', '', $name), '_'); - $name = (strlen($name) < 1) ? str_random(6) : $name; - - return strtolower(substr($name, 0, 6) . '_' . $unique); - } - - /** - * Adds a new server to the system. - * - * @param array $data - * @return \Pterodactyl\Models\Server - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\AutoDeploymentException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function create(array $data) - { - $validator = Validator::make($data, [ - 'user_id' => 'required|exists:users,id', - 'name' => 'required|regex:/^([\w .-]{1,200})$/', - 'description' => 'sometimes|nullable|string', - 'memory' => 'required|numeric|min:0', - 'swap' => 'required|numeric|min:-1', - 'io' => 'required|numeric|min:10|max:1000', - 'cpu' => 'required|numeric|min:0', - 'disk' => 'required|numeric|min:0', - 'service_id' => 'required|numeric|min:1|exists:services,id', - 'option_id' => 'required|numeric|min:1|exists:service_options,id', - 'location_id' => 'required|numeric|min:1|exists:locations,id', - 'pack_id' => 'sometimes|nullable|numeric|min:0', - 'custom_container' => 'string', - 'startup' => 'string', - 'auto_deploy' => 'sometimes|required|accepted', - 'custom_id' => 'sometimes|required|numeric|unique:servers,id', - 'skip_scripts' => 'sometimes|required|boolean', - ]); - - $validator->sometimes('node_id', 'required|numeric|min:1|exists:nodes,id', function ($input) { - return ! ($input->auto_deploy); - }); - - $validator->sometimes('allocation_id', 'required|numeric|exists:allocations,id', function ($input) { - return ! ($input->auto_deploy); - }); - - $validator->sometimes('allocation_additional.*', 'sometimes|required|numeric|exists:allocations,id', function ($input) { - return ! ($input->auto_deploy); - }); - - // Run validator, throw catchable and displayable exception if it fails. - // Exception includes a JSON result of failed validation rules. - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - $user = User::findOrFail($data['user_id']); - - $deployment = false; - if (isset($data['auto_deploy'])) { - $deployment = new DeploymentService; - - if (isset($data['location_id'])) { - $deployment->setLocation($data['location_id']); - } - - $deployment->setMemory($data['memory'])->setDisk($data['disk'])->select(); - } - - $node = (! $deployment) ? Node::findOrFail($data['node_id']) : $deployment->node(); - - // Verify IP & Port are a.) free and b.) assigned to the node. - // We know the node exists because of 'exists:nodes,id' in the validation - if (! $deployment) { - $allocation = Allocation::where('id', $data['allocation_id'])->where('node_id', $data['node_id'])->whereNull('server_id')->first(); - } else { - $allocation = $deployment->allocation(); - } - - // Something failed in the query, either that combo doesn't exist, or it is in use. - if (! $allocation) { - throw new DisplayException('The selected Allocation ID is either already in use, or unavaliable for this node.'); - } - - // Validate those Service Option Variables - // We know the service and option exists because of the validation. - // We need to verify that the option exists for the service, and then check for - // any required variable fields. (fields are labeled env_) - $option = ServiceOption::where('id', $data['option_id'])->where('service_id', $data['service_id'])->first(); - if (! $option) { - throw new DisplayException('The requested service option does not exist for the specified service.'); - } - - // Validate the Pack - if (! isset($data['pack_id']) || (int) $data['pack_id'] < 1) { - $data['pack_id'] = null; - } else { - $pack = Pack::where('id', $data['pack_id'])->where('option_id', $data['option_id'])->first(); - if (! $pack) { - throw new DisplayException('The requested service pack does not seem to exist for this combination.'); - } - } - - // Load up the Service Information - $service = Service::find($option->service_id); - - // Check those Variables - $variables = ServiceVariable::where('option_id', $data['option_id'])->get(); - $variableList = []; - if ($variables) { - foreach ($variables as $variable) { - - // Is the variable required? - if (! isset($data['env_' . $variable->env_variable])) { - if ($variable->required) { - throw new DisplayException('A required service option variable field (env_' . $variable->env_variable . ') was missing from the request.'); - } - $variableList[] = [ - 'id' => $variable->id, - 'env' => $variable->env_variable, - 'val' => $variable->default_value, - ]; - continue; - } - - // Check aganist Regex Pattern - if (! is_null($variable->regex) && ! preg_match($variable->regex, $data['env_' . $variable->env_variable])) { - throw new DisplayException('Failed to validate service option variable field (env_' . $variable->env_variable . ') aganist regex (' . $variable->regex . ').'); - } - - $variableList[] = [ - 'id' => $variable->id, - 'env' => $variable->env_variable, - 'val' => $data['env_' . $variable->env_variable], - ]; - continue; - } - } - - // Check Overallocation - if (! $deployment) { - if (is_numeric($node->memory_overallocate) || is_numeric($node->disk_overallocate)) { - $totals = Server::select(DB::raw('SUM(memory) as memory, SUM(disk) as disk'))->where('node_id', $node->id)->first(); - - // Check memory limits - if (is_numeric($node->memory_overallocate)) { - $newMemory = $totals->memory + $data['memory']; - $memoryLimit = ($node->memory * (1 + ($node->memory_overallocate / 100))); - if ($newMemory > $memoryLimit) { - throw new DisplayException('The amount of memory allocated to this server would put the node over its allocation limits. This node is allowed ' . ($node->memory_overallocate + 100) . '% of its assigned ' . $node->memory . 'Mb of memory (' . $memoryLimit . 'Mb) of which ' . (($totals->memory / $node->memory) * 100) . '% (' . $totals->memory . 'Mb) is in use already. By allocating this server the node would be at ' . (($newMemory / $node->memory) * 100) . '% (' . $newMemory . 'Mb) usage.'); - } - } - - // Check Disk Limits - if (is_numeric($node->disk_overallocate)) { - $newDisk = $totals->disk + $data['disk']; - $diskLimit = ($node->disk * (1 + ($node->disk_overallocate / 100))); - if ($newDisk > $diskLimit) { - throw new DisplayException('The amount of disk allocated to this server would put the node over its allocation limits. This node is allowed ' . ($node->disk_overallocate + 100) . '% of its assigned ' . $node->disk . 'Mb of disk (' . $diskLimit . 'Mb) of which ' . (($totals->disk / $node->disk) * 100) . '% (' . $totals->disk . 'Mb) is in use already. By allocating this server the node would be at ' . (($newDisk / $node->disk) * 100) . '% (' . $newDisk . 'Mb) usage.'); - } - } - } - } - - DB::beginTransaction(); - - try { - $uuid = new UuidService; - - // Add Server to the Database - $server = new Server; - $genUuid = $uuid->generate('servers', 'uuid'); - $genShortUuid = $uuid->generateShort('servers', 'uuidShort', $genUuid); - - if (isset($data['custom_id'])) { - $server->id = $data['custom_id']; - } - - $server->fill([ - 'uuid' => $genUuid, - 'uuidShort' => $genShortUuid, - 'node_id' => $node->id, - 'name' => $data['name'], - 'description' => $data['description'], - 'skip_scripts' => isset($data['skip_scripts']), - 'suspended' => false, - 'owner_id' => $user->id, - 'memory' => $data['memory'], - 'swap' => $data['swap'], - 'disk' => $data['disk'], - 'io' => $data['io'], - 'cpu' => $data['cpu'], - 'oom_disabled' => isset($data['oom_disabled']), - 'allocation_id' => $allocation->id, - 'service_id' => $data['service_id'], - 'option_id' => $data['option_id'], - 'pack_id' => $data['pack_id'], - 'startup' => $data['startup'], - 'daemonSecret' => $uuid->generate('servers', 'daemonSecret'), - 'image' => (isset($data['custom_container']) && ! empty($data['custom_container'])) ? $data['custom_container'] : $option->docker_image, - 'username' => $this->generateSFTPUsername($data['name'], $genShortUuid), - 'sftp_password' => Crypt::encrypt('not set'), - ]); - $server->save(); - - // Mark Allocation in Use - $allocation->server_id = $server->id; - $allocation->save(); - - // Add Additional Allocations - if (isset($data['allocation_additional']) && is_array($data['allocation_additional'])) { - foreach ($data['allocation_additional'] as $allocation) { - $model = Allocation::where('id', $allocation)->where('node_id', $data['node_id'])->whereNull('server_id')->first(); - if (! $model) { - continue; - } - - $model->server_id = $server->id; - $model->save(); - } - } - - foreach ($variableList as $item) { - ServerVariable::create([ - 'server_id' => $server->id, - 'variable_id' => $item['id'], - 'variable_value' => $item['val'], - ]); - } - - $environment = $this->parseVariables($server); - $server->load('allocation', 'allocations'); - - $node->guzzleClient(['X-Access-Token' => $node->daemonSecret])->request('POST', '/servers', [ - 'json' => [ - 'uuid' => (string) $server->uuid, - 'user' => $server->username, - 'build' => [ - 'default' => [ - 'ip' => $server->allocation->ip, - 'port' => $server->allocation->port, - ], - 'ports' => $server->allocations->groupBy('ip')->map(function ($item) { - return $item->pluck('port'); - })->toArray(), - 'env' => $environment->pluck('value', 'variable')->toArray(), - 'memory' => (int) $server->memory, - 'swap' => (int) $server->swap, - 'io' => (int) $server->io, - 'cpu' => (int) $server->cpu, - 'disk' => (int) $server->disk, - 'image' => $server->image, - ], - 'service' => [ - 'type' => $service->folder, - 'option' => $option->tag, - 'pack' => (isset($pack)) ? $pack->uuid : null, - 'skip_scripts' => $server->skip_scripts, - ], - 'keys' => [ - (string) $server->daemonSecret => $this->daemonPermissions, - ], - 'rebuild' => false, - 'start_on_completion' => isset($data['start_on_completion']), - ], - ]); - - DB::commit(); - - return $server; - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } - } - - /** - * Update the details for a server. - * - * @param int $id - * @param array $data - * @return \Pterodactyl\Models\Server - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function updateDetails($id, array $data) - { - $uuid = new UuidService; - $resetDaemonKey = false; - - // Validate Fields - $validator = Validator::make($data, [ - 'owner_id' => 'sometimes|required|integer|exists:users,id', - 'name' => 'sometimes|required|regex:([\w .-]{1,200})', - 'description' => 'sometimes|nullable|string', - 'reset_token' => 'sometimes|required|accepted', - ]); - - // Run validator, throw catchable and displayable exception if it fails. - // Exception includes a JSON result of failed validation rules. - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - DB::beginTransaction(); - - try { - $server = Server::with('user')->findOrFail($id); - - // Update daemon secret if it was passed. - if (isset($data['reset_token']) || (isset($data['owner_id']) && (int) $data['owner_id'] !== $server->user->id)) { - $oldDaemonKey = $server->daemonSecret; - $server->daemonSecret = $uuid->generate('servers', 'daemonSecret'); - $resetDaemonKey = true; - } - - // Save our changes - $server->fill($data)->save(); - - // Do we need to update? If not, return successful. - if (! $resetDaemonKey) { - return DB::commit(); - } - - $res = $server->node->guzzleClient([ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $server->node->daemonSecret, - ])->request('PATCH', '/server', [ - 'exceptions' => false, - 'json' => [ - 'keys' => [ - (string) $oldDaemonKey => [], - (string) $server->daemonSecret => $this->daemonPermissions, - ], - ], - ]); - - if ($res->getStatusCode() === 204) { - DB::commit(); - - return $server; - } else { - throw new DisplayException('Daemon returned a a non HTTP/204 error code. HTTP/' + $res->getStatusCode()); - } - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } - } - - /** - * Update the container for a server. - * - * @param int $id - * @param array $data - * @return \Pterodactyl\Models\Server - * - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function updateContainer($id, array $data) - { - $validator = Validator::make($data, [ - 'docker_image' => 'required|string', - ]); - - // Run validator, throw catchable and displayable exception if it fails. - // Exception includes a JSON result of failed validation rules. - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - DB::beginTransaction(); - try { - $server = Server::findOrFail($id); - - $server->image = $data['docker_image']; - $server->save(); - - $server->node->guzzleClient([ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $server->node->daemonSecret, - ])->request('PATCH', '/server', [ - 'json' => [ - 'build' => [ - 'image' => $server->image, - ], - ], - ]); - - DB::commit(); - - return $server; - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } - } - - /** - * Update the build details for a server. - * - * @param int $id - * @param array $data - * @return \Pterodactyl\Models\Server - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function changeBuild($id, array $data) - { - $validator = Validator::make($data, [ - 'allocation_id' => 'sometimes|required|exists:allocations,id', - 'add_allocations' => 'sometimes|required|array', - 'remove_allocations' => 'sometimes|required|array', - 'memory' => 'sometimes|required|integer|min:0', - 'swap' => 'sometimes|required|integer|min:-1', - 'io' => 'sometimes|required|integer|min:10|max:1000', - 'cpu' => 'sometimes|required|integer|min:0', - 'disk' => 'sometimes|required|integer|min:0', - ]); - - // Run validator, throw catchable and displayable exception if it fails. - // Exception includes a JSON result of failed validation rules. - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - DB::beginTransaction(); - - try { - $server = Server::with('allocation', 'allocations')->findOrFail($id); - $newBuild = []; - $newAllocations = []; - - if (isset($data['allocation_id'])) { - if ((int) $data['allocation_id'] !== $server->allocation_id) { - $selection = $server->allocations->where('id', $data['allocation_id'])->first(); - if (! $selection) { - throw new DisplayException('The requested default connection is not allocated to this server.'); - } - - $server->allocation_id = $selection->id; - $newBuild['default'] = ['ip' => $selection->ip, 'port' => $selection->port]; - - $server->load('allocation'); - } - } - - $newPorts = false; - $firstNewAllocation = null; - // Add Assignments - if (isset($data['add_allocations'])) { - foreach ($data['add_allocations'] as $allocation) { - $model = Allocation::where('id', $allocation)->whereNull('server_id')->first(); - if (! $model) { - continue; - } - - $newPorts = true; - $firstNewAllocation = $firstNewAllocation ?? $model; - $model->update([ - 'server_id' => $server->id, - ]); - } - - $server->load('allocations'); - } - - // Remove Assignments - if (isset($data['remove_allocations'])) { - foreach ($data['remove_allocations'] as $allocation) { - // Can't remove the assigned IP/Port combo - if ((int) $allocation === $server->allocation_id) { - // No New Allocation - if (is_null($firstNewAllocation)) { - continue; - } - - // New Allocation, set as the default. - $server->allocation_id = $firstNewAllocation->id; - $newBuild['default'] = ['ip' => $firstNewAllocation->ip, 'port' => $firstNewAllocation->port]; - } - - $newPorts = true; - Allocation::where('id', $allocation)->where('server_id', $server->id)->update([ - 'server_id' => null, - ]); - } - - $server->load('allocations'); - } - - if ($newPorts) { - $newBuild['ports|overwrite'] = $server->allocations->groupBy('ip')->map(function ($item) { - return $item->pluck('port'); - })->toArray(); - - $newBuild['env|overwrite'] = $this->parseVariables($server)->pluck('value', 'variable')->toArray(); - } - - // @TODO: verify that server can be set to this much memory without - // going over node limits. - if (isset($data['memory']) && $server->memory !== (int) $data['memory']) { - $server->memory = $data['memory']; - $newBuild['memory'] = (int) $server->memory; - } - - if (isset($data['swap']) && $server->swap !== (int) $data['swap']) { - $server->swap = $data['swap']; - $newBuild['swap'] = (int) $server->swap; - } - - // @TODO: verify that server can be set to this much disk without - // going over node limits. - if (isset($data['disk']) && $server->disk !== (int) $data['disk']) { - $server->disk = $data['disk']; - $newBuild['disk'] = (int) $server->disk; - } - - if (isset($data['cpu']) && $server->cpu !== (int) $data['cpu']) { - $server->cpu = $data['cpu']; - $newBuild['cpu'] = (int) $server->cpu; - } - - if (isset($data['io']) && $server->io !== (int) $data['io']) { - $server->io = $data['io']; - $newBuild['io'] = (int) $server->io; - } - - // Try save() here so if it fails we haven't contacted the daemon - // This won't be committed unless the HTTP request succeedes anyways - $server->save(); - - if (! empty($newBuild)) { - $server->node->guzzleClient([ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $server->node->daemonSecret, - ])->request('PATCH', '/server', [ - 'json' => [ - 'build' => $newBuild, - ], - ]); - } - - DB::commit(); - - return $server; - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } - } - - /** - * Process the variables for a server, and save to the database. - * - * @param \Pterodactyl\Models\Server $server - * @param array $data - * @param bool $admin - * @return \Illuminate\Support\Collection - * - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - protected function processVariables(Server $server, $data, $admin = false) - { - $server->load('option.variables'); - - if ($admin) { - $server->startup = $data['startup']; - $server->save(); - } - - if ($server->option->variables) { - foreach ($server->option->variables as &$variable) { - $set = isset($data['env_' . $variable->id]); - - // If user is not an admin and are trying to edit a non-editable field - // or an invisible field just silently skip the variable. - if (! $admin && (! $variable->user_editable || ! $variable->user_viewable)) { - continue; - } - - // Perform Field Validation - $validator = Validator::make([ - 'variable_value' => ($set) ? $data['env_' . $variable->id] : null, - ], [ - 'variable_value' => $variable->rules, - ]); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode( - collect([ - 'notice' => ['There was a validation error with the `' . $variable->name . '` variable.'], - ])->merge($validator->errors()->toArray()) - )); - } - - $svar = ServerVariable::firstOrNew([ - 'server_id' => $server->id, - 'variable_id' => $variable->id, - ]); - - // Set the value; if one was not passed set it to the default value - if ($set) { - $svar->variable_value = $data['env_' . $variable->id]; - - // Not passed, check if this record exists if so keep value, otherwise set default - } else { - $svar->variable_value = ($svar->exists) ? $svar->variable_value : $variable->default_value; - } - - $svar->save(); - } - } - - return $this->parseVariables($server); - } - - /** - * Parse the variables and return in a standardized format. - * - * @param \Pterodactyl\Models\Server $server - * @return \Illuminate\Support\Collection - */ - protected function parseVariables(Server $server) - { - // Reload Variables - $server->load('variables'); - - $parsed = $server->option->variables->map(function ($item, $key) use ($server) { - $display = $server->variables->where('variable_id', $item->id)->pluck('variable_value')->first(); - - return [ - 'variable' => $item->env_variable, - 'value' => (! is_null($display)) ? $display : $item->default_value, - ]; - }); - - $merge = [[ - 'variable' => 'STARTUP', - 'value' => $server->startup, - ], [ - 'variable' => 'P_VARIABLE__LOCATION', - 'value' => $server->location->short, - ]]; - - $allocations = $server->allocations->where('id', '!=', $server->allocation_id); - $i = 0; - - foreach ($allocations as $allocation) { - $merge[] = [ - 'variable' => 'ALLOC_' . $i . '__PORT', - 'value' => $allocation->port, - ]; - - $i++; - } - - if ($parsed->count() === 0) { - return collect($merge); - } - - return $parsed->merge($merge); - } - - /** - * Update the startup details for a server. - * - * @param int $id - * @param array $data - * @param bool $admin - * @return bool - * - * @throws \GuzzleHttp\Exception\RequestException - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function updateStartup($id, array $data, $admin = false) - { - $server = Server::with('variables', 'option.variables')->findOrFail($id); - $hasServiceChanges = false; - - if ($admin) { - // User is an admin, lots of things to do here. - $validator = Validator::make($data, [ - 'startup' => 'required|string', - 'skip_scripts' => 'sometimes|required|boolean', - 'service_id' => 'required|numeric|min:1|exists:services,id', - 'option_id' => 'required|numeric|min:1|exists:service_options,id', - 'pack_id' => 'sometimes|nullable|numeric|min:0', - ]); - - if ((int) $data['pack_id'] < 1) { - $data['pack_id'] = null; - } - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - if ( - $server->service_id != $data['service_id'] || - $server->option_id != $data['option_id'] || - $server->pack_id != $data['pack_id'] - ) { - $hasServiceChanges = true; - } - } - - // If user isn't an administrator, this function is being access from the front-end - // Just try to update specific variables. - if (! $admin || ! $hasServiceChanges) { - return DB::transaction(function () use ($admin, $data, $server) { - $environment = $this->processVariables($server, $data, $admin); - - $server->node->guzzleClient([ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $server->node->daemonSecret, - ])->request('PATCH', '/server', [ - 'json' => [ - 'build' => [ - 'env|overwrite' => $environment->pluck('value', 'variable')->toArray(), - ], - ], - ]); - - return false; - }); - } - - // Validate those Service Option Variables - // We know the service and option exists because of the validation. - // We need to verify that the option exists for the service, and then check for - // any required variable fields. (fields are labeled env_) - $option = ServiceOption::where('id', $data['option_id'])->where('service_id', $data['service_id'])->first(); - if (! $option) { - throw new DisplayException('The requested service option does not exist for the specified service.'); - } - - // Validate the Pack - if (! isset($data['pack_id']) || (int) $data['pack_id'] < 1) { - $data['pack_id'] = null; - } else { - $pack = Pack::where('id', $data['pack_id'])->where('option_id', $data['option_id'])->first(); - if (! $pack) { - throw new DisplayException('The requested service pack does not seem to exist for this combination.'); - } - } - - return DB::transaction(function () use ($admin, $data, $server) { - $server->installed = 0; - $server->service_id = $data['service_id']; - $server->option_id = $data['option_id']; - $server->pack_id = $data['pack_id']; - $server->skip_scripts = isset($data['skip_scripts']); - $server->save(); - - $server->variables->each->delete(); - - $server->load('service', 'pack'); - - // Send New Environment - $environment = $this->processVariables($server, $data, $admin); - - $server->node->guzzleClient([ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $server->node->daemonSecret, - ])->request('POST', '/server/reinstall', [ - 'json' => [ - 'build' => [ - 'env|overwrite' => $environment->pluck('value', 'variable')->toArray(), - ], - 'service' => [ - 'type' => $server->option->service->folder, - 'option' => $server->option->tag, - 'pack' => (! is_null($server->pack_id)) ? $server->pack->uuid : null, - 'skip_scripts' => $server->skip_scripts, - ], - ], - ]); - - return true; - }); - } - - /** - * Delete a server from the system permanetly. - * - * @param int $id - * @param bool $force - * @return void - * - * @throws \Pterodactyl\Exceptions\DisplayException - */ - public function delete($id, $force = false) - { - $server = Server::with('node', 'allocations', 'variables')->findOrFail($id); - - // Due to MySQL lockouts if the daemon response fails, we need to - // delete the server from the daemon first. If it succeedes and then - // MySQL fails, users just need to force delete the server. - // - // If this is a force delete, continue anyways. - try { - $server->node->guzzleClient([ - 'X-Access-Token' => $server->node->daemonSecret, - 'X-Access-Server' => $server->uuid, - ])->request('DELETE', '/servers'); - } catch (ClientException $ex) { - // Exception is thrown on 4XX HTTP errors, so catch and determine - // if we should continue, or if there is a permissions error. - // - // Daemon throws a 404 if the server doesn't exist, if that is returned - // continue with deletion, even if not a force deletion. - $response = $ex->getResponse(); - if ($ex->getResponse()->getStatusCode() !== 404 && ! $force) { - throw new DisplayException($ex->getMessage()); - } - } catch (TransferException $ex) { - if (! $force) { - throw new DisplayException($ex->getMessage()); - } - } catch (\Exception $ex) { - throw $ex; - } - - DB::transaction(function () use ($server) { - $server->allocations->each(function ($item) { - $item->server_id = null; - $item->save(); - }); - - $server->variables->each->delete(); - - $server->load('subusers.permissions'); - $server->subusers->each(function ($subuser) { - $subuser->permissions->each->delete(); - $subuser->delete(); - }); - - $server->tasks->each->delete(); - - // Delete Databases - // This is the one un-recoverable point where - // transactions will not save us. - $repository = new DatabaseRepository; - $server->databases->each(function ($item) use ($repository) { - $repository->drop($item->id); - }); - - // Fully delete the server. - $server->delete(); - }); - } - - /** - * Toggle the install status of a serve. - * - * @param int $id - * @return bool - * - * @throws \Pterodactyl\Exceptions\DisplayException - */ - public function toggleInstall($id) - { - $server = Server::findOrFail($id); - if ($server->installed > 1) { - throw new DisplayException('This server was marked as having a failed install or being deleted, you cannot override this.'); - } - $server->installed = ! $server->installed; - - return $server->save(); - } - - /** - * Suspends or unsuspends a server. - * - * @param int $id - * @param bool $unsuspend - * @return void - */ - public function toggleAccess($id, $unsuspend = true) - { - $server = Server::with('node')->findOrFail($id); - - DB::transaction(function () use ($server, $unsuspend) { - if ( - (! $unsuspend && $server->suspended) || - ($unsuspend && ! $server->suspended) - ) { - return true; - } - - $server->suspended = ! $unsuspend; - $server->save(); - - $server->node->guzzleClient([ - 'X-Access-Token' => $server->node->daemonSecret, - 'X-Access-Server' => $server->uuid, - ])->request('POST', ($unsuspend) ? '/server/unsuspend' : '/server/suspend'); - }); - } - - /** - * Updates the SFTP password for a server. - * - * @param int $id - * @param string $password - * @return void - * - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function updateSFTPPassword($id, $password) - { - $server = Server::with('node')->findOrFail($id); - - $validator = Validator::make(['password' => $password], [ - 'password' => 'required|regex:/^((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})$/', - ]); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - DB::transaction(function () use ($password, $server) { - $server->sftp_password = Crypt::encrypt($password); - $server->save(); - - $server->node->guzzleClient([ - 'X-Access-Token' => $server->node->daemonSecret, - 'X-Access-Server' => $server->uuid, - ])->request('POST', '/server/password', [ - 'json' => ['password' => $password], - ]); - }); - } - - /** - * Marks a server for reinstallation on the node. - * - * @param int $id - * @return void - */ - public function reinstall($id) - { - $server = Server::with('node')->findOrFail($id); - - DB::transaction(function () use ($server) { - $server->installed = 0; - $server->save(); - - $server->node->guzzleClient([ - 'X-Access-Token' => $server->node->daemonSecret, - 'X-Access-Server' => $server->uuid, - ])->request('POST', '/server/reinstall'); - }); - } -} diff --git a/app/Repositories/ServiceRepository.php b/app/Repositories/ServiceRepository.php deleted file mode 100644 index a0d1716cc..000000000 --- a/app/Repositories/ServiceRepository.php +++ /dev/null @@ -1,135 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Repositories; - -use DB; -use Validator; -use Pterodactyl\Models\Service; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\DisplayValidationException; - -class ServiceRepository -{ - /** - * Creates a new service on the system. - * - * @param array $data - * @return \Pterodactyl\Models\Service - * - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function create(array $data) - { - $validator = Validator::make($data, [ - 'name' => 'required|string|min:1|max:255', - 'description' => 'required|nullable|string', - 'folder' => 'required|unique:services,folder|regex:/^[\w.-]{1,50}$/', - 'startup' => 'required|nullable|string', - ]); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - return DB::transaction(function () use ($data) { - $service = new Service; - $service->author = config('pterodactyl.service.author'); - $service->fill([ - 'name' => $data['name'], - 'description' => (isset($data['description'])) ? $data['description'] : null, - 'folder' => $data['folder'], - 'startup' => (isset($data['startup'])) ? $data['startup'] : null, - 'index_file' => Service::defaultIndexFile(), - ])->save(); - - // It is possible for an event to return false or throw an exception - // which won't necessarily be detected by this transaction. - // - // This check ensures the model was actually saved. - if (! $service->exists) { - throw new \Exception('Service model was created however the response appears to be invalid. Did an event fire wrongly?'); - } - - return $service; - }); - } - - /** - * Updates a service. - * - * @param int $id - * @param array $data - * @return \Pterodactyl\Models\Service - * - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function update($id, array $data) - { - $service = Service::findOrFail($id); - - $validator = Validator::make($data, [ - 'name' => 'sometimes|required|string|min:1|max:255', - 'description' => 'sometimes|required|nullable|string', - 'folder' => 'sometimes|required|regex:/^[\w.-]{1,50}$/', - 'startup' => 'sometimes|required|nullable|string', - 'index_file' => 'sometimes|required|string', - ]); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - return DB::transaction(function () use ($data, $service) { - $service->fill($data)->save(); - - return $service; - }); - } - - /** - * Deletes a service and associated files and options. - * - * @param int $id - * @return void - * - * @throws \Pterodactyl\Exceptions\DisplayException - */ - public function delete($id) - { - $service = Service::withCount('servers')->with('options')->findOrFail($id); - - if ($service->servers_count > 0) { - throw new DisplayException('You cannot delete a service that has servers associated with it.'); - } - - DB::transaction(function () use ($service) { - foreach ($service->options as $option) { - (new OptionRepository)->delete($option->id); - } - - $service->delete(); - }); - } -} diff --git a/app/Repositories/SubuserRepository.php b/app/Repositories/SubuserRepository.php deleted file mode 100644 index 7a2aef8cc..000000000 --- a/app/Repositories/SubuserRepository.php +++ /dev/null @@ -1,264 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Repositories; - -use DB; -use Validator; -use Pterodactyl\Models\User; -use Pterodactyl\Models\Server; -use Pterodactyl\Models\Subuser; -use Pterodactyl\Models\Permission; -use Pterodactyl\Services\UuidService; -use GuzzleHttp\Exception\TransferException; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\DisplayValidationException; - -class SubuserRepository -{ - /** - * Core permissions required for every subuser on the daemon. - * Without this we cannot connect the websocket or get basic - * information about the server. - * - * @var array - */ - protected $coreDaemonPermissions = [ - 's:get', - 's:console', - ]; - - /** - * Creates a new subuser on the server. - * - * @param int $sid - * @param array $data - * @return \Pterodactyl\Models\Subuser - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function create($sid, array $data) - { - $server = Server::with('node')->findOrFail($sid); - - $validator = Validator::make($data, [ - 'permissions' => 'required|array', - 'email' => 'required|email', - ]); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - DB::beginTransaction(); - - try { - // Determine if this user exists or if we need to make them an account. - $user = User::where('email', $data['email'])->first(); - if (! $user) { - try { - $repo = new UserRepository; - $user = $repo->create([ - 'email' => $data['email'], - 'username' => str_random(8), - 'name_first' => 'Unassigned', - 'name_last' => 'Name', - 'root_admin' => false, - ]); - } catch (\Exception $ex) { - throw $ex; - } - } elseif ($server->owner_id === $user->id) { - throw new DisplayException('You cannot add the owner of a server as a subuser.'); - } elseif (Subuser::select('id')->where('user_id', $user->id)->where('server_id', $server->id)->first()) { - throw new DisplayException('A subuser with that email already exists for this server.'); - } - - $uuid = new UuidService; - $subuser = Subuser::create([ - 'user_id' => $user->id, - 'server_id' => $server->id, - 'daemonSecret' => (string) $uuid->generate('servers', 'uuid'), - ]); - - $perms = Permission::listPermissions(true); - $daemonPermissions = $this->coreDaemonPermissions; - - foreach ($data['permissions'] as $permission) { - if (array_key_exists($permission, $perms)) { - // Build the daemon permissions array for sending. - if (! is_null($perms[$permission])) { - array_push($daemonPermissions, $perms[$permission]); - } - - Permission::create([ - 'subuser_id' => $subuser->id, - 'permission' => $permission, - ]); - } - } - - // Contact Daemon - // We contact even if they don't have any daemon permissions to overwrite - // if they did have them previously. - - $server->node->guzzleClient([ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $server->node->daemonSecret, - ])->request('PATCH', '/server', [ - 'json' => [ - 'keys' => [ - $subuser->daemonSecret => $daemonPermissions, - ], - ], - ]); - - DB::commit(); - - return $subuser; - } catch (TransferException $ex) { - DB::rollBack(); - throw new DisplayException('There was an error attempting to connect to the daemon to add this user.', $ex); - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } - - return false; - } - - /** - * Revokes a users permissions on a server. - * - * @param int $id - * @return void - * - * @throws \Pterodactyl\Exceptions\DisplayException - */ - public function delete($id) - { - $subuser = Subuser::with('server.node')->findOrFail($id); - $server = $subuser->server; - - DB::beginTransaction(); - - try { - $server->node->guzzleClient([ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $server->node->daemonSecret, - ])->request('PATCH', '/server', [ - 'json' => [ - 'keys' => [ - $subuser->daemonSecret => [], - ], - ], - ]); - - foreach ($subuser->permissions as &$permission) { - $permission->delete(); - } - $subuser->delete(); - DB::commit(); - } catch (TransferException $ex) { - DB::rollBack(); - throw new DisplayException('There was an error attempting to connect to the daemon to delete this subuser.', $ex); - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } - } - - /** - * Updates permissions for a given subuser. - * - * @param int $id - * @param array $data - * @return void - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function update($id, array $data) - { - $validator = Validator::make($data, [ - 'permissions' => 'required|array', - 'user' => 'required|exists:users,id', - 'server' => 'required|exists:servers,id', - ]); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->all())); - } - - $subuser = Subuser::with('server.node')->findOrFail($id); - $server = $subuser->server; - - DB::beginTransaction(); - - try { - foreach ($subuser->permissions as &$permission) { - $permission->delete(); - } - - $perms = Permission::listPermissions(true); - $daemonPermissions = $this->coreDaemonPermissions; - - foreach ($data['permissions'] as $permission) { - if (array_key_exists($permission, $perms)) { - // Build the daemon permissions array for sending. - if (! is_null($perms[$permission])) { - array_push($daemonPermissions, $perms[$permission]); - } - Permission::create([ - 'subuser_id' => $subuser->id, - 'permission' => $permission, - ]); - } - } - - // Contact Daemon - // We contact even if they don't have any daemon permissions to overwrite - // if they did have them previously. - $server->node->guzzleClient([ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $server->node->daemonSecret, - ])->request('PATCH', '/server', [ - 'json' => [ - 'keys' => [ - $subuser->daemonSecret => $daemonPermissions, - ], - ], - ]); - - DB::commit(); - } catch (TransferException $ex) { - DB::rollBack(); - throw new DisplayException('There was an error attempting to connect to the daemon to update permissions.', $ex); - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } - } -} diff --git a/app/Repositories/TaskRepository.php b/app/Repositories/TaskRepository.php deleted file mode 100644 index 24290e253..000000000 --- a/app/Repositories/TaskRepository.php +++ /dev/null @@ -1,163 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Repositories; - -use Cron; -use Validator; -use Pterodactyl\Models\Task; -use Pterodactyl\Models\User; -use Pterodactyl\Models\Server; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\DisplayValidationException; - -class TaskRepository -{ - /** - * The default values to use for new tasks. - * - * @var array - */ - protected $defaults = [ - 'year' => '*', - 'day_of_week' => '*', - 'month' => '*', - 'day_of_month' => '*', - 'hour' => '*', - 'minute' => '*/30', - ]; - - /** - * Task action types. - * - * @var array - */ - protected $actions = [ - 'command', - 'power', - ]; - - /** - * Deletes a given task. - * - * @param int $id - * @return bool - */ - public function delete($id) - { - $task = Task::findOrFail($id); - $task->delete(); - } - - /** - * Toggles a task active or inactive. - * - * @param int $id - * @return bool - */ - public function toggle($id) - { - $task = Task::findOrFail($id); - - $task->active = ! $task->active; - $task->queued = false; - $task->save(); - - return $task->active; - } - - /** - * Create a new scheduled task for a given server. - * - * @param int $server - * @param int $user - * @param array $data - * @return \Pterodactyl\Models\Task - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function create($server, $user, $data) - { - $server = Server::findOrFail($server); - $user = User::findOrFail($user); - - $validator = Validator::make($data, [ - 'action' => 'string|required', - 'data' => 'string|required', - 'year' => 'string|sometimes', - 'day_of_week' => 'string|sometimes', - 'month' => 'string|sometimes', - 'day_of_month' => 'string|sometimes', - 'hour' => 'string|sometimes', - 'minute' => 'string|sometimes', - ]); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - if (! in_array($data['action'], $this->actions)) { - throw new DisplayException('The action provided is not valid.'); - } - - $cron = $this->defaults; - foreach ($this->defaults as $setting => $value) { - if (array_key_exists($setting, $data) && ! is_null($data[$setting]) && $data[$setting] !== '') { - $cron[$setting] = $data[$setting]; - } - } - - // Check that is this a valid Cron Entry - try { - $buildCron = Cron::factory(sprintf('%s %s %s %s %s %s', - $cron['minute'], - $cron['hour'], - $cron['day_of_month'], - $cron['month'], - $cron['day_of_week'], - $cron['year'] - )); - } catch (\Exception $ex) { - throw $ex; - } - - return Task::create([ - 'user_id' => $user->id, - 'server_id' => $server->id, - 'active' => 1, - 'action' => $data['action'], - 'data' => $data['data'], - 'queued' => 0, - 'year' => $cron['year'], - 'day_of_week' => $cron['day_of_week'], - 'month' => $cron['month'], - 'day_of_month' => $cron['day_of_month'], - 'hour' => $cron['hour'], - 'minute' => $cron['minute'], - 'last_run' => null, - 'next_run' => $buildCron->getNextRunDate(), - ]); - } -} diff --git a/app/Repositories/UserRepository.php b/app/Repositories/UserRepository.php deleted file mode 100644 index 1608afe9c..000000000 --- a/app/Repositories/UserRepository.php +++ /dev/null @@ -1,182 +0,0 @@ - - * Some Modifications (c) 2015 Dylan Seidt . - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Repositories; - -use DB; -use Auth; -use Hash; -use Settings; -use Validator; -use Pterodactyl\Models; -use Pterodactyl\Services\UuidService; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\DisplayValidationException; - -class UserRepository -{ - /** - * Creates a user on the panel. Returns the created user's ID. - * - * @param array $data - * @return \Pterodactyl\Models\User - * - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function create(array $data) - { - $validator = Validator::make($data, [ - 'email' => 'required|email|unique:users,email', - 'username' => 'required|string|between:1,255|unique:users,username|' . Models\User::USERNAME_RULES, - 'name_first' => 'required|string|between:1,255', - 'name_last' => 'required|string|between:1,255', - 'password' => 'sometimes|nullable|' . Models\User::PASSWORD_RULES, - 'root_admin' => 'required|boolean', - 'custom_id' => 'sometimes|nullable|unique:users,id', - ]); - - // Run validator, throw catchable and displayable exception if it fails. - // Exception includes a JSON result of failed validation rules. - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - DB::beginTransaction(); - - try { - $user = new Models\User; - $uuid = new UuidService; - - // Support for API Services - if (isset($data['custom_id']) && ! is_null($data['custom_id'])) { - $user->id = $token; - } - - // UUIDs are not mass-fillable. - $user->uuid = $uuid->generate('users', 'uuid'); - - $user->fill([ - 'email' => $data['email'], - 'username' => $data['username'], - 'name_first' => $data['name_first'], - 'name_last' => $data['name_last'], - 'password' => (empty($data['password'])) ? 'unset' : Hash::make($data['password']), - 'root_admin' => $data['root_admin'], - 'language' => Settings::get('default_language', 'en'), - ]); - $user->save(); - - DB::commit(); - - return $user; - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } - } - - /** - * Updates a user on the panel. - * - * @param int $id - * @param array $data - * @return \Pterodactyl\Models\User - * - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function update($id, array $data) - { - $user = Models\User::findOrFail($id); - - $validator = Validator::make($data, [ - 'email' => 'sometimes|required|email|unique:users,email,' . $id, - 'username' => 'sometimes|required|string|between:1,255|unique:users,username,' . $user->id . '|' . Models\User::USERNAME_RULES, - 'name_first' => 'sometimes|required|string|between:1,255', - 'name_last' => 'sometimes|required|string|between:1,255', - 'password' => 'sometimes|nullable|' . Models\User::PASSWORD_RULES, - 'root_admin' => 'sometimes|required|boolean', - 'language' => 'sometimes|required|string|min:1|max:5', - 'use_totp' => 'sometimes|required|boolean', - 'totp_secret' => 'sometimes|required|size:16', - ]); - - // Run validator, throw catchable and displayable exception if it fails. - // Exception includes a JSON result of failed validation rules. - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - // The password and root_admin fields are not mass assignable. - if (! empty($data['password'])) { - $data['password'] = Hash::make($data['password']); - } else { - unset($data['password']); - } - - $user->fill($data)->save(); - - return $user; - } - - /** - * Deletes a user on the panel. - * - * @param int $id - * @return void - * @todo Move user self-deletion checking to the controller, rather than the repository. - * - * @throws \Pterodactyl\Exceptions\DisplayException - */ - public function delete($id) - { - $user = Models\User::findOrFail($id); - - if (Models\Server::where('owner_id', $id)->count() > 0) { - throw new DisplayException('Cannot delete a user with active servers attached to thier account.'); - } - - if (! is_null(Auth::user()) && (int) Auth::user()->id === (int) $id) { - throw new DisplayException('Cannot delete your own account.'); - } - - DB::beginTransaction(); - - try { - foreach (Models\Subuser::with('permissions')->where('user_id', $id)->get() as &$subuser) { - foreach ($subuser->permissions as &$permission) { - $permission->delete(); - } - - $subuser->delete(); - } - - $user->delete(); - DB::commit(); - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } - } -} diff --git a/app/Repositories/VariableRepository.php b/app/Repositories/VariableRepository.php deleted file mode 100644 index e0ae408a7..000000000 --- a/app/Repositories/VariableRepository.php +++ /dev/null @@ -1,175 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Repositories; - -use DB; -use Validator; -use Pterodactyl\Models\ServiceOption; -use Pterodactyl\Models\ServiceVariable; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Exceptions\DisplayValidationException; - -class VariableRepository -{ - /** - * Create a new service variable. - * - * @param int $option - * @param array $data - * @return \Pterodactyl\Models\ServiceVariable - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function create($option, array $data) - { - $option = ServiceOption::select('id')->findOrFail($option); - - // If there is not a rules present let's populate it with the default/placeholder value. - if (! array_key_exists('rules', $data) || empty($data['rules'])) { - $data['rules'] = 'required|string|max:20'; - } - - $validator = Validator::make($data, [ - 'name' => 'required|string|min:1|max:255', - 'description' => 'sometimes|nullable|string', - 'env_variable' => 'required|regex:/^[\w]{1,255}$/', - 'default_value' => 'string', - 'options' => 'sometimes|required|array', - 'rules' => 'bail|required|string', - ]); - - // Ensure the default value is allowed by the rules provided. - $validator->sometimes('default_value', $data['rules'] ?? null, function ($input) { - return $input->default_value; - }); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - if (in_array($data['env_variable'], ServiceVariable::reservedNames())) { - throw new DisplayException('The environment variable name provided is a reserved keyword for the daemon.'); - } - - $search = ServiceVariable::where('env_variable', $data['env_variable'])->where('option_id', $option->id); - if ($search->first()) { - throw new DisplayException('The envionment variable name assigned to this variable must be unique for this service option.'); - } - - if (! isset($data['options']) || ! is_array($data['options'])) { - $data['options'] = []; - } - - $data['option_id'] = $option->id; - $data['user_viewable'] = (in_array('user_viewable', $data['options'])); - $data['user_editable'] = (in_array('user_editable', $data['options'])); - - // Remove field that isn't used. - unset($data['options']); - - return ServiceVariable::create($data); - } - - /** - * Deletes a specified option variable as well as all server - * variables currently assigned. - * - * @param int $id - * @return void - */ - public function delete($id) - { - $variable = ServiceVariable::with('serverVariable')->findOrFail($id); - - DB::transaction(function () use ($variable) { - foreach ($variable->serverVariable as $v) { - $v->delete(); - } - - $variable->delete(); - }); - } - - /** - * Updates a given service variable. - * - * @param int $id - * @param array $data - * @return \Pterodactyl\Models\ServiceVariable - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\DisplayValidationException - */ - public function update($id, array $data) - { - $variable = ServiceVariable::findOrFail($id); - - $validator = Validator::make($data, [ - 'name' => 'required|string|min:1|max:255', - 'description' => 'nullable|string', - 'env_variable' => 'required|regex:/^[\w]{1,255}$/', - 'rules' => 'bail|required|string', - 'options' => 'sometimes|required|array', - ]); - - // Ensure the default value is allowed by the rules provided. - $rules = (isset($data['rules'])) ? $data['rules'] : $variable->rules; - $validator->sometimes('default_value', $rules, function ($input) { - return $input->default_value; - }); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode($validator->errors())); - } - - if (isset($data['env_variable'])) { - if (in_array($data['env_variable'], ServiceVariable::reservedNames())) { - throw new DisplayException('The environment variable name provided is a reserved keyword for the daemon.'); - } - - $search = ServiceVariable::where('env_variable', $data['env_variable']) - ->where('option_id', $variable->option_id) - ->where('id', '!=', $variable->id); - if ($search->first()) { - throw new DisplayException('The envionment variable name assigned to this variable must be unique for this service option.'); - } - } - - if (! isset($data['options']) || ! is_array($data['options'])) { - $data['options'] = []; - } - - $data['user_viewable'] = (in_array('user_viewable', $data['options'])); - $data['user_editable'] = (in_array('user_editable', $data['options'])); - - // Remove field that isn't used. - unset($data['options']); - - $variable->fill($data)->save(); - - return $variable; - } -} diff --git a/app/Services/Allocations/AssignmentService.php b/app/Services/Allocations/AssignmentService.php new file mode 100644 index 000000000..9ff713c8f --- /dev/null +++ b/app/Services/Allocations/AssignmentService.php @@ -0,0 +1,125 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Allocations; + +use IPTools\Network; +use Pterodactyl\Models\Node; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; + +class AssignmentService +{ + const CIDR_MAX_BITS = 27; + const CIDR_MIN_BITS = 32; + const PORT_RANGE_LIMIT = 1000; + const PORT_RANGE_REGEX = '/^(\d{1,5})-(\d{1,5})$/'; + + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected $connection; + + /** + * @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface + */ + protected $repository; + + /** + * AssignmentService constructor. + * + * @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $repository + * @param \Illuminate\Database\ConnectionInterface $connection + */ + public function __construct( + AllocationRepositoryInterface $repository, + ConnectionInterface $connection + ) { + $this->connection = $connection; + $this->repository = $repository; + } + + /** + * Insert allocations into the database and link them to a specific node. + * + * @param int|\Pterodactyl\Models\Node $node + * @param array $data + * + * @throws \Pterodactyl\Exceptions\DisplayException + */ + public function handle($node, array $data) + { + if ($node instanceof Node) { + $node = $node->id; + } + + $explode = explode('/', $data['allocation_ip']); + if (count($explode) !== 1) { + if (! ctype_digit($explode[1]) || ($explode[1] > self::CIDR_MIN_BITS || $explode[1] < self::CIDR_MAX_BITS)) { + throw new DisplayException(trans('exceptions.allocations.cidr_out_of_range')); + } + } + + $this->connection->beginTransaction(); + foreach (Network::parse(gethostbyname($data['allocation_ip'])) as $ip) { + foreach ($data['allocation_ports'] as $port) { + if (! ctype_digit($port) && ! preg_match(self::PORT_RANGE_REGEX, $port)) { + throw new DisplayException(trans('exceptions.allocations.invalid_mapping', ['port' => $port])); + } + + $insertData = []; + if (preg_match(self::PORT_RANGE_REGEX, $port, $matches)) { + $block = range($matches[1], $matches[2]); + + if (count($block) > self::PORT_RANGE_LIMIT) { + throw new DisplayException(trans('exceptions.allocations.too_many_ports')); + } + + foreach ($block as $unit) { + $insertData[] = [ + 'node_id' => $node, + 'ip' => $ip->__toString(), + 'port' => (int) $unit, + 'ip_alias' => array_get($data, 'allocation_alias'), + 'server_id' => null, + ]; + } + } else { + $insertData[] = [ + 'node_id' => $node, + 'ip' => $ip->__toString(), + 'port' => (int) $port, + 'ip_alias' => array_get($data, 'allocation_alias'), + 'server_id' => null, + ]; + } + + $this->repository->insertIgnore($insertData); + } + } + + $this->connection->commit(); + } +} diff --git a/app/Services/Api/KeyCreationService.php b/app/Services/Api/KeyCreationService.php new file mode 100644 index 000000000..4beaf3a4d --- /dev/null +++ b/app/Services/Api/KeyCreationService.php @@ -0,0 +1,135 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Api; + +use Illuminate\Database\ConnectionInterface; +use Illuminate\Contracts\Encryption\Encrypter; +use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface; + +class KeyCreationService +{ + const PUB_CRYPTO_BYTES = 8; + const PRIV_CRYPTO_BYTES = 32; + + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected $connection; + + /** + * @var \Illuminate\Contracts\Encryption\Encrypter + */ + protected $encrypter; + + /** + * @var \Pterodactyl\Services\Api\PermissionService + */ + protected $permissionService; + + /** + * @var \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface + */ + protected $repository; + + /** + * ApiKeyService constructor. + * + * @param \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface $repository + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter + * @param \Pterodactyl\Services\Api\PermissionService $permissionService + */ + public function __construct( + ApiKeyRepositoryInterface $repository, + ConnectionInterface $connection, + Encrypter $encrypter, + PermissionService $permissionService + ) { + $this->repository = $repository; + $this->connection = $connection; + $this->encrypter = $encrypter; + $this->permissionService = $permissionService; + } + + /** + * Create a new API Key on the system with the given permissions. + * + * @param array $data + * @param array $permissions + * @param array $administrative + * @return string + * + * @throws \Exception + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function handle(array $data, array $permissions, array $administrative = []) + { + $publicKey = bin2hex(random_bytes(self::PUB_CRYPTO_BYTES)); + $secretKey = bin2hex(random_bytes(self::PRIV_CRYPTO_BYTES)); + + // Start a Transaction + $this->connection->beginTransaction(); + + $data = array_merge($data, [ + 'public' => $publicKey, + 'secret' => $this->encrypter->encrypt($secretKey), + ]); + + $instance = $this->repository->create($data, true, true); + $nodes = $this->permissionService->getPermissions(); + + foreach ($permissions as $permission) { + @list($block, $search) = explode('-', $permission); + + if ( + (empty($block) || empty($search)) || + ! array_key_exists($block, $nodes['_user']) || + ! in_array($search, $nodes['_user'][$block]) + ) { + continue; + } + + $this->permissionService->create($instance->id, sprintf('user.%s', $permission)); + } + + foreach ($administrative as $permission) { + @list($block, $search) = explode('-', $permission); + + if ( + (empty($block) || empty($search)) || + ! array_key_exists($block, $nodes) || + ! in_array($search, $nodes[$block]) + ) { + continue; + } + + $this->permissionService->create($instance->id, $permission); + } + + $this->connection->commit(); + + return $secretKey; + } +} diff --git a/app/Services/Api/PermissionService.php b/app/Services/Api/PermissionService.php new file mode 100644 index 000000000..050599794 --- /dev/null +++ b/app/Services/Api/PermissionService.php @@ -0,0 +1,73 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Api; + +use Pterodactyl\Contracts\Repository\ApiPermissionRepositoryInterface; + +class PermissionService +{ + /** + * @var \Pterodactyl\Contracts\Repository\ApiPermissionRepositoryInterface + */ + protected $repository; + + /** + * ApiPermissionService constructor. + * + * @param \Pterodactyl\Contracts\Repository\ApiPermissionRepositoryInterface $repository + */ + public function __construct(ApiPermissionRepositoryInterface $repository) + { + $this->repository = $repository; + } + + /** + * Store a permission key in the database. + * + * @param string $key + * @param string $permission + * @return bool + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function create($key, $permission) + { + // @todo handle an array of permissions to do a mass assignment? + return $this->repository->withoutFresh()->create([ + 'key_id' => $key, + 'permission' => $permission, + ]); + } + + /** + * Return all of the permissions available for an API Key. + * + * @return array + */ + public function getPermissions() + { + return $this->repository->getModel()::CONST_PERMISSIONS; + } +} diff --git a/app/Services/UuidService.php b/app/Services/Components/UuidService.php similarity index 89% rename from app/Services/UuidService.php rename to app/Services/Components/UuidService.php index 2b043731a..7f5b0535a 100644 --- a/app/Services/UuidService.php +++ b/app/Services/Components/UuidService.php @@ -22,7 +22,7 @@ * SOFTWARE. */ -namespace Pterodactyl\Services; +namespace Pterodactyl\Services\Components; use DB; use Uuid; @@ -33,10 +33,11 @@ class UuidService * Generate a unique UUID validating against specified table and column. * Defaults to `users.uuid`. * - * @param string $table - * @param string $field - * @param int $type + * @param string $table + * @param string $field + * @param int $type * @return string + * @deprecated */ public function generate($table = 'users', $field = 'uuid', $type = 4) { @@ -54,10 +55,11 @@ class UuidService /** * Generates a ShortUUID code which is 8 characters long and is used for identifying servers in the system. * - * @param string $table - * @param string $field - * @param null|string $attachedUuid + * @param string $table + * @param string $field + * @param null|string $attachedUuid * @return string + * @deprecated */ public function generateShort($table = 'servers', $field = 'uuidShort', $attachedUuid = null) { diff --git a/app/Services/Database/DatabaseHostService.php b/app/Services/Database/DatabaseHostService.php new file mode 100644 index 000000000..10e45e53c --- /dev/null +++ b/app/Services/Database/DatabaseHostService.php @@ -0,0 +1,163 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Database; + +use Illuminate\Database\DatabaseManager; +use Pterodactyl\Exceptions\DisplayException; +use Illuminate\Contracts\Encryption\Encrypter; +use Pterodactyl\Extensions\DynamicDatabaseConnection; +use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; +use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface; + +class DatabaseHostService +{ + /** + * @var \Illuminate\Database\DatabaseManager + */ + protected $database; + + /** + * @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface + */ + protected $databaseRepository; + + /** + * @var \Pterodactyl\Extensions\DynamicDatabaseConnection + */ + protected $dynamic; + + /** + * @var \Illuminate\Contracts\Encryption\Encrypter + */ + protected $encrypter; + + /** + * @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface + */ + protected $repository; + + /** + * DatabaseHostService constructor. + * + * @param \Illuminate\Database\DatabaseManager $database + * @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $databaseRepository + * @param \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface $repository + * @param \Pterodactyl\Extensions\DynamicDatabaseConnection $dynamic + * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter + */ + public function __construct( + DatabaseManager $database, + DatabaseRepositoryInterface $databaseRepository, + DatabaseHostRepositoryInterface $repository, + DynamicDatabaseConnection $dynamic, + Encrypter $encrypter + ) { + $this->database = $database; + $this->databaseRepository = $databaseRepository; + $this->dynamic = $dynamic; + $this->encrypter = $encrypter; + $this->repository = $repository; + } + + /** + * Create a new database host and persist it to the database. + * + * @param array $data + * @return \Pterodactyl\Models\DatabaseHost + * + * @throws \Throwable + * @throws \PDOException + */ + public function create(array $data) + { + $this->database->beginTransaction(); + + $host = $this->repository->create([ + 'password' => $this->encrypter->encrypt(array_get($data, 'password')), + 'name' => array_get($data, 'name'), + 'host' => array_get($data, 'host'), + 'port' => array_get($data, 'port'), + 'username' => array_get($data, 'username'), + 'max_databases' => null, + 'node_id' => array_get($data, 'node_id'), + ]); + + // Check Access + $this->dynamic->set('dynamic', $host); + $this->database->connection('dynamic')->select('SELECT 1 FROM dual'); + + $this->database->commit(); + + return $host; + } + + /** + * Update a database host and persist to the database. + * + * @param int $id + * @param array $data + * @return mixed + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function update($id, array $data) + { + $this->database->beginTransaction(); + + if (! empty(array_get($data, 'password'))) { + $data['password'] = $this->encrypter->encrypt($data['password']); + } else { + unset($data['password']); + } + + $host = $this->repository->update($id, $data); + + $this->dynamic->set('dynamic', $host); + $this->database->connection('dynamic')->select('SELECT 1 FROM dual'); + + $this->database->commit(); + + return $host; + } + + /** + * Delete a database host if it has no active databases attached to it. + * + * @param int $id + * @return bool|null + * + * @throws \Pterodactyl\Exceptions\DisplayException + */ + public function delete($id) + { + $count = $this->databaseRepository->findCountWhere([['database_host_id', '=', $id]]); + if ($count > 0) { + throw new DisplayException(trans('exceptions.databases.delete_has_databases')); + } + + return $this->repository->delete($id); + } +} diff --git a/app/Services/Database/DatabaseManagementService.php b/app/Services/Database/DatabaseManagementService.php new file mode 100644 index 000000000..beecfa9a4 --- /dev/null +++ b/app/Services/Database/DatabaseManagementService.php @@ -0,0 +1,190 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Database; + +use Illuminate\Database\DatabaseManager; +use Illuminate\Contracts\Encryption\Encrypter; +use Pterodactyl\Extensions\DynamicDatabaseConnection; +use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; + +class DatabaseManagementService +{ + /** + * @var \Illuminate\Database\DatabaseManager + */ + protected $database; + + /** + * @var \Pterodactyl\Extensions\DynamicDatabaseConnection + */ + protected $dynamic; + + /** + * @var \Illuminate\Contracts\Encryption\Encrypter + */ + protected $encrypter; + + /** + * @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface + */ + protected $repository; + + /** + * CreationService constructor. + * + * @param \Illuminate\Database\DatabaseManager $database + * @param \Pterodactyl\Extensions\DynamicDatabaseConnection $dynamic + * @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $repository + * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter + */ + public function __construct( + DatabaseManager $database, + DynamicDatabaseConnection $dynamic, + DatabaseRepositoryInterface $repository, + Encrypter $encrypter + ) { + $this->database = $database; + $this->dynamic = $dynamic; + $this->encrypter = $encrypter; + $this->repository = $repository; + } + + /** + * Create a new database that is linked to a specific host. + * + * @param int $server + * @param array $data + * @return \Illuminate\Database\Eloquent\Model + * + * @throws \Exception + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function create($server, array $data) + { + $data['server_id'] = $server; + $data['database'] = sprintf('d%d_%s', $server, $data['database']); + $data['username'] = sprintf('u%d_%s', $server, str_random(10)); + $data['password'] = $this->encrypter->encrypt(str_random(16)); + + $this->database->beginTransaction(); + try { + $database = $this->repository->createIfNotExists($data); + $this->dynamic->set('dynamic', $data['database_host_id']); + + $this->repository->createDatabase($database->database, 'dynamic'); + $this->repository->createUser( + $database->username, + $database->remote, + $this->encrypter->decrypt($database->password), + 'dynamic' + ); + $this->repository->assignUserToDatabase( + $database->database, + $database->username, + $database->remote, + 'dynamic' + ); + $this->repository->flush('dynamic'); + + $this->database->commit(); + } catch (\Exception $ex) { + try { + if (isset($database)) { + $this->repository->dropDatabase($database->database, 'dynamic'); + $this->repository->dropUser($database->username, $database->remote, 'dynamic'); + $this->repository->flush('dynamic'); + } + } catch (\Exception $exTwo) { + // ignore an exception + } + + $this->database->rollBack(); + throw $ex; + } + + return $database; + } + + /** + * Change the password for a specific user and database combination. + * + * @param int $id + * @param string $password + * @return bool + * + * @throws \Exception + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function changePassword($id, $password) + { + $database = $this->repository->find($id); + $this->dynamic->set('dynamic', $database->database_host_id); + + $this->database->beginTransaction(); + + try { + $updated = $this->repository->withoutFresh()->update($id, [ + 'password' => $this->encrypter->encrypt($password), + ]); + + $this->repository->dropUser($database->username, $database->remote, 'dynamic'); + $this->repository->createUser($database->username, $database->remote, $password, 'dynamic'); + $this->repository->assignUserToDatabase( + $database->database, + $database->username, + $database->remote, + 'dynamic' + ); + $this->repository->flush('dynamic'); + + $this->database->commit(); + } catch (\Exception $ex) { + $this->database->rollBack(); + throw $ex; + } + + return $updated; + } + + /** + * Delete a database from the given host server. + * + * @param int $id + * @return bool|null + */ + public function delete($id) + { + $database = $this->repository->find($id); + $this->dynamic->set('dynamic', $database->database_host_id); + + $this->repository->dropDatabase($database->database, 'dynamic'); + $this->repository->dropUser($database->username, $database->remote, 'dynamic'); + $this->repository->flush('dynamic'); + + return $this->repository->delete($id); + } +} diff --git a/app/Services/DeploymentService.php b/app/Services/DeploymentService.php deleted file mode 100644 index f25c04e93..000000000 --- a/app/Services/DeploymentService.php +++ /dev/null @@ -1,258 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Services; - -use DB; -use Pterodactyl\Models\Node; -use Pterodactyl\Models\Server; -use Pterodactyl\Models\Location; -use Pterodactyl\Exceptions\AutoDeploymentException; - -class DeploymentService -{ - /** - * Eloquent model representing the allocation to use. - * - * @var \Pterodactyl\Models\Allocation - */ - protected $allocation; - - /** - * Amount of disk to be used by the server. - * - * @var int - */ - protected $disk; - - /** - * Amount of memory to be used by the sever. - * - * @var int - */ - protected $memory; - - /** - * Eloquent model representing the location to use. - * - * @var \Pterodactyl\Models\Location - */ - protected $location; - - /** - * Eloquent model representing the node to use. - * - * @var \Pterodactyl\Models\Node - */ - protected $node; - - /** - * Set the location to use when auto-deploying. - * - * @param int|\Pterodactyl\Models\Location $location - * @return void - */ - public function setLocation($location) - { - $this->location = ($location instanceof Location) ? $location : Location::with('nodes')->findOrFail($location); - if (! $this->location->relationLoaded('nodes')) { - $this->location->load('nodes'); - } - - if (count($this->location->nodes) < 1) { - throw new AutoDeploymentException('The location provided does not contain any nodes and cannot be used.'); - } - - return $this; - } - - /** - * Set the node to use when auto-deploying. - * - * @param int|\Pterodactyl\Models\Node $node - * @return void - */ - public function setNode($node) - { - $this->node = ($node instanceof Node) ? $node : Node::findOrFail($node); - if (! $this->node->relationLoaded('allocations')) { - $this->node->load('allocations'); - } - - $this->setLocation($this->node->location); - - return $this; - } - - /** - * Set the amount of disk space to be used by the new server. - * - * @param int $disk - * @return void - */ - public function setDisk(int $disk) - { - $this->disk = $disk; - - return $this; - } - - /** - * Set the amount of memory to be used by the new server. - * - * @param int $memory - * @return void - */ - public function setMemory(int $memory) - { - $this->memory = $memory; - - return $this; - } - - /** - * Return a random location model. - * - * @param array $exclude - * @return void; - */ - protected function findLocation(array $exclude = []) - { - $location = Location::with('nodes')->whereNotIn('id', $exclude)->inRandomOrder()->first(); - - if (! $location) { - throw new AutoDeploymentException('Unable to locate a suitable location to select a node from.'); - } - - if (count($location->nodes) < 1) { - return $this->findLocation(array_merge($exclude, [$location->id])); - } - - $this->setLocation($location); - } - - /** - * Return a model instance of a random node. - * - * @return void; - */ - protected function findNode(array $exclude = []) - { - if (! $this->location) { - $this->setLocation($this->findLocation()); - } - - $select = $this->location->nodes->whereNotIn('id', $exclude); - if (count($select) < 1) { - throw new AutoDeploymentException('Unable to find a suitable node within the assigned location with enough space.'); - } - - // Check usage, select new node if necessary - $this->setNode($select->random()); - if (! $this->checkNodeUsage()) { - return $this->findNode(array_merge($exclude, [$this->node()->id])); - } - } - - /** - * Checks that a node's allocation limits will not be passed - * with the assigned limits. - * - * @return bool - */ - protected function checkNodeUsage() - { - if (! $this->disk && ! $this->memory) { - return true; - } - - $totals = Server::select(DB::raw('SUM(memory) as memory, SUM(disk) as disk'))->where('node_id', $this->node()->id)->first(); - - if ($this->memory) { - $limit = ($this->node()->memory * (1 + ($this->node()->memory_overallocate / 100))); - - if (($totals->memory + $this->memory) > $limit) { - return false; - } - } - - if ($this->disk) { - $limit = ($this->node()->disk * (1 + ($this->node()->disk_overallocate / 100))); - - if (($totals->disk + $this->disk) > $limit) { - return false; - } - } - - return true; - } - - /** - * Return the assigned node for this auto-deployment. - * - * @return \Pterodactyl\Models\Node - */ - public function node() - { - return $this->node; - } - - /** - * Return the assigned location for this auto-deployment. - * - * @return \Pterodactyl\Models\Location - */ - public function location() - { - return $this->location; - } - - /** - * Return the assigned location for this auto-deployment. - * - * @return \Pterodactyl\Models\Allocation - */ - public function allocation() - { - return $this->allocation; - } - - /** - * Select and return the node to be used by the auto-deployment system. - * - * @return void - */ - public function select() - { - if (! $this->node) { - $this->findNode(); - } - - // Set the Allocation - $this->allocation = $this->node()->allocations->where('server_id', null)->random(); - if (! $this->allocation) { - throw new AutoDeploymentException('Unable to find a suitable allocation to assign to this server.'); - } - } -} diff --git a/app/Services/Helpers/ApiAllowedIpsValidatorService.php b/app/Services/Helpers/ApiAllowedIpsValidatorService.php new file mode 100644 index 000000000..2b420f9c6 --- /dev/null +++ b/app/Services/Helpers/ApiAllowedIpsValidatorService.php @@ -0,0 +1,23 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ diff --git a/app/Services/Helpers/SoftwareVersionService.php b/app/Services/Helpers/SoftwareVersionService.php new file mode 100644 index 000000000..15b4445f9 --- /dev/null +++ b/app/Services/Helpers/SoftwareVersionService.php @@ -0,0 +1,150 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Helpers; + +use stdClass; +use Exception; +use GuzzleHttp\Client; +use Illuminate\Contracts\Cache\Repository as CacheRepository; +use Illuminate\Contracts\Config\Repository as ConfigRepository; +use Pterodactyl\Exceptions\Service\Helper\CdnVersionFetchingException; + +class SoftwareVersionService +{ + const VERSION_CACHE_KEY = 'pterodactyl:versions'; + + /** + * @var \Illuminate\Contracts\Cache\Repository + */ + protected $cache; + + /** + * @var \GuzzleHttp\Client + */ + protected $client; + + /** + * @var \Illuminate\Contracts\Config\Repository + */ + protected $config; + + /** + * SoftwareVersionService constructor. + * + * @param \Illuminate\Contracts\Cache\Repository $cache + * @param \GuzzleHttp\Client $client + * @param \Illuminate\Contracts\Config\Repository $config + */ + public function __construct( + CacheRepository $cache, + Client $client, + ConfigRepository $config + ) { + $this->cache = $cache; + $this->client = $client; + $this->config = $config; + + $this->cacheVersionData(); + } + + /** + * Get the latest version of the panel from the CDN servers. + * + * @return string + */ + public function getPanel() + { + return object_get($this->cache->get(self::VERSION_CACHE_KEY), 'panel', 'error'); + } + + /** + * Get the latest version of the daemon from the CDN servers. + * + * @return string + */ + public function getDaemon() + { + return object_get($this->cache->get(self::VERSION_CACHE_KEY), 'daemon', 'error'); + } + + /** + * Get the URL to the discord server. + * + * @return string + */ + public function getDiscord() + { + return object_get($this->cache->get(self::VERSION_CACHE_KEY), 'discord', 'https://pterodactyl.io/discord'); + } + + /** + * Determine if the current version of the panel is the latest. + * + * @return bool + */ + public function isLatestPanel() + { + if ($this->config->get('app.version') === 'canary') { + return true; + } + + return version_compare($this->config->get('app.version'), $this->getPanel()) >= 0; + } + + /** + * Determine if a passed daemon version string is the latest. + * + * @param string $version + * @return bool + */ + public function isLatestDaemon($version) + { + if ($version === '0.0.0-canary') { + return true; + } + + return version_compare($version, $this->getDaemon()) >= 0; + } + + /** + * Keeps the versioning cache up-to-date with the latest results from the CDN. + */ + protected function cacheVersionData() + { + $this->cache->remember(self::VERSION_CACHE_KEY, $this->config->get('pterodactyl.cdn.cache_time'), function () { + try { + $response = $this->client->request('GET', $this->config->get('pterodactyl.cdn.url')); + + if ($response->getStatusCode() === 200) { + return json_decode($response->getBody()); + } + + throw new CdnVersionFetchingException; + } catch (Exception $exception) { + return new stdClass(); + } + }); + } +} diff --git a/app/Services/Helpers/TemporaryPasswordService.php b/app/Services/Helpers/TemporaryPasswordService.php new file mode 100644 index 000000000..58bff6b76 --- /dev/null +++ b/app/Services/Helpers/TemporaryPasswordService.php @@ -0,0 +1,84 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Helpers; + +use Illuminate\Contracts\Hashing\Hasher; +use Illuminate\Database\DatabaseManager; +use Illuminate\Config\Repository as ConfigRepository; + +class TemporaryPasswordService +{ + const HMAC_ALGO = 'sha256'; + + /** + * @var \Illuminate\Config\Repository + */ + protected $config; + + /** + * @var \Illuminate\Database\DatabaseManager + */ + protected $database; + + /** + * @var \Illuminate\Contracts\Hashing\Hasher + */ + protected $hasher; + + /** + * TemporaryPasswordService constructor. + * + * @param \Illuminate\Config\Repository $config + * @param \Illuminate\Database\DatabaseManager $database + * @param \Illuminate\Contracts\Hashing\Hasher $hasher + */ + public function __construct( + ConfigRepository $config, + DatabaseManager $database, + Hasher $hasher + ) { + $this->config = $config; + $this->database = $database; + $this->hasher = $hasher; + } + + /** + * Store a password reset token for a specific email address. + * + * @param string $email + * @return string + */ + public function generateReset($email) + { + $token = hash_hmac(self::HMAC_ALGO, str_random(40), $this->config->get('app.key')); + + $this->database->table('password_resets')->insert([ + 'email' => $email, + 'token' => $this->hasher->make($token), + ]); + + return $token; + } +} diff --git a/app/Services/LocationService.php b/app/Services/LocationService.php new file mode 100644 index 000000000..fc8880335 --- /dev/null +++ b/app/Services/LocationService.php @@ -0,0 +1,85 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services; + +use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; + +class LocationService +{ + /** + * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface + */ + protected $repository; + + /** + * LocationService constructor. + * + * @param \Pterodactyl\Contracts\Repository\LocationRepositoryInterface $repository + */ + public function __construct(LocationRepositoryInterface $repository) + { + $this->repository = $repository; + } + + /** + * Create the location in the database and return it. + * + * @param array $data + * @return \Pterodactyl\Models\Location + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function create(array $data) + { + return $this->repository->create($data); + } + + /** + * Update location model in the DB. + * + * @param int $id + * @param array $data + * @return \Pterodactyl\Models\Location + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function update($id, array $data) + { + return $this->repository->update($id, $data); + } + + /** + * Delete a model from the DB. + * + * @param int $id + * @return bool + * + * @throws \Pterodactyl\Exceptions\DisplayException + */ + public function delete($id) + { + return $this->repository->deleteIfNoNodes($id); + } +} diff --git a/app/Services/Nodes/NodeCreationService.php b/app/Services/Nodes/NodeCreationService.php new file mode 100644 index 000000000..30f31a29b --- /dev/null +++ b/app/Services/Nodes/NodeCreationService.php @@ -0,0 +1,62 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Nodes; + +use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; + +class NodeCreationService +{ + const DAEMON_SECRET_LENGTH = 18; + + /** + * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface + */ + protected $repository; + + /** + * CreationService constructor. + * + * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $repository + */ + public function __construct(NodeRepositoryInterface $repository) + { + $this->repository = $repository; + } + + /** + * Create a new node on the panel. + * + * @param array $data + * @return \Pterodactyl\Models\Node + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function handle(array $data) + { + $data['daemonSecret'] = bin2hex(random_bytes(self::DAEMON_SECRET_LENGTH)); + + return $this->repository->create($data); + } +} diff --git a/app/Services/Nodes/NodeDeletionService.php b/app/Services/Nodes/NodeDeletionService.php new file mode 100644 index 000000000..26238339b --- /dev/null +++ b/app/Services/Nodes/NodeDeletionService.php @@ -0,0 +1,88 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Nodes; + +use Pterodactyl\Models\Node; +use Illuminate\Contracts\Translation\Translator; +use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; +use Pterodactyl\Exceptions\Service\HasActiveServersException; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; + +class NodeDeletionService +{ + /** + * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $serverRepository; + + /** + * @var \Illuminate\Contracts\Translation\Translator + */ + protected $translator; + + /** + * DeletionService constructor. + * + * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $repository + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository + * @param \Illuminate\Contracts\Translation\Translator $translator + */ + public function __construct( + NodeRepositoryInterface $repository, + ServerRepositoryInterface $serverRepository, + Translator $translator + ) { + $this->repository = $repository; + $this->serverRepository = $serverRepository; + $this->translator = $translator; + } + + /** + * Delete a node from the panel if no servers are attached to it. + * + * @param int|\Pterodactyl\Models\Node $node + * @return bool|null + * + * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException + */ + public function handle($node) + { + if ($node instanceof Node) { + $node = $node->id; + } + + $servers = $this->serverRepository->withColumns('id')->findCountWhere([['node_id', '=', $node]]); + if ($servers > 0) { + throw new HasActiveServersException($this->translator->trans('exceptions.node.servers_attached')); + } + + return $this->repository->delete($node); + } +} diff --git a/app/Services/Nodes/NodeUpdateService.php b/app/Services/Nodes/NodeUpdateService.php new file mode 100644 index 000000000..9fd27332d --- /dev/null +++ b/app/Services/Nodes/NodeUpdateService.php @@ -0,0 +1,105 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Nodes; + +use Illuminate\Log\Writer; +use Pterodactyl\Models\Node; +use GuzzleHttp\Exception\RequestException; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; +use Pterodactyl\Contracts\Repository\Daemon\ConfigurationRepositoryInterface; + +class NodeUpdateService +{ + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\ConfigurationRepositoryInterface + */ + protected $configRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface + */ + protected $repository; + + /** + * @var \Illuminate\Log\Writer + */ + protected $writer; + + /** + * UpdateService constructor. + * + * @param \Pterodactyl\Contracts\Repository\Daemon\ConfigurationRepositoryInterface $configurationRepository + * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $repository + * @param \Illuminate\Log\Writer $writer + */ + public function __construct( + ConfigurationRepositoryInterface $configurationRepository, + NodeRepositoryInterface $repository, + Writer $writer + ) { + $this->configRepository = $configurationRepository; + $this->repository = $repository; + $this->writer = $writer; + } + + /** + * Update the configuration values for a given node on the machine. + * + * @param int|\Pterodactyl\Models\Node $node + * @param array $data + * @return mixed + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle($node, array $data) + { + if (! $node instanceof Node) { + $node = $this->repository->find($node); + } + + if (! is_null(array_get($data, 'reset_secret'))) { + $data['daemonSecret'] = bin2hex(random_bytes(NodeCreationService::DAEMON_SECRET_LENGTH)); + unset($data['reset_secret']); + } + + $updateResponse = $this->repository->withoutFresh()->update($node->id, $data); + + try { + $this->configRepository->setNode($node->id)->setAccessToken($node->daemonSecret)->update(); + } catch (RequestException $exception) { + $response = $exception->getResponse(); + $this->writer->warning($exception); + + throw new DisplayException(trans('exceptions.node.daemon_off_config_updated', [ + 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), + ])); + } + + return $updateResponse; + } +} diff --git a/app/Services/Packs/ExportPackService.php b/app/Services/Packs/ExportPackService.php new file mode 100644 index 000000000..1f5ac7b42 --- /dev/null +++ b/app/Services/Packs/ExportPackService.php @@ -0,0 +1,112 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Packs; + +use ZipArchive; +use Pterodactyl\Models\Pack; +use Pterodactyl\Contracts\Repository\PackRepositoryInterface; +use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory; +use Pterodactyl\Exceptions\Service\Pack\ZipArchiveCreationException; + +class ExportPackService +{ + /** + * @var \ZipArchive + */ + protected $archive; + + /** + * @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface + */ + protected $repository; + + /** + * @var \Illuminate\Contracts\Filesystem\Factory + */ + protected $storage; + + /** + * ExportPackService constructor. + * + * @param \Illuminate\Contracts\Filesystem\Factory $storage + * @param \Pterodactyl\Contracts\Repository\PackRepositoryInterface $repository + * @param \ZipArchive $archive + */ + public function __construct( + FilesystemFactory $storage, + PackRepositoryInterface $repository, + ZipArchive $archive + ) { + $this->archive = $archive; + $this->repository = $repository; + $this->storage = $storage; + } + + /** + * Prepare a pack for export. + * + * @param int|\Pterodactyl\Models\Pack $pack + * @param bool $files + * @return string + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Pack\ZipArchiveCreationException + */ + public function handle($pack, $files = false) + { + if (! $pack instanceof Pack) { + $pack = $this->repository->find($pack); + } + + $json = [ + 'name' => $pack->name, + 'version' => $pack->version, + 'description' => $pack->description, + 'selectable' => $pack->selectable, + 'visible' => $pack->visible, + 'locked' => $pack->locked, + ]; + + $filename = tempnam(sys_get_temp_dir(), 'pterodactyl_'); + if ($files) { + if (! $this->archive->open($filename, $this->archive::CREATE)) { + throw new ZipArchiveCreationException; + } + + foreach ($this->storage->disk()->files('packs/' . $pack->uuid) as $file) { + $this->archive->addFile(storage_path('app/' . $file), basename(storage_path('app/' . $file))); + } + + $this->archive->addFromString('import.json', json_encode($json, JSON_PRETTY_PRINT)); + $this->archive->close(); + } else { + $fp = fopen($filename, 'a+'); + fwrite($fp, json_encode($json, JSON_PRETTY_PRINT)); + fclose($fp); + } + + return $filename; + } +} diff --git a/app/Services/Packs/PackCreationService.php b/app/Services/Packs/PackCreationService.php new file mode 100644 index 000000000..3943d122b --- /dev/null +++ b/app/Services/Packs/PackCreationService.php @@ -0,0 +1,119 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Packs; + +use Ramsey\Uuid\Uuid; +use Illuminate\Http\UploadedFile; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Contracts\Repository\PackRepositoryInterface; +use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory; +use Pterodactyl\Exceptions\Service\Pack\InvalidFileUploadException; +use Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException; + +class PackCreationService +{ + const VALID_UPLOAD_TYPES = [ + 'application/gzip', + 'application/x-gzip', + ]; + + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected $connection; + + /** + * @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface + */ + protected $repository; + + /** + * @var \Illuminate\Contracts\Filesystem\Factory + */ + protected $storage; + + /** + * PackCreationService constructor. + * + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Illuminate\Contracts\Filesystem\Factory $storage + * @param \Pterodactyl\Contracts\Repository\PackRepositoryInterface $repository + */ + public function __construct( + ConnectionInterface $connection, + FilesystemFactory $storage, + PackRepositoryInterface $repository + ) { + $this->connection = $connection; + $this->repository = $repository; + $this->storage = $storage; + } + + /** + * Add a new service pack to the system. + * + * @param array $data + * @param \Illuminate\Http\UploadedFile|null $file + * @return \Pterodactyl\Models\Pack + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException + * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileUploadException + */ + public function handle(array $data, UploadedFile $file = null) + { + if (! is_null($file)) { + if (! $file->isValid()) { + throw new InvalidFileUploadException(trans('exceptions.packs.invalid_upload')); + } + + if (! in_array($file->getMimeType(), self::VALID_UPLOAD_TYPES)) { + throw new InvalidFileMimeTypeException(trans('exceptions.packs.invalid_mime', [ + 'type' => implode(', ', self::VALID_UPLOAD_TYPES), + ])); + } + } + + // Transform values to boolean + $data['selectable'] = isset($data['selectable']); + $data['visible'] = isset($data['visible']); + $data['locked'] = isset($data['locked']); + + $this->connection->beginTransaction(); + $pack = $this->repository->create(array_merge( + ['uuid' => Uuid::uuid4()], + $data + )); + + $this->storage->disk()->makeDirectory('packs/' . $pack->uuid); + if (! is_null($file)) { + $file->storeAs('packs/' . $pack->uuid, 'archive.tar.gz'); + } + + $this->connection->commit(); + + return $pack; + } +} diff --git a/app/Services/Packs/PackDeletionService.php b/app/Services/Packs/PackDeletionService.php new file mode 100644 index 000000000..c288a3d04 --- /dev/null +++ b/app/Services/Packs/PackDeletionService.php @@ -0,0 +1,100 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Packs; + +use Pterodactyl\Models\Pack; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Contracts\Repository\PackRepositoryInterface; +use Pterodactyl\Exceptions\Service\HasActiveServersException; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory; + +class PackDeletionService +{ + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected $connection; + + /** + * @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $serverRepository; + + /** + * @var \Illuminate\Contracts\Filesystem\Factory + */ + protected $storage; + + /** + * PackDeletionService constructor. + * + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Illuminate\Contracts\Filesystem\Factory $storage + * @param \Pterodactyl\Contracts\Repository\PackRepositoryInterface $repository + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository + */ + public function __construct( + ConnectionInterface $connection, + FilesystemFactory $storage, + PackRepositoryInterface $repository, + ServerRepositoryInterface $serverRepository + ) { + $this->connection = $connection; + $this->repository = $repository; + $this->serverRepository = $serverRepository; + $this->storage = $storage; + } + + /** + * Delete a pack from the database as well as the archive stored on the server. + * + * @param int|\Pterodactyl\Models\Pack$pack + * + * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle($pack) + { + if (! $pack instanceof Pack) { + $pack = $this->repository->withColumns(['id', 'uuid'])->find($pack); + } + + $count = $this->serverRepository->findCountWhere([['pack_id', '=', $pack->id]]); + if ($count !== 0) { + throw new HasActiveServersException(trans('exceptions.packs.delete_has_servers')); + } + + $this->connection->beginTransaction(); + $this->repository->delete($pack->id); + $this->storage->disk()->deleteDirectory('packs/' . $pack->uuid); + $this->connection->commit(); + } +} diff --git a/app/Services/Packs/PackUpdateService.php b/app/Services/Packs/PackUpdateService.php new file mode 100644 index 000000000..f03903767 --- /dev/null +++ b/app/Services/Packs/PackUpdateService.php @@ -0,0 +1,90 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Packs; + +use Pterodactyl\Models\Pack; +use Pterodactyl\Contracts\Repository\PackRepositoryInterface; +use Pterodactyl\Exceptions\Service\HasActiveServersException; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; + +class PackUpdateService +{ + /** + * @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $serverRepository; + + /** + * PackUpdateService constructor. + * + * @param \Pterodactyl\Contracts\Repository\PackRepositoryInterface $repository + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository + */ + public function __construct( + PackRepositoryInterface $repository, + ServerRepositoryInterface $serverRepository + ) { + $this->repository = $repository; + $this->serverRepository = $serverRepository; + } + + /** + * Update a pack. + * + * @param int|\Pterodactyl\Models\Pack $pack + * @param array $data + * @return bool + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle($pack, array $data) + { + if (! $pack instanceof Pack) { + $pack = $this->repository->withColumns(['id', 'option_id'])->find($pack); + } + + if ((int) array_get($data, 'option_id', $pack->option_id) !== $pack->option_id) { + $count = $this->serverRepository->findCountWhere([['pack_id', '=', $pack->id]]); + + if ($count !== 0) { + throw new HasActiveServersException(trans('exceptions.packs.update_has_servers')); + } + } + + // Transform values to boolean + $data['selectable'] = isset($data['selectable']); + $data['visible'] = isset($data['visible']); + $data['locked'] = isset($data['locked']); + + return $this->repository->withoutFresh()->update($pack->id, $data); + } +} diff --git a/app/Services/Packs/TemplateUploadService.php b/app/Services/Packs/TemplateUploadService.php new file mode 100644 index 000000000..131757361 --- /dev/null +++ b/app/Services/Packs/TemplateUploadService.php @@ -0,0 +1,140 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Packs; + +use ZipArchive; +use Illuminate\Http\UploadedFile; +use Pterodactyl\Exceptions\Service\Pack\ZipExtractionException; +use Pterodactyl\Exceptions\Service\Pack\InvalidFileUploadException; +use Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException; +use Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException; +use Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException; + +class TemplateUploadService +{ + const VALID_UPLOAD_TYPES = [ + 'application/zip', + 'text/plain', + 'application/json', + ]; + + /** + * @var \ZipArchive + */ + protected $archive; + + /** + * @var \Pterodactyl\Services\Packs\PackCreationService + */ + protected $creationService; + + /** + * TemplateUploadService constructor. + * + * @param \Pterodactyl\Services\Packs\PackCreationService $creationService + * @param \ZipArchive $archive + */ + public function __construct( + PackCreationService $creationService, + ZipArchive $archive + ) { + $this->archive = $archive; + $this->creationService = $creationService; + } + + /** + * Process an uploaded file to create a new pack from a JSON or ZIP format. + * + * @param int $option + * @param \Illuminate\Http\UploadedFile $file + * @return \Pterodactyl\Models\Pack + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Service\Pack\ZipExtractionException + * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileUploadException + * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException + * @throws \Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException + * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException + */ + public function handle($option, UploadedFile $file) + { + if (! $file->isValid()) { + throw new InvalidFileUploadException(trans('exceptions.packs.invalid_upload')); + } + + if (! in_array($file->getMimeType(), self::VALID_UPLOAD_TYPES)) { + throw new InvalidFileMimeTypeException(trans('exceptions.packs.invalid_mime', [ + 'type' => implode(', ', self::VALID_UPLOAD_TYPES), + ])); + } + + if ($file->getMimeType() === 'application/zip') { + return $this->handleArchive($option, $file); + } else { + $json = json_decode($file->openFile()->fread($file->getSize()), true); + $json['option_id'] = $option; + + return $this->creationService->handle($json); + } + } + + /** + * Process a ZIP file to create a pack and stored archive. + * + * @param int $option + * @param \Illuminate\Http\UploadedFile $file + * @return \Pterodactyl\Models\Pack + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Service\Pack\ZipExtractionException + * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileUploadException + * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException + * @throws \Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException + * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException + */ + protected function handleArchive($option, $file) + { + if (! $this->archive->open($file->getRealPath())) { + throw new UnreadableZipArchiveException(trans('exceptions.packs.unreadable')); + } + + if (! $this->archive->locateName('import.json') || ! $this->archive->locateName('archive.tar.gz')) { + throw new InvalidPackArchiveFormatException(trans('exceptions.packs.invalid_archive_exception')); + } + + $json = json_decode($this->archive->getFromName('import.json'), true); + $json['option_id'] = $option; + + $pack = $this->creationService->handle($json); + if (! $this->archive->extractTo(storage_path('app/packs/' . $pack->uuid), 'archive.tar.gz')) { + // @todo delete the pack that was created. + throw new ZipExtractionException(trans('exceptions.packs.zip_extraction')); + } + + $this->archive->close(); + + return $pack; + } +} diff --git a/app/Services/Servers/BuildModificationService.php b/app/Services/Servers/BuildModificationService.php new file mode 100644 index 000000000..3b40b9d2c --- /dev/null +++ b/app/Services/Servers/BuildModificationService.php @@ -0,0 +1,257 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Servers; + +use Illuminate\Log\Writer; +use Pterodactyl\Models\Server; +use GuzzleHttp\Exception\RequestException; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Exceptions\Repository\RecordNotFoundException; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; +use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; + +class BuildModificationService +{ + /** + * @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface + */ + protected $allocationRepository; + + /** + * @var array + */ + protected $build = []; + + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface + */ + protected $daemonServerRepository; + + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected $database; + + /** + * @var null|int + */ + protected $firstAllocationId = null; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $repository; + + /** + * @var \Illuminate\Log\Writer + */ + protected $writer; + + /** + * BuildModificationService constructor. + * + * @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $allocationRepository + * @param \Illuminate\Database\ConnectionInterface $database + * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonServerRepository + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository + * @param \Illuminate\Log\Writer $writer + */ + public function __construct( + AllocationRepositoryInterface $allocationRepository, + ConnectionInterface $database, + DaemonServerRepositoryInterface $daemonServerRepository, + ServerRepositoryInterface $repository, + Writer $writer + ) { + $this->allocationRepository = $allocationRepository; + $this->daemonServerRepository = $daemonServerRepository; + $this->database = $database; + $this->repository = $repository; + $this->writer = $writer; + } + + /** + * Set build array parameters. + * + * @param string $key + * @param mixed $value + */ + public function setBuild($key, $value) + { + $this->build[$key] = $value; + } + + /** + * Return the build array or an item out of the build array. + * + * @param string|null $attribute + * @return array|mixed|null + */ + public function getBuild($attribute = null) + { + if (is_null($attribute)) { + return $this->build; + } + + return array_get($this->build, $attribute); + } + + /** + * Change the build details for a specified server. + * + * @param int|\Pterodactyl\Models\Server $server + * @param array $data + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function handle($server, array $data) + { + if (! $server instanceof Server) { + $server = $this->repository->find($server); + } + + $data['allocation_id'] = array_get($data, 'allocation_id', $server->allocation_id); + $this->database->beginTransaction(); + + $this->processAllocations($server, $data); + if (isset($data['allocation_id']) && $data['allocation_id'] != $server->allocation_id) { + try { + $allocation = $this->allocationRepository->findFirstWhere([ + ['id', '=', $data['allocation_id']], + ['server_id', '=', $server->id], + ]); + } catch (RecordNotFoundException $ex) { + throw new DisplayException(trans('admin/server.exceptions.default_allocation_not_found')); + } + + $this->setBuild('default', ['ip' => $allocation->ip, 'port' => $allocation->port]); + } + + $server = $this->repository->update($server->id, [ + 'memory' => array_get($data, 'memory', $server->memory), + 'swap' => array_get($data, 'swap', $server->swap), + 'io' => array_get($data, 'io', $server->io), + 'cpu' => array_get($data, 'cpu', $server->cpu), + 'disk' => array_get($data, 'disk', $server->disk), + 'allocation_id' => array_get($data, 'allocation_id', $server->allocation_id), + ]); + + $allocations = $this->allocationRepository->findWhere([ + ['server_id', '=', $server->id], + ]); + + $this->setBuild('memory', (int) $server->memory); + $this->setBuild('swap', (int) $server->swap); + $this->setBuild('io', (int) $server->io); + $this->setBuild('cpu', (int) $server->cpu); + $this->setBuild('disk', (int) $server->disk); + $this->setBuild('ports|overwrite', $allocations->groupBy('ip')->map(function ($item) { + return $item->pluck('port'); + })->toArray()); + + try { + $this->daemonServerRepository->setNode($server->node_id)->setAccessServer($server->uuid)->update([ + 'build' => $this->getBuild(), + ]); + + $this->database->commit(); + } catch (RequestException $exception) { + $response = $exception->getResponse(); + $this->writer->warning($exception); + + throw new DisplayException(trans('admin/server.exceptions.daemon_exception', [ + 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), + ])); + } + } + + /** + * Process the allocations being assigned in the data and ensure they are available for a server. + * + * @param \Pterodactyl\Models\Server $server + * @param array $data + * + * @throws \Pterodactyl\Exceptions\DisplayException + */ + public function processAllocations(Server $server, array &$data) + { + if (! array_key_exists('add_allocations', $data) && ! array_key_exists('remove_allocations', $data)) { + return; + } + + // Loop through allocations to add. + if (array_key_exists('add_allocations', $data) && ! empty($data['add_allocations'])) { + $unassigned = $this->allocationRepository->findWhere([ + ['server_id', '=', null], + ['node_id', '=', $server->node_id], + ])->pluck('id')->toArray(); + + foreach ($data['add_allocations'] as $allocation) { + if (! in_array($allocation, $unassigned)) { + continue; + } + + $this->firstAllocationId = $this->firstAllocationId ?? $allocation; + $toUpdate[] = [$allocation]; + } + + if (isset($toUpdate)) { + $this->allocationRepository->updateWhereIn('id', $toUpdate, ['server_id' => $server->id]); + unset($toUpdate); + } + } + + // Loop through allocations to remove. + if (array_key_exists('remove_allocations', $data) && ! empty($data['remove_allocations'])) { + $assigned = $this->allocationRepository->findWhere([ + ['server_id', '=', $server->id], + ])->pluck('id')->toArray(); + + foreach ($data['remove_allocations'] as $allocation) { + if (! in_array($allocation, $assigned)) { + continue; + } + + if ($allocation == $data['allocation_id']) { + if (is_null($this->firstAllocationId)) { + throw new DisplayException(trans('admin/server.exceptions.no_new_default_allocation')); + } + + $data['allocation_id'] = $this->firstAllocationId; + } + + $toUpdate[] = [$allocation]; + } + + if (isset($toUpdate)) { + $this->allocationRepository->updateWhereIn('id', $toUpdate, ['server_id' => null]); + unset($toUpdate); + } + } + } +} diff --git a/app/Services/Servers/ContainerRebuildService.php b/app/Services/Servers/ContainerRebuildService.php new file mode 100644 index 000000000..d6e63268e --- /dev/null +++ b/app/Services/Servers/ContainerRebuildService.php @@ -0,0 +1,92 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Servers; + +use Illuminate\Log\Writer; +use Pterodactyl\Models\Server; +use GuzzleHttp\Exception\RequestException; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; + +class ContainerRebuildService +{ + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface + */ + protected $daemonServerRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $repository; + + /** + * @var \Illuminate\Log\Writer + */ + protected $writer; + + /** + * ContainerRebuildService constructor. + * + * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonServerRepository + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository + * @param \Illuminate\Log\Writer $writer + */ + public function __construct( + DaemonServerRepositoryInterface $daemonServerRepository, + ServerRepositoryInterface $repository, + Writer $writer + ) { + $this->daemonServerRepository = $daemonServerRepository; + $this->repository = $repository; + $this->writer = $writer; + } + + /** + * Mark a server for rebuild on next boot cycle. + * + * @param int|\Pterodactyl\Models\Server $server + * + * @throws \Pterodactyl\Exceptions\DisplayException + */ + public function rebuild($server) + { + if (! $server instanceof Server) { + $server = $this->repository->find($server); + } + + try { + $this->daemonServerRepository->setNode($server->node_id)->setAccessServer($server->uuid)->rebuild(); + } catch (RequestException $exception) { + $response = $exception->getResponse(); + $this->writer->warning($exception); + + throw new DisplayException(trans('admin/server.exceptions.daemon_exception', [ + 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), + ])); + } + } +} diff --git a/app/Services/Servers/DetailsModificationService.php b/app/Services/Servers/DetailsModificationService.php new file mode 100644 index 000000000..6e31e2de8 --- /dev/null +++ b/app/Services/Servers/DetailsModificationService.php @@ -0,0 +1,170 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Servers; + +use Illuminate\Log\Writer; +use Pterodactyl\Models\Server; +use Illuminate\Database\DatabaseManager; +use GuzzleHttp\Exception\RequestException; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Repositories\Eloquent\ServerRepository; +use Pterodactyl\Repositories\Daemon\ServerRepository as DaemonServerRepository; + +class DetailsModificationService +{ + /** + * @var \Illuminate\Database\DatabaseManager + */ + protected $database; + + /** + * @var \Pterodactyl\Repositories\Daemon\ServerRepository + */ + protected $daemonServerRepository; + + /** + * @var \Pterodactyl\Repositories\Eloquent\ServerRepository + */ + protected $repository; + + /** + * @var \Illuminate\Log\Writer + */ + protected $writer; + + /** + * DetailsModificationService constructor. + * + * @param \Illuminate\Database\DatabaseManager $database + * @param \Pterodactyl\Repositories\Daemon\ServerRepository $daemonServerRepository + * @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository + * @param \Illuminate\Log\Writer $writer + */ + public function __construct( + DatabaseManager $database, + DaemonServerRepository $daemonServerRepository, + ServerRepository $repository, + Writer $writer + ) { + $this->database = $database; + $this->daemonServerRepository = $daemonServerRepository; + $this->repository = $repository; + $this->writer = $writer; + } + + /** + * Update the details for a single server instance. + * + * @param int|\Pterodactyl\Models\Server $server + * @param array $data + * @return bool + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function edit($server, array $data) + { + if (! $server instanceof Server) { + $server = $this->repository->find($server); + } + + $this->database->beginTransaction(); + $currentSecret = $server->daemonSecret; + + if ( + (isset($data['reset_token']) && ! is_null($data['reset_token'])) || + (isset($data['owner_id']) && $data['owner_id'] != $server->owner_id) + ) { + $data['daemonSecret'] = bin2hex(random_bytes(18)); + $shouldUpdate = true; + } + + $this->repository->withoutFresh()->update($server->id, [ + 'owner_id' => array_get($data, 'owner_id') ?? $server->owner_id, + 'name' => array_get($data, 'name') ?? $server->name, + 'description' => array_get($data, 'description') ?? $server->description, + 'daemonSecret' => array_get($data, 'daemonSecret') ?? $server->daemonSecret, + ], true, true); + + // If there are no updates, lets save the changes and return. + if (! isset($shouldUpdate)) { + return $this->database->commit(); + } + + try { + $this->daemonServerRepository->setNode($server->node_id)->setAccessServer($server->uuid)->update([ + 'keys' => [ + (string) $currentSecret => [], + (string) $data['daemonSecret'] => $this->daemonServerRepository::DAEMON_PERMISSIONS, + ], + ]); + + return $this->database->commit(); + } catch (RequestException $exception) { + $response = $exception->getResponse(); + $this->writer->warning($exception); + + throw new DisplayException(trans('admin/server.exceptions.daemon_exception', [ + 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), + ])); + } + } + + /** + * Update the docker container for a specified server. + * + * @param int|\Pterodactyl\Models\Server $server + * @param string $image + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function setDockerImage($server, $image) + { + if (! $server instanceof Server) { + $server = $this->repository->find($server); + } + + $this->database->beginTransaction(); + $this->repository->withoutFresh()->update($server->id, ['image' => $image]); + + try { + $this->daemonServerRepository->setNode($server->node_id)->setAccessServer($server->uuid)->update([ + 'build' => [ + 'image' => $image, + ], + ]); + + $this->database->commit(); + } catch (RequestException $exception) { + $response = $exception->getResponse(); + $this->writer->warning($exception); + + throw new DisplayException(trans('admin/server.exceptions.daemon_exception', [ + 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), + ])); + } + } +} diff --git a/app/Services/Servers/EnvironmentService.php b/app/Services/Servers/EnvironmentService.php new file mode 100644 index 000000000..a8bd8517c --- /dev/null +++ b/app/Services/Servers/EnvironmentService.php @@ -0,0 +1,106 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Servers; + +use Pterodactyl\Models\Server; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; + +class EnvironmentService +{ + const ENVIRONMENT_CASTS = [ + 'STARTUP' => 'startup', + 'P_SERVER_LOCATION' => 'location.short', + 'P_SERVER_UUID' => 'uuid', + ]; + + /** + * @var array + */ + protected $additional = []; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $repository; + + /** + * EnvironmentService constructor. + * + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository + */ + public function __construct(ServerRepositoryInterface $repository) + { + $this->repository = $repository; + } + + /** + * Dynamically configure additional environment variables to be assigned + * with a specific server. + * + * @param string $key + * @param callable $closure + * @return $this + */ + public function setEnvironmentKey($key, callable $closure) + { + $this->additional[] = [$key, $closure]; + + return $this; + } + + /** + * Take all of the environment variables configured for this server and return + * them in an easy to process format. + * + * @param int|\Pterodactyl\Models\Server $server + * @return array + */ + public function process($server) + { + if (! $server instanceof Server) { + if (! is_numeric($server)) { + throw new \InvalidArgumentException( + 'First argument passed to process() must be an instance of \\Pterodactyl\\Models\\Server or numeric.' + ); + } + + $server = $this->repository->find($server); + } + + $variables = $this->repository->getVariablesWithValues($server->id); + + // Process static environment variables defined in this file. + foreach (self::ENVIRONMENT_CASTS as $key => $object) { + $variables[$key] = object_get($server, $object); + } + + // Process dynamically included environment variables. + foreach ($this->additional as $item) { + $variables[$item[0]] = call_user_func($item[1], $server); + } + + return $variables; + } +} diff --git a/app/Services/Servers/ReinstallServerService.php b/app/Services/Servers/ReinstallServerService.php new file mode 100644 index 000000000..1aec0c7b7 --- /dev/null +++ b/app/Services/Servers/ReinstallServerService.php @@ -0,0 +1,106 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Servers; + +use Illuminate\Log\Writer; +use Pterodactyl\Models\Server; +use GuzzleHttp\Exception\RequestException; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; + +class ReinstallServerService +{ + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface + */ + protected $daemonServerRepository; + + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected $database; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $repository; + + /** + * @var \Illuminate\Log\Writer + */ + protected $writer; + + /** + * ReinstallService constructor. + * + * @param \Illuminate\Database\ConnectionInterface $database + * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonServerRepository + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository + * @param \Illuminate\Log\Writer $writer + */ + public function __construct( + ConnectionInterface $database, + DaemonServerRepositoryInterface $daemonServerRepository, + ServerRepositoryInterface $repository, + Writer $writer + ) { + $this->daemonServerRepository = $daemonServerRepository; + $this->database = $database; + $this->repository = $repository; + $this->writer = $writer; + } + + /** + * @param int|\Pterodactyl\Models\Server $server + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function reinstall($server) + { + if (! $server instanceof Server) { + $server = $this->repository->find($server); + } + + $this->database->beginTransaction(); + $this->repository->withoutFresh()->update($server->id, [ + 'installed' => 0, + ]); + + try { + $this->daemonServerRepository->setNode($server->node_id)->setAccessServer($server->uuid)->reinstall(); + $this->database->commit(); + } catch (RequestException $exception) { + $response = $exception->getResponse(); + $this->writer->warning($exception); + + throw new DisplayException(trans('admin/server.exceptions.daemon_exception', [ + 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), + ])); + } + } +} diff --git a/app/Services/Servers/ServerAccessHelperService.php b/app/Services/Servers/ServerAccessHelperService.php new file mode 100644 index 000000000..4618d323b --- /dev/null +++ b/app/Services/Servers/ServerAccessHelperService.php @@ -0,0 +1,82 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Servers; + +use Pterodactyl\Models\User; +use Pterodactyl\Models\Server; +use Illuminate\Cache\Repository as CacheRepository; +use Pterodactyl\Contracts\Repository\UserRepositoryInterface; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; +use Pterodactyl\Exceptions\Service\Server\UserNotLinkedToServerException; + +class ServerAccessHelperService +{ + public function __construct( + CacheRepository $cache, + ServerRepositoryInterface $repository, + SubuserRepositoryInterface $subuserRepository, + UserRepositoryInterface $userRepository + ) { + $this->cache = $cache; + $this->repository = $repository; + $this->subuserRepository = $subuserRepository; + $this->userRepository = $userRepository; + } + + /** + * @param int|\Pterodactyl\Models\Server $server + * @param int|\Pterodactyl\Models\User $user + * @return string + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Server\UserNotLinkedToServerException + */ + public function handle($server, $user) + { + if (! $server instanceof Server) { + $server = $this->repository->find($server); + } + + if (! $user instanceof User) { + $user = $this->userRepository->find($user); + } + + if ($user->root_admin || $server->owner_id === $user->id) { + return $server->daemonSecret; + } + + if (! in_array($server->id, $this->repository->getUserAccessServers($user->id))) { + throw new UserNotLinkedToServerException; + } + + $subuser = $this->subuserRepository->withColumns('daemonSecret')->findWhere([ + ['user_id', '=', $user->id], + ['server_id', '=', $server->id], + ]); + + return $subuser->daemonSecret; + } +} diff --git a/app/Services/Servers/ServerCreationService.php b/app/Services/Servers/ServerCreationService.php new file mode 100644 index 000000000..a2863424e --- /dev/null +++ b/app/Services/Servers/ServerCreationService.php @@ -0,0 +1,208 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Servers; + +use Ramsey\Uuid\Uuid; +use Illuminate\Log\Writer; +use Illuminate\Database\DatabaseManager; +use GuzzleHttp\Exception\RequestException; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; +use Pterodactyl\Contracts\Repository\UserRepositoryInterface; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; +use Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface; +use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; + +class ServerCreationService +{ + /** + * @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface + */ + protected $allocationRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface + */ + protected $daemonServerRepository; + + /** + * @var \Illuminate\Database\DatabaseManager + */ + protected $database; + + /** + * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface + */ + protected $nodeRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface + */ + protected $serverVariableRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface + */ + protected $userRepository; + + /** + * @var \Pterodactyl\Services\Servers\UsernameGenerationService + */ + protected $usernameService; + + /** + * @var \Pterodactyl\Services\Servers\VariableValidatorService + */ + protected $validatorService; + + /** + * @var \Illuminate\Log\Writer + */ + protected $writer; + + /** + * CreationService constructor. + * + * @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $allocationRepository + * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonServerRepository + * @param \Illuminate\Database\DatabaseManager $database + * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $nodeRepository + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository + * @param \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface $serverVariableRepository + * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $userRepository + * @param \Pterodactyl\Services\Servers\UsernameGenerationService $usernameService + * @param \Pterodactyl\Services\Servers\VariableValidatorService $validatorService + * @param \Illuminate\Log\Writer $writer + */ + public function __construct( + AllocationRepositoryInterface $allocationRepository, + DaemonServerRepositoryInterface $daemonServerRepository, + DatabaseManager $database, + NodeRepositoryInterface $nodeRepository, + ServerRepositoryInterface $repository, + ServerVariableRepositoryInterface $serverVariableRepository, + UserRepositoryInterface $userRepository, + UsernameGenerationService $usernameService, + VariableValidatorService $validatorService, + Writer $writer + ) { + $this->allocationRepository = $allocationRepository; + $this->daemonServerRepository = $daemonServerRepository; + $this->database = $database; + $this->nodeRepository = $nodeRepository; + $this->repository = $repository; + $this->serverVariableRepository = $serverVariableRepository; + $this->userRepository = $userRepository; + $this->usernameService = $usernameService; + $this->validatorService = $validatorService; + $this->writer = $writer; + } + + /** + * Create a server on both the panel and daemon. + * + * @param array $data + * @return mixed + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function create(array $data) + { + // @todo auto-deployment + $validator = $this->validatorService->isAdmin()->setFields($data['environment'])->validate($data['option_id']); + $uniqueShort = bin2hex(random_bytes(4)); + + $this->database->beginTransaction(); + + $server = $this->repository->create([ + 'uuid' => Uuid::uuid4()->toString(), + 'uuidShort' => $uniqueShort, + 'node_id' => $data['node_id'], + 'name' => $data['name'], + 'description' => $data['description'], + 'skip_scripts' => isset($data['skip_scripts']), + 'suspended' => false, + 'owner_id' => $data['owner_id'], + 'memory' => $data['memory'], + 'swap' => $data['swap'], + 'disk' => $data['disk'], + 'io' => $data['io'], + 'cpu' => $data['cpu'], + 'oom_disabled' => isset($data['oom_disabled']), + 'allocation_id' => $data['allocation_id'], + 'service_id' => $data['service_id'], + 'option_id' => $data['option_id'], + 'pack_id' => (! isset($data['pack_id']) || $data['pack_id'] == 0) ? null : $data['pack_id'], + 'startup' => $data['startup'], + 'daemonSecret' => bin2hex(random_bytes(18)), + 'image' => $data['docker_image'], + 'username' => $this->usernameService->generate($data['name'], $uniqueShort), + 'sftp_password' => null, + ]); + + // Process allocations and assign them to the server in the database. + $records = [$data['allocation_id']]; + if (isset($data['allocation_additional']) && is_array($data['allocation_additional'])) { + $records = array_merge($records, $data['allocation_additional']); + } + + $this->allocationRepository->assignAllocationsToServer($server->id, $records); + + // Process the passed variables and store them in the database. + $records = []; + foreach ($validator->getResults() as $result) { + $records[] = [ + 'server_id' => $server->id, + 'variable_id' => $result['id'], + 'variable_value' => $result['value'], + ]; + } + + $this->serverVariableRepository->insert($records); + + // Create the server on the daemon & commit it to the database. + try { + $this->daemonServerRepository->setNode($server->node_id)->create($server->id); + $this->database->commit(); + } catch (RequestException $exception) { + $response = $exception->getResponse(); + $this->writer->warning($exception); + $this->database->rollBack(); + + throw new DisplayException(trans('admin/server.exceptions.daemon_exception', [ + 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), + ])); + } + + return $server; + } +} diff --git a/app/Services/Servers/ServerDeletionService.php b/app/Services/Servers/ServerDeletionService.php new file mode 100644 index 000000000..3f510baaf --- /dev/null +++ b/app/Services/Servers/ServerDeletionService.php @@ -0,0 +1,153 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Servers; + +use Illuminate\Log\Writer; +use Pterodactyl\Models\Server; +use GuzzleHttp\Exception\RequestException; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Services\Database\DatabaseManagementService; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; +use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; + +class ServerDeletionService +{ + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected $connection; + + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface + */ + protected $daemonServerRepository; + + /** + * @var \Pterodactyl\Services\Database\DatabaseManagementService + */ + protected $databaseManagementService; + + /** + * @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface + */ + protected $databaseRepository; + + /** + * @var bool + */ + protected $force = false; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $repository; + + /** + * @var \Illuminate\Log\Writer + */ + protected $writer; + + /** + * DeletionService constructor. + * + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonServerRepository + * @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $databaseRepository + * @param \Pterodactyl\Services\Database\DatabaseManagementService $databaseManagementService + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository + * @param \Illuminate\Log\Writer $writer + */ + public function __construct( + ConnectionInterface $connection, + DaemonServerRepositoryInterface $daemonServerRepository, + DatabaseRepositoryInterface $databaseRepository, + DatabaseManagementService $databaseManagementService, + ServerRepositoryInterface $repository, + Writer $writer + ) { + $this->daemonServerRepository = $daemonServerRepository; + $this->connection = $connection; + $this->databaseManagementService = $databaseManagementService; + $this->databaseRepository = $databaseRepository; + $this->repository = $repository; + $this->writer = $writer; + } + + /** + * Set if the server should be forcibly deleted from the panel (ignoring daemon errors) or not. + * + * @param bool $bool + * @return $this + */ + public function withForce($bool = true) + { + $this->force = $bool; + + return $this; + } + + /** + * Delete a server from the panel and remove any associated databases from hosts. + * + * @param int|\Pterodactyl\Models\Server $server + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle($server) + { + if (! $server instanceof Server) { + $server = $this->repository->withColumns(['id', 'node_id', 'uuid'])->find($server); + } + + try { + $this->daemonServerRepository->setNode($server->node_id)->setAccessServer($server->uuid)->delete(); + } catch (RequestException $exception) { + $response = $exception->getResponse(); + + if (is_null($response) || (! is_null($response) && $response->getStatusCode() !== 404)) { + $this->writer->warning($exception); + + // If not forcing the deletion, throw an exception, otherwise just log it and + // continue with server deletion process in the panel. + if (! $this->force) { + throw new DisplayException(trans('admin/server.exceptions.daemon_exception', [ + 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), + ])); + } + } + } + + $this->connection->beginTransaction(); + $this->databaseRepository->withColumns('id')->findWhere([['server_id', '=', $server->id]])->each(function ($item) { + $this->databaseManagementService->delete($item->id); + }); + + $this->repository->delete($server->id); + $this->connection->commit(); + } +} diff --git a/app/Services/Servers/StartupModificationService.php b/app/Services/Servers/StartupModificationService.php new file mode 100644 index 000000000..73af5c799 --- /dev/null +++ b/app/Services/Servers/StartupModificationService.php @@ -0,0 +1,196 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Servers; + +use Illuminate\Log\Writer; +use Pterodactyl\Models\Server; +use GuzzleHttp\Exception\RequestException; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface; +use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; + +class StartupModificationService +{ + /** + * @var bool + */ + protected $admin = false; + + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface + */ + protected $daemonServerRepository; + + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected $database; + + /** + * @var \Pterodactyl\Services\Servers\EnvironmentService + */ + protected $environmentService; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface + */ + protected $serverVariableRepository; + + /** + * @var \Pterodactyl\Services\Servers\VariableValidatorService + */ + protected $validatorService; + + /** + * @var \Illuminate\Log\Writer + */ + protected $writer; + + /** + * StartupModificationService constructor. + * + * @param \Illuminate\Database\ConnectionInterface $database + * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonServerRepository + * @param \Pterodactyl\Services\Servers\EnvironmentService $environmentService + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository + * @param \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface $serverVariableRepository + * @param \Pterodactyl\Services\Servers\VariableValidatorService $validatorService + * @param \Illuminate\Log\Writer $writer + */ + public function __construct( + ConnectionInterface $database, + DaemonServerRepositoryInterface $daemonServerRepository, + EnvironmentService $environmentService, + ServerRepositoryInterface $repository, + ServerVariableRepositoryInterface $serverVariableRepository, + VariableValidatorService $validatorService, + Writer $writer + ) { + $this->daemonServerRepository = $daemonServerRepository; + $this->database = $database; + $this->environmentService = $environmentService; + $this->repository = $repository; + $this->serverVariableRepository = $serverVariableRepository; + $this->validatorService = $validatorService; + $this->writer = $writer; + } + + /** + * Determine if this function should run at an administrative level. + * + * @param bool $bool + * @return $this + */ + public function isAdmin($bool = true) + { + $this->admin = $bool; + + return $this; + } + + /** + * Process startup modification for a server. + * + * @param int|\Pterodactyl\Models\Server $server + * @param array $data + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle($server, array $data) + { + if (! $server instanceof Server) { + $server = $this->repository->find($server); + } + + if ( + $server->service_id != array_get($data, 'service_id', $server->service_id) || + $server->option_id != array_get($data, 'option_id', $server->option_id) || + $server->pack_id != array_get($data, 'pack_id', $server->pack_id) + ) { + $hasServiceChanges = true; + } + + $this->database->beginTransaction(); + if (isset($data['environment'])) { + $validator = $this->validatorService->isAdmin($this->admin) + ->setFields($data['environment']) + ->validate(array_get($data, 'option_id', $server->option_id)); + + foreach ($validator->getResults() as $result) { + $this->serverVariableRepository->withoutFresh()->updateOrCreate([ + 'server_id' => $server->id, + 'variable_id' => $result['id'], + ], [ + 'variable_value' => $result['value'], + ]); + } + } + + $daemonData = [ + 'build' => [ + 'env|overwrite' => $this->environmentService->process($server), + ], + ]; + + if ($this->admin) { + $server = $this->repository->update($server->id, [ + 'installed' => 0, + 'startup' => array_get($data, 'startup', $server->startup), + 'service_id' => array_get($data, 'service_id', $server->service_id), + 'option_id' => array_get($data, 'option_id', $server->service_id), + 'pack_id' => array_get($data, 'pack_id', $server->pack_id), + 'skip_scripts' => isset($data['skip_scripts']), + ]); + + if (isset($hasServiceChanges)) { + $daemonData['service'] = array_merge( + $this->repository->withColumns(['id', 'option_id', 'pack_id'])->getDaemonServiceData($server), + ['skip_scripts' => isset($data['skip_scripts'])] + ); + } + } + + try { + $this->daemonServerRepository->setNode($server->node_id)->setAccessServer($server->uuid)->update($daemonData); + $this->database->commit(); + } catch (RequestException $exception) { + $response = $exception->getResponse(); + $this->writer->warning($exception); + + throw new DisplayException(trans('admin/server.exceptions.daemon_exception', [ + 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), + ])); + } + } +} diff --git a/app/Services/Servers/SuspensionService.php b/app/Services/Servers/SuspensionService.php new file mode 100644 index 000000000..4996bb609 --- /dev/null +++ b/app/Services/Servers/SuspensionService.php @@ -0,0 +1,127 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Servers; + +use Illuminate\Log\Writer; +use Pterodactyl\Models\Server; +use GuzzleHttp\Exception\RequestException; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; + +class SuspensionService +{ + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface + */ + protected $daemonServerRepository; + + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected $database; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $repository; + + /** + * @var \Illuminate\Log\Writer + */ + protected $writer; + + /** + * SuspensionService constructor. + * + * @param \Illuminate\Database\ConnectionInterface $database + * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonServerRepository + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository + * @param \Illuminate\Log\Writer $writer + */ + public function __construct( + ConnectionInterface $database, + DaemonServerRepositoryInterface $daemonServerRepository, + ServerRepositoryInterface $repository, + Writer $writer + ) { + $this->daemonServerRepository = $daemonServerRepository; + $this->database = $database; + $this->repository = $repository; + $this->writer = $writer; + } + + /** + * Suspends a server on the system. + * + * @param int|\Pterodactyl\Models\Server $server + * @param string $action + * @return bool + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function toggle($server, $action = 'suspend') + { + if (! $server instanceof Server) { + $server = $this->repository->find($server); + } + + if (! in_array($action, ['suspend', 'unsuspend'])) { + throw new \InvalidArgumentException(sprintf( + 'Action must be either suspend or unsuspend, %s passed.', + $action + )); + } + + if ( + $action === 'suspend' && $server->suspended || + $action === 'unsuspend' && ! $server->suspended + ) { + return true; + } + + $this->database->beginTransaction(); + $this->repository->withoutFresh()->update($server->id, [ + 'suspended' => $action === 'suspend', + ]); + + try { + $this->daemonServerRepository->setNode($server->node_id)->setAccessServer($server->uuid)->$action(); + $this->database->commit(); + + return true; + } catch (RequestException $exception) { + $response = $exception->getResponse(); + $this->writer->warning($exception); + + throw new DisplayException(trans('admin/server.exceptions.daemon_exception', [ + 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), + ])); + } + } +} diff --git a/app/Services/APILogService.php b/app/Services/Servers/UsernameGenerationService.php similarity index 50% rename from app/Services/APILogService.php rename to app/Services/Servers/UsernameGenerationService.php index c8e5c098c..488288b47 100644 --- a/app/Services/APILogService.php +++ b/app/Services/Servers/UsernameGenerationService.php @@ -1,5 +1,5 @@ . * @@ -22,45 +22,34 @@ * SOFTWARE. */ -namespace Pterodactyl\Services; +namespace Pterodactyl\Services\Servers; -use Log; -use Illuminate\Http\Request; -use Pterodactyl\Models\APILog; - -class APILogService +class UsernameGenerationService { /** - * Log an API Request. + * Generate a unique username to be used for SFTP connections and identification + * of the server docker container on the host system. * - * @param \Illuminate\Http\Request $request - * @param null|string $error - * @param bool $authorized - * @return void + * @param string $name + * @param null $identifier + * @return string */ - public static function log(Request $request, $error = null, $authorized = false) + public function generate($name, $identifier = null) { - if ($request->bearerToken() && ! empty($request->bearerToken())) { - list($public, $hashed) = explode('.', $request->bearerToken()); + if (is_null($identifier) || ! ctype_alnum($identifier)) { + $unique = bin2hex(random_bytes(4)); } else { - $public = null; + if (strlen($identifier) < 8) { + $unique = $identifier . str_random((8 - strlen($identifier))); + } else { + $unique = substr($identifier, 0, 8); + } } - try { - $log = APILog::create([ - 'authorized' => $authorized, - 'error' => $error, - 'key' => $public, - 'method' => $request->method(), - 'route' => $request->fullUrl(), - 'content' => (empty($request->getContent())) ? null : $request->getContent(), - 'user_agent' => $request->header('User-Agent'), - 'request_ip' => $request->ip(), - ]); - $log->save(); - } catch (\Exception $ex) { - // Simply log it and move on. - Log::error($ex); - } + // Filter the Server Name + $name = trim(preg_replace('/[^A-Za-z0-9]+/', '', $name), '_'); + $name = (strlen($name) < 1) ? str_random(6) : $name; + + return strtolower(substr($name, 0, 6) . '_' . $unique); } } diff --git a/app/Services/Servers/VariableValidatorService.php b/app/Services/Servers/VariableValidatorService.php new file mode 100644 index 000000000..749349047 --- /dev/null +++ b/app/Services/Servers/VariableValidatorService.php @@ -0,0 +1,173 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Servers; + +use Pterodactyl\Exceptions\DisplayValidationException; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Illuminate\Contracts\Validation\Factory as ValidationFactory; +use Pterodactyl\Contracts\Repository\OptionVariableRepositoryInterface; +use Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface; + +class VariableValidatorService +{ + /** + * @var bool + */ + protected $isAdmin = false; + + /** + * @var array + */ + protected $fields = []; + + /** + * @var array + */ + protected $results = []; + + /** + * @var \Pterodactyl\Contracts\Repository\OptionVariableRepositoryInterface + */ + protected $optionVariableRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $serverRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface + */ + protected $serverVariableRepository; + + /** + * @var \Illuminate\Contracts\Validation\Factory + */ + protected $validator; + + /** + * VariableValidatorService constructor. + * + * @param \Pterodactyl\Contracts\Repository\OptionVariableRepositoryInterface $optionVariableRepository + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository + * @param \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface $serverVariableRepository + * @param \Illuminate\Contracts\Validation\Factory $validator + */ + public function __construct( + OptionVariableRepositoryInterface $optionVariableRepository, + ServerRepositoryInterface $serverRepository, + ServerVariableRepositoryInterface $serverVariableRepository, + ValidationFactory $validator + ) { + $this->optionVariableRepository = $optionVariableRepository; + $this->serverRepository = $serverRepository; + $this->serverVariableRepository = $serverVariableRepository; + $this->validator = $validator; + } + + /** + * Set the fields with populated data to validate. + * + * @param array $fields + * @return $this + */ + public function setFields(array $fields) + { + $this->fields = $fields; + + return $this; + } + + /** + * Set this function to be running at the administrative level. + * + * @param bool $bool + * @return $this + */ + public function isAdmin($bool = true) + { + $this->isAdmin = $bool; + + return $this; + } + + /** + * Validate all of the passed data aganist the given service option variables. + * + * @param int $option + * @return $this + */ + public function validate($option) + { + $variables = $this->optionVariableRepository->findWhere([['option_id', '=', $option]]); + if (count($variables) === 0) { + $this->results = []; + + return $this; + } + + $variables->each(function ($item) { + // Skip doing anything if user is not an admin and variable is not user viewable + // or editable. + if (! $this->isAdmin && (! $item->user_editable || ! $item->user_viewable)) { + return; + } + + $validator = $this->validator->make([ + 'variable_value' => array_key_exists($item->env_variable, $this->fields) ? $this->fields[$item->env_variable] : null, + ], [ + 'variable_value' => $item->rules, + ]); + + if ($validator->fails()) { + throw new DisplayValidationException(json_encode( + collect([ + 'notice' => [ + trans('admin/server.exceptions.bad_variable', ['name' => $item->name]), + ], + ])->merge($validator->errors()->toArray()) + )); + } + + $this->results[] = [ + 'id' => $item->id, + 'key' => $item->env_variable, + 'value' => $this->fields[$item->env_variable], + ]; + }); + + return $this; + } + + /** + * Return the final results after everything has been validated. + * + * @return array + */ + public function getResults() + { + return $this->results; + } +} diff --git a/app/Services/Services/Options/InstallScriptUpdateService.php b/app/Services/Services/Options/InstallScriptUpdateService.php new file mode 100644 index 000000000..976c21446 --- /dev/null +++ b/app/Services/Services/Options/InstallScriptUpdateService.php @@ -0,0 +1,78 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Services\Options; + +use Pterodactyl\Models\ServiceOption; +use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface; +use Pterodactyl\Exceptions\Service\ServiceOption\InvalidCopyFromException; + +class InstallScriptUpdateService +{ + /** + * @var \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface + */ + protected $repository; + + /** + * InstallScriptUpdateService constructor. + * + * @param \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface $repository + */ + public function __construct(ServiceOptionRepositoryInterface $repository) + { + $this->repository = $repository; + } + + /** + * Modify the option install script for a given service option. + * + * @param int|\Pterodactyl\Models\ServiceOption $option + * @param array $data + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\ServiceOption\InvalidCopyFromException + */ + public function handle($option, array $data) + { + if (! $option instanceof ServiceOption) { + $option = $this->repository->find($option); + } + + if (! is_null(array_get($data, 'copy_script_from'))) { + if (! $this->repository->isCopiableScript(array_get($data, 'copy_script_from'), $option->service_id)) { + throw new InvalidCopyFromException(trans('exceptions.service.options.invalid_copy_id')); + } + } + + $this->repository->withoutFresh()->update($option->id, [ + 'script_install' => array_get($data, 'script_install'), + 'script_is_privileged' => array_get($data, 'script_is_privileged'), + 'script_entry' => array_get($data, 'script_entry'), + 'script_container' => array_get($data, 'script_container'), + 'copy_script_from' => array_get($data, 'copy_script_from'), + ]); + } +} diff --git a/app/Services/Services/Options/OptionCreationService.php b/app/Services/Services/Options/OptionCreationService.php new file mode 100644 index 000000000..509f64c16 --- /dev/null +++ b/app/Services/Services/Options/OptionCreationService.php @@ -0,0 +1,73 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Services\Options; + +use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface; +use Pterodactyl\Exceptions\Service\ServiceOption\NoParentConfigurationFoundException; + +class OptionCreationService +{ + /** + * @var \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface + */ + protected $repository; + + /** + * CreationService constructor. + * + * @param \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface $repository + */ + public function __construct(ServiceOptionRepositoryInterface $repository) + { + $this->repository = $repository; + } + + /** + * Create a new service option and assign it to the given service. + * + * @param array $data + * @return \Pterodactyl\Models\ServiceOption + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Service\ServiceOption\NoParentConfigurationFoundException + */ + public function handle(array $data) + { + if (! is_null(array_get($data, 'config_from'))) { + $results = $this->repository->findCountWhere([ + ['service_id', '=', array_get($data, 'service_id')], + ['id', '=', array_get($data, 'config_from')], + ]); + + if ($results !== 1) { + throw new NoParentConfigurationFoundException(trans('exceptions.service.options.must_be_child')); + } + } else { + $data['config_from'] = null; + } + + return $this->repository->create($data); + } +} diff --git a/app/Services/Services/Options/OptionDeletionService.php b/app/Services/Services/Options/OptionDeletionService.php new file mode 100644 index 000000000..02a1e734e --- /dev/null +++ b/app/Services/Services/Options/OptionDeletionService.php @@ -0,0 +1,77 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Services\Options; + +use Pterodactyl\Exceptions\Service\HasActiveServersException; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface; + +class OptionDeletionService +{ + /** + * @var \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $serverRepository; + + /** + * OptionDeletionService constructor. + * + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository + * @param \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface $repository + */ + public function __construct( + ServerRepositoryInterface $serverRepository, + ServiceOptionRepositoryInterface $repository + ) { + $this->repository = $repository; + $this->serverRepository = $serverRepository; + } + + /** + * Delete an option from the database if it has no active servers attached to it. + * + * @param int $option + * @return int + * + * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException + */ + public function handle($option) + { + $servers = $this->serverRepository->findCountWhere([ + ['option_id', '=', $option], + ]); + + if ($servers > 0) { + throw new HasActiveServersException(trans('exceptions.service.options.delete_has_servers')); + } + + return $this->repository->delete($option); + } +} diff --git a/app/Services/Services/Options/OptionUpdateService.php b/app/Services/Services/Options/OptionUpdateService.php new file mode 100644 index 000000000..62bf5d393 --- /dev/null +++ b/app/Services/Services/Options/OptionUpdateService.php @@ -0,0 +1,77 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Services\Options; + +use Pterodactyl\Models\ServiceOption; +use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface; +use Pterodactyl\Exceptions\Service\ServiceOption\NoParentConfigurationFoundException; + +class OptionUpdateService +{ + /** + * @var \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface + */ + protected $repository; + + /** + * OptionUpdateService constructor. + * + * @param \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface $repository + */ + public function __construct(ServiceOptionRepositoryInterface $repository) + { + $this->repository = $repository; + } + + /** + * Update a service option. + * + * @param int|\Pterodactyl\Models\ServiceOption $option + * @param array $data + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\ServiceOption\NoParentConfigurationFoundException + */ + public function handle($option, array $data) + { + if (! $option instanceof ServiceOption) { + $option = $this->repository->find($option); + } + + if (! is_null(array_get($data, 'config_from'))) { + $results = $this->repository->findCountWhere([ + ['service_id', '=', $option->service_id], + ['id', '=', array_get($data, 'config_from')], + ]); + + if ($results !== 1) { + throw new NoParentConfigurationFoundException(trans('exceptions.service.options.must_be_child')); + } + } + + $this->repository->withoutFresh()->update($option->id, $data); + } +} diff --git a/app/Services/Services/ServiceCreationService.php b/app/Services/Services/ServiceCreationService.php new file mode 100644 index 000000000..fc76f99d9 --- /dev/null +++ b/app/Services/Services/ServiceCreationService.php @@ -0,0 +1,79 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Services; + +use Pterodactyl\Traits\Services\CreatesServiceIndex; +use Illuminate\Contracts\Config\Repository as ConfigRepository; +use Pterodactyl\Contracts\Repository\ServiceRepositoryInterface; + +class ServiceCreationService +{ + use CreatesServiceIndex; + + /** + * @var \Illuminate\Contracts\Config\Repository + */ + protected $config; + + /** + * @var \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface + */ + protected $repository; + + /** + * ServiceCreationService constructor. + * + * @param \Illuminate\Contracts\Config\Repository $config + * @param \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface $repository + */ + public function __construct( + ConfigRepository $config, + ServiceRepositoryInterface $repository + ) { + $this->config = $config; + $this->repository = $repository; + } + + /** + * Create a new service on the system. + * + * @param array $data + * @return \Pterodactyl\Models\Service + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function handle(array $data) + { + return $this->repository->create(array_merge([ + 'author' => $this->config->get('pterodactyl.service.author'), + ], [ + 'name' => array_get($data, 'name'), + 'description' => array_get($data, 'description'), + 'folder' => array_get($data, 'folder'), + 'startup' => array_get($data, 'startup'), + 'index_file' => $this->getIndexScript(), + ])); + } +} diff --git a/app/Services/Services/ServiceDeletionService.php b/app/Services/Services/ServiceDeletionService.php new file mode 100644 index 000000000..27e291ed3 --- /dev/null +++ b/app/Services/Services/ServiceDeletionService.php @@ -0,0 +1,74 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Services; + +use Pterodactyl\Exceptions\Service\HasActiveServersException; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Contracts\Repository\ServiceRepositoryInterface; + +class ServiceDeletionService +{ + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $serverRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface + */ + protected $repository; + + /** + * ServiceDeletionService constructor. + * + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository + * @param \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface $repository + */ + public function __construct( + ServerRepositoryInterface $serverRepository, + ServiceRepositoryInterface $repository + ) { + $this->serverRepository = $serverRepository; + $this->repository = $repository; + } + + /** + * Delete a service from the system only if there are no servers attached to it. + * + * @param int $service + * @return int + * + * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException + */ + public function handle($service) + { + $count = $this->serverRepository->findCountWhere([['service_id', '=', $service]]); + if ($count > 0) { + throw new HasActiveServersException(trans('exceptions.service.delete_has_servers')); + } + + return $this->repository->delete($service); + } +} diff --git a/app/Services/Services/ServiceUpdateService.php b/app/Services/Services/ServiceUpdateService.php new file mode 100644 index 000000000..3c573ff15 --- /dev/null +++ b/app/Services/Services/ServiceUpdateService.php @@ -0,0 +1,62 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Services; + +use Pterodactyl\Contracts\Repository\ServiceRepositoryInterface; + +class ServiceUpdateService +{ + /** + * @var \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface + */ + protected $repository; + + /** + * ServiceUpdateService constructor. + * + * @param \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface $repository + */ + public function __construct(ServiceRepositoryInterface $repository) + { + $this->repository = $repository; + } + + /** + * Update a service and prevent changing the author once it is set. + * + * @param int $service + * @param array $data + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle($service, array $data) + { + if (! is_null(array_get($data, 'author'))) { + unset($data['author']); + } + + $this->repository->withoutFresh()->update($service, $data); + } +} diff --git a/app/Services/Services/Variables/VariableCreationService.php b/app/Services/Services/Variables/VariableCreationService.php new file mode 100644 index 000000000..78301ac7e --- /dev/null +++ b/app/Services/Services/Variables/VariableCreationService.php @@ -0,0 +1,84 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Services\Variables; + +use Pterodactyl\Models\ServiceOption; +use Pterodactyl\Models\ServiceVariable; +use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface; +use Pterodactyl\Contracts\Repository\ServiceVariableRepositoryInterface; +use Pterodactyl\Exceptions\Service\ServiceVariable\ReservedVariableNameException; + +class VariableCreationService +{ + /** + * @var \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface + */ + protected $serviceOptionRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\ServiceVariableRepositoryInterface + */ + protected $serviceVariableRepository; + + public function __construct( + ServiceOptionRepositoryInterface $serviceOptionRepository, + ServiceVariableRepositoryInterface $serviceVariableRepository + ) { + $this->serviceOptionRepository = $serviceOptionRepository; + $this->serviceVariableRepository = $serviceVariableRepository; + } + + /** + * Create a new variable for a given service option. + * + * @param int|\Pterodactyl\Models\ServiceOption $option + * @param array $data + * @return \Pterodactyl\Models\ServiceVariable + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Service\ServiceVariable\ReservedVariableNameException + */ + public function handle($option, array $data) + { + if ($option instanceof ServiceOption) { + $option = $option->id; + } + + if (in_array(strtoupper(array_get($data, 'env_variable')), explode(',', ServiceVariable::RESERVED_ENV_NAMES))) { + throw new ReservedVariableNameException(sprintf( + 'Cannot use the protected name %s for this environment variable.', + array_get($data, 'env_variable') + )); + } + + $options = array_get($data, 'options', []); + + return $this->serviceVariableRepository->create(array_merge([ + 'option_id' => $option, + 'user_viewable' => in_array('user_viewable', $options), + 'user_editable' => in_array('user_editable', $options), + ], $data)); + } +} diff --git a/app/Services/Services/Variables/VariableUpdateService.php b/app/Services/Services/Variables/VariableUpdateService.php new file mode 100644 index 000000000..4792ad6bb --- /dev/null +++ b/app/Services/Services/Variables/VariableUpdateService.php @@ -0,0 +1,94 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Services\Variables; + +use Pterodactyl\Models\ServiceVariable; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Contracts\Repository\ServiceVariableRepositoryInterface; +use Pterodactyl\Exceptions\Service\ServiceVariable\ReservedVariableNameException; + +class VariableUpdateService +{ + /** + * @var \Pterodactyl\Contracts\Repository\ServiceVariableRepositoryInterface + */ + protected $repository; + + /** + * VariableUpdateService constructor. + * + * @param \Pterodactyl\Contracts\Repository\ServiceVariableRepositoryInterface $repository + */ + public function __construct(ServiceVariableRepositoryInterface $repository) + { + $this->repository = $repository; + } + + /** + * Update a specific service variable. + * + * @param int|\Pterodactyl\Models\ServiceVariable $variable + * @param array $data + * @return mixed + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\ServiceVariable\ReservedVariableNameException + */ + public function handle($variable, array $data) + { + if (! $variable instanceof ServiceVariable) { + $variable = $this->repository->find($variable); + } + + if (! is_null(array_get($data, 'env_variable'))) { + if (in_array(strtoupper(array_get($data, 'env_variable')), explode(',', ServiceVariable::RESERVED_ENV_NAMES))) { + throw new ReservedVariableNameException(trans('exceptions.service.variables.reserved_name', [ + 'name' => array_get($data, 'env_variable'), + ])); + } + + $search = $this->repository->withColumns('id')->findCountWhere([ + ['env_variable', '=', array_get($data, 'env_variable')], + ['option_id', '=', $variable->option_id], + ['id', '!=', $variable->id], + ]); + + if ($search > 0) { + throw new DisplayException(trans('exceptions.service.variables.env_not_unique', [ + 'name' => array_get($data, 'env_variable'), + ])); + } + } + + $options = array_get($data, 'options', []); + + return $this->repository->withoutFresh()->update($variable->id, array_merge([ + 'user_viewable' => in_array('user_viewable', $options), + 'user_editable' => in_array('user_editable', $options), + ], $data)); + } +} diff --git a/app/Services/Subusers/PermissionCreationService.php b/app/Services/Subusers/PermissionCreationService.php new file mode 100644 index 000000000..8414e6c7c --- /dev/null +++ b/app/Services/Subusers/PermissionCreationService.php @@ -0,0 +1,84 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Subusers; + +use Pterodactyl\Models\Permission; +use Pterodactyl\Contracts\Repository\PermissionRepositoryInterface; + +class PermissionCreationService +{ + const CORE_DAEMON_PERMISSIONS = [ + 's:get', + 's:console', + ]; + + /** + * @var \Pterodactyl\Contracts\Repository\PermissionRepositoryInterface + */ + protected $repository; + + /** + * PermissionCreationService constructor. + * + * @param \Pterodactyl\Contracts\Repository\PermissionRepositoryInterface $repository + */ + public function __construct(PermissionRepositoryInterface $repository) + { + $this->repository = $repository; + } + + /** + * Assign permissions to a given subuser. + * + * @param int $subuser + * @param array $permissions + * @return array + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function handle($subuser, array $permissions) + { + $permissionMappings = Permission::getPermissions(true); + $daemonPermissions = self::CORE_DAEMON_PERMISSIONS; + $insertPermissions = []; + + foreach ($permissions as $permission) { + if (array_key_exists($permission, $permissionMappings)) { + if (! is_null($permissionMappings[$permission])) { + array_push($daemonPermissions, $permissionMappings[$permission]); + } + + array_push($insertPermissions, [ + 'subuser_id' => $subuser, + 'permission' => $permission, + ]); + } + } + + $this->repository->insert($insertPermissions); + + return $daemonPermissions; + } +} diff --git a/app/Services/Subusers/SubuserCreationService.php b/app/Services/Subusers/SubuserCreationService.php new file mode 100644 index 000000000..4a13adfde --- /dev/null +++ b/app/Services/Subusers/SubuserCreationService.php @@ -0,0 +1,182 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Subusers; + +use Illuminate\Log\Writer; +use Pterodactyl\Models\Server; +use GuzzleHttp\Exception\RequestException; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Services\Users\UserCreationService; +use Pterodactyl\Contracts\Repository\UserRepositoryInterface; +use Pterodactyl\Exceptions\Repository\RecordNotFoundException; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; +use Pterodactyl\Exceptions\Service\Subuser\UserIsServerOwnerException; +use Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException; +use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; + +class SubuserCreationService +{ + const DAEMON_SECRET_BYTES = 18; + + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected $connection; + + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface + */ + protected $daemonRepository; + + /** + * @var \Pterodactyl\Services\Subusers\PermissionCreationService + */ + protected $permissionService; + + /** + * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface + */ + protected $subuserRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $serverRepository; + + /** + * @var \Pterodactyl\Services\Users\UserCreationService + */ + protected $userCreationService; + + /** + * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface + */ + protected $userRepository; + + /** + * @var \Illuminate\Log\Writer + */ + protected $writer; + + /** + * SubuserCreationService constructor. + * + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Pterodactyl\Services\Users\UserCreationService $userCreationService + * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonRepository + * @param \Pterodactyl\Services\Subusers\PermissionCreationService $permissionService + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository + * @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $subuserRepository + * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $userRepository + * @param \Illuminate\Log\Writer $writer + */ + public function __construct( + ConnectionInterface $connection, + UserCreationService $userCreationService, + DaemonServerRepositoryInterface $daemonRepository, + PermissionCreationService $permissionService, + ServerRepositoryInterface $serverRepository, + SubuserRepositoryInterface $subuserRepository, + UserRepositoryInterface $userRepository, + Writer $writer + ) { + $this->connection = $connection; + $this->daemonRepository = $daemonRepository; + $this->permissionService = $permissionService; + $this->subuserRepository = $subuserRepository; + $this->serverRepository = $serverRepository; + $this->userRepository = $userRepository; + $this->userCreationService = $userCreationService; + $this->writer = $writer; + } + + /** + * @param int|\Pterodactyl\Models\Server $server + * @param string $email + * @param array $permissions + * @return \Pterodactyl\Models\Subuser + * + * @throws \Exception + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException + * @throws \Pterodactyl\Exceptions\Service\Subuser\UserIsServerOwnerException + */ + public function handle($server, $email, array $permissions) + { + if (! $server instanceof Server) { + $server = $this->serverRepository->find($server); + } + + $this->connection->beginTransaction(); + try { + $user = $this->userRepository->findFirstWhere([['email', '=', $email]]); + + if ($server->owner_id === $user->id) { + throw new UserIsServerOwnerException(trans('exceptions.subusers.user_is_owner')); + } + + $subuserCount = $this->subuserRepository->findCountWhere([['user_id', '=', $user->id], ['server_id', '=', $server->id]]); + if ($subuserCount !== 0) { + throw new ServerSubuserExistsException(trans('exceptions.subusers.subuser_exists')); + } + } catch (RecordNotFoundException $exception) { + $user = $this->userCreationService->handle([ + 'email' => $email, + 'username' => substr(strtok($email, '@'), 0, 8) . '_' . str_random(6), + 'name_first' => 'Server', + 'name_last' => 'Subuser', + 'root_admin' => false, + ]); + } + + $subuser = $this->subuserRepository->create([ + 'user_id' => $user->id, + 'server_id' => $server->id, + 'daemonSecret' => bin2hex(random_bytes(self::DAEMON_SECRET_BYTES)), + ]); + + $daemonPermissions = $this->permissionService->handle($subuser->id, $permissions); + + try { + $this->daemonRepository->setNode($server->node_id)->setAccessServer($server->uuid) + ->setSubuserKey($subuser->daemonSecret, $daemonPermissions); + $this->connection->commit(); + + return $subuser; + } catch (RequestException $exception) { + $this->connection->rollBack(); + $this->writer->warning($exception); + + $response = $exception->getResponse(); + throw new DisplayException(trans('exceptions.daemon_connection_failed', [ + 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), + ])); + } + } +} diff --git a/app/Services/Subusers/SubuserDeletionService.php b/app/Services/Subusers/SubuserDeletionService.php new file mode 100644 index 000000000..97b723a50 --- /dev/null +++ b/app/Services/Subusers/SubuserDeletionService.php @@ -0,0 +1,108 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Subusers; + +use Illuminate\Log\Writer; +use GuzzleHttp\Exception\RequestException; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; +use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; + +class SubuserDeletionService +{ + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected $connection; + + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface + */ + protected $daemonRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface + */ + protected $repository; + + /** + * @var \Illuminate\Log\Writer + */ + protected $writer; + + /** + * SubuserDeletionService constructor. + * + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonRepository + * @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $repository + * @param \Illuminate\Log\Writer $writer + */ + public function __construct( + ConnectionInterface $connection, + DaemonServerRepositoryInterface $daemonRepository, + SubuserRepositoryInterface $repository, + Writer $writer + ) { + $this->connection = $connection; + $this->daemonRepository = $daemonRepository; + $this->repository = $repository; + $this->writer = $writer; + } + + /** + * Delete a subuser and their associated permissions from the Panel and Daemon. + * + * @param int $subuser + * @return int|null + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle($subuser) + { + $subuser = $this->repository->getWithServer($subuser); + + $this->connection->beginTransaction(); + $response = $this->repository->delete($subuser->id); + + try { + $this->daemonRepository->setNode($subuser->server->node_id)->setAccessServer($subuser->server->uuid) + ->setSubuserKey($subuser->daemonSecret, []); + $this->connection->commit(); + + return $response; + } catch (RequestException $exception) { + $this->connection->rollBack(); + $this->writer->warning($exception); + + $response = $exception->getResponse(); + throw new DisplayException(trans('exceptions.daemon_connection_failed', [ + 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), + ])); + } + } +} diff --git a/app/Services/Subusers/SubuserUpdateService.php b/app/Services/Subusers/SubuserUpdateService.php new file mode 100644 index 000000000..c11c551e9 --- /dev/null +++ b/app/Services/Subusers/SubuserUpdateService.php @@ -0,0 +1,125 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Subusers; + +use Illuminate\Log\Writer; +use GuzzleHttp\Exception\RequestException; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; +use Pterodactyl\Contracts\Repository\PermissionRepositoryInterface; +use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; + +class SubuserUpdateService +{ + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected $connection; + + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface + */ + protected $daemonRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\PermissionRepositoryInterface + */ + protected $permissionRepository; + + /** + * @var \Pterodactyl\Services\Subusers\PermissionCreationService + */ + protected $permissionService; + + /** + * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface + */ + protected $repository; + + /** + * @var \Illuminate\Log\Writer + */ + protected $writer; + + /** + * SubuserUpdateService constructor. + * + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonRepository + * @param \Pterodactyl\Services\Subusers\PermissionCreationService $permissionService + * @param \Pterodactyl\Contracts\Repository\PermissionRepositoryInterface $permissionRepository + * @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $repository + * @param \Illuminate\Log\Writer $writer + */ + public function __construct( + ConnectionInterface $connection, + DaemonServerRepositoryInterface $daemonRepository, + PermissionCreationService $permissionService, + PermissionRepositoryInterface $permissionRepository, + SubuserRepositoryInterface $repository, + Writer $writer + ) { + $this->connection = $connection; + $this->daemonRepository = $daemonRepository; + $this->permissionRepository = $permissionRepository; + $this->permissionService = $permissionService; + $this->repository = $repository; + $this->writer = $writer; + } + + /** + * Update permissions for a given subuser. + * + * @param int $subuser + * @param array $permissions + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle($subuser, array $permissions) + { + $subuser = $this->repository->getWithServer($subuser); + + $this->connection->beginTransaction(); + $this->permissionRepository->deleteWhere([['subuser_id', '=', $subuser->id]]); + $daemonPermissions = $this->permissionService->handle($subuser->id, $permissions); + + try { + $this->daemonRepository->setNode($subuser->server->node_id)->setAccessServer($subuser->server->uuid) + ->setSubuserKey($subuser->daemonSecret, $daemonPermissions); + $this->connection->commit(); + } catch (RequestException $exception) { + $this->connection->rollBack(); + $this->writer->warning($exception); + + $response = $exception->getResponse(); + throw new DisplayException(trans('exceptions.daemon_connection_failed', [ + 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), + ])); + } + } +} diff --git a/app/Services/Tasks/TaskCreationService.php b/app/Services/Tasks/TaskCreationService.php new file mode 100644 index 000000000..d7549bf02 --- /dev/null +++ b/app/Services/Tasks/TaskCreationService.php @@ -0,0 +1,108 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Tasks; + +use Pterodactyl\Models\Server; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Contracts\Repository\TaskRepositoryInterface; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; + +class TaskCreationService +{ + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected $connection; + + /** + * @var \Pterodactyl\Contracts\Repository\TaskRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $serverRepository; + + /** + * TaskCreationService constructor. + * + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository + * @param \Pterodactyl\Contracts\Repository\TaskRepositoryInterface $repository + */ + public function __construct( + ConnectionInterface $connection, + ServerRepositoryInterface $serverRepository, + TaskRepositoryInterface $repository + ) { + $this->connection = $connection; + $this->repository = $repository; + $this->serverRepository = $serverRepository; + } + + /** + * @param int|\Pterodactyl\Models\Server $server + * @param array $data + * @param array|null $chain + * @return \Pterodactyl\Models\Task + * + * @throws \Exception + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle($server, array $data, array $chain = null) + { + if (! $server instanceof Server) { + $server = $this->serverRepository->find($server); + } + + $this->connection->beginTransaction(); + + $data['server_id'] = $server->id; + $task = $this->repository->create($data); + + if (is_array($chain)) { + foreach ($chain as $index => $values) { + if ($values['time_interval'] === 'm' && $values['time_value'] > 15) { + throw new \Exception('I should fix this.'); + } + + $delay = $values['time_interval'] === 'm' ? $values['time_value'] * 60 : $values['time_value']; + $this->repository->withoutFresh()->create([ + 'parent_task_id' => $task->id, + 'chain_order' => $index + 1, + 'server_id' => $server->id, + 'action' => $values['action'], + 'data' => $values['payload'], + 'chain_delay' => $delay, + ]); + } + } + $this->connection->commit(); + + return $task; + } +} diff --git a/app/Services/Tasks/TaskUpdateService.php b/app/Services/Tasks/TaskUpdateService.php new file mode 100644 index 000000000..af227fdcf --- /dev/null +++ b/app/Services/Tasks/TaskUpdateService.php @@ -0,0 +1,47 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Tasks; + +use Pterodactyl\Contracts\Repository\TaskRepositoryInterface; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; + +class TaskUpdateService +{ + protected $repository; + + protected $serverRepository; + + public function __construct( + ServerRepositoryInterface $serverRepository, + TaskRepositoryInterface $repository + ) { + $this->repository = $repository; + $this->serverRepository = $serverRepository; + } + + public function handle($server, array $data, array $chain = null) + { + } +} diff --git a/app/Services/Users/ToggleTwoFactorService.php b/app/Services/Users/ToggleTwoFactorService.php new file mode 100644 index 000000000..9de97c364 --- /dev/null +++ b/app/Services/Users/ToggleTwoFactorService.php @@ -0,0 +1,84 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Users; + +use Pterodactyl\Models\User; +use PragmaRX\Google2FA\Contracts\Google2FA; +use Pterodactyl\Contracts\Repository\UserRepositoryInterface; +use Pterodactyl\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid; + +class ToggleTwoFactorService +{ + /** + * @var \PragmaRX\Google2FA\Contracts\Google2FA + */ + protected $google2FA; + + /** + * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface + */ + protected $repository; + + /** + * ToggleTwoFactorService constructor. + * + * @param \PragmaRX\Google2FA\Contracts\Google2FA $google2FA + * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository + */ + public function __construct( + Google2FA $google2FA, + UserRepositoryInterface $repository + ) { + $this->google2FA = $google2FA; + $this->repository = $repository; + } + + /** + * @param int|\Pterodactyl\Models\User $user + * @param string $token + * @param null|bool $toggleState + * @return bool + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid + */ + public function handle($user, $token, $toggleState = null) + { + if (! $user instanceof User) { + $user = $this->repository->find($user); + } + + if (! $this->google2FA->verifyKey($user->totp_secret, $token, 2)) { + throw new TwoFactorAuthenticationTokenInvalid; + } + + $this->repository->withoutFresh()->update($user->id, [ + 'use_totp' => (is_null($toggleState) ? ! $user->use_totp : $toggleState), + ]); + + return true; + } +} diff --git a/app/Services/Users/TwoFactorSetupService.php b/app/Services/Users/TwoFactorSetupService.php new file mode 100644 index 000000000..e9b269554 --- /dev/null +++ b/app/Services/Users/TwoFactorSetupService.php @@ -0,0 +1,91 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Users; + +use Pterodactyl\Models\User; +use PragmaRX\Google2FA\Contracts\Google2FA; +use Pterodactyl\Contracts\Repository\UserRepositoryInterface; +use Illuminate\Contracts\Config\Repository as ConfigRepository; + +class TwoFactorSetupService +{ + /** + * @var \Illuminate\Contracts\Config\Repository + */ + protected $config; + + /** + * @var \PragmaRX\Google2FA\Contracts\Google2FA + */ + protected $google2FA; + + /** + * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface + */ + protected $repository; + + /** + * TwoFactorSetupService constructor. + * + * @param \Illuminate\Contracts\Config\Repository $config + * @param \PragmaRX\Google2FA\Contracts\Google2FA $google2FA + * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository + */ + public function __construct( + ConfigRepository $config, + Google2FA $google2FA, + UserRepositoryInterface $repository + ) { + $this->config = $config; + $this->google2FA = $google2FA; + $this->repository = $repository; + } + + /** + * Generate a 2FA token and store it in the database. + * + * @param int|\Pterodactyl\Models\User $user + * @return array + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle($user) + { + if (! $user instanceof User) { + $user = $this->repository->find($user); + } + + $secret = $this->google2FA->generateSecretKey(); + $image = $this->google2FA->getQRCodeGoogleUrl($this->config->get('app.name'), $user->email, $secret); + + $this->repository->withoutFresh()->update($user->id, ['totp_secret' => $secret]); + + return [ + 'qrImage' => $image, + 'secret' => $secret, + ]; + } +} diff --git a/app/Services/Users/UserCreationService.php b/app/Services/Users/UserCreationService.php new file mode 100644 index 000000000..f0eee69b9 --- /dev/null +++ b/app/Services/Users/UserCreationService.php @@ -0,0 +1,128 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Users; + +use Illuminate\Foundation\Application; +use Illuminate\Contracts\Hashing\Hasher; +use Illuminate\Database\ConnectionInterface; +use Illuminate\Notifications\ChannelManager; +use Pterodactyl\Notifications\AccountCreated; +use Pterodactyl\Services\Helpers\TemporaryPasswordService; +use Pterodactyl\Contracts\Repository\UserRepositoryInterface; + +class UserCreationService +{ + /** + * @var \Illuminate\Foundation\Application + */ + protected $app; + + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected $connection; + + /** + * @var \Illuminate\Contracts\Hashing\Hasher + */ + protected $hasher; + + /** + * @var \Illuminate\Notifications\ChannelManager + */ + protected $notification; + + /** + * @var \Pterodactyl\Services\Helpers\TemporaryPasswordService + */ + protected $passwordService; + + /** + * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface + */ + protected $repository; + + /** + * CreationService constructor. + * + * @param \Illuminate\Foundation\Application $application + * @param \Illuminate\Notifications\ChannelManager $notification + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Illuminate\Contracts\Hashing\Hasher $hasher + * @param \Pterodactyl\Services\Helpers\TemporaryPasswordService $passwordService + * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository + */ + public function __construct( + Application $application, + ChannelManager $notification, + ConnectionInterface $connection, + Hasher $hasher, + TemporaryPasswordService $passwordService, + UserRepositoryInterface $repository + ) { + $this->app = $application; + $this->connection = $connection; + $this->hasher = $hasher; + $this->notification = $notification; + $this->passwordService = $passwordService; + $this->repository = $repository; + } + + /** + * Create a new user on the system. + * + * @param array $data + * @return \Pterodactyl\Models\User + * + * @throws \Exception + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function handle(array $data) + { + if (array_key_exists('password', $data) && ! empty($data['password'])) { + $data['password'] = $this->hasher->make($data['password']); + } + + $this->connection->beginTransaction(); + if (! isset($data['password']) || empty($data['password'])) { + $data['password'] = $this->hasher->make(str_random(30)); + $token = $this->passwordService->generateReset($data['email']); + } + + $user = $this->repository->create($data); + $this->connection->commit(); + + // @todo fire event, handle notification there + $this->notification->send($user, $this->app->makeWith(AccountCreated::class, [ + 'user' => [ + 'name' => $user->name_first, + 'username' => $user->username, + 'token' => $token ?? null, + ], + ])); + + return $user; + } +} diff --git a/app/Services/Users/UserDeletionService.php b/app/Services/Users/UserDeletionService.php new file mode 100644 index 000000000..6a4b38e73 --- /dev/null +++ b/app/Services/Users/UserDeletionService.php @@ -0,0 +1,88 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Users; + +use Pterodactyl\Models\User; +use Pterodactyl\Exceptions\DisplayException; +use Illuminate\Contracts\Translation\Translator; +use Pterodactyl\Contracts\Repository\UserRepositoryInterface; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; + +class UserDeletionService +{ + /** + * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface + */ + protected $repository; + + /** + * @var \Illuminate\Contracts\Translation\Translator + */ + protected $translator; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $serverRepository; + + /** + * DeletionService constructor. + * + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository + * @param \Illuminate\Contracts\Translation\Translator $translator + * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository + */ + public function __construct( + ServerRepositoryInterface $serverRepository, + Translator $translator, + UserRepositoryInterface $repository + ) { + $this->repository = $repository; + $this->translator = $translator; + $this->serverRepository = $serverRepository; + } + + /** + * Delete a user from the panel only if they have no servers attached to their account. + * + * @param int|\Pterodactyl\Models\User $user + * @return bool|null + * + * @throws \Pterodactyl\Exceptions\DisplayException + */ + public function handle($user) + { + if ($user instanceof User) { + $user = $user->id; + } + + $servers = $this->serverRepository->withColumns('id')->findCountWhere([['owner_id', '=', $user]]); + if ($servers > 0) { + throw new DisplayException($this->translator->trans('admin/user.exceptions.user_has_servers')); + } + + return $this->repository->delete($user); + } +} diff --git a/app/Services/Users/UserUpdateService.php b/app/Services/Users/UserUpdateService.php new file mode 100644 index 000000000..99ce63667 --- /dev/null +++ b/app/Services/Users/UserUpdateService.php @@ -0,0 +1,74 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Services\Users; + +use Illuminate\Contracts\Hashing\Hasher; +use Pterodactyl\Contracts\Repository\UserRepositoryInterface; + +class UserUpdateService +{ + /** + * @var \Illuminate\Contracts\Hashing\Hasher + */ + protected $hasher; + + /** + * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface + */ + protected $repository; + + /** + * UpdateService constructor. + * + * @param \Illuminate\Contracts\Hashing\Hasher $hasher + * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository + */ + public function __construct( + Hasher $hasher, + UserRepositoryInterface $repository + ) { + $this->hasher = $hasher; + $this->repository = $repository; + } + + /** + * Update the user model instance. + * + * @param int $id + * @param array $data + * @return mixed + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle($id, array $data) + { + if (isset($data['password'])) { + $data['password'] = $this->hasher->make($data['password']); + } + + return $this->repository->update($id, $data); + } +} diff --git a/app/Services/VersionService.php b/app/Services/VersionService.php deleted file mode 100644 index e3eaf8ade..000000000 --- a/app/Services/VersionService.php +++ /dev/null @@ -1,135 +0,0 @@ -. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -namespace Pterodactyl\Services; - -use Cache; -use GuzzleHttp\Client; - -class VersionService -{ - /** - * The cached CDN response. - * - * @var object - */ - protected static $versions; - - /** - * Version constructor. - * - * @return void - */ - public function __construct() - { - self::$versions = Cache::remember('versions', config('pterodactyl.cdn.cache'), function () { - $client = new Client(); - - try { - $response = $client->request('GET', config('pterodactyl.cdn.url')); - - if ($response->getStatusCode() === 200) { - return json_decode($response->getBody()); - } else { - throw new \Exception('Invalid response code.'); - } - } catch (\Exception $ex) { - // Failed request, just return errored version. - return (object) [ - 'panel' => 'error', - 'daemon' => 'error', - 'discord' => 'https://pterodactyl.io/discord', - ]; - } - }); - } - - /** - * Return current panel version from CDN. - * - * @return string - */ - public static function getPanel() - { - return self::$versions->panel; - } - - /** - * Return current daemon version from CDN. - * - * @return string - */ - public static function getDaemon() - { - return self::$versions->daemon; - } - - /** - * Return Discord link from CDN. - * - * @return string - */ - public static function getDiscord() - { - return self::$versions->discord; - } - - /** - * Return current panel version. - * - * @return null|string - */ - public function getCurrentPanel() - { - return config('app.version'); - } - - /** - * Determine if panel is latest version. - * - * @return bool - */ - public static function isLatestPanel() - { - if (config('app.version') === 'canary') { - return true; - } - - return version_compare(config('app.version'), self::$versions->panel) >= 0; - } - - /** - * Determine if daemon is latest version. - * - * @return bool - */ - public static function isLatestDaemon($daemon) - { - if ($daemon === '0.0.0-canary') { - return true; - } - - return version_compare($daemon, self::$versions->daemon) >= 0; - } -} diff --git a/app/Traits/Controllers/JavascriptInjection.php b/app/Traits/Controllers/JavascriptInjection.php new file mode 100644 index 000000000..5a8f0b337 --- /dev/null +++ b/app/Traits/Controllers/JavascriptInjection.php @@ -0,0 +1,64 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Traits\Controllers; + +use Javascript; + +trait JavascriptInjection +{ + /** + * @var \Illuminate\Contracts\Session\Session + */ + protected $session; + + /** + * Injects server javascript into the page to be used by other services. + * + * @param array $args + * @param bool $overwrite + * @return mixed + */ + public function injectJavascript($args = [], $overwrite = false) + { + $server = $this->session->get('server_data.model'); + $token = $this->session->get('server_data.token'); + + $response = array_merge([ + 'server' => [ + 'uuid' => $server->uuid, + 'uuidShort' => $server->uuidShort, + 'daemonSecret' => $token, + 'username' => $server->username, + ], + 'node' => [ + 'fqdn' => $server->node->fqdn, + 'scheme' => $server->node->scheme, + 'daemonListen' => $server->node->daemonListen, + ], + ], $args); + + return Javascript::put($overwrite ? $args : $response); + } +} diff --git a/app/Traits/Services/CreatesServiceIndex.php b/app/Traits/Services/CreatesServiceIndex.php new file mode 100644 index 000000000..dcff053b0 --- /dev/null +++ b/app/Traits/Services/CreatesServiceIndex.php @@ -0,0 +1,71 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Traits\Services; + +trait CreatesServiceIndex +{ + /** + * Returns the default index.js file that is used for services on the daemon. + * + * @return string + */ + public function getIndexScript() + { + return <<<'EOF' +'use strict'; + +/** + * Pterodactyl - Daemon + * Copyright (c) 2015 - 2017 Dane Everitt + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +const rfr = require('rfr'); +const _ = require('lodash'); + +const Core = rfr('src/services/index.js'); + +class Service extends Core {} + +module.exports = Service; +EOF; + } +} diff --git a/app/Transformers/Admin/AllocationTransformer.php b/app/Transformers/Admin/AllocationTransformer.php index e7bd15c36..246d1a75c 100644 --- a/app/Transformers/Admin/AllocationTransformer.php +++ b/app/Transformers/Admin/AllocationTransformer.php @@ -47,9 +47,8 @@ class AllocationTransformer extends TransformerAbstract /** * Setup request object for transformer. * - * @param \Illuminate\Http\Request|bool $request - * @param bool $filter - * @return void + * @param \Illuminate\Http\Request|bool $request + * @param bool $filter */ public function __construct($request = false, $filter = false) { diff --git a/app/Transformers/Admin/LocationTransformer.php b/app/Transformers/Admin/LocationTransformer.php index f3fd95885..c3277ea19 100644 --- a/app/Transformers/Admin/LocationTransformer.php +++ b/app/Transformers/Admin/LocationTransformer.php @@ -50,8 +50,7 @@ class LocationTransformer extends TransformerAbstract /** * Setup request object for transformer. * - * @param \Illuminate\Http\Request|bool $request - * @return void + * @param \Illuminate\Http\Request|bool $request */ public function __construct($request = false) { diff --git a/app/Transformers/Admin/NodeTransformer.php b/app/Transformers/Admin/NodeTransformer.php index d18b64f23..bba4162aa 100644 --- a/app/Transformers/Admin/NodeTransformer.php +++ b/app/Transformers/Admin/NodeTransformer.php @@ -51,8 +51,7 @@ class NodeTransformer extends TransformerAbstract /** * Setup request object for transformer. * - * @param \Illuminate\Http\Request|bool $request - * @return void + * @param \Illuminate\Http\Request|bool $request */ public function __construct($request = false) { diff --git a/app/Transformers/Admin/OptionTransformer.php b/app/Transformers/Admin/OptionTransformer.php index 5b86b53d6..b0836fea5 100644 --- a/app/Transformers/Admin/OptionTransformer.php +++ b/app/Transformers/Admin/OptionTransformer.php @@ -52,8 +52,7 @@ class OptionTransformer extends TransformerAbstract /** * Setup request object for transformer. * - * @param \Illuminate\Http\Request|bool $request - * @return void + * @param \Illuminate\Http\Request|bool $request */ public function __construct($request = false) { diff --git a/app/Transformers/Admin/PackTransformer.php b/app/Transformers/Admin/PackTransformer.php index 8d059faaf..e1239c0fd 100644 --- a/app/Transformers/Admin/PackTransformer.php +++ b/app/Transformers/Admin/PackTransformer.php @@ -50,8 +50,7 @@ class PackTransformer extends TransformerAbstract /** * Setup request object for transformer. * - * @param \Illuminate\Http\Request|bool $request - * @return void + * @param \Illuminate\Http\Request|bool $request */ public function __construct($request = false) { diff --git a/app/Transformers/Admin/ServerTransformer.php b/app/Transformers/Admin/ServerTransformer.php index 4d94b7e10..8a9bf3a6b 100644 --- a/app/Transformers/Admin/ServerTransformer.php +++ b/app/Transformers/Admin/ServerTransformer.php @@ -57,8 +57,7 @@ class ServerTransformer extends TransformerAbstract /** * Setup request object for transformer. * - * @param \Illuminate\Http\Request|bool $request - * @return void + * @param \Illuminate\Http\Request|bool $request */ public function __construct($request = false) { diff --git a/app/Transformers/Admin/ServerVariableTransformer.php b/app/Transformers/Admin/ServerVariableTransformer.php index 3211e0295..120f8ef30 100644 --- a/app/Transformers/Admin/ServerVariableTransformer.php +++ b/app/Transformers/Admin/ServerVariableTransformer.php @@ -47,8 +47,7 @@ class ServerVariableTransformer extends TransformerAbstract /** * Setup request object for transformer. * - * @param \Illuminate\Http\Request|bool $request - * @return void + * @param \Illuminate\Http\Request|bool $request */ public function __construct($request = false) { diff --git a/app/Transformers/Admin/ServiceTransformer.php b/app/Transformers/Admin/ServiceTransformer.php index 2df1fc8cc..57639df90 100644 --- a/app/Transformers/Admin/ServiceTransformer.php +++ b/app/Transformers/Admin/ServiceTransformer.php @@ -51,8 +51,7 @@ class ServiceTransformer extends TransformerAbstract /** * Setup request object for transformer. * - * @param \Illuminate\Http\Request|bool $request - * @return void + * @param \Illuminate\Http\Request|bool $request */ public function __construct($request = false) { diff --git a/app/Transformers/Admin/ServiceVariableTransformer.php b/app/Transformers/Admin/ServiceVariableTransformer.php index aa10428d9..190a08a67 100644 --- a/app/Transformers/Admin/ServiceVariableTransformer.php +++ b/app/Transformers/Admin/ServiceVariableTransformer.php @@ -47,8 +47,7 @@ class ServiceVariableTransformer extends TransformerAbstract /** * Setup request object for transformer. * - * @param \Illuminate\Http\Request|bool $request - * @return void + * @param \Illuminate\Http\Request|bool $request */ public function __construct($request = false) { diff --git a/app/Transformers/Admin/SubuserTransformer.php b/app/Transformers/Admin/SubuserTransformer.php index 129da7ad3..cfaf28b77 100644 --- a/app/Transformers/Admin/SubuserTransformer.php +++ b/app/Transformers/Admin/SubuserTransformer.php @@ -41,8 +41,7 @@ class SubuserTransformer extends TransformerAbstract /** * Setup request object for transformer. * - * @param \Illuminate\Http\Request|bool $request - * @return void + * @param \Illuminate\Http\Request|bool $request */ public function __construct($request = false) { diff --git a/app/Transformers/Admin/UserTransformer.php b/app/Transformers/Admin/UserTransformer.php index 0d26961b9..2cbc37e77 100644 --- a/app/Transformers/Admin/UserTransformer.php +++ b/app/Transformers/Admin/UserTransformer.php @@ -50,8 +50,7 @@ class UserTransformer extends TransformerAbstract /** * Setup request object for transformer. * - * @param \Illuminate\Http\Request|bool $request - * @return void + * @param \Illuminate\Http\Request|bool $request */ public function __construct($request = false) { diff --git a/app/Transformers/User/AllocationTransformer.php b/app/Transformers/User/AllocationTransformer.php index 0fd0be453..eb0f28c94 100644 --- a/app/Transformers/User/AllocationTransformer.php +++ b/app/Transformers/User/AllocationTransformer.php @@ -39,8 +39,6 @@ class AllocationTransformer extends TransformerAbstract /** * Setup allocation transformer with access to server data. - * - * @return void */ public function __construct(Server $server) { diff --git a/app/helpers.php b/app/helpers.php new file mode 100644 index 000000000..0b51c7f06 --- /dev/null +++ b/app/helpers.php @@ -0,0 +1,47 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +if (! function_exists('human_readable')) { + /** + * Generate a human-readable filesize for a given file path. + * + * @param string $path + * @param int $precision + * @return string + */ + function human_readable($path, $precision = 2) + { + if (is_numeric($path)) { + $i = 0; + while (($path / 1024) > 0.9) { + $path = $path / 1024; + ++$i; + } + + return round($path, $precision) . ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'][$i]; + } + + return app('file')->humanReadableSize($path, $precision); + } +} diff --git a/composer.json b/composer.json index 3eb02fb85..a81baeec8 100644 --- a/composer.json +++ b/composer.json @@ -11,44 +11,52 @@ } ], "require": { - "php": ">=7.0.0", - "aws/aws-sdk-php": "3.29.7", - "barryvdh/laravel-debugbar": "2.4.0", - "daneeveritt/login-notifications": "1.0.0", - "doctrine/dbal": "2.5.12", - "edvinaskrucas/settings": "2.0.0", + "php": ">=7.0", "ext-mbstring": "*", - "ext-zip": "*", "ext-pdo_mysql": "*", - "fideloper/proxy": "3.3.3", - "guzzlehttp/guzzle": "6.2.3", - "igaster/laravel-theme": "1.16.0", - "laracasts/utilities": "2.1.0", + "ext-zip": "*", + "aws/aws-sdk-php": "^3.29", + "daneeveritt/login-notifications": "^1.0", + "doctrine/dbal": "^2.5", + "edvinaskrucas/settings": "^2.0", + "fideloper/proxy": "^3.3", + "guzzlehttp/guzzle": "~6.3.0", + "hashids/hashids": "^2.0", + "igaster/laravel-theme": "^1.16", + "laracasts/utilities": "^3.0", "laravel/framework": "5.4.27", "laravel/tinker": "1.0.1", - "lord/laroute": "2.4.4", - "mtdowling/cron-expression": "1.2.0", - "nesbot/carbon": "1.22.1", - "nicolaslopezj/searchable": "1.9.5", - "pragmarx/google2fa": "1.0.1", - "predis/predis": "1.1.1", - "prologue/alerts": "0.4.1", - "s1lentium/iptools": "1.1.0", - "spatie/laravel-fractal": "4.0.1", - "webpatser/laravel-uuid": "2.0.1" + "lord/laroute": "~2.4.5", + "mtdowling/cron-expression": "^1.2", + "nesbot/carbon": "^1.22", + "nicolaslopezj/searchable": "^1.9", + "pragmarx/google2fa": "^1.0", + "predis/predis": "^1.1", + "prologue/alerts": "^0.4", + "ramsey/uuid": "^3.7", + "s1lentium/iptools": "^1.1", + "sofa/eloquence": "~5.4.1", + "spatie/laravel-fractal": "^4.0", + "watson/validating": "^3.0", + "webmozart/assert": "^1.2", + "webpatser/laravel-uuid": "^2.0" }, "require-dev": { - "barryvdh/laravel-ide-helper": "^2.3", - "friendsofphp/php-cs-fixer": "1.*", - "fzaninotto/faker": "~1.4", - "mockery/mockery": "0.9.*", - "phpunit/phpunit": "~5.7", - "sllh/php-cs-fixer-styleci-bridge": "^2.1" + "barryvdh/laravel-debugbar": "^2.4", + "barryvdh/laravel-ide-helper": "^2.4", + "friendsofphp/php-cs-fixer": "~2.4.0", + "fzaninotto/faker": "^1.6", + "mockery/mockery": "^0.9", + "php-mock/php-mock-phpunit": "^1.1", + "phpunit/phpunit": "^5.7" }, "autoload": { "classmap": [ "database" ], + "files": [ + "app/helpers.php" + ], "psr-4": { "Pterodactyl\\": "app/" } @@ -78,6 +86,9 @@ }, "prefer-stable": true, "config": { + "platform": { + "php": "7.0" + }, "preferred-install": "dist", "sort-packages": true, "optimize-autoloader": true diff --git a/composer.lock b/composer.lock index 4fcf88be1..5b4809a02 100644 --- a/composer.lock +++ b/composer.lock @@ -4,23 +4,27 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "c82fadb556adb7d7f4405c35dee2ef64", + "content-hash": "15a4dc6de122bc1e47d1d9ca3b1224d6", "packages": [ { "name": "aws/aws-sdk-php", - "version": "3.29.7", + "version": "3.36.0", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "76540001ff938c072db5367a7c945296984b999b" + "reference": "69321675769dd3e3d00a94bfdc747bd3a5b75b3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/76540001ff938c072db5367a7c945296984b999b", - "reference": "76540001ff938c072db5367a7c945296984b999b", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/69321675769dd3e3d00a94bfdc747bd3a5b75b3b", + "reference": "69321675769dd3e3d00a94bfdc747bd3a5b75b3b", "shasum": "" }, "require": { + "ext-json": "*", + "ext-pcre": "*", + "ext-simplexml": "*", + "ext-spl": "*", "guzzlehttp/guzzle": "^5.3.1|^6.2.1", "guzzlehttp/promises": "~1.0", "guzzlehttp/psr7": "^1.4.1", @@ -33,11 +37,7 @@ "behat/behat": "~3.0", "doctrine/cache": "~1.4", "ext-dom": "*", - "ext-json": "*", "ext-openssl": "*", - "ext-pcre": "*", - "ext-simplexml": "*", - "ext-spl": "*", "nette/neon": "^2.3", "phpunit/phpunit": "^4.8.35|^5.4.0", "psr/cache": "^1.0" @@ -84,69 +84,7 @@ "s3", "sdk" ], - "time": "2017-06-16T17:29:33+00:00" - }, - { - "name": "barryvdh/laravel-debugbar", - "version": "v2.4.0", - "source": { - "type": "git", - "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "de15d00a74696db62e1b4782474c27ed0c4fc763" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/de15d00a74696db62e1b4782474c27ed0c4fc763", - "reference": "de15d00a74696db62e1b4782474c27ed0c4fc763", - "shasum": "" - }, - "require": { - "illuminate/support": "5.1.*|5.2.*|5.3.*|5.4.*|5.5.*", - "maximebf/debugbar": "~1.13.0", - "php": ">=5.5.9", - "symfony/finder": "~2.7|~3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.4-dev" - }, - "laravel": { - "providers": [ - "Barryvdh\\Debugbar\\ServiceProvider" - ], - "aliases": { - "Debugbar": "Barryvdh\\Debugbar\\Facade" - } - } - }, - "autoload": { - "psr-4": { - "Barryvdh\\Debugbar\\": "src/" - }, - "files": [ - "src/helpers.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Barry vd. Heuvel", - "email": "barryvdh@gmail.com" - } - ], - "description": "PHP Debugbar integration for Laravel", - "keywords": [ - "debug", - "debugbar", - "laravel", - "profiler", - "webprofiler" - ], - "time": "2017-06-01T17:46:08+00:00" + "time": "2017-09-01T22:52:38+00:00" }, { "name": "christian-riesen/base32", @@ -350,16 +288,16 @@ }, { "name": "doctrine/cache", - "version": "v1.6.1", + "version": "v1.6.2", "source": { "type": "git", "url": "https://github.com/doctrine/cache.git", - "reference": "b6f544a20f4807e81f7044d31e679ccbb1866dc3" + "reference": "eb152c5100571c7a45470ff2a35095ab3f3b900b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/b6f544a20f4807e81f7044d31e679ccbb1866dc3", - "reference": "b6f544a20f4807e81f7044d31e679ccbb1866dc3", + "url": "https://api.github.com/repos/doctrine/cache/zipball/eb152c5100571c7a45470ff2a35095ab3f3b900b", + "reference": "eb152c5100571c7a45470ff2a35095ab3f3b900b", "shasum": "" }, "require": { @@ -416,7 +354,7 @@ "cache", "caching" ], - "time": "2016-10-29T11:16:17+00:00" + "time": "2017-07-22T12:49:21+00:00" }, { "name": "doctrine/collections", @@ -487,16 +425,16 @@ }, { "name": "doctrine/common", - "version": "v2.7.2", + "version": "v2.7.3", "source": { "type": "git", "url": "https://github.com/doctrine/common.git", - "reference": "930297026c8009a567ac051fd545bf6124150347" + "reference": "4acb8f89626baafede6ee5475bc5844096eba8a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/common/zipball/930297026c8009a567ac051fd545bf6124150347", - "reference": "930297026c8009a567ac051fd545bf6124150347", + "url": "https://api.github.com/repos/doctrine/common/zipball/4acb8f89626baafede6ee5475bc5844096eba8a9", + "reference": "4acb8f89626baafede6ee5475bc5844096eba8a9", "shasum": "" }, "require": { @@ -556,20 +494,20 @@ "persistence", "spl" ], - "time": "2017-01-13T14:02:13+00:00" + "time": "2017-07-22T08:35:12+00:00" }, { "name": "doctrine/dbal", - "version": "v2.5.12", + "version": "v2.5.13", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "7b9e911f9d8b30d43b96853dab26898c710d8f44" + "reference": "729340d8d1eec8f01bff708e12e449a3415af873" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/7b9e911f9d8b30d43b96853dab26898c710d8f44", - "reference": "7b9e911f9d8b30d43b96853dab26898c710d8f44", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/729340d8d1eec8f01bff708e12e449a3415af873", + "reference": "729340d8d1eec8f01bff708e12e449a3415af873", "shasum": "" }, "require": { @@ -627,37 +565,37 @@ "persistence", "queryobject" ], - "time": "2017-02-08T12:53:47+00:00" + "time": "2017-07-22T20:44:48+00:00" }, { "name": "doctrine/inflector", - "version": "v1.1.0", + "version": "v1.2.0", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "90b2128806bfde671b6952ab8bea493942c1fdae" + "reference": "e11d84c6e018beedd929cff5220969a3c6d1d462" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/90b2128806bfde671b6952ab8bea493942c1fdae", - "reference": "90b2128806bfde671b6952ab8bea493942c1fdae", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/e11d84c6e018beedd929cff5220969a3c6d1d462", + "reference": "e11d84c6e018beedd929cff5220969a3c6d1d462", "shasum": "" }, "require": { - "php": ">=5.3.2" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "4.*" + "phpunit/phpunit": "^6.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "1.2.x-dev" } }, "autoload": { - "psr-0": { - "Doctrine\\Common\\Inflector\\": "lib/" + "psr-4": { + "Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector" } }, "notification-url": "https://packagist.org/downloads/", @@ -694,7 +632,7 @@ "singularize", "string" ], - "time": "2015-11-06T14:35:42+00:00" + "time": "2017-07-22T12:18:28+00:00" }, { "name": "doctrine/lexer", @@ -803,16 +741,16 @@ }, { "name": "erusev/parsedown", - "version": "1.6.2", + "version": "1.6.3", "source": { "type": "git", "url": "https://github.com/erusev/parsedown.git", - "reference": "1bf24f7334fe16c88bf9d467863309ceaf285b01" + "reference": "728952b90a333b5c6f77f06ea9422b94b585878d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/erusev/parsedown/zipball/1bf24f7334fe16c88bf9d467863309ceaf285b01", - "reference": "1bf24f7334fe16c88bf9d467863309ceaf285b01", + "url": "https://api.github.com/repos/erusev/parsedown/zipball/728952b90a333b5c6f77f06ea9422b94b585878d", + "reference": "728952b90a333b5c6f77f06ea9422b94b585878d", "shasum": "" }, "require": { @@ -841,20 +779,20 @@ "markdown", "parser" ], - "time": "2017-03-29T16:04:15+00:00" + "time": "2017-05-14T14:47:48+00:00" }, { "name": "fideloper/proxy", - "version": "3.3.3", + "version": "3.3.4", "source": { "type": "git", "url": "https://github.com/fideloper/TrustedProxy.git", - "reference": "985eac8f966c03b4d9503cad9b5e5a51d41ce477" + "reference": "9cdf6f118af58d89764249bbcc7bb260c132924f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fideloper/TrustedProxy/zipball/985eac8f966c03b4d9503cad9b5e5a51d41ce477", - "reference": "985eac8f966c03b4d9503cad9b5e5a51d41ce477", + "url": "https://api.github.com/repos/fideloper/TrustedProxy/zipball/9cdf6f118af58d89764249bbcc7bb260c132924f", + "reference": "9cdf6f118af58d89764249bbcc7bb260c132924f", "shasum": "" }, "require": { @@ -870,6 +808,11 @@ "extra": { "branch-alias": { "dev-master": "3.3-dev" + }, + "laravel": { + "providers": [ + "Fideloper\\Proxy\\TrustedProxyServiceProvider" + ] } }, "autoload": { @@ -893,20 +836,20 @@ "proxy", "trusted proxy" ], - "time": "2017-05-31T12:50:41+00:00" + "time": "2017-06-15T17:19:42+00:00" }, { "name": "guzzlehttp/guzzle", - "version": "6.2.3", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "8d6c6cc55186db87b7dc5009827429ba4e9dc006" + "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/8d6c6cc55186db87b7dc5009827429ba4e9dc006", - "reference": "8d6c6cc55186db87b7dc5009827429ba4e9dc006", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/f4db5a78a5ea468d4831de7f0bf9d9415e348699", + "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699", "shasum": "" }, "require": { @@ -916,9 +859,12 @@ }, "require-dev": { "ext-curl": "*", - "phpunit/phpunit": "^4.0", + "phpunit/phpunit": "^4.0 || ^5.0", "psr/log": "^1.0" }, + "suggest": { + "psr/log": "Required for using the Log middleware" + }, "type": "library", "extra": { "branch-alias": { @@ -955,7 +901,7 @@ "rest", "web service" ], - "time": "2017-02-28T22:50:30+00:00" + "time": "2017-06-22T18:50:49+00:00" }, { "name": "guzzlehttp/promises", @@ -1073,6 +1019,69 @@ ], "time": "2017-03-20T17:10:46+00:00" }, + { + "name": "hashids/hashids", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/ivanakimov/hashids.php.git", + "reference": "28889ed83cdc91f4a55637daff0fb5c799eb324e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ivanakimov/hashids.php/zipball/28889ed83cdc91f4a55637daff0fb5c799eb324e", + "reference": "28889ed83cdc91f4a55637daff0fb5c799eb324e", + "shasum": "" + }, + "require": { + "ext-bcmath": "*", + "php": "^5.6.4 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "psr-4": { + "Hashids\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ivan Akimov", + "email": "ivan@barreleye.com", + "homepage": "https://twitter.com/IvanAkimov" + }, + { + "name": "Vincent Klaiber", + "email": "hello@vinkla.com", + "homepage": "https://vinkla.com" + } + ], + "description": "Generate short, unique, non-sequential ids (like YouTube and Bitly) from numbers", + "homepage": "http://hashids.org/php", + "keywords": [ + "bitly", + "decode", + "encode", + "hash", + "hashid", + "hashids", + "ids", + "obfuscate", + "youtube" + ], + "time": "2017-01-01T13:33:33+00:00" + }, { "name": "igaster/laravel-theme", "version": "v1.16", @@ -1218,16 +1227,16 @@ }, { "name": "laracasts/utilities", - "version": "2.1", + "version": "3.0", "source": { "type": "git", "url": "https://github.com/laracasts/PHP-Vars-To-Js-Transformer.git", - "reference": "4402a0ed774f8eb36ea7ba169341d9d5b6049378" + "reference": "298fb3c6f29901a4550c4f98b57c05f368341d04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laracasts/PHP-Vars-To-Js-Transformer/zipball/4402a0ed774f8eb36ea7ba169341d9d5b6049378", - "reference": "4402a0ed774f8eb36ea7ba169341d9d5b6049378", + "url": "https://api.github.com/repos/laracasts/PHP-Vars-To-Js-Transformer/zipball/298fb3c6f29901a4550c4f98b57c05f368341d04", + "reference": "298fb3c6f29901a4550c4f98b57c05f368341d04", "shasum": "" }, "require": { @@ -1238,7 +1247,20 @@ "phpspec/phpspec": "~2.0" }, "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laracasts\\Utilities\\JavaScript\\JavaScriptServiceProvider" + ], + "aliases": { + "JavaScript": "Laracasts\\Utilities\\JavaScript\\JavaScriptFacade" + } + } + }, "autoload": { + "files": [ + "src/helpers.php" + ], "psr-4": { "Laracasts\\Utilities\\JavaScript\\": "src/" } @@ -1258,7 +1280,7 @@ "javascript", "laravel" ], - "time": "2015-10-01T05:16:28+00:00" + "time": "2017-09-01T17:25:57+00:00" }, { "name": "laravel/framework", @@ -1454,16 +1476,16 @@ }, { "name": "league/flysystem", - "version": "1.0.40", + "version": "1.0.41", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "3828f0b24e2c1918bb362d57a53205d6dc8fde61" + "reference": "f400aa98912c561ba625ea4065031b7a41e5a155" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/3828f0b24e2c1918bb362d57a53205d6dc8fde61", - "reference": "3828f0b24e2c1918bb362d57a53205d6dc8fde61", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/f400aa98912c561ba625ea4065031b7a41e5a155", + "reference": "f400aa98912c561ba625ea4065031b7a41e5a155", "shasum": "" }, "require": { @@ -1484,13 +1506,13 @@ "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", - "league/flysystem-copy": "Allows you to use Copy.com storage", "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", "league/flysystem-webdav": "Allows you to use WebDAV storage", "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", - "spatie/flysystem-dropbox": "Allows you to use Dropbox storage" + "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", + "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" }, "type": "library", "extra": { @@ -1533,20 +1555,20 @@ "sftp", "storage" ], - "time": "2017-04-28T10:15:08+00:00" + "time": "2017-08-06T17:41:04+00:00" }, { "name": "league/fractal", - "version": "0.16.0", + "version": "0.17.0", "source": { "type": "git", "url": "https://github.com/thephpleague/fractal.git", - "reference": "d0445305e308d9207430680acfd580557b679ddc" + "reference": "a0b350824f22fc2fdde2500ce9d6851a3f275b0e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/fractal/zipball/d0445305e308d9207430680acfd580557b679ddc", - "reference": "d0445305e308d9207430680acfd580557b679ddc", + "url": "https://api.github.com/repos/thephpleague/fractal/zipball/a0b350824f22fc2fdde2500ce9d6851a3f275b0e", + "reference": "a0b350824f22fc2fdde2500ce9d6851a3f275b0e", "shasum": "" }, "require": { @@ -1597,28 +1619,28 @@ "league", "rest" ], - "time": "2017-03-12T01:28:43+00:00" + "time": "2017-06-12T11:04:56+00:00" }, { "name": "lord/laroute", - "version": "v2.4.4", + "version": "2.4.5", "source": { "type": "git", "url": "https://github.com/aaronlord/laroute.git", - "reference": "2adee9daa5491f1ad7b953fc01df36ebc7294c3e" + "reference": "342af22147de90e02d5104447b7c5aea056b5548" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aaronlord/laroute/zipball/2adee9daa5491f1ad7b953fc01df36ebc7294c3e", - "reference": "2adee9daa5491f1ad7b953fc01df36ebc7294c3e", + "url": "https://api.github.com/repos/aaronlord/laroute/zipball/342af22147de90e02d5104447b7c5aea056b5548", + "reference": "342af22147de90e02d5104447b7c5aea056b5548", "shasum": "" }, "require": { - "illuminate/config": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*", - "illuminate/console": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*", - "illuminate/filesystem": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*", - "illuminate/routing": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*", - "illuminate/support": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*", + "illuminate/config": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*|5.5.*", + "illuminate/console": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*|5.5.*", + "illuminate/filesystem": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*|5.5.*", + "illuminate/routing": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*|5.5.*", + "illuminate/support": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*|5.5.*", "php": ">=5.4.0" }, "require-dev": { @@ -1648,81 +1670,20 @@ "routes", "routing" ], - "time": "2017-02-08T11:05:52+00:00" - }, - { - "name": "maximebf/debugbar", - "version": "1.13.1", - "source": { - "type": "git", - "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "afee79a236348e39a44cb837106b7c5b4897ac2a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/afee79a236348e39a44cb837106b7c5b4897ac2a", - "reference": "afee79a236348e39a44cb837106b7c5b4897ac2a", - "shasum": "" - }, - "require": { - "php": ">=5.3.0", - "psr/log": "^1.0", - "symfony/var-dumper": "^2.6|^3.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0|^5.0" - }, - "suggest": { - "kriswallsmith/assetic": "The best way to manage assets", - "monolog/monolog": "Log using Monolog", - "predis/predis": "Redis storage" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.13-dev" - } - }, - "autoload": { - "psr-4": { - "DebugBar\\": "src/DebugBar/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Maxime Bouroumeau-Fuseau", - "email": "maxime.bouroumeau@gmail.com", - "homepage": "http://maximebf.com" - }, - { - "name": "Barry vd. Heuvel", - "email": "barryvdh@gmail.com" - } - ], - "description": "Debug bar in the browser for php application", - "homepage": "https://github.com/maximebf/php-debugbar", - "keywords": [ - "debug", - "debugbar" - ], - "time": "2017-01-05T08:46:19+00:00" + "time": "2017-06-29T09:34:21+00:00" }, { "name": "monolog/monolog", - "version": "1.22.1", + "version": "1.23.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "1e044bc4b34e91743943479f1be7a1d5eb93add0" + "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/1e044bc4b34e91743943479f1be7a1d5eb93add0", - "reference": "1e044bc4b34e91743943479f1be7a1d5eb93add0", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fd8c787753b3a2ad11bc60c063cff1358a32a3b4", + "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4", "shasum": "" }, "require": { @@ -1743,7 +1704,7 @@ "phpunit/phpunit-mock-objects": "2.3.0", "ruflin/elastica": ">=0.90 <3.0", "sentry/sentry": "^0.13", - "swiftmailer/swiftmailer": "~5.3" + "swiftmailer/swiftmailer": "^5.3|^6.0" }, "suggest": { "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", @@ -1787,7 +1748,7 @@ "logging", "psr-3" ], - "time": "2017-03-13T07:08:03+00:00" + "time": "2017-06-19T01:22:40+00:00" }, { "name": "mtdowling/cron-expression", @@ -1943,16 +1904,16 @@ }, { "name": "nicolaslopezj/searchable", - "version": "1.9.5", + "version": "1.9.6", "source": { "type": "git", "url": "https://github.com/nicolaslopezj/searchable.git", - "reference": "1351b1b21ae9be9e0f49f375f56488df839723d4" + "reference": "c067f11a993c274c9da0d4a2e70ca07614bb92da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nicolaslopezj/searchable/zipball/1351b1b21ae9be9e0f49f375f56488df839723d4", - "reference": "1351b1b21ae9be9e0f49f375f56488df839723d4", + "url": "https://api.github.com/repos/nicolaslopezj/searchable/zipball/c067f11a993c274c9da0d4a2e70ca07614bb92da", + "reference": "c067f11a993c274c9da0d4a2e70ca07614bb92da", "shasum": "" }, "require": { @@ -1985,20 +1946,20 @@ "search", "searchable" ], - "time": "2016-12-16T21:23:34+00:00" + "time": "2017-08-09T04:37:57+00:00" }, { "name": "nikic/php-parser", - "version": "v3.0.5", + "version": "v3.1.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "2b9e2f71b722f7c53918ab0c25f7646c2013f17d" + "reference": "a1e8e1a30e1352f118feff1a8481066ddc2f234a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/2b9e2f71b722f7c53918ab0c25f7646c2013f17d", - "reference": "2b9e2f71b722f7c53918ab0c25f7646c2013f17d", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a1e8e1a30e1352f118feff1a8481066ddc2f234a", + "reference": "a1e8e1a30e1352f118feff1a8481066ddc2f234a", "shasum": "" }, "require": { @@ -2036,7 +1997,7 @@ "parser", "php" ], - "time": "2017-03-05T18:23:57+00:00" + "time": "2017-09-02T17:10:46+00:00" }, { "name": "paragonie/random_compat", @@ -2346,16 +2307,16 @@ }, { "name": "psy/psysh", - "version": "v0.8.6", + "version": "v0.8.11", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "7028d6d525fb183d50b249b7c07598e3d386b27d" + "reference": "b193cd020e8c6b66cea6457826ae005e94e6d2c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/7028d6d525fb183d50b249b7c07598e3d386b27d", - "reference": "7028d6d525fb183d50b249b7c07598e3d386b27d", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/b193cd020e8c6b66cea6457826ae005e94e6d2c0", + "reference": "b193cd020e8c6b66cea6457826ae005e94e6d2c0", "shasum": "" }, "require": { @@ -2415,20 +2376,20 @@ "interactive", "shell" ], - "time": "2017-06-04T10:34:20+00:00" + "time": "2017-07-29T19:30:02+00:00" }, { "name": "ramsey/uuid", - "version": "3.6.1", + "version": "3.7.0", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "4ae32dd9ab8860a4bbd750ad269cba7f06f7934e" + "reference": "0ef23d1b10cf1bc576e9d865a7e9c47982c5715e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/4ae32dd9ab8860a4bbd750ad269cba7f06f7934e", - "reference": "4ae32dd9ab8860a4bbd750ad269cba7f06f7934e", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/0ef23d1b10cf1bc576e9d865a7e9c47982c5715e", + "reference": "0ef23d1b10cf1bc576e9d865a7e9c47982c5715e", "shasum": "" }, "require": { @@ -2497,7 +2458,7 @@ "identifier", "uuid" ], - "time": "2017-03-26T20:37:53+00:00" + "time": "2017-08-04T13:39:04+00:00" }, { "name": "s1lentium/iptools", @@ -2550,21 +2511,123 @@ "time": "2016-08-21T15:57:09+00:00" }, { - "name": "spatie/fractalistic", - "version": "2.2.0", + "name": "sofa/eloquence", + "version": "5.4.1", "source": { "type": "git", - "url": "https://github.com/spatie/fractalistic.git", - "reference": "8f00c666a8b8dfb06f79286f97255e6ab1c89639" + "url": "https://github.com/jarektkaczyk/eloquence.git", + "reference": "6f821bcf24950b58e633cb0cac7bbee6acb0f34a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/fractalistic/zipball/8f00c666a8b8dfb06f79286f97255e6ab1c89639", - "reference": "8f00c666a8b8dfb06f79286f97255e6ab1c89639", + "url": "https://api.github.com/repos/jarektkaczyk/eloquence/zipball/6f821bcf24950b58e633cb0cac7bbee6acb0f34a", + "reference": "6f821bcf24950b58e633cb0cac7bbee6acb0f34a", "shasum": "" }, "require": { - "league/fractal": "^0.16.0", + "illuminate/database": "5.4.*", + "php": ">=5.6.4", + "sofa/hookable": "5.4.*" + }, + "require-dev": { + "mockery/mockery": "0.9.4", + "phpunit/phpunit": "4.5.0", + "squizlabs/php_codesniffer": "2.3.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Sofa\\Eloquence\\": "src" + }, + "files": [ + "src/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jarek Tkaczyk", + "email": "jarek@softonsofa.com", + "homepage": "http://softonsofa.com/", + "role": "Developer" + } + ], + "description": "Flexible Searchable, Mappable, Metable, Validation and more extensions for Laravel Eloquent ORM.", + "keywords": [ + "eloquent", + "laravel", + "mappable", + "metable", + "mutable", + "searchable" + ], + "time": "2017-04-22T14:38:11+00:00" + }, + { + "name": "sofa/hookable", + "version": "5.4.1", + "source": { + "type": "git", + "url": "https://github.com/jarektkaczyk/hookable.git", + "reference": "1791d001bdf483136a11b3ea600462f446b82401" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jarektkaczyk/hookable/zipball/1791d001bdf483136a11b3ea600462f446b82401", + "reference": "1791d001bdf483136a11b3ea600462f446b82401", + "shasum": "" + }, + "require": { + "illuminate/database": "5.3.*|5.4.*", + "php": ">=5.6.4" + }, + "require-dev": { + "crysalead/kahlan": "~1.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Sofa\\Hookable\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jarek Tkaczyk", + "email": "jarek@softonsofa.com", + "homepage": "http://softonsofa.com/", + "role": "Developer" + } + ], + "description": "Laravel Eloquent hooks system.", + "keywords": [ + "eloquent", + "laravel" + ], + "time": "2017-05-27T15:48:52+00:00" + }, + { + "name": "spatie/fractalistic", + "version": "2.5.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/fractalistic.git", + "reference": "fdd536f8c1b172edfc3a742f6074680b80db2606" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/fractalistic/zipball/fdd536f8c1b172edfc3a742f6074680b80db2606", + "reference": "fdd536f8c1b172edfc3a742f6074680b80db2606", + "shasum": "" + }, + "require": { + "league/fractal": "^0.17.0", "php": "^5.6|^7.0" }, "require-dev": { @@ -2598,33 +2661,43 @@ "spatie", "transform" ], - "time": "2017-05-29T14:16:20+00:00" + "time": "2017-08-18T23:58:17+00:00" }, { "name": "spatie/laravel-fractal", - "version": "4.0.1", + "version": "4.5.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-fractal.git", - "reference": "3b95780f5f3ca79e29d445a5df87eac9f7c7c053" + "reference": "445364122d4e6d6da6f599939962e689668c2b5f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-fractal/zipball/3b95780f5f3ca79e29d445a5df87eac9f7c7c053", - "reference": "3b95780f5f3ca79e29d445a5df87eac9f7c7c053", + "url": "https://api.github.com/repos/spatie/laravel-fractal/zipball/445364122d4e6d6da6f599939962e689668c2b5f", + "reference": "445364122d4e6d6da6f599939962e689668c2b5f", "shasum": "" }, "require": { "illuminate/contracts": "~5.1.0|~5.2.0|~5.3.0|~5.4.0", "illuminate/support": "~5.1.0|~5.2.0|~5.3.0|~5.4.0", "php": "^5.6|^7.0", - "spatie/fractalistic": "^2.0" + "spatie/fractalistic": "^2.5" }, "require-dev": { "orchestra/testbench": "~3.2.0|3.3.0|~3.4.0", "phpunit/phpunit": "^5.7.5" }, "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\Fractal\\FractalServiceProvider" + ], + "aliases": { + "Fractal": "Spatie\\Fractal\\FractalFacade" + } + } + }, "autoload": { "psr-4": { "Spatie\\Fractal\\": "src" @@ -2656,7 +2729,7 @@ "spatie", "transform" ], - "time": "2017-05-05T19:01:43+00:00" + "time": "2017-08-22T17:52:05+00:00" }, { "name": "swiftmailer/swiftmailer", @@ -2714,16 +2787,16 @@ }, { "name": "symfony/console", - "version": "v3.3.2", + "version": "v3.3.6", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "70d2a29b2911cbdc91a7e268046c395278238b2e" + "reference": "b0878233cb5c4391347e5495089c7af11b8e6201" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/70d2a29b2911cbdc91a7e268046c395278238b2e", - "reference": "70d2a29b2911cbdc91a7e268046c395278238b2e", + "url": "https://api.github.com/repos/symfony/console/zipball/b0878233cb5c4391347e5495089c7af11b8e6201", + "reference": "b0878233cb5c4391347e5495089c7af11b8e6201", "shasum": "" }, "require": { @@ -2779,11 +2852,11 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2017-06-02T19:24:58+00:00" + "time": "2017-07-29T21:27:59+00:00" }, { "name": "symfony/css-selector", - "version": "v3.3.2", + "version": "v3.3.6", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", @@ -2836,16 +2909,16 @@ }, { "name": "symfony/debug", - "version": "v3.3.2", + "version": "v3.3.6", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "e9c50482841ef696e8fa1470d950a79c8921f45d" + "reference": "7c13ae8ce1e2adbbd574fc39de7be498e1284e13" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/e9c50482841ef696e8fa1470d950a79c8921f45d", - "reference": "e9c50482841ef696e8fa1470d950a79c8921f45d", + "url": "https://api.github.com/repos/symfony/debug/zipball/7c13ae8ce1e2adbbd574fc39de7be498e1284e13", + "reference": "7c13ae8ce1e2adbbd574fc39de7be498e1284e13", "shasum": "" }, "require": { @@ -2888,20 +2961,20 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2017-06-01T21:01:25+00:00" + "time": "2017-07-28T15:27:31+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v3.3.2", + "version": "v3.3.6", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "4054a102470665451108f9b59305c79176ef98f0" + "reference": "67535f1e3fd662bdc68d7ba317c93eecd973617e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/4054a102470665451108f9b59305c79176ef98f0", - "reference": "4054a102470665451108f9b59305c79176ef98f0", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/67535f1e3fd662bdc68d7ba317c93eecd973617e", + "reference": "67535f1e3fd662bdc68d7ba317c93eecd973617e", "shasum": "" }, "require": { @@ -2951,11 +3024,11 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2017-06-04T18:15:29+00:00" + "time": "2017-06-09T14:53:08+00:00" }, { "name": "symfony/finder", - "version": "v3.3.2", + "version": "v3.3.6", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", @@ -3004,16 +3077,16 @@ }, { "name": "symfony/http-foundation", - "version": "v3.3.2", + "version": "v3.3.6", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "80eb5a1f968448b77da9e8b2c0827f6e8d767846" + "reference": "49e8cd2d59a7aa9bfab19e46de680c76e500a031" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/80eb5a1f968448b77da9e8b2c0827f6e8d767846", - "reference": "80eb5a1f968448b77da9e8b2c0827f6e8d767846", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/49e8cd2d59a7aa9bfab19e46de680c76e500a031", + "reference": "49e8cd2d59a7aa9bfab19e46de680c76e500a031", "shasum": "" }, "require": { @@ -3053,20 +3126,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2017-06-05T13:06:51+00:00" + "time": "2017-07-21T11:04:46+00:00" }, { "name": "symfony/http-kernel", - "version": "v3.3.2", + "version": "v3.3.6", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "be8280f7fa8e95b86514f1e1be997668a53b2888" + "reference": "db10d05f1d95e4168e638db7a81c79616f568ea5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/be8280f7fa8e95b86514f1e1be997668a53b2888", - "reference": "be8280f7fa8e95b86514f1e1be997668a53b2888", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/db10d05f1d95e4168e638db7a81c79616f568ea5", + "reference": "db10d05f1d95e4168e638db7a81c79616f568ea5", "shasum": "" }, "require": { @@ -3139,20 +3212,20 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2017-06-06T03:59:58+00:00" + "time": "2017-08-01T10:25:59+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.4.0", + "version": "v1.5.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "f29dca382a6485c3cbe6379f0c61230167681937" + "reference": "7c8fae0ac1d216eb54349e6a8baa57d515fe8803" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/f29dca382a6485c3cbe6379f0c61230167681937", - "reference": "f29dca382a6485c3cbe6379f0c61230167681937", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7c8fae0ac1d216eb54349e6a8baa57d515fe8803", + "reference": "7c8fae0ac1d216eb54349e6a8baa57d515fe8803", "shasum": "" }, "require": { @@ -3164,7 +3237,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "1.5-dev" } }, "autoload": { @@ -3198,20 +3271,20 @@ "portable", "shim" ], - "time": "2017-06-09T14:24:12+00:00" + "time": "2017-06-14T15:44:48+00:00" }, { "name": "symfony/polyfill-php56", - "version": "v1.4.0", + "version": "v1.5.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php56.git", - "reference": "bc0b7d6cb36b10cfabb170a3e359944a95174929" + "reference": "e85ebdef569b84e8709864e1a290c40f156b30ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/bc0b7d6cb36b10cfabb170a3e359944a95174929", - "reference": "bc0b7d6cb36b10cfabb170a3e359944a95174929", + "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/e85ebdef569b84e8709864e1a290c40f156b30ca", + "reference": "e85ebdef569b84e8709864e1a290c40f156b30ca", "shasum": "" }, "require": { @@ -3221,7 +3294,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "1.5-dev" } }, "autoload": { @@ -3254,20 +3327,20 @@ "portable", "shim" ], - "time": "2017-06-09T08:25:21+00:00" + "time": "2017-06-14T15:44:48+00:00" }, { "name": "symfony/polyfill-util", - "version": "v1.4.0", + "version": "v1.5.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-util.git", - "reference": "ebccbde4aad410f6438d86d7d261c6b4d2b9a51d" + "reference": "67925d1cf0b84bd234a83bebf26d4eb281744c6d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/ebccbde4aad410f6438d86d7d261c6b4d2b9a51d", - "reference": "ebccbde4aad410f6438d86d7d261c6b4d2b9a51d", + "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/67925d1cf0b84bd234a83bebf26d4eb281744c6d", + "reference": "67925d1cf0b84bd234a83bebf26d4eb281744c6d", "shasum": "" }, "require": { @@ -3276,7 +3349,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "1.5-dev" } }, "autoload": { @@ -3306,20 +3379,20 @@ "polyfill", "shim" ], - "time": "2017-06-09T08:25:21+00:00" + "time": "2017-07-05T15:09:33+00:00" }, { "name": "symfony/process", - "version": "v3.3.2", + "version": "v3.3.6", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "8e30690c67aafb6c7992d6d8eb0d707807dd3eaf" + "reference": "07432804942b9f6dd7b7377faf9920af5f95d70a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/8e30690c67aafb6c7992d6d8eb0d707807dd3eaf", - "reference": "8e30690c67aafb6c7992d6d8eb0d707807dd3eaf", + "url": "https://api.github.com/repos/symfony/process/zipball/07432804942b9f6dd7b7377faf9920af5f95d70a", + "reference": "07432804942b9f6dd7b7377faf9920af5f95d70a", "shasum": "" }, "require": { @@ -3355,20 +3428,20 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2017-05-22T12:32:03+00:00" + "time": "2017-07-13T13:05:09+00:00" }, { "name": "symfony/routing", - "version": "v3.3.2", + "version": "v3.3.6", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "39804eeafea5cca851946e1eed122eb94459fdb4" + "reference": "4aee1a917fd4859ff8b51b9fd1dfb790a5ecfa26" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/39804eeafea5cca851946e1eed122eb94459fdb4", - "reference": "39804eeafea5cca851946e1eed122eb94459fdb4", + "url": "https://api.github.com/repos/symfony/routing/zipball/4aee1a917fd4859ff8b51b9fd1dfb790a5ecfa26", + "reference": "4aee1a917fd4859ff8b51b9fd1dfb790a5ecfa26", "shasum": "" }, "require": { @@ -3433,20 +3506,20 @@ "uri", "url" ], - "time": "2017-06-02T09:51:43+00:00" + "time": "2017-07-21T17:43:13+00:00" }, { "name": "symfony/translation", - "version": "v3.3.2", + "version": "v3.3.6", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "dc3b2a0c6cfff60327ba1c043a82092735397543" + "reference": "35dd5fb003c90e8bd4d8cabdf94bf9c96d06fdc3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/dc3b2a0c6cfff60327ba1c043a82092735397543", - "reference": "dc3b2a0c6cfff60327ba1c043a82092735397543", + "url": "https://api.github.com/repos/symfony/translation/zipball/35dd5fb003c90e8bd4d8cabdf94bf9c96d06fdc3", + "reference": "35dd5fb003c90e8bd4d8cabdf94bf9c96d06fdc3", "shasum": "" }, "require": { @@ -3498,20 +3571,20 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2017-05-22T07:42:36+00:00" + "time": "2017-06-24T16:45:30+00:00" }, { "name": "symfony/var-dumper", - "version": "v3.3.2", + "version": "v3.3.6", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "347c4247a3e40018810b476fcd5dec36d46d08dc" + "reference": "b2623bccb969ad595c2090f9be498b74670d0663" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/347c4247a3e40018810b476fcd5dec36d46d08dc", - "reference": "347c4247a3e40018810b476fcd5dec36d46d08dc", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/b2623bccb969ad595c2090f9be498b74670d0663", + "reference": "b2623bccb969ad595c2090f9be498b74670d0663", "shasum": "" }, "require": { @@ -3566,7 +3639,7 @@ "debug", "dump" ], - "time": "2017-06-02T09:10:29+00:00" + "time": "2017-07-28T06:06:09+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -3666,17 +3739,117 @@ "time": "2016-09-01T10:05:43+00:00" }, { - "name": "webpatser/laravel-uuid", - "version": "2.0.1", + "name": "watson/validating", + "version": "3.0.2", "source": { "type": "git", - "url": "https://github.com/webpatser/laravel-uuid.git", - "reference": "6ed2705775e3edf066b90e1292f76f157ec00507" + "url": "https://github.com/dwightwatson/validating.git", + "reference": "c6e9194b034a24d3b6e447bbc7a94d88fad9a1fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webpatser/laravel-uuid/zipball/6ed2705775e3edf066b90e1292f76f157ec00507", - "reference": "6ed2705775e3edf066b90e1292f76f157ec00507", + "url": "https://api.github.com/repos/dwightwatson/validating/zipball/c6e9194b034a24d3b6e447bbc7a94d88fad9a1fd", + "reference": "c6e9194b034a24d3b6e447bbc7a94d88fad9a1fd", + "shasum": "" + }, + "require": { + "illuminate/contracts": ">=5.3", + "illuminate/database": ">=5.3", + "illuminate/events": ">=5.3", + "illuminate/support": ">=5.3", + "illuminate/validation": ">=5.3", + "php": ">=5.4.0" + }, + "require-dev": { + "mockery/mockery": "0.9.*", + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Watson\\Validating\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dwight Watson", + "email": "dwight@studiousapp.com" + } + ], + "description": "Eloquent model validating trait.", + "keywords": [ + "eloquent", + "laravel", + "validation" + ], + "time": "2017-08-25T02:12:38+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/2db61e59ff05fe5126d152bd0655c9ea113e550f", + "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2016-11-23T20:04:58+00:00" + }, + { + "name": "webpatser/laravel-uuid", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/webpatser/laravel-uuid.git", + "reference": "317b835cf492240187e00673582e056ec4dc076a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webpatser/laravel-uuid/zipball/317b835cf492240187e00673582e056ec4dc076a", + "reference": "317b835cf492240187e00673582e056ec4dc076a", "shasum": "" }, "require": { @@ -3690,6 +3863,13 @@ "paragonie/random_compat": "A random_bytes Php 5.x polyfill." }, "type": "library", + "extra": { + "laravel": { + "aliases": { + "Uuid": "Webpatser\\Uuid\\Uuid" + } + } + }, "autoload": { "psr-0": { "Webpatser\\Uuid": "src/" @@ -3710,34 +3890,85 @@ "keywords": [ "UUID RFC4122" ], - "time": "2016-05-09T09:22:18+00:00" + "time": "2017-08-30T20:45:29+00:00" } ], "packages-dev": [ { - "name": "barryvdh/laravel-ide-helper", - "version": "v2.3.2", + "name": "barryvdh/laravel-debugbar", + "version": "v2.4.3", "source": { "type": "git", - "url": "https://github.com/barryvdh/laravel-ide-helper.git", - "reference": "e82de98cef0d6597b1b686be0b5813a3a4bb53c5" + "url": "https://github.com/barryvdh/laravel-debugbar.git", + "reference": "d7c88f08131f6404cb714f3f6cf0642f6afa3903" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/e82de98cef0d6597b1b686be0b5813a3a4bb53c5", - "reference": "e82de98cef0d6597b1b686be0b5813a3a4bb53c5", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/d7c88f08131f6404cb714f3f6cf0642f6afa3903", + "reference": "d7c88f08131f6404cb714f3f6cf0642f6afa3903", + "shasum": "" + }, + "require": { + "illuminate/support": "5.1.*|5.2.*|5.3.*|5.4.*|5.5.*", + "maximebf/debugbar": "~1.13.0", + "php": ">=5.5.9", + "symfony/finder": "~2.7|~3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Barryvdh\\Debugbar\\": "src/" + }, + "files": [ + "src/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "PHP Debugbar integration for Laravel", + "keywords": [ + "debug", + "debugbar", + "laravel", + "profiler", + "webprofiler" + ], + "time": "2017-07-21T11:56:48+00:00" + }, + { + "name": "barryvdh/laravel-ide-helper", + "version": "v2.4.1", + "source": { + "type": "git", + "url": "https://github.com/barryvdh/laravel-ide-helper.git", + "reference": "2b1273c45e2f8df7a625563e2283a17c14f02ae8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/2b1273c45e2f8df7a625563e2283a17c14f02ae8", + "reference": "2b1273c45e2f8df7a625563e2283a17c14f02ae8", "shasum": "" }, "require": { "barryvdh/reflection-docblock": "^2.0.4", - "illuminate/console": "^5.0,<5.5", - "illuminate/filesystem": "^5.0,<5.5", - "illuminate/support": "^5.0,<5.5", + "illuminate/console": "^5.0,<5.6", + "illuminate/filesystem": "^5.0,<5.6", + "illuminate/support": "^5.0,<5.6", "php": ">=5.4.0", "symfony/class-loader": "^2.3|^3.0" }, "require-dev": { "doctrine/dbal": "~2.3", + "illuminate/config": "^5.0,<5.6", + "illuminate/view": "^5.0,<5.6", "phpunit/phpunit": "4.*", "scrutinizer/ocular": "~1.1", "squizlabs/php_codesniffer": "~2.3" @@ -3749,6 +3980,11 @@ "extra": { "branch-alias": { "dev-master": "2.3-dev" + }, + "laravel": { + "providers": [ + "Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider" + ] } }, "autoload": { @@ -3778,7 +4014,7 @@ "phpstorm", "sublime" ], - "time": "2017-02-22T12:27:33+00:00" + "time": "2017-07-16T00:24:12+00:00" }, { "name": "barryvdh/reflection-docblock", @@ -3829,68 +4065,6 @@ ], "time": "2016-06-13T19:28:20+00:00" }, - { - "name": "composer/semver", - "version": "1.4.2", - "source": { - "type": "git", - "url": "https://github.com/composer/semver.git", - "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/c7cb9a2095a074d131b65a8a0cd294479d785573", - "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573", - "shasum": "" - }, - "require": { - "php": "^5.3.2 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.5 || ^5.0.5", - "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Semver\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - }, - { - "name": "Rob Bast", - "email": "rob.bast@gmail.com", - "homepage": "http://robbast.nl" - } - ], - "description": "Semver library that offers utilities, version constraint parsing and validation.", - "keywords": [ - "semantic", - "semver", - "validation", - "versioning" - ], - "time": "2016-08-30T16:08:34+00:00" - }, { "name": "doctrine/instantiator", "version": "1.0.5", @@ -3947,44 +4121,69 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v1.13.1", + "version": "v2.4.1", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "0ea4f7ed06ca55da1d8fc45da26ff87f261c4088" + "reference": "b4983586c8e7b1f99ec05dd1e75c8b673315da70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/0ea4f7ed06ca55da1d8fc45da26ff87f261c4088", - "reference": "0ea4f7ed06ca55da1d8fc45da26ff87f261c4088", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/b4983586c8e7b1f99ec05dd1e75c8b673315da70", + "reference": "b4983586c8e7b1f99ec05dd1e75c8b673315da70", "shasum": "" }, "require": { + "doctrine/annotations": "^1.2", + "ext-json": "*", "ext-tokenizer": "*", - "php": "^5.3.6 || >=7.0 <7.2", - "sebastian/diff": "^1.1", - "symfony/console": "^2.3 || ^3.0", - "symfony/event-dispatcher": "^2.1 || ^3.0", - "symfony/filesystem": "^2.1 || ^3.0", - "symfony/finder": "^2.1 || ^3.0", - "symfony/process": "^2.3 || ^3.0", - "symfony/stopwatch": "^2.5 || ^3.0" + "gecko-packages/gecko-php-unit": "^2.0", + "php": "^5.6 || >=7.0 <7.2", + "sebastian/diff": "^1.4", + "symfony/console": "^3.0", + "symfony/event-dispatcher": "^3.0", + "symfony/filesystem": "^3.0", + "symfony/finder": "^3.0", + "symfony/options-resolver": "^3.0", + "symfony/polyfill-php70": "^1.0", + "symfony/polyfill-php72": "^1.4", + "symfony/process": "^3.0", + "symfony/stopwatch": "^3.0" }, "conflict": { - "hhvm": "<3.9" + "hhvm": "*" }, "require-dev": { - "phpunit/phpunit": "^4.5|^5", - "satooshi/php-coveralls": "^1.0" + "johnkary/phpunit-speedtrap": "^1.1", + "justinrainbow/json-schema": "^5.0", + "phpunit/phpunit": "^4.8.35 || ^5.4.3", + "satooshi/php-coveralls": "^1.0", + "symfony/phpunit-bridge": "^3.2.2" + }, + "suggest": { + "ext-mbstring": "For handling non-UTF8 characters in cache signature.", + "symfony/polyfill-mbstring": "When enabling `ext-mbstring` is not possible." }, "bin": [ "php-cs-fixer" ], "type": "application", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, "autoload": { "psr-4": { - "Symfony\\CS\\": "Symfony/CS/" - } + "PhpCsFixer\\": "src/" + }, + "classmap": [ + "tests/Test/Assert/AssertTokensTrait.php", + "tests/Test/AbstractFixerTestCase.php", + "tests/Test/AbstractIntegrationTestCase.php", + "tests/Test/IntegrationCase.php", + "tests/Test/IntegrationCaseFactory.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4001,33 +4200,35 @@ } ], "description": "A tool to automatically fix PHP code style", - "time": "2016-12-01T00:05:05+00:00" + "time": "2017-08-22T14:11:01+00:00" }, { "name": "fzaninotto/faker", - "version": "v1.6.0", + "version": "v1.7.1", "source": { "type": "git", "url": "https://github.com/fzaninotto/Faker.git", - "reference": "44f9a286a04b80c76a4e5fb7aad8bb539b920123" + "reference": "d3ed4cc37051c1ca52d22d76b437d14809fc7e0d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/44f9a286a04b80c76a4e5fb7aad8bb539b920123", - "reference": "44f9a286a04b80c76a4e5fb7aad8bb539b920123", + "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/d3ed4cc37051c1ca52d22d76b437d14809fc7e0d", + "reference": "d3ed4cc37051c1ca52d22d76b437d14809fc7e0d", "shasum": "" }, "require": { - "php": "^5.3.3|^7.0" + "php": "^5.3.3 || ^7.0" }, "require-dev": { "ext-intl": "*", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~1.5" + "phpunit/phpunit": "^4.0 || ^5.0", + "squizlabs/php_codesniffer": "^1.5" }, "type": "library", "extra": { - "branch-alias": [] + "branch-alias": { + "dev-master": "1.8-dev" + } }, "autoload": { "psr-4": { @@ -4049,7 +4250,51 @@ "faker", "fixtures" ], - "time": "2016-04-29T12:21:54+00:00" + "time": "2017-08-15T16:48:10+00:00" + }, + { + "name": "gecko-packages/gecko-php-unit", + "version": "v2.2", + "source": { + "type": "git", + "url": "https://github.com/GeckoPackages/GeckoPHPUnit.git", + "reference": "ab525fac9a9ffea219687f261b02008b18ebf2d1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GeckoPackages/GeckoPHPUnit/zipball/ab525fac9a9ffea219687f261b02008b18ebf2d1", + "reference": "ab525fac9a9ffea219687f261b02008b18ebf2d1", + "shasum": "" + }, + "require": { + "php": "^5.3.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.4.3" + }, + "suggest": { + "ext-dom": "When testing with xml.", + "ext-libxml": "When testing with xml.", + "phpunit/phpunit": "This is an extension for it so make sure you have it some way." + }, + "type": "library", + "autoload": { + "psr-4": { + "GeckoPackages\\PHPUnit\\": "src/PHPUnit" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Additional PHPUnit asserts and constraints.", + "homepage": "https://github.com/GeckoPackages", + "keywords": [ + "extension", + "filesystem", + "phpunit" + ], + "time": "2017-08-23T07:39:54+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -4096,6 +4341,67 @@ ], "time": "2015-05-11T14:41:42+00:00" }, + { + "name": "maximebf/debugbar", + "version": "1.13.1", + "source": { + "type": "git", + "url": "https://github.com/maximebf/php-debugbar.git", + "reference": "afee79a236348e39a44cb837106b7c5b4897ac2a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/afee79a236348e39a44cb837106b7c5b4897ac2a", + "reference": "afee79a236348e39a44cb837106b7c5b4897ac2a", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/log": "^1.0", + "symfony/var-dumper": "^2.6|^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0|^5.0" + }, + "suggest": { + "kriswallsmith/assetic": "The best way to manage assets", + "monolog/monolog": "Log using Monolog", + "predis/predis": "Redis storage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.13-dev" + } + }, + "autoload": { + "psr-4": { + "DebugBar\\": "src/DebugBar/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maxime Bouroumeau-Fuseau", + "email": "maxime.bouroumeau@gmail.com", + "homepage": "http://maximebf.com" + }, + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "Debug bar in the browser for php application", + "homepage": "https://github.com/maximebf/php-debugbar", + "keywords": [ + "debug", + "debugbar" + ], + "time": "2017-01-05T08:46:19+00:00" + }, { "name": "mockery/mockery", "version": "0.9.9", @@ -4203,6 +4509,175 @@ ], "time": "2017-04-12T18:52:22+00:00" }, + { + "name": "php-mock/php-mock", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-mock/php-mock.git", + "reference": "bfa2d17d64dbf129073a7ba2051a96ce52749570" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-mock/php-mock/zipball/bfa2d17d64dbf129073a7ba2051a96ce52749570", + "reference": "bfa2d17d64dbf129073a7ba2051a96ce52749570", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpunit/php-text-template": "^1" + }, + "replace": { + "malkusch/php-mock": "*" + }, + "require-dev": { + "phpunit/phpunit": "^4|^5" + }, + "suggest": { + "php-mock/php-mock-mockery": "Allows using PHPMockery for Mockery integration", + "php-mock/php-mock-phpunit": "Allows integration into PHPUnit testcase with the trait PHPMock." + }, + "type": "library", + "autoload": { + "psr-4": { + "phpmock\\": [ + "classes/", + "tests/unit/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "WTFPL" + ], + "authors": [ + { + "name": "Markus Malkusch", + "email": "markus@malkusch.de", + "homepage": "http://markus.malkusch.de", + "role": "Developer" + } + ], + "description": "PHP-Mock can mock built-in PHP functions (e.g. time()). PHP-Mock relies on PHP's namespace fallback policy. No further extension is needed.", + "homepage": "https://github.com/php-mock/php-mock", + "keywords": [ + "BDD", + "TDD", + "function", + "mock", + "stub", + "test", + "test double" + ], + "time": "2015-11-11T22:37:09+00:00" + }, + { + "name": "php-mock/php-mock-integration", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-mock/php-mock-integration.git", + "reference": "e83fb65dd20cd3cf250d554cbd4682b96b684f4b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-mock/php-mock-integration/zipball/e83fb65dd20cd3cf250d554cbd4682b96b684f4b", + "reference": "e83fb65dd20cd3cf250d554cbd4682b96b684f4b", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "php-mock/php-mock": "^1", + "phpunit/php-text-template": "^1" + }, + "require-dev": { + "phpunit/phpunit": "^4|^5" + }, + "type": "library", + "autoload": { + "psr-4": { + "phpmock\\integration\\": "classes/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "WTFPL" + ], + "authors": [ + { + "name": "Markus Malkusch", + "email": "markus@malkusch.de", + "homepage": "http://markus.malkusch.de", + "role": "Developer" + } + ], + "description": "Integration package for PHP-Mock", + "homepage": "https://github.com/php-mock/php-mock-integration", + "keywords": [ + "BDD", + "TDD", + "function", + "mock", + "stub", + "test", + "test double" + ], + "time": "2015-10-26T21:21:42+00:00" + }, + { + "name": "php-mock/php-mock-phpunit", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-mock/php-mock-phpunit.git", + "reference": "359e3038c016cee4c8f8db6387bcab3fcdebada0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-mock/php-mock-phpunit/zipball/359e3038c016cee4c8f8db6387bcab3fcdebada0", + "reference": "359e3038c016cee4c8f8db6387bcab3fcdebada0", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "php-mock/php-mock-integration": "^1", + "phpunit/phpunit": "^4.0.0 || ^5.0.0" + }, + "conflict": { + "phpunit/phpunit-mock-objects": "3.2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "phpmock\\phpunit\\": "classes/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "WTFPL" + ], + "authors": [ + { + "name": "Markus Malkusch", + "email": "markus@malkusch.de", + "homepage": "http://markus.malkusch.de", + "role": "Developer" + } + ], + "description": "Mock built-in PHP functions (e.g. time()) with PHPUnit. This package relies on PHP's namespace fallback policy. No further extension is needed.", + "homepage": "https://github.com/php-mock/php-mock-phpunit", + "keywords": [ + "BDD", + "TDD", + "function", + "mock", + "phpunit", + "stub", + "test", + "test double" + ], + "time": "2016-06-15T23:36:13+00:00" + }, { "name": "phpdocumentor/reflection-common", "version": "1.0", @@ -4259,22 +4734,22 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "3.1.1", + "version": "3.2.3", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e" + "reference": "86e24012a3139b42a7b71155cfaa325389f00f1f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/8331b5efe816ae05461b7ca1e721c01b46bafb3e", - "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/86e24012a3139b42a7b71155cfaa325389f00f1f", + "reference": "86e24012a3139b42a7b71155cfaa325389f00f1f", "shasum": "" }, "require": { - "php": ">=5.5", + "php": "^7.0", "phpdocumentor/reflection-common": "^1.0@dev", - "phpdocumentor/type-resolver": "^0.2.0", + "phpdocumentor/type-resolver": "^0.4.0", "webmozart/assert": "^1.0" }, "require-dev": { @@ -4300,24 +4775,24 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2016-09-30T07:12:33+00:00" + "time": "2017-08-29T19:37:41+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "0.2.1", + "version": "0.4.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb" + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", - "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", "shasum": "" }, "require": { - "php": ">=5.5", + "php": "^5.5 || ^7.0", "phpdocumentor/reflection-common": "^1.0" }, "require-dev": { @@ -4347,7 +4822,7 @@ "email": "me@mikevanriel.com" } ], - "time": "2016-11-25T06:54:22+00:00" + "time": "2017-07-14T14:27:02+00:00" }, { "name": "phpspec/prophecy", @@ -4614,29 +5089,29 @@ }, { "name": "phpunit/php-token-stream", - "version": "1.4.11", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7" + "reference": "9a02332089ac48e704c70f6cefed30c224e3c0b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e03f8f67534427a787e21a385a67ec3ca6978ea7", - "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/9a02332089ac48e704c70f6cefed30c224e3c0b0", + "reference": "9a02332089ac48e704c70f6cefed30c224e3c0b0", "shasum": "" }, "require": { "ext-tokenizer": "*", - "php": ">=5.3.3" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "~4.2" + "phpunit/phpunit": "^6.2.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -4659,20 +5134,20 @@ "keywords": [ "tokenizer" ], - "time": "2017-02-27T10:12:30+00:00" + "time": "2017-08-20T05:47:52+00:00" }, { "name": "phpunit/phpunit", - "version": "5.7.20", + "version": "5.7.21", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "3cb94a5f8c07a03c8b7527ed7468a2926203f58b" + "reference": "3b91adfb64264ddec5a2dee9851f354aa66327db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3cb94a5f8c07a03c8b7527ed7468a2926203f58b", - "reference": "3cb94a5f8c07a03c8b7527ed7468a2926203f58b", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3b91adfb64264ddec5a2dee9851f354aa66327db", + "reference": "3b91adfb64264ddec5a2dee9851f354aa66327db", "shasum": "" }, "require": { @@ -4741,20 +5216,20 @@ "testing", "xunit" ], - "time": "2017-05-22T07:42:55+00:00" + "time": "2017-06-21T08:11:54+00:00" }, { "name": "phpunit/phpunit-mock-objects", - "version": "3.4.3", + "version": "3.4.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "3ab72b65b39b491e0c011e2e09bb2206c2aa8e24" + "reference": "a23b761686d50a560cc56233b9ecf49597cc9118" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/3ab72b65b39b491e0c011e2e09bb2206c2aa8e24", - "reference": "3ab72b65b39b491e0c011e2e09bb2206c2aa8e24", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/a23b761686d50a560cc56233b9ecf49597cc9118", + "reference": "a23b761686d50a560cc56233b9ecf49597cc9118", "shasum": "" }, "require": { @@ -4800,7 +5275,7 @@ "mock", "xunit" ], - "time": "2016-12-08T20:27:08+00:00" + "time": "2017-06-30T09:13:00+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -5315,119 +5790,9 @@ "homepage": "https://github.com/sebastianbergmann/version", "time": "2016-10-03T07:35:21+00:00" }, - { - "name": "sllh/php-cs-fixer-styleci-bridge", - "version": "v2.1.1", - "source": { - "type": "git", - "url": "https://github.com/Soullivaneuh/php-cs-fixer-styleci-bridge.git", - "reference": "2406ac6ab7a243e1b4d4f5f73c12e5905c629877" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Soullivaneuh/php-cs-fixer-styleci-bridge/zipball/2406ac6ab7a243e1b4d4f5f73c12e5905c629877", - "reference": "2406ac6ab7a243e1b4d4f5f73c12e5905c629877", - "shasum": "" - }, - "require": { - "composer/semver": "^1.0", - "doctrine/inflector": "^1.0", - "php": "^5.3 || ^7.0", - "sllh/styleci-fixers": "^3.0 || ^4.0", - "symfony/config": "^2.3 || ^3.0", - "symfony/console": "^2.3 || ^3.0", - "symfony/yaml": "^2.3 || ^3.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^1.6.1", - "matthiasnoback/symfony-config-test": "^1.2", - "symfony/phpunit-bridge": "^2.7.4 || ^3.0", - "twig/twig": "^1.22" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - }, - "autoload": { - "psr-4": { - "SLLH\\StyleCIBridge\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Sullivan SENECHAL", - "email": "soullivaneuh@gmail.com" - } - ], - "description": "Auto configure PHP-CS-Fixer from StyleCI config file", - "keywords": [ - "PSR-1", - "PSR-2", - "PSR-4", - "StyleCI", - "configuration", - "laravel", - "php-cs-fixer", - "psr", - "symfony" - ], - "time": "2016-06-22T13:26:46+00:00" - }, - { - "name": "sllh/styleci-fixers", - "version": "v4.10.0", - "source": { - "type": "git", - "url": "https://github.com/Soullivaneuh/styleci-fixers.git", - "reference": "f2547f5cd465325bc7b996d53657189019e7ac05" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Soullivaneuh/styleci-fixers/zipball/f2547f5cd465325bc7b996d53657189019e7ac05", - "reference": "f2547f5cd465325bc7b996d53657189019e7ac05", - "shasum": "" - }, - "require": { - "php": "^5.3 || ^7.0" - }, - "require-dev": { - "styleci/sdk": "^1.0", - "symfony/console": "^3.0", - "twig/twig": "^2.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "SLLH\\StyleCIFixers\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Sullivan SENECHAL", - "email": "soullivaneuh@gmail.com" - } - ], - "description": "Auto configure PHP-CS-Fixer from StyleCI config file", - "keywords": [ - "StyleCI", - "configuration", - "php-cs-fixer" - ], - "time": "2017-05-10T08:16:59+00:00" - }, { "name": "symfony/class-loader", - "version": "v3.3.2", + "version": "v3.3.6", "source": { "type": "git", "url": "https://github.com/symfony/class-loader.git", @@ -5481,78 +5846,18 @@ "homepage": "https://symfony.com", "time": "2017-06-02T09:51:43+00:00" }, - { - "name": "symfony/config", - "version": "v3.3.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/config.git", - "reference": "35716d4904e0506a7a5a9bcf23f854aeb5719bca" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/35716d4904e0506a7a5a9bcf23f854aeb5719bca", - "reference": "35716d4904e0506a7a5a9bcf23f854aeb5719bca", - "shasum": "" - }, - "require": { - "php": ">=5.5.9", - "symfony/filesystem": "~2.8|~3.0" - }, - "conflict": { - "symfony/dependency-injection": "<3.3" - }, - "require-dev": { - "symfony/dependency-injection": "~3.3", - "symfony/yaml": "~3.0" - }, - "suggest": { - "symfony/yaml": "To use the yaml reference dumper" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.3-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Config\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Config Component", - "homepage": "https://symfony.com", - "time": "2017-06-02T18:07:20+00:00" - }, { "name": "symfony/filesystem", - "version": "v3.3.2", + "version": "v3.3.6", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "c709670bf64721202ddbe4162846f250735842c0" + "reference": "427987eb4eed764c3b6e38d52a0f87989e010676" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/c709670bf64721202ddbe4162846f250735842c0", - "reference": "c709670bf64721202ddbe4162846f250735842c0", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/427987eb4eed764c3b6e38d52a0f87989e010676", + "reference": "427987eb4eed764c3b6e38d52a0f87989e010676", "shasum": "" }, "require": { @@ -5588,11 +5893,179 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2017-05-28T14:08:56+00:00" + "time": "2017-07-11T07:17:58+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v3.3.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "ff48982d295bcac1fd861f934f041ebc73ae40f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/ff48982d295bcac1fd861f934f041ebc73ae40f0", + "reference": "ff48982d295bcac1fd861f934f041ebc73ae40f0", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony OptionsResolver Component", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "time": "2017-04-12T14:14:56+00:00" + }, + { + "name": "symfony/polyfill-php70", + "version": "v1.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php70.git", + "reference": "b6482e68974486984f59449ecea1fbbb22ff840f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/b6482e68974486984f59449ecea1fbbb22ff840f", + "reference": "b6482e68974486984f59449ecea1fbbb22ff840f", + "shasum": "" + }, + "require": { + "paragonie/random_compat": "~1.0|~2.0", + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php70\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2017-06-14T15:44:48+00:00" + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "8abc9097f5001d310f0edba727469c988acc6ea7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/8abc9097f5001d310f0edba727469c988acc6ea7", + "reference": "8abc9097f5001d310f0edba727469c988acc6ea7", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2017-07-11T13:25:55+00:00" }, { "name": "symfony/stopwatch", - "version": "v3.3.2", + "version": "v3.3.6", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", @@ -5641,16 +6114,16 @@ }, { "name": "symfony/yaml", - "version": "v3.3.2", + "version": "v3.3.6", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "9752a30000a8ca9f4b34b5227d15d0101b96b063" + "reference": "ddc23324e6cfe066f3dd34a37ff494fa80b617ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/9752a30000a8ca9f4b34b5227d15d0101b96b063", - "reference": "9752a30000a8ca9f4b34b5227d15d0101b96b063", + "url": "https://api.github.com/repos/symfony/yaml/zipball/ddc23324e6cfe066f3dd34a37ff494fa80b617ed", + "reference": "ddc23324e6cfe066f3dd34a37ff494fa80b617ed", "shasum": "" }, "require": { @@ -5692,57 +6165,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2017-06-02T22:05:06+00:00" - }, - { - "name": "webmozart/assert", - "version": "1.2.0", - "source": { - "type": "git", - "url": "https://github.com/webmozart/assert.git", - "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/2db61e59ff05fe5126d152bd0655c9ea113e550f", - "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f", - "shasum": "" - }, - "require": { - "php": "^5.3.3 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.6", - "sebastian/version": "^1.0.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3-dev" - } - }, - "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" - ], - "time": "2016-11-23T20:04:58+00:00" + "time": "2017-07-23T12:43:26+00:00" } ], "aliases": [], @@ -5751,10 +6174,13 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": ">=7.0.0", + "php": ">=7.0", "ext-mbstring": "*", - "ext-zip": "*", - "ext-pdo_mysql": "*" + "ext-pdo_mysql": "*", + "ext-zip": "*" }, - "platform-dev": [] + "platform-dev": [], + "platform-overrides": { + "php": "7.0" + } } diff --git a/config/app.php b/config/app.php index 3c3fb772d..e3cb71701 100644 --- a/config/app.php +++ b/config/app.php @@ -1,7 +1,6 @@ env('APP_ENV', 'production'), 'version' => env('APP_VERSION', 'canary'), @@ -126,7 +125,6 @@ return [ */ 'providers' => [ - /* * Laravel Framework Service Providers... */ @@ -163,9 +161,12 @@ return [ Pterodactyl\Providers\AppServiceProvider::class, Pterodactyl\Providers\AuthServiceProvider::class, Pterodactyl\Providers\EventServiceProvider::class, + Pterodactyl\Providers\HashidsServiceProvider::class, Pterodactyl\Providers\RouteServiceProvider::class, Pterodactyl\Providers\MacroServiceProvider::class, Pterodactyl\Providers\PhraseAppTranslationProvider::class, + Pterodactyl\Providers\RepositoryServiceProvider::class, + Pterodactyl\Providers\ViewComposerServiceProvider::class, /* * Additional Dependencies @@ -179,7 +180,7 @@ return [ Laracasts\Utilities\JavaScript\JavaScriptServiceProvider::class, Lord\Laroute\LarouteServiceProvider::class, Spatie\Fractal\FractalServiceProvider::class, - + Sofa\Eloquence\ServiceProvider::class, ], /* @@ -194,53 +195,49 @@ return [ */ 'aliases' => [ - - 'Alert' => Prologue\Alerts\Facades\Alert::class, - 'App' => Illuminate\Support\Facades\App::class, - 'Artisan' => Illuminate\Support\Facades\Artisan::class, - 'Auth' => Illuminate\Support\Facades\Auth::class, - 'Blade' => Illuminate\Support\Facades\Blade::class, - 'Bus' => Illuminate\Support\Facades\Bus::class, - 'Cache' => Illuminate\Support\Facades\Cache::class, - 'Carbon' => Carbon\Carbon::class, - 'Config' => Illuminate\Support\Facades\Config::class, - 'Cookie' => Illuminate\Support\Facades\Cookie::class, - 'Cron' => Cron\CronExpression::class, - 'Crypt' => Illuminate\Support\Facades\Crypt::class, - 'DB' => Illuminate\Support\Facades\DB::class, - 'Debugbar' => Barryvdh\Debugbar\Facade::class, - 'Eloquent' => Illuminate\Database\Eloquent\Model::class, - 'Event' => Illuminate\Support\Facades\Event::class, - 'File' => Illuminate\Support\Facades\File::class, - 'Fractal' => Spatie\Fractal\FractalFacade::class, - 'Gate' => Illuminate\Support\Facades\Gate::class, + 'Alert' => Prologue\Alerts\Facades\Alert::class, + 'App' => Illuminate\Support\Facades\App::class, + 'Artisan' => Illuminate\Support\Facades\Artisan::class, + 'Auth' => Illuminate\Support\Facades\Auth::class, + 'Blade' => Illuminate\Support\Facades\Blade::class, + 'Bus' => Illuminate\Support\Facades\Bus::class, + 'Cache' => Illuminate\Support\Facades\Cache::class, + 'Carbon' => Carbon\Carbon::class, + 'Config' => Illuminate\Support\Facades\Config::class, + 'Cookie' => Illuminate\Support\Facades\Cookie::class, + 'Cron' => Cron\CronExpression::class, + 'Crypt' => Illuminate\Support\Facades\Crypt::class, + 'DB' => Illuminate\Support\Facades\DB::class, + 'Debugbar' => Barryvdh\Debugbar\Facade::class, + 'Eloquent' => Illuminate\Database\Eloquent\Model::class, + 'Event' => Illuminate\Support\Facades\Event::class, + 'File' => Illuminate\Support\Facades\File::class, + 'Fractal' => Spatie\Fractal\FractalFacade::class, + 'Gate' => Illuminate\Support\Facades\Gate::class, 'Google2FA' => PragmaRX\Google2FA\Vendor\Laravel\Facade::class, - 'Hash' => Illuminate\Support\Facades\Hash::class, - 'Input' => Illuminate\Support\Facades\Input::class, + 'Hash' => Illuminate\Support\Facades\Hash::class, + 'Input' => Illuminate\Support\Facades\Input::class, 'Inspiring' => Illuminate\Foundation\Inspiring::class, 'Javascript' => Laracasts\Utilities\JavaScript\JavaScriptFacade::class, - 'Lang' => Illuminate\Support\Facades\Lang::class, - 'Log' => Illuminate\Support\Facades\Log::class, - 'Mail' => Illuminate\Support\Facades\Mail::class, + 'Lang' => Illuminate\Support\Facades\Lang::class, + 'Log' => Illuminate\Support\Facades\Log::class, + 'Mail' => Illuminate\Support\Facades\Mail::class, 'Notification' => Illuminate\Support\Facades\Notification::class, - 'Password' => Illuminate\Support\Facades\Password::class, - 'Queue' => Illuminate\Support\Facades\Queue::class, - 'Redirect' => Illuminate\Support\Facades\Redirect::class, - 'Redis' => Illuminate\Support\Facades\Redis::class, - 'Request' => Illuminate\Support\Facades\Request::class, - 'Response' => Illuminate\Support\Facades\Response::class, - 'Route' => Illuminate\Support\Facades\Route::class, - 'Schema' => Illuminate\Support\Facades\Schema::class, - 'Settings' => Krucas\Settings\Facades\Settings::class, - 'Session' => Illuminate\Support\Facades\Session::class, - 'Storage' => Illuminate\Support\Facades\Storage::class, - 'Theme' => igaster\laravelTheme\Facades\Theme::class, - 'URL' => Illuminate\Support\Facades\URL::class, - 'Uuid' => Webpatser\Uuid\Uuid::class, + 'Password' => Illuminate\Support\Facades\Password::class, + 'Queue' => Illuminate\Support\Facades\Queue::class, + 'Redirect' => Illuminate\Support\Facades\Redirect::class, + 'Redis' => Illuminate\Support\Facades\Redis::class, + 'Request' => Illuminate\Support\Facades\Request::class, + 'Response' => Illuminate\Support\Facades\Response::class, + 'Route' => Illuminate\Support\Facades\Route::class, + 'Schema' => Illuminate\Support\Facades\Schema::class, + 'Settings' => Krucas\Settings\Facades\Settings::class, + 'Session' => Illuminate\Support\Facades\Session::class, + 'Storage' => Illuminate\Support\Facades\Storage::class, + 'Theme' => igaster\laravelTheme\Facades\Theme::class, + 'URL' => Illuminate\Support\Facades\URL::class, + 'Uuid' => Webpatser\Uuid\Uuid::class, 'Validator' => Illuminate\Support\Facades\Validator::class, - 'Version' => Pterodactyl\Facades\Version::class, - 'View' => Illuminate\Support\Facades\View::class, - + 'View' => Illuminate\Support\Facades\View::class, ], - ]; diff --git a/config/auth.php b/config/auth.php index 6f7fc83c2..b83dd9eca 100644 --- a/config/auth.php +++ b/config/auth.php @@ -1,7 +1,6 @@ 60, ], ], - ]; diff --git a/config/broadcasting.php b/config/broadcasting.php index 85e045124..9c4c792de 100644 --- a/config/broadcasting.php +++ b/config/broadcasting.php @@ -1,7 +1,6 @@ [ - 'pusher' => [ 'driver' => 'pusher', 'key' => env('PUSHER_KEY'), @@ -48,7 +46,5 @@ return [ 'null' => [ 'driver' => 'null', ], - ], - ]; diff --git a/config/cache.php b/config/cache.php index 7bd9dd70e..6109216c7 100644 --- a/config/cache.php +++ b/config/cache.php @@ -1,7 +1,6 @@ [ - 'apc' => [ 'driver' => 'apc', ], @@ -69,7 +67,6 @@ return [ 'driver' => 'redis', 'connection' => 'default', ], - ], /* @@ -83,6 +80,5 @@ return [ | */ - 'prefix' => 'laravel', - + 'prefix' => 'pterodactyl', ]; diff --git a/config/compile.php b/config/compile.php index 04807eac4..cbf7739a6 100644 --- a/config/compile.php +++ b/config/compile.php @@ -1,7 +1,6 @@ [ - // ], /* @@ -29,7 +27,5 @@ return [ */ 'providers' => [ - // ], - ]; diff --git a/config/database.php b/config/database.php index 58324a0b5..b9ce78c03 100644 --- a/config/database.php +++ b/config/database.php @@ -1,7 +1,6 @@ [ 'mysql' => [ - 'driver' => 'mysql', - 'host' => env('DB_HOST', 'localhost'), - 'port' => env('DB_PORT', '3306'), - 'database' => env('DB_DATABASE', 'forge'), - 'username' => env('DB_USERNAME', 'forge'), - 'password' => env('DB_PASSWORD', ''), - 'charset' => 'utf8', + 'driver' => 'mysql', + 'host' => env('DB_HOST', 'localhost'), + 'port' => env('DB_PORT', '3306'), + 'database' => env('DB_DATABASE', 'forge'), + 'username' => env('DB_USERNAME', 'forge'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => 'utf8', 'collation' => 'utf8_unicode_ci', - 'prefix' => '', - 'strict' => false, + 'prefix' => '', + 'strict' => false, ], ], @@ -79,5 +78,4 @@ return [ 'database' => 0, ], ], - ]; diff --git a/config/debugbar.php b/config/debugbar.php index 05e78c34c..f1a1fd753 100644 --- a/config/debugbar.php +++ b/config/debugbar.php @@ -1,7 +1,6 @@ [ - 'phpinfo' => true, // Php version - 'messages' => true, // Messages - 'time' => true, // Time Datalogger - 'memory' => true, // Memory usage - 'exceptions' => true, // Exception displayer - 'log' => true, // Logs from Monolog (merged in messages if enabled) - 'db' => true, // Show database (PDO) queries and bindings - 'views' => true, // Views with their data - 'route' => true, // Current route information - 'laravel' => false, // Laravel version and environment - 'events' => true, // All events fired + 'phpinfo' => true, // Php version + 'messages' => true, // Messages + 'time' => true, // Time Datalogger + 'memory' => true, // Memory usage + 'exceptions' => true, // Exception displayer + 'log' => true, // Logs from Monolog (merged in messages if enabled) + 'db' => true, // Show database (PDO) queries and bindings + 'views' => true, // Views with their data + 'route' => true, // Current route information + 'laravel' => false, // Laravel version and environment + 'events' => true, // All events fired 'default_request' => false, // Regular or special Symfony request logger 'symfony_request' => true, // Only one can be enabled.. - 'mail' => true, // Catch mail messages - 'logs' => false, // Add the latest log messages - 'files' => false, // Show the included files - 'config' => false, // Display config settings - 'auth' => false, // Display Laravel authentication status - 'gate' => false, // Display Laravel Gate checks - 'session' => true, // Display session data + 'mail' => true, // Catch mail messages + 'logs' => false, // Add the latest log messages + 'files' => false, // Show the included files + 'config' => false, // Display config settings + 'auth' => false, // Display Laravel authentication status + 'gate' => false, // Display Laravel Gate checks + 'session' => true, // Display session data ], /* @@ -118,14 +117,14 @@ return [ 'show_name' => false, // Also show the users name/email in the debugbar ], 'db' => [ - 'with_params' => true, // Render SQL with the parameters substituted - 'timeline' => true, // Add the queries to the timeline - 'backtrace' => true, // EXPERIMENTAL: Use a backtrace to find the origin of the query in your files. + 'with_params' => true, // Render SQL with the parameters substituted + 'timeline' => true, // Add the queries to the timeline + 'backtrace' => true, // EXPERIMENTAL: Use a backtrace to find the origin of the query in your files. 'explain' => [ // EXPERIMENTAL: Show EXPLAIN output on queries 'enabled' => false, 'types' => ['SELECT', 'INSERT', 'UPDATE', 'DELETE'], // array('SELECT', 'INSERT', 'UPDATE', 'DELETE'); for MySQL 5.6.3+ ], - 'hints' => false, // Show hints for common mistakes + 'hints' => false, // Show hints for common mistakes ], 'mail' => [ 'full_log' => false, @@ -165,5 +164,4 @@ return [ | */ 'route_prefix' => '_debugbar', - ]; diff --git a/config/filesystems.php b/config/filesystems.php index e726fda0c..305142930 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -1,7 +1,6 @@ [ - 'local' => [ 'driver' => 'local', 'root' => storage_path('app'), @@ -63,7 +61,5 @@ return [ 'region' => env('AWS_REGION'), 'bucket' => env('AWS_BUCKET'), ], - ], - ]; diff --git a/config/hashids.php b/config/hashids.php new file mode 100644 index 000000000..199de1a32 --- /dev/null +++ b/config/hashids.php @@ -0,0 +1,15 @@ + env('HASHIDS_SALT'), + 'length' => env('HASHIDS_LENGTH', 8), + 'alphabet' => env('HASHIDS_ALPHABET', 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'), +]; diff --git a/config/ide-helper.php b/config/ide-helper.php new file mode 100644 index 000000000..9f10873f6 --- /dev/null +++ b/config/ide-helper.php @@ -0,0 +1,175 @@ + '_ide_helper', + 'format' => 'php', + + /* + |-------------------------------------------------------------------------- + | Fluent helpers + |-------------------------------------------------------------------------- + | + | Set to true to generate commonly used Fluent methods + | + */ + + 'include_fluent' => true, + + /* + |-------------------------------------------------------------------------- + | Write Model Magic methods + |-------------------------------------------------------------------------- + | + | Set to false to disable write magic methods of model + | + */ + + 'write_model_magic_where' => true, + + /* + |-------------------------------------------------------------------------- + | Helper files to include + |-------------------------------------------------------------------------- + | + | Include helper files. By default not included, but can be toggled with the + | -- helpers (-H) option. Extra helper files can be included. + | + */ + + 'include_helpers' => false, + + 'helper_files' => [ + base_path() . '/vendor/laravel/framework/src/Illuminate/Support/helpers.php', + ], + + /* + |-------------------------------------------------------------------------- + | Model locations to include + |-------------------------------------------------------------------------- + | + | Define in which directories the ide-helper:models command should look + | for models. + | + */ + + 'model_locations' => [ + 'app/Models', + ], + + /* + |-------------------------------------------------------------------------- + | Extra classes + |-------------------------------------------------------------------------- + | + | These implementations are not really extended, but called with magic functions + | + */ + + 'extra' => [ + 'Eloquent' => ['Illuminate\Database\Eloquent\Builder', 'Illuminate\Database\Query\Builder'], + 'Session' => ['Illuminate\Session\Store'], + ], + + 'magic' => [ + 'Log' => [ + 'debug' => 'Monolog\Logger::addDebug', + 'info' => 'Monolog\Logger::addInfo', + 'notice' => 'Monolog\Logger::addNotice', + 'warning' => 'Monolog\Logger::addWarning', + 'error' => 'Monolog\Logger::addError', + 'critical' => 'Monolog\Logger::addCritical', + 'alert' => 'Monolog\Logger::addAlert', + 'emergency' => 'Monolog\Logger::addEmergency', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Interface implementations + |-------------------------------------------------------------------------- + | + | These interfaces will be replaced with the implementing class. Some interfaces + | are detected by the helpers, others can be listed below. + | + */ + + 'interfaces' => [ + ], + + /* + |-------------------------------------------------------------------------- + | Support for custom DB types + |-------------------------------------------------------------------------- + | + | This setting allow you to map any custom database type (that you may have + | created using CREATE TYPE statement or imported using database plugin + | / extension to a Doctrine type. + | + | Each key in this array is a name of the Doctrine2 DBAL Platform. Currently valid names are: + | 'postgresql', 'db2', 'drizzle', 'mysql', 'oracle', 'sqlanywhere', 'sqlite', 'mssql' + | + | This name is returned by getName() method of the specific Doctrine/DBAL/Platforms/AbstractPlatform descendant + | + | The value of the array is an array of type mappings. Key is the name of the custom type, + | (for example, "jsonb" from Postgres 9.4) and the value is the name of the corresponding Doctrine2 type (in + | our case it is 'json_array'. Doctrine types are listed here: + | http://doctrine-dbal.readthedocs.org/en/latest/reference/types.html + | + | So to support jsonb in your models when working with Postgres, just add the following entry to the array below: + | + | "postgresql" => array( + | "jsonb" => "json_array", + | ), + | + */ + 'custom_db_types' => [ + ], + + /* + |-------------------------------------------------------------------------- + | Support for camel cased models + |-------------------------------------------------------------------------- + | + | There are some Laravel packages (such as Eloquence) that allow for accessing + | Eloquent model properties via camel case, instead of snake case. + | + | Enabling this option will support these packages by saving all model + | properties as camel case, instead of snake case. + | + | For example, normally you would see this: + | + | * @property \Carbon\Carbon $created_at + | * @property \Carbon\Carbon $updated_at + | + | With this enabled, the properties will be this: + | + | * @property \Carbon\Carbon $createdAt + | * @property \Carbon\Carbon $updatedAt + | + | Note, it is currently an all-or-nothing option. + | + */ + 'model_camel_case_properties' => false, + + /* + |-------------------------------------------------------------------------- + | Property Casts + |-------------------------------------------------------------------------- + | + | Cast the given "real type" to the given "type". + | + */ + 'type_overrides' => [ + 'integer' => 'int', + 'boolean' => 'bool', + ], +]; diff --git a/config/javascript.php b/config/javascript.php index 1aeb222e0..57504395d 100644 --- a/config/javascript.php +++ b/config/javascript.php @@ -1,7 +1,6 @@ 'Pterodactyl', - ]; diff --git a/config/laravel-fractal.php b/config/laravel-fractal.php index 32ced203e..92bfe2cea 100644 --- a/config/laravel-fractal.php +++ b/config/laravel-fractal.php @@ -1,7 +1,6 @@ League\Fractal\Serializer\JsonApiSerializer::class, - ]; diff --git a/config/laroute.php b/config/laroute.php index 7b332c40a..b2b4a2f30 100644 --- a/config/laroute.php +++ b/config/laroute.php @@ -1,7 +1,6 @@ '', - ]; diff --git a/config/mail.php b/config/mail.php index 146e1b11c..f47793438 100644 --- a/config/mail.php +++ b/config/mail.php @@ -1,7 +1,6 @@ 'alert_messages', - ]; diff --git a/config/pterodactyl.php b/config/pterodactyl.php index bd10183c6..7c66f3224 100644 --- a/config/pterodactyl.php +++ b/config/pterodactyl.php @@ -1,7 +1,6 @@ [ 'servers' => env('APP_PAGINATE_FRONT_SERVERS', 15), ], + 'admin' => [ + 'servers' => env('APP_PAGINATE_ADMIN_SERVERS', 25), + 'users' => env('APP_PAGINATE_ADMIN_USERS', 25), + 'packs' => env('APP_PAGINATE_ADMIN_PACKS', 50), + ], 'api' => [ 'nodes' => env('APP_PAGINATE_API_NODES', 25), 'servers' => env('APP_PAGINATE_API_SERVERS', 25), @@ -115,7 +119,7 @@ return [ | if panel is up to date. */ 'cdn' => [ - 'cache' => 60, + 'cache_time' => 60, 'url' => 'https://cdn.pterodactyl.io/releases/latest.json', ], @@ -131,6 +135,30 @@ return [ 'in_context' => env('PHRASE_IN_CONTEXT', false), ], + /* + |-------------------------------------------------------------------------- + | File Editor + |-------------------------------------------------------------------------- + | + | This array includes the MIME filetypes that can be edited via the web. + */ + 'files' => [ + 'max_edit_size' => env('PTERODACTYL_FILES_MAX_EDIT_SIZE', 50000), + 'editable' => [ + 'application/json', + 'application/javascript', + 'application/xml', + 'application/xhtml+xml', + 'inode/x-empty', + 'text/xml', + 'text/css', + 'text/html', + 'text/plain', + 'text/x-perl', + 'text/x-shellscript', + ], + ], + /* |-------------------------------------------------------------------------- | JSON Response Routes diff --git a/config/queue.php b/config/queue.php index a3d726f4a..ed004a52a 100644 --- a/config/queue.php +++ b/config/queue.php @@ -1,7 +1,6 @@ [ - 'sync' => [ 'driver' => 'sync', ], @@ -43,20 +41,19 @@ return [ 'sqs' => [ 'driver' => 'sqs', - 'key' => env('SQS_KEY'), + 'key' => env('SQS_KEY'), 'secret' => env('SQS_SECRET'), 'prefix' => env('SQS_QUEUE_PREFIX'), - 'queue' => env('QUEUE_STANDARD', 'standard'), + 'queue' => env('QUEUE_STANDARD', 'standard'), 'region' => env('SQS_REGION', 'us-east-1'), ], 'redis' => [ 'driver' => 'redis', 'connection' => 'default', - 'queue' => env('QUEUE_STANDARD', 'standard'), + 'queue' => env('QUEUE_STANDARD', 'standard'), 'retry_after' => 90, ], - ], /* @@ -74,5 +71,4 @@ return [ 'database' => 'mysql', 'table' => 'failed_jobs', ], - ]; diff --git a/config/recaptcha.php b/config/recaptcha.php index 646c9931a..1bac5a877 100644 --- a/config/recaptcha.php +++ b/config/recaptcha.php @@ -1,7 +1,6 @@ true, - ]; diff --git a/config/services.php b/config/services.php index 6ea80674c..be637e501 100644 --- a/config/services.php +++ b/config/services.php @@ -1,10 +1,9 @@ [ - 'key' => env('SES_KEY'), + 'key' => env('SES_KEY'), 'secret' => env('SES_SECRET'), 'region' => 'us-east-1', ], @@ -32,5 +31,4 @@ return [ 'sparkpost' => [ 'secret' => env('SPARKPOST_SECRET'), ], - ]; diff --git a/config/session.php b/config/session.php index 0c1b3bb8e..08d97fd56 100644 --- a/config/session.php +++ b/config/session.php @@ -1,7 +1,6 @@ true, - ]; diff --git a/config/settings.php b/config/settings.php index e5c82eea0..c6a75fb01 100644 --- a/config/settings.php +++ b/config/settings.php @@ -1,7 +1,6 @@ [ - 'database' => [ 'driver' => 'database', 'connection' => env('DB_CONNECTION', 'mysql'), 'table' => 'settings', ], - ], /* @@ -112,7 +109,5 @@ return [ */ 'override' => [ - ], - ]; diff --git a/config/themes.php b/config/themes.php index f90a29f68..1a7083681 100644 --- a/config/themes.php +++ b/config/themes.php @@ -8,9 +8,9 @@ return [ 'themes' => [ 'pterodactyl' => [ - 'extends' => null, - 'views-path' => 'pterodactyl', - 'asset-path' => 'themes/pterodactyl', + 'extends' => null, + 'views-path' => 'pterodactyl', + 'asset-path' => 'themes/pterodactyl', ], ], ]; diff --git a/config/trustedproxy.php b/config/trustedproxy.php index a06211497..c60aa6a06 100644 --- a/config/trustedproxy.php +++ b/config/trustedproxy.php @@ -1,7 +1,6 @@ [ - \Illuminate\Http\Request::HEADER_CLIENT_IP => 'X_FORWARDED_FOR', - \Illuminate\Http\Request::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST', + \Illuminate\Http\Request::HEADER_CLIENT_IP => 'X_FORWARDED_FOR', + \Illuminate\Http\Request::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST', \Illuminate\Http\Request::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO', - \Illuminate\Http\Request::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT', + \Illuminate\Http\Request::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT', ], ]; diff --git a/config/view.php b/config/view.php index 2acfd9cc9..8796b0abc 100644 --- a/config/view.php +++ b/config/view.php @@ -1,7 +1,6 @@ realpath(storage_path('framework/views')), - ]; diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index 7295947ce..8726b2f4f 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -11,11 +11,169 @@ | */ -$factory->define(Pterodactyl\Models\User::class, function (Faker\Generator $faker) { +\Sofa\Eloquence\Model::unsetEventDispatcher(); + +$factory->define(Pterodactyl\Models\Server::class, function (Faker\Generator $faker) { return [ - 'name' => $faker->name, - 'email' => $faker->email, - 'password' => bcrypt(str_random(10)), - 'remember_token' => str_random(10), + 'id' => $faker->unique()->randomNumber(), + 'node_id' => $faker->randomNumber(), + 'uuid' => $faker->unique()->uuid, + 'uuidShort' => str_random(8), + 'name' => $faker->firstName, + 'description' => implode(' ', $faker->sentences()), + 'skip_scripts' => 0, + 'suspended' => 0, + 'memory' => 512, + 'swap' => 0, + 'disk' => 512, + 'io' => 500, + 'cpu' => 0, + 'oom_disabled' => 0, + 'pack_id' => null, + 'daemonSecret' => $faker->uuid, + 'username' => $faker->userName, + 'sftp_password' => null, + 'installed' => 1, + 'created_at' => \Carbon\Carbon::now(), + 'updated_at' => \Carbon\Carbon::now(), + ]; +}); + +$factory->define(Pterodactyl\Models\User::class, function (Faker\Generator $faker) { + return [ + 'id' => $faker->unique()->randomNumber(), + 'external_id' => null, + 'uuid' => $faker->uuid, + 'username' => $faker->userName, + 'email' => $faker->safeEmail, + 'name_first' => $faker->firstName, + 'name_last' => $faker->lastName, + 'password' => bcrypt('password'), + 'language' => 'en', + 'root_admin' => false, + 'use_totp' => false, + ]; +}); + +$factory->state(Pterodactyl\Models\User::class, 'admin', function () { + return [ + 'root_admin' => true, + ]; +}); + +$factory->define(Pterodactyl\Models\Location::class, function (Faker\Generator $faker) { + return [ + 'id' => $faker->unique()->randomNumber(), + 'short' => $faker->domainWord, + 'long' => $faker->catchPhrase, + ]; +}); + +$factory->define(Pterodactyl\Models\Node::class, function (Faker\Generator $faker) { + return [ + 'id' => $faker->unique()->randomNumber(), + 'public' => true, + 'name' => $faker->firstName, + 'fqdn' => $faker->ipv4, + 'scheme' => 'http', + 'behind_proxy' => false, + 'memory' => 1024, + 'memory_overallocate' => 0, + 'disk' => 10240, + 'disk_overallocate' => 0, + 'upload_size' => 100, + 'daemonSecret' => $faker->uuid, + 'daemonListen' => 8080, + 'daemonSFTP' => 2022, + 'daemonBase' => '/srv/daemon', + ]; +}); + +$factory->define(Pterodactyl\Models\Service::class, function (Faker\Generator $faker) { + return [ + 'id' => $faker->unique()->randomNumber(), + 'author' => $faker->unique()->uuid, + 'name' => $faker->word, + 'description' => null, + 'folder' => strtolower($faker->unique()->word), + 'startup' => 'java -jar test.jar', + 'index_file' => 'indexjs', + ]; +}); + +$factory->define(Pterodactyl\Models\ServiceOption::class, function (Faker\Generator $faker) { + return [ + 'id' => $faker->unique()->randomNumber(), + 'service_id' => $faker->unique()->randomNumber(), + 'name' => $faker->name, + 'description' => implode(' ', $faker->sentences(3)), + 'tag' => $faker->unique()->randomNumber(5), + ]; +}); + +$factory->define(Pterodactyl\Models\ServiceVariable::class, function (Faker\Generator $faker) { + return [ + 'id' => $faker->unique()->randomNumber(), + 'name' => $faker->firstName, + 'description' => $faker->sentence(), + 'env_variable' => strtoupper(str_replace(' ', '_', $faker->words(2, true))), + 'default_value' => $faker->colorName, + 'user_viewable' => 0, + 'user_editable' => 0, + 'rules' => 'required|string', + 'created_at' => \Carbon\Carbon::now(), + 'updated_at' => \Carbon\Carbon::now(), + ]; +}); + +$factory->state(Pterodactyl\Models\ServiceVariable::class, 'viewable', function () { + return ['user_viewable' => 1]; +}); + +$factory->state(Pterodactyl\Models\ServiceVariable::class, 'editable', function () { + return ['user_editable' => 1]; +}); + +$factory->define(Pterodactyl\Models\Pack::class, function (Faker\Generator $faker) { + return [ + 'id' => $faker->unique()->randomNumber(), + 'option_id' => $faker->randomNumber(), + 'uuid' => $faker->uuid, + 'name' => $faker->word, + 'description' => null, + 'version' => $faker->randomNumber(), + 'selectable' => 1, + 'visible' => 1, + 'locked' => 0, + ]; +}); + +$factory->define(Pterodactyl\Models\Subuser::class, function (Faker\Generator $faker) { + return [ + 'id' => $faker->unique()->randomNumber(), + 'user_id' => $faker->randomNumber(), + 'server_id' => $faker->randomNumber(), + 'daemonSecret' => $faker->unique()->uuid, + ]; +}); + +$factory->define(Pterodactyl\Models\Allocation::class, function (Faker\Generator $faker) { + return [ + 'id' => $faker->unique()->randomNumber(), + 'node_id' => $faker->randomNumber(), + 'ip' => $faker->ipv4, + 'port' => $faker->randomNumber(5), + ]; +}); + +$factory->define(Pterodactyl\Models\DatabaseHost::class, function (Faker\Generator $faker) { + return [ + 'id' => $faker->unique()->randomNumber(), + 'name' => $faker->colorName, + 'host' => $faker->unique()->ipv4, + 'port' => 3306, + 'username' => $faker->colorName, + 'password' => Crypt::encrypt($faker->word), + 'node_id' => $faker->randomNumber(), ]; }); diff --git a/database/migrations/2016_01_23_195641_add_allocations_table.php b/database/migrations/2016_01_23_195641_add_allocations_table.php index 7384b7de2..cfff2b359 100644 --- a/database/migrations/2016_01_23_195641_add_allocations_table.php +++ b/database/migrations/2016_01_23_195641_add_allocations_table.php @@ -7,8 +7,6 @@ class AddAllocationsTable extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -24,8 +22,6 @@ class AddAllocationsTable extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_01_23_195851_add_api_keys.php b/database/migrations/2016_01_23_195851_add_api_keys.php index 290c124a0..af7deb62d 100644 --- a/database/migrations/2016_01_23_195851_add_api_keys.php +++ b/database/migrations/2016_01_23_195851_add_api_keys.php @@ -7,8 +7,6 @@ class AddApiKeys extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -23,8 +21,6 @@ class AddApiKeys extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_01_23_200044_add_api_permissions.php b/database/migrations/2016_01_23_200044_add_api_permissions.php index f1843aad5..e6f6bcbf8 100644 --- a/database/migrations/2016_01_23_200044_add_api_permissions.php +++ b/database/migrations/2016_01_23_200044_add_api_permissions.php @@ -7,8 +7,6 @@ class AddApiPermissions extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -21,8 +19,6 @@ class AddApiPermissions extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_01_23_200159_add_downloads.php b/database/migrations/2016_01_23_200159_add_downloads.php index f1c192432..b1771c5e4 100644 --- a/database/migrations/2016_01_23_200159_add_downloads.php +++ b/database/migrations/2016_01_23_200159_add_downloads.php @@ -7,8 +7,6 @@ class AddDownloads extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -23,8 +21,6 @@ class AddDownloads extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_01_23_200421_create_failed_jobs_table.php b/database/migrations/2016_01_23_200421_create_failed_jobs_table.php index 63eaf53a6..83923e7d0 100644 --- a/database/migrations/2016_01_23_200421_create_failed_jobs_table.php +++ b/database/migrations/2016_01_23_200421_create_failed_jobs_table.php @@ -7,8 +7,6 @@ class CreateFailedJobsTable extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -23,8 +21,6 @@ class CreateFailedJobsTable extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_01_23_200440_create_jobs_table.php b/database/migrations/2016_01_23_200440_create_jobs_table.php index 01d3a9e65..277acae31 100644 --- a/database/migrations/2016_01_23_200440_create_jobs_table.php +++ b/database/migrations/2016_01_23_200440_create_jobs_table.php @@ -7,8 +7,6 @@ class CreateJobsTable extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -27,8 +25,6 @@ class CreateJobsTable extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_01_23_200528_add_locations.php b/database/migrations/2016_01_23_200528_add_locations.php index 6ad7de75b..b34a5fbcc 100644 --- a/database/migrations/2016_01_23_200528_add_locations.php +++ b/database/migrations/2016_01_23_200528_add_locations.php @@ -7,8 +7,6 @@ class AddLocations extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -22,8 +20,6 @@ class AddLocations extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_01_23_200648_add_nodes.php b/database/migrations/2016_01_23_200648_add_nodes.php index 57613cabd..52c0a29e6 100644 --- a/database/migrations/2016_01_23_200648_add_nodes.php +++ b/database/migrations/2016_01_23_200648_add_nodes.php @@ -7,8 +7,6 @@ class AddNodes extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -33,8 +31,6 @@ class AddNodes extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_01_23_201433_add_password_resets.php b/database/migrations/2016_01_23_201433_add_password_resets.php index 45454b801..0584e3617 100644 --- a/database/migrations/2016_01_23_201433_add_password_resets.php +++ b/database/migrations/2016_01_23_201433_add_password_resets.php @@ -7,8 +7,6 @@ class AddPasswordResets extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -21,8 +19,6 @@ class AddPasswordResets extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_01_23_201531_add_permissions.php b/database/migrations/2016_01_23_201531_add_permissions.php index bf6327ac6..12c9bbe0f 100644 --- a/database/migrations/2016_01_23_201531_add_permissions.php +++ b/database/migrations/2016_01_23_201531_add_permissions.php @@ -7,8 +7,6 @@ class AddPermissions extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -23,8 +21,6 @@ class AddPermissions extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_01_23_201649_add_server_variables.php b/database/migrations/2016_01_23_201649_add_server_variables.php index 229b33c60..d9a436e6d 100644 --- a/database/migrations/2016_01_23_201649_add_server_variables.php +++ b/database/migrations/2016_01_23_201649_add_server_variables.php @@ -7,8 +7,6 @@ class AddServerVariables extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -23,8 +21,6 @@ class AddServerVariables extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_01_23_201748_add_servers.php b/database/migrations/2016_01_23_201748_add_servers.php index f4ae554c7..5e1061069 100644 --- a/database/migrations/2016_01_23_201748_add_servers.php +++ b/database/migrations/2016_01_23_201748_add_servers.php @@ -7,8 +7,6 @@ class AddServers extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -40,8 +38,6 @@ class AddServers extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_01_23_202544_add_service_options.php b/database/migrations/2016_01_23_202544_add_service_options.php index 172f1eb7b..7b0a33609 100644 --- a/database/migrations/2016_01_23_202544_add_service_options.php +++ b/database/migrations/2016_01_23_202544_add_service_options.php @@ -7,8 +7,6 @@ class AddServiceOptions extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -25,8 +23,6 @@ class AddServiceOptions extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_01_23_202731_add_service_varibles.php b/database/migrations/2016_01_23_202731_add_service_varibles.php index 5ea938986..e79fa1fe9 100644 --- a/database/migrations/2016_01_23_202731_add_service_varibles.php +++ b/database/migrations/2016_01_23_202731_add_service_varibles.php @@ -7,8 +7,6 @@ class AddServiceVaribles extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -29,8 +27,6 @@ class AddServiceVaribles extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_01_23_202943_add_services.php b/database/migrations/2016_01_23_202943_add_services.php index c837dc923..31f723445 100644 --- a/database/migrations/2016_01_23_202943_add_services.php +++ b/database/migrations/2016_01_23_202943_add_services.php @@ -7,8 +7,6 @@ class AddServices extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -25,8 +23,6 @@ class AddServices extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_01_23_203119_create_settings_table.php b/database/migrations/2016_01_23_203119_create_settings_table.php index 0e2c0cbfe..2cd6922c2 100644 --- a/database/migrations/2016_01_23_203119_create_settings_table.php +++ b/database/migrations/2016_01_23_203119_create_settings_table.php @@ -7,8 +7,6 @@ class CreateSettingsTable extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,8 +18,6 @@ class CreateSettingsTable extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_01_23_203150_add_subusers.php b/database/migrations/2016_01_23_203150_add_subusers.php index 1cc8e334c..2f0e46310 100644 --- a/database/migrations/2016_01_23_203150_add_subusers.php +++ b/database/migrations/2016_01_23_203150_add_subusers.php @@ -7,8 +7,6 @@ class AddSubusers extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -23,8 +21,6 @@ class AddSubusers extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_01_23_203159_add_users.php b/database/migrations/2016_01_23_203159_add_users.php index 14e28eeba..05ace7e22 100644 --- a/database/migrations/2016_01_23_203159_add_users.php +++ b/database/migrations/2016_01_23_203159_add_users.php @@ -7,8 +7,6 @@ class AddUsers extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -28,8 +26,6 @@ class AddUsers extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_01_23_203947_create_sessions_table.php b/database/migrations/2016_01_23_203947_create_sessions_table.php index f1c84aa4b..533fa8aa2 100644 --- a/database/migrations/2016_01_23_203947_create_sessions_table.php +++ b/database/migrations/2016_01_23_203947_create_sessions_table.php @@ -7,8 +7,6 @@ class CreateSessionsTable extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -24,8 +22,6 @@ class CreateSessionsTable extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_01_25_234418_rename_permissions_column.php b/database/migrations/2016_01_25_234418_rename_permissions_column.php index c0632b1d9..ae46dceb2 100644 --- a/database/migrations/2016_01_25_234418_rename_permissions_column.php +++ b/database/migrations/2016_01_25_234418_rename_permissions_column.php @@ -7,8 +7,6 @@ class RenamePermissionsColumn extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -19,13 +17,10 @@ class RenamePermissionsColumn extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { Schema::table('permissions', function (Blueprint $table) { - // }); } } diff --git a/database/migrations/2016_02_07_172148_add_databases_tables.php b/database/migrations/2016_02_07_172148_add_databases_tables.php index 9a36a690a..7b1048b15 100644 --- a/database/migrations/2016_02_07_172148_add_databases_tables.php +++ b/database/migrations/2016_02_07_172148_add_databases_tables.php @@ -7,8 +7,6 @@ class AddDatabasesTables extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -26,8 +24,6 @@ class AddDatabasesTables extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_02_07_181319_add_database_servers_table.php b/database/migrations/2016_02_07_181319_add_database_servers_table.php index 9d6da726e..5a6740ae6 100644 --- a/database/migrations/2016_02_07_181319_add_database_servers_table.php +++ b/database/migrations/2016_02_07_181319_add_database_servers_table.php @@ -7,8 +7,6 @@ class AddDatabaseServersTable extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -27,8 +25,6 @@ class AddDatabaseServersTable extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_02_13_154306_add_service_option_default_startup.php b/database/migrations/2016_02_13_154306_add_service_option_default_startup.php index 1f8ad36fc..c8255ff47 100644 --- a/database/migrations/2016_02_13_154306_add_service_option_default_startup.php +++ b/database/migrations/2016_02_13_154306_add_service_option_default_startup.php @@ -7,8 +7,6 @@ class AddServiceOptionDefaultStartup extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,8 +18,6 @@ class AddServiceOptionDefaultStartup extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_02_20_155318_add_unique_service_field.php b/database/migrations/2016_02_20_155318_add_unique_service_field.php index 798ac4ba9..01ff91359 100644 --- a/database/migrations/2016_02_20_155318_add_unique_service_field.php +++ b/database/migrations/2016_02_20_155318_add_unique_service_field.php @@ -7,8 +7,6 @@ class AddUniqueServiceField extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -19,8 +17,6 @@ class AddUniqueServiceField extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_02_27_163411_add_tasks_table.php b/database/migrations/2016_02_27_163411_add_tasks_table.php index 2cdaa30b6..aee1e7cae 100644 --- a/database/migrations/2016_02_27_163411_add_tasks_table.php +++ b/database/migrations/2016_02_27_163411_add_tasks_table.php @@ -7,8 +7,6 @@ class AddTasksTable extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -33,8 +31,6 @@ class AddTasksTable extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_02_27_163447_add_tasks_log_table.php b/database/migrations/2016_02_27_163447_add_tasks_log_table.php index 6d9352dff..265e7fd96 100644 --- a/database/migrations/2016_02_27_163447_add_tasks_log_table.php +++ b/database/migrations/2016_02_27_163447_add_tasks_log_table.php @@ -7,8 +7,6 @@ class AddTasksLogTable extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -24,8 +22,6 @@ class AddTasksLogTable extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_03_18_155649_add_nullable_field_lastrun.php b/database/migrations/2016_03_18_155649_add_nullable_field_lastrun.php index 9065947d6..9d4752eb6 100644 --- a/database/migrations/2016_03_18_155649_add_nullable_field_lastrun.php +++ b/database/migrations/2016_03_18_155649_add_nullable_field_lastrun.php @@ -6,8 +6,6 @@ class AddNullableFieldLastrun extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -17,8 +15,6 @@ class AddNullableFieldLastrun extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_08_30_212718_add_ip_alias.php b/database/migrations/2016_08_30_212718_add_ip_alias.php index e75930edd..26aa5eaa5 100644 --- a/database/migrations/2016_08_30_212718_add_ip_alias.php +++ b/database/migrations/2016_08_30_212718_add_ip_alias.php @@ -7,8 +7,6 @@ class AddIpAlias extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -30,8 +28,6 @@ class AddIpAlias extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_08_30_213301_modify_ip_storage_method.php b/database/migrations/2016_08_30_213301_modify_ip_storage_method.php index b77ccbea6..ee7e704fb 100644 --- a/database/migrations/2016_08_30_213301_modify_ip_storage_method.php +++ b/database/migrations/2016_08_30_213301_modify_ip_storage_method.php @@ -7,8 +7,6 @@ class ModifyIpStorageMethod extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -48,8 +46,6 @@ class ModifyIpStorageMethod extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_09_01_193520_add_suspension_for_servers.php b/database/migrations/2016_09_01_193520_add_suspension_for_servers.php index 39717253b..7bfb75b20 100644 --- a/database/migrations/2016_09_01_193520_add_suspension_for_servers.php +++ b/database/migrations/2016_09_01_193520_add_suspension_for_servers.php @@ -7,8 +7,6 @@ class AddSuspensionForServers extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -19,8 +17,6 @@ class AddSuspensionForServers extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_09_01_211924_remove_active_column.php b/database/migrations/2016_09_01_211924_remove_active_column.php index 746559a80..22a2bde13 100644 --- a/database/migrations/2016_09_01_211924_remove_active_column.php +++ b/database/migrations/2016_09_01_211924_remove_active_column.php @@ -7,8 +7,6 @@ class RemoveActiveColumn extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -19,8 +17,6 @@ class RemoveActiveColumn extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_09_02_190647_add_sftp_password_storage.php b/database/migrations/2016_09_02_190647_add_sftp_password_storage.php index b950c631b..565957d59 100644 --- a/database/migrations/2016_09_02_190647_add_sftp_password_storage.php +++ b/database/migrations/2016_09_02_190647_add_sftp_password_storage.php @@ -7,8 +7,6 @@ class AddSftpPasswordStorage extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -19,8 +17,6 @@ class AddSftpPasswordStorage extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_09_04_171338_update_jobs_tables.php b/database/migrations/2016_09_04_171338_update_jobs_tables.php index 482bb08c7..840ecacb5 100644 --- a/database/migrations/2016_09_04_171338_update_jobs_tables.php +++ b/database/migrations/2016_09_04_171338_update_jobs_tables.php @@ -8,8 +8,6 @@ class UpdateJobsTables extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -22,8 +20,6 @@ class UpdateJobsTables extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_09_04_172028_update_failed_jobs_table.php b/database/migrations/2016_09_04_172028_update_failed_jobs_table.php index 38f2ef3a5..a00f5f18d 100644 --- a/database/migrations/2016_09_04_172028_update_failed_jobs_table.php +++ b/database/migrations/2016_09_04_172028_update_failed_jobs_table.php @@ -8,8 +8,6 @@ class UpdateFailedJobsTable extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,8 +18,6 @@ class UpdateFailedJobsTable extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_09_04_182835_create_notifications_table.php b/database/migrations/2016_09_04_182835_create_notifications_table.php index 160a1c42f..30fc23a59 100644 --- a/database/migrations/2016_09_04_182835_create_notifications_table.php +++ b/database/migrations/2016_09_04_182835_create_notifications_table.php @@ -7,8 +7,6 @@ class CreateNotificationsTable extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -24,8 +22,6 @@ class CreateNotificationsTable extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_09_07_163017_add_unique_identifier.php b/database/migrations/2016_09_07_163017_add_unique_identifier.php index d26e5995a..e1bab9ccc 100644 --- a/database/migrations/2016_09_07_163017_add_unique_identifier.php +++ b/database/migrations/2016_09_07_163017_add_unique_identifier.php @@ -8,8 +8,6 @@ class AddUniqueIdentifier extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,8 +18,6 @@ class AddUniqueIdentifier extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_09_14_145945_allow_longer_regex_field.php b/database/migrations/2016_09_14_145945_allow_longer_regex_field.php index d8a4e8c65..a7df1ca1b 100644 --- a/database/migrations/2016_09_14_145945_allow_longer_regex_field.php +++ b/database/migrations/2016_09_14_145945_allow_longer_regex_field.php @@ -8,8 +8,6 @@ class AllowLongerRegexField extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,8 +18,6 @@ class AllowLongerRegexField extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_09_17_194246_add_docker_image_column.php b/database/migrations/2016_09_17_194246_add_docker_image_column.php index 58e4b87a3..05d26112e 100644 --- a/database/migrations/2016_09_17_194246_add_docker_image_column.php +++ b/database/migrations/2016_09_17_194246_add_docker_image_column.php @@ -8,8 +8,6 @@ class AddDockerImageColumn extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -33,8 +31,6 @@ class AddDockerImageColumn extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_09_21_165554_update_servers_column_name.php b/database/migrations/2016_09_21_165554_update_servers_column_name.php index 064c57c3b..14ae07c4a 100644 --- a/database/migrations/2016_09_21_165554_update_servers_column_name.php +++ b/database/migrations/2016_09_21_165554_update_servers_column_name.php @@ -8,8 +8,6 @@ class UpdateServersColumnName extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,8 +18,6 @@ class UpdateServersColumnName extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_09_29_213518_rename_double_insurgency.php b/database/migrations/2016_09_29_213518_rename_double_insurgency.php index 4fecb8bdf..adb577754 100644 --- a/database/migrations/2016_09_29_213518_rename_double_insurgency.php +++ b/database/migrations/2016_09_29_213518_rename_double_insurgency.php @@ -6,8 +6,6 @@ class RenameDoubleInsurgency extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -22,11 +20,8 @@ class RenameDoubleInsurgency extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { - // } } diff --git a/database/migrations/2016_10_07_152117_build_api_log_table.php b/database/migrations/2016_10_07_152117_build_api_log_table.php index 5f1ceb22f..08ea312dc 100644 --- a/database/migrations/2016_10_07_152117_build_api_log_table.php +++ b/database/migrations/2016_10_07_152117_build_api_log_table.php @@ -8,8 +8,6 @@ class BuildApiLogTable extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -29,8 +27,6 @@ class BuildApiLogTable extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_10_14_164802_update_api_keys.php b/database/migrations/2016_10_14_164802_update_api_keys.php index 80d7f9501..56c3e8097 100644 --- a/database/migrations/2016_10_14_164802_update_api_keys.php +++ b/database/migrations/2016_10_14_164802_update_api_keys.php @@ -8,8 +8,6 @@ class UpdateApiKeys extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -22,8 +20,6 @@ class UpdateApiKeys extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_10_23_181719_update_misnamed_bungee.php b/database/migrations/2016_10_23_181719_update_misnamed_bungee.php index 0a5316755..70ec18b33 100644 --- a/database/migrations/2016_10_23_181719_update_misnamed_bungee.php +++ b/database/migrations/2016_10_23_181719_update_misnamed_bungee.php @@ -6,8 +6,6 @@ class UpdateMisnamedBungee extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -18,8 +16,6 @@ class UpdateMisnamedBungee extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_10_23_193810_add_foreign_keys_servers.php b/database/migrations/2016_10_23_193810_add_foreign_keys_servers.php index 59bc0ac39..3a2663527 100644 --- a/database/migrations/2016_10_23_193810_add_foreign_keys_servers.php +++ b/database/migrations/2016_10_23_193810_add_foreign_keys_servers.php @@ -8,8 +8,6 @@ class AddForeignKeysServers extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -33,8 +31,6 @@ class AddForeignKeysServers extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_10_23_201624_add_foreign_allocations.php b/database/migrations/2016_10_23_201624_add_foreign_allocations.php index 8ff9bdd2f..0660081cb 100644 --- a/database/migrations/2016_10_23_201624_add_foreign_allocations.php +++ b/database/migrations/2016_10_23_201624_add_foreign_allocations.php @@ -8,8 +8,6 @@ class AddForeignAllocations extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -24,24 +22,22 @@ class AddForeignAllocations extends Migration }); } - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::table('allocations', function (Blueprint $table) { - $table->dropForeign('allocations_assigned_to_foreign'); - $table->dropForeign('allocations_node_foreign'); + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('allocations', function (Blueprint $table) { + $table->dropForeign('allocations_assigned_to_foreign'); + $table->dropForeign('allocations_node_foreign'); - $table->dropIndex('allocations_assigned_to_foreign'); - $table->dropIndex('allocations_node_foreign'); - }); + $table->dropIndex('allocations_assigned_to_foreign'); + $table->dropIndex('allocations_node_foreign'); + }); - DB::statement('ALTER TABLE allocations + DB::statement('ALTER TABLE allocations MODIFY COLUMN assigned_to MEDIUMINT(8) UNSIGNED NULL, MODIFY COLUMN node MEDIUMINT(8) UNSIGNED NOT NULL '); - } + } } diff --git a/database/migrations/2016_10_23_202222_add_foreign_api_keys.php b/database/migrations/2016_10_23_202222_add_foreign_api_keys.php index 67dcd3ce3..700342d74 100644 --- a/database/migrations/2016_10_23_202222_add_foreign_api_keys.php +++ b/database/migrations/2016_10_23_202222_add_foreign_api_keys.php @@ -8,8 +8,6 @@ class AddForeignApiKeys extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,8 +18,6 @@ class AddForeignApiKeys extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_10_23_202703_add_foreign_api_permissions.php b/database/migrations/2016_10_23_202703_add_foreign_api_permissions.php index 58bf1a5a5..d8eb3504d 100644 --- a/database/migrations/2016_10_23_202703_add_foreign_api_permissions.php +++ b/database/migrations/2016_10_23_202703_add_foreign_api_permissions.php @@ -8,8 +8,6 @@ class AddForeignApiPermissions extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,18 +18,16 @@ class AddForeignApiPermissions extends Migration }); } - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::table('api_permissions', function (Blueprint $table) { - $table->dropForeign('api_permissions_key_id_foreign'); - $table->dropIndex('api_permissions_key_id_foreign'); - }); + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('api_permissions', function (Blueprint $table) { + $table->dropForeign('api_permissions_key_id_foreign'); + $table->dropIndex('api_permissions_key_id_foreign'); + }); - DB::statement('ALTER TABLE api_permissions MODIFY key_id MEDIUMINT(8) UNSIGNED NOT NULL'); - } + DB::statement('ALTER TABLE api_permissions MODIFY key_id MEDIUMINT(8) UNSIGNED NOT NULL'); + } } diff --git a/database/migrations/2016_10_23_202953_add_foreign_database_servers.php b/database/migrations/2016_10_23_202953_add_foreign_database_servers.php index b6f012dff..769b7daa3 100644 --- a/database/migrations/2016_10_23_202953_add_foreign_database_servers.php +++ b/database/migrations/2016_10_23_202953_add_foreign_database_servers.php @@ -8,8 +8,6 @@ class AddForeignDatabaseServers extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,8 +18,6 @@ class AddForeignDatabaseServers extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_10_23_203105_add_foreign_databases.php b/database/migrations/2016_10_23_203105_add_foreign_databases.php index 2d6b6e9a2..be26e3cb0 100644 --- a/database/migrations/2016_10_23_203105_add_foreign_databases.php +++ b/database/migrations/2016_10_23_203105_add_foreign_databases.php @@ -8,8 +8,6 @@ class AddForeignDatabases extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -21,8 +19,6 @@ class AddForeignDatabases extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_10_23_203335_add_foreign_nodes.php b/database/migrations/2016_10_23_203335_add_foreign_nodes.php index 8fa516c00..f861e0a7d 100644 --- a/database/migrations/2016_10_23_203335_add_foreign_nodes.php +++ b/database/migrations/2016_10_23_203335_add_foreign_nodes.php @@ -8,8 +8,6 @@ class AddForeignNodes extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -22,8 +20,6 @@ class AddForeignNodes extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_10_23_203522_add_foreign_permissions.php b/database/migrations/2016_10_23_203522_add_foreign_permissions.php index 153ab27ce..a43f0eacf 100644 --- a/database/migrations/2016_10_23_203522_add_foreign_permissions.php +++ b/database/migrations/2016_10_23_203522_add_foreign_permissions.php @@ -8,8 +8,6 @@ class AddForeignPermissions extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -19,19 +17,17 @@ class AddForeignPermissions extends Migration }); } - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::table('permissions', function (Blueprint $table) { - $table->dropForeign('permissions_user_id_foreign'); - $table->dropForeign('permissions_server_id_foreign'); + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('permissions', function (Blueprint $table) { + $table->dropForeign('permissions_user_id_foreign'); + $table->dropForeign('permissions_server_id_foreign'); - $table->dropIndex('permissions_user_id_foreign'); - $table->dropIndex('permissions_server_id_foreign'); - }); - } + $table->dropIndex('permissions_user_id_foreign'); + $table->dropIndex('permissions_server_id_foreign'); + }); + } } diff --git a/database/migrations/2016_10_23_203857_add_foreign_server_variables.php b/database/migrations/2016_10_23_203857_add_foreign_server_variables.php index af78a161c..b4720495d 100644 --- a/database/migrations/2016_10_23_203857_add_foreign_server_variables.php +++ b/database/migrations/2016_10_23_203857_add_foreign_server_variables.php @@ -8,8 +8,6 @@ class AddForeignServerVariables extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -24,21 +22,19 @@ class AddForeignServerVariables extends Migration }); } - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::table('server_variables', function (Blueprint $table) { - $table->dropForeign(['server_id']); - $table->dropForeign(['variable_id']); - }); + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('server_variables', function (Blueprint $table) { + $table->dropForeign(['server_id']); + $table->dropForeign(['variable_id']); + }); - DB::statement('ALTER TABLE server_variables + DB::statement('ALTER TABLE server_variables MODIFY COLUMN server_id MEDIUMINT(8) UNSIGNED NULL, MODIFY COLUMN variable_id MEDIUMINT(8) UNSIGNED NOT NULL '); - } + } } diff --git a/database/migrations/2016_10_23_204157_add_foreign_service_options.php b/database/migrations/2016_10_23_204157_add_foreign_service_options.php index 0baad0c36..cb8c0e2e8 100644 --- a/database/migrations/2016_10_23_204157_add_foreign_service_options.php +++ b/database/migrations/2016_10_23_204157_add_foreign_service_options.php @@ -8,8 +8,6 @@ class AddForeignServiceOptions extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,18 +18,16 @@ class AddForeignServiceOptions extends Migration }); } - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::table('service_options', function (Blueprint $table) { - $table->dropForeign('service_options_parent_service_foreign'); - $table->dropIndex('service_options_parent_service_foreign'); - }); + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('service_options', function (Blueprint $table) { + $table->dropForeign('service_options_parent_service_foreign'); + $table->dropIndex('service_options_parent_service_foreign'); + }); - DB::statement('ALTER TABLE service_options MODIFY parent_service MEDIUMINT(8) UNSIGNED NOT NULL'); - } + DB::statement('ALTER TABLE service_options MODIFY parent_service MEDIUMINT(8) UNSIGNED NOT NULL'); + } } diff --git a/database/migrations/2016_10_23_204321_add_foreign_service_variables.php b/database/migrations/2016_10_23_204321_add_foreign_service_variables.php index 291ca24e2..02bbc46f2 100644 --- a/database/migrations/2016_10_23_204321_add_foreign_service_variables.php +++ b/database/migrations/2016_10_23_204321_add_foreign_service_variables.php @@ -8,8 +8,6 @@ class AddForeignServiceVariables extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,18 +18,16 @@ class AddForeignServiceVariables extends Migration }); } - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::table('service_variables', function (Blueprint $table) { - $table->dropForeign('service_variables_option_id_foreign'); - $table->dropIndex('service_variables_option_id_foreign'); - }); + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('service_variables', function (Blueprint $table) { + $table->dropForeign('service_variables_option_id_foreign'); + $table->dropIndex('service_variables_option_id_foreign'); + }); - DB::statement('ALTER TABLE service_variables MODIFY option_id MEDIUMINT(8) UNSIGNED NOT NULL'); - } + DB::statement('ALTER TABLE service_variables MODIFY option_id MEDIUMINT(8) UNSIGNED NOT NULL'); + } } diff --git a/database/migrations/2016_10_23_204454_add_foreign_subusers.php b/database/migrations/2016_10_23_204454_add_foreign_subusers.php index 2f4045669..b637c80ae 100644 --- a/database/migrations/2016_10_23_204454_add_foreign_subusers.php +++ b/database/migrations/2016_10_23_204454_add_foreign_subusers.php @@ -8,8 +8,6 @@ class AddForeignSubusers extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -21,8 +19,6 @@ class AddForeignSubusers extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_10_23_204610_add_foreign_tasks.php b/database/migrations/2016_10_23_204610_add_foreign_tasks.php index 3821caffc..18ea297e5 100644 --- a/database/migrations/2016_10_23_204610_add_foreign_tasks.php +++ b/database/migrations/2016_10_23_204610_add_foreign_tasks.php @@ -8,8 +8,6 @@ class AddForeignTasks extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,8 +18,6 @@ class AddForeignTasks extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_11_04_000949_add_ark_service_option_fixed.php b/database/migrations/2016_11_04_000949_add_ark_service_option_fixed.php index 5a2dd6da4..4383c11cd 100644 --- a/database/migrations/2016_11_04_000949_add_ark_service_option_fixed.php +++ b/database/migrations/2016_11_04_000949_add_ark_service_option_fixed.php @@ -6,8 +6,6 @@ class AddArkServiceOptionFixed extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -74,8 +72,6 @@ class AddArkServiceOptionFixed extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_11_11_220649_add_pack_support.php b/database/migrations/2016_11_11_220649_add_pack_support.php index 7cb3eb10e..b6fa0972b 100644 --- a/database/migrations/2016_11_11_220649_add_pack_support.php +++ b/database/migrations/2016_11_11_220649_add_pack_support.php @@ -8,8 +8,6 @@ class AddPackSupport extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -30,8 +28,6 @@ class AddPackSupport extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_11_11_231731_set_service_name_unique.php b/database/migrations/2016_11_11_231731_set_service_name_unique.php index 4db76f8e8..42b0f6953 100644 --- a/database/migrations/2016_11_11_231731_set_service_name_unique.php +++ b/database/migrations/2016_11_11_231731_set_service_name_unique.php @@ -8,8 +8,6 @@ class SetServiceNameUnique extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,8 +18,6 @@ class SetServiceNameUnique extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_11_27_142519_add_pack_column.php b/database/migrations/2016_11_27_142519_add_pack_column.php index 4e3507c39..d520466a8 100644 --- a/database/migrations/2016_11_27_142519_add_pack_column.php +++ b/database/migrations/2016_11_27_142519_add_pack_column.php @@ -8,8 +8,6 @@ class AddPackColumn extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -22,8 +20,6 @@ class AddPackColumn extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_12_01_173018_add_configurable_upload_limit.php b/database/migrations/2016_12_01_173018_add_configurable_upload_limit.php index 91ef1fbbd..d2d14f4d0 100644 --- a/database/migrations/2016_12_01_173018_add_configurable_upload_limit.php +++ b/database/migrations/2016_12_01_173018_add_configurable_upload_limit.php @@ -8,8 +8,6 @@ class AddConfigurableUploadLimit extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,8 +18,6 @@ class AddConfigurableUploadLimit extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2016_12_02_185206_correct_service_variables.php b/database/migrations/2016_12_02_185206_correct_service_variables.php index dd99c1223..e9c87989a 100644 --- a/database/migrations/2016_12_02_185206_correct_service_variables.php +++ b/database/migrations/2016_12_02_185206_correct_service_variables.php @@ -6,8 +6,6 @@ class CorrectServiceVariables extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -67,8 +65,6 @@ class CorrectServiceVariables extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_01_03_150436_fix_misnamed_option_tag.php b/database/migrations/2017_01_03_150436_fix_misnamed_option_tag.php index a03584ca0..7cdf96807 100644 --- a/database/migrations/2017_01_03_150436_fix_misnamed_option_tag.php +++ b/database/migrations/2017_01_03_150436_fix_misnamed_option_tag.php @@ -6,8 +6,6 @@ class FixMisnamedOptionTag extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -24,8 +22,6 @@ class FixMisnamedOptionTag extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_01_07_154228_create_node_configuration_tokens_table.php b/database/migrations/2017_01_07_154228_create_node_configuration_tokens_table.php index 905d28a46..77693c265 100644 --- a/database/migrations/2017_01_07_154228_create_node_configuration_tokens_table.php +++ b/database/migrations/2017_01_07_154228_create_node_configuration_tokens_table.php @@ -8,8 +8,6 @@ class CreateNodeConfigurationTokensTable extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -25,8 +23,6 @@ class CreateNodeConfigurationTokensTable extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_01_12_135449_add_more_user_data.php b/database/migrations/2017_01_12_135449_add_more_user_data.php index 67bc3f59d..0206040b5 100644 --- a/database/migrations/2017_01_12_135449_add_more_user_data.php +++ b/database/migrations/2017_01_12_135449_add_more_user_data.php @@ -9,8 +9,6 @@ class AddMoreUserData extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -35,8 +33,6 @@ class AddMoreUserData extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_02_02_175548_UpdateColumnNames.php b/database/migrations/2017_02_02_175548_UpdateColumnNames.php index 233780bfd..c88aa8de7 100644 --- a/database/migrations/2017_02_02_175548_UpdateColumnNames.php +++ b/database/migrations/2017_02_02_175548_UpdateColumnNames.php @@ -8,8 +8,6 @@ class UpdateColumnNames extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -48,8 +46,6 @@ class UpdateColumnNames extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_02_03_140948_UpdateNodesTable.php b/database/migrations/2017_02_03_140948_UpdateNodesTable.php index 4408e612b..58ec63ef4 100644 --- a/database/migrations/2017_02_03_140948_UpdateNodesTable.php +++ b/database/migrations/2017_02_03_140948_UpdateNodesTable.php @@ -8,8 +8,6 @@ class UpdateNodesTable extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -24,8 +22,6 @@ class UpdateNodesTable extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_02_03_155554_RenameColumns.php b/database/migrations/2017_02_03_155554_RenameColumns.php index 519ccc826..5f617abec 100644 --- a/database/migrations/2017_02_03_155554_RenameColumns.php +++ b/database/migrations/2017_02_03_155554_RenameColumns.php @@ -8,8 +8,6 @@ class RenameColumns extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -28,8 +26,6 @@ class RenameColumns extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_02_05_164123_AdjustColumnNames.php b/database/migrations/2017_02_05_164123_AdjustColumnNames.php index ddb37b891..c7688f056 100644 --- a/database/migrations/2017_02_05_164123_AdjustColumnNames.php +++ b/database/migrations/2017_02_05_164123_AdjustColumnNames.php @@ -8,8 +8,6 @@ class AdjustColumnNames extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -24,8 +22,6 @@ class AdjustColumnNames extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_02_05_164516_AdjustColumnNamesForServicePacks.php b/database/migrations/2017_02_05_164516_AdjustColumnNamesForServicePacks.php index 5e57ffef3..6f86b3b6e 100644 --- a/database/migrations/2017_02_05_164516_AdjustColumnNamesForServicePacks.php +++ b/database/migrations/2017_02_05_164516_AdjustColumnNamesForServicePacks.php @@ -8,8 +8,6 @@ class AdjustColumnNamesForServicePacks extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -24,8 +22,6 @@ class AdjustColumnNamesForServicePacks extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_02_09_174834_SetupPermissionsPivotTable.php b/database/migrations/2017_02_09_174834_SetupPermissionsPivotTable.php index 7194bf075..45efce83a 100644 --- a/database/migrations/2017_02_09_174834_SetupPermissionsPivotTable.php +++ b/database/migrations/2017_02_09_174834_SetupPermissionsPivotTable.php @@ -10,8 +10,6 @@ class SetupPermissionsPivotTable extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -43,8 +41,6 @@ class SetupPermissionsPivotTable extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_02_10_171858_UpdateAPIKeyColumnNames.php b/database/migrations/2017_02_10_171858_UpdateAPIKeyColumnNames.php index 358f9938d..8b541d941 100644 --- a/database/migrations/2017_02_10_171858_UpdateAPIKeyColumnNames.php +++ b/database/migrations/2017_02_10_171858_UpdateAPIKeyColumnNames.php @@ -8,8 +8,6 @@ class UpdateAPIKeyColumnNames extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -23,8 +21,6 @@ class UpdateAPIKeyColumnNames extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_03_03_224254_UpdateNodeConfigTokensColumns.php b/database/migrations/2017_03_03_224254_UpdateNodeConfigTokensColumns.php index 5931518d6..4f27346fa 100644 --- a/database/migrations/2017_03_03_224254_UpdateNodeConfigTokensColumns.php +++ b/database/migrations/2017_03_03_224254_UpdateNodeConfigTokensColumns.php @@ -8,8 +8,6 @@ class UpdateNodeConfigTokensColumns extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -24,8 +22,6 @@ class UpdateNodeConfigTokensColumns extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_03_05_212803_DeleteServiceExecutableOption.php b/database/migrations/2017_03_05_212803_DeleteServiceExecutableOption.php index e4af2511a..2d6413ede 100644 --- a/database/migrations/2017_03_05_212803_DeleteServiceExecutableOption.php +++ b/database/migrations/2017_03_05_212803_DeleteServiceExecutableOption.php @@ -9,8 +9,6 @@ class DeleteServiceExecutableOption extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -51,8 +49,6 @@ class DeleteServiceExecutableOption extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_03_10_162934_AddNewServiceOptionsColumns.php b/database/migrations/2017_03_10_162934_AddNewServiceOptionsColumns.php index 351327d3c..385004fa4 100644 --- a/database/migrations/2017_03_10_162934_AddNewServiceOptionsColumns.php +++ b/database/migrations/2017_03_10_162934_AddNewServiceOptionsColumns.php @@ -8,8 +8,6 @@ class AddNewServiceOptionsColumns extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -28,8 +26,6 @@ class AddNewServiceOptionsColumns extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_03_10_173607_MigrateToNewServiceSystem.php b/database/migrations/2017_03_10_173607_MigrateToNewServiceSystem.php index ee247ee96..172979303 100644 --- a/database/migrations/2017_03_10_173607_MigrateToNewServiceSystem.php +++ b/database/migrations/2017_03_10_173607_MigrateToNewServiceSystem.php @@ -29,8 +29,6 @@ class MigrateToNewServiceSystem extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -56,8 +54,6 @@ class MigrateToNewServiceSystem extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_03_11_215455_ChangeServiceVariablesValidationRules.php b/database/migrations/2017_03_11_215455_ChangeServiceVariablesValidationRules.php index 617c349fa..7784083a1 100644 --- a/database/migrations/2017_03_11_215455_ChangeServiceVariablesValidationRules.php +++ b/database/migrations/2017_03_11_215455_ChangeServiceVariablesValidationRules.php @@ -9,8 +9,6 @@ class ChangeServiceVariablesValidationRules extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -32,8 +30,6 @@ class ChangeServiceVariablesValidationRules extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_03_12_150648_MoveFunctionsFromFileToDatabase.php b/database/migrations/2017_03_12_150648_MoveFunctionsFromFileToDatabase.php index bbd5fda42..5d5a3d164 100644 --- a/database/migrations/2017_03_12_150648_MoveFunctionsFromFileToDatabase.php +++ b/database/migrations/2017_03_12_150648_MoveFunctionsFromFileToDatabase.php @@ -85,8 +85,6 @@ EOF; /** * Run the migrations. - * - * @return void */ public function up() { @@ -107,8 +105,6 @@ EOF; /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_03_14_175631_RenameServicePacksToSingluarPacks.php b/database/migrations/2017_03_14_175631_RenameServicePacksToSingluarPacks.php index 910fb79df..d01012e41 100644 --- a/database/migrations/2017_03_14_175631_RenameServicePacksToSingluarPacks.php +++ b/database/migrations/2017_03_14_175631_RenameServicePacksToSingluarPacks.php @@ -8,8 +8,6 @@ class RenameServicePacksToSingluarPacks extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -26,8 +24,6 @@ class RenameServicePacksToSingluarPacks extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_03_14_200326_AddLockedStatusToTable.php b/database/migrations/2017_03_14_200326_AddLockedStatusToTable.php index 4916cd028..b1a8ee3a0 100644 --- a/database/migrations/2017_03_14_200326_AddLockedStatusToTable.php +++ b/database/migrations/2017_03_14_200326_AddLockedStatusToTable.php @@ -8,8 +8,6 @@ class AddLockedStatusToTable extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,8 +18,6 @@ class AddLockedStatusToTable extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_03_16_181109_ReOrganizeDatabaseServersToDatabaseHost.php b/database/migrations/2017_03_16_181109_ReOrganizeDatabaseServersToDatabaseHost.php index 50699a688..a7166df9e 100644 --- a/database/migrations/2017_03_16_181109_ReOrganizeDatabaseServersToDatabaseHost.php +++ b/database/migrations/2017_03_16_181109_ReOrganizeDatabaseServersToDatabaseHost.php @@ -8,8 +8,6 @@ class ReOrganizeDatabaseServersToDatabaseHost extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -28,8 +26,6 @@ class ReOrganizeDatabaseServersToDatabaseHost extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_03_16_181515_CleanupDatabasesDatabase.php b/database/migrations/2017_03_16_181515_CleanupDatabasesDatabase.php index 07192f898..bc6fb45c7 100644 --- a/database/migrations/2017_03_16_181515_CleanupDatabasesDatabase.php +++ b/database/migrations/2017_03_16_181515_CleanupDatabasesDatabase.php @@ -8,8 +8,6 @@ class CleanupDatabasesDatabase extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -24,8 +22,6 @@ class CleanupDatabasesDatabase extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_03_18_204953_AddForeignKeyToPacks.php b/database/migrations/2017_03_18_204953_AddForeignKeyToPacks.php index 0271616a0..3f26a1e34 100644 --- a/database/migrations/2017_03_18_204953_AddForeignKeyToPacks.php +++ b/database/migrations/2017_03_18_204953_AddForeignKeyToPacks.php @@ -8,8 +8,6 @@ class AddForeignKeyToPacks extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,8 +18,6 @@ class AddForeignKeyToPacks extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_03_31_221948_AddServerDescriptionColumn.php b/database/migrations/2017_03_31_221948_AddServerDescriptionColumn.php index 1e5ce0273..e8ebcb20d 100644 --- a/database/migrations/2017_03_31_221948_AddServerDescriptionColumn.php +++ b/database/migrations/2017_03_31_221948_AddServerDescriptionColumn.php @@ -8,8 +8,6 @@ class AddServerDescriptionColumn extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,8 +18,6 @@ class AddServerDescriptionColumn extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_04_02_163232_DropDeletedAtColumnFromServers.php b/database/migrations/2017_04_02_163232_DropDeletedAtColumnFromServers.php index ccd318654..3cd08f1a9 100644 --- a/database/migrations/2017_04_02_163232_DropDeletedAtColumnFromServers.php +++ b/database/migrations/2017_04_02_163232_DropDeletedAtColumnFromServers.php @@ -8,8 +8,6 @@ class DropDeletedAtColumnFromServers extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,8 +18,6 @@ class DropDeletedAtColumnFromServers extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_04_15_125021_UpgradeTaskSystem.php b/database/migrations/2017_04_15_125021_UpgradeTaskSystem.php index 5264122b4..dc58df4d9 100644 --- a/database/migrations/2017_04_15_125021_UpgradeTaskSystem.php +++ b/database/migrations/2017_04_15_125021_UpgradeTaskSystem.php @@ -9,8 +9,6 @@ class UpgradeTaskSystem extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -34,8 +32,6 @@ class UpgradeTaskSystem extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_04_20_171943_AddScriptsToServiceOptions.php b/database/migrations/2017_04_20_171943_AddScriptsToServiceOptions.php index 610f18e5f..ba2f57c41 100644 --- a/database/migrations/2017_04_20_171943_AddScriptsToServiceOptions.php +++ b/database/migrations/2017_04_20_171943_AddScriptsToServiceOptions.php @@ -8,8 +8,6 @@ class AddScriptsToServiceOptions extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -23,8 +21,6 @@ class AddScriptsToServiceOptions extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_04_21_151432_AddServiceScriptTrackingToServers.php b/database/migrations/2017_04_21_151432_AddServiceScriptTrackingToServers.php index 07afdfeea..2bc8f27b3 100644 --- a/database/migrations/2017_04_21_151432_AddServiceScriptTrackingToServers.php +++ b/database/migrations/2017_04_21_151432_AddServiceScriptTrackingToServers.php @@ -8,8 +8,6 @@ class AddServiceScriptTrackingToServers extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,8 +18,6 @@ class AddServiceScriptTrackingToServers extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_04_27_145300_AddCopyScriptFromColumn.php b/database/migrations/2017_04_27_145300_AddCopyScriptFromColumn.php index 027d1964b..514d17e1c 100644 --- a/database/migrations/2017_04_27_145300_AddCopyScriptFromColumn.php +++ b/database/migrations/2017_04_27_145300_AddCopyScriptFromColumn.php @@ -8,8 +8,6 @@ class AddCopyScriptFromColumn extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -22,8 +20,6 @@ class AddCopyScriptFromColumn extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_04_27_223629_AddAbilityToDefineConnectionOverSSLWithDaemonBehindProxy.php b/database/migrations/2017_04_27_223629_AddAbilityToDefineConnectionOverSSLWithDaemonBehindProxy.php index f82d39258..aa5e04498 100644 --- a/database/migrations/2017_04_27_223629_AddAbilityToDefineConnectionOverSSLWithDaemonBehindProxy.php +++ b/database/migrations/2017_04_27_223629_AddAbilityToDefineConnectionOverSSLWithDaemonBehindProxy.php @@ -8,8 +8,6 @@ class AddAbilityToDefineConnectionOverSSLWithDaemonBehindProxy extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -20,8 +18,6 @@ class AddAbilityToDefineConnectionOverSSLWithDaemonBehindProxy extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_05_01_141528_DeleteDownloadTable.php b/database/migrations/2017_05_01_141528_DeleteDownloadTable.php index 90a7f7a6a..7dcae3c6f 100644 --- a/database/migrations/2017_05_01_141528_DeleteDownloadTable.php +++ b/database/migrations/2017_05_01_141528_DeleteDownloadTable.php @@ -8,8 +8,6 @@ class DeleteDownloadTable extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -18,8 +16,6 @@ class DeleteDownloadTable extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_05_01_141559_DeleteNodeConfigurationTable.php b/database/migrations/2017_05_01_141559_DeleteNodeConfigurationTable.php index 369c867be..90c8c4b1e 100644 --- a/database/migrations/2017_05_01_141559_DeleteNodeConfigurationTable.php +++ b/database/migrations/2017_05_01_141559_DeleteNodeConfigurationTable.php @@ -8,8 +8,6 @@ class DeleteNodeConfigurationTable extends Migration { /** * Run the migrations. - * - * @return void */ public function up() { @@ -18,8 +16,6 @@ class DeleteNodeConfigurationTable extends Migration /** * Reverse the migrations. - * - * @return void */ public function down() { diff --git a/database/migrations/2017_06_10_152951_add_external_id_to_users.php b/database/migrations/2017_06_10_152951_add_external_id_to_users.php new file mode 100644 index 000000000..9ce5057e8 --- /dev/null +++ b/database/migrations/2017_06_10_152951_add_external_id_to_users.php @@ -0,0 +1,28 @@ +unsignedInteger('external_id')->after('id')->nullable()->unique(); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('external_id'); + }); + } +} diff --git a/database/migrations/2017_06_25_133923_ChangeForeignKeyToBeOnCascadeDelete.php b/database/migrations/2017_06_25_133923_ChangeForeignKeyToBeOnCascadeDelete.php new file mode 100644 index 000000000..a089ab4db --- /dev/null +++ b/database/migrations/2017_06_25_133923_ChangeForeignKeyToBeOnCascadeDelete.php @@ -0,0 +1,32 @@ +dropForeign(['key_id']); + + $table->foreign('key_id')->references('id')->on('api_keys')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('api_permissions', function (Blueprint $table) { + $table->dropForeign(['key_id']); + + $table->foreign('key_id')->references('id')->on('api_keys'); + }); + } +} diff --git a/database/migrations/2017_07_08_152806_ChangeUserPermissionsToDeleteOnUserDeletion.php b/database/migrations/2017_07_08_152806_ChangeUserPermissionsToDeleteOnUserDeletion.php new file mode 100644 index 000000000..0bfc7d527 --- /dev/null +++ b/database/migrations/2017_07_08_152806_ChangeUserPermissionsToDeleteOnUserDeletion.php @@ -0,0 +1,48 @@ +dropForeign(['subuser_id']); + + $table->foreign('subuser_id')->references('id')->on('subusers')->onDelete('cascade'); + }); + + Schema::table('subusers', function (Blueprint $table) { + $table->dropForeign(['user_id']); + $table->dropForeign(['server_id']); + + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + $table->foreign('server_id')->references('id')->on('servers')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('subusers', function (Blueprint $table) { + $table->dropForeign(['user_id']); + $table->dropForeign(['server_id']); + + $table->foreign('user_id')->references('id')->on('users'); + $table->foreign('server_id')->references('id')->on('servers'); + }); + + Schema::table('permissions', function (Blueprint $table) { + $table->dropForeign(['subuser_id']); + + $table->foreign('subuser_id')->references('id')->on('subusers'); + }); + } +} diff --git a/database/migrations/2017_07_08_154416_SetAllocationToReferenceNullOnServerDelete.php b/database/migrations/2017_07_08_154416_SetAllocationToReferenceNullOnServerDelete.php new file mode 100644 index 000000000..fb156ba8c --- /dev/null +++ b/database/migrations/2017_07_08_154416_SetAllocationToReferenceNullOnServerDelete.php @@ -0,0 +1,32 @@ +dropForeign(['server_id']); + + $table->foreign('server_id')->references('id')->on('servers')->onDelete('set null'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('allocations', function (Blueprint $table) { + $table->dropForeign(['server_id']); + + $table->foreign('server_id')->references('id')->on('servers'); + }); + } +} diff --git a/database/migrations/2017_07_08_154650_CascadeDeletionWhenAServerOrVariableIsDeleted.php b/database/migrations/2017_07_08_154650_CascadeDeletionWhenAServerOrVariableIsDeleted.php new file mode 100644 index 000000000..5ae9a29f9 --- /dev/null +++ b/database/migrations/2017_07_08_154650_CascadeDeletionWhenAServerOrVariableIsDeleted.php @@ -0,0 +1,36 @@ +dropForeign(['server_id']); + $table->dropForeign(['variable_id']); + + $table->foreign('server_id')->references('id')->on('servers')->onDelete('cascade'); + $table->foreign('variable_id')->references('id')->on('service_variables')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('server_variables', function (Blueprint $table) { + $table->dropForeign(['server_id']); + $table->dropForeign(['variable_id']); + + $table->foreign('server_id')->references('id')->on('servers'); + $table->foreign('variable_id')->references('id')->on('service_variables'); + }); + } +} diff --git a/database/migrations/2017_07_24_194433_DeleteTaskWhenParentServerIsDeleted.php b/database/migrations/2017_07_24_194433_DeleteTaskWhenParentServerIsDeleted.php new file mode 100644 index 000000000..88e2e0135 --- /dev/null +++ b/database/migrations/2017_07_24_194433_DeleteTaskWhenParentServerIsDeleted.php @@ -0,0 +1,32 @@ +dropForeign(['server_id']); + + $table->foreign('server_id')->references('id')->on('servers')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('tasks', function (Blueprint $table) { + $table->dropForeign(['server_id']); + + $table->foreign('server_id')->references('id')->on('servers'); + }); + } +} diff --git a/database/migrations/2017_08_05_115800_CascadeNullValuesForDatabaseHostWhenNodeIsDeleted.php b/database/migrations/2017_08_05_115800_CascadeNullValuesForDatabaseHostWhenNodeIsDeleted.php new file mode 100644 index 000000000..a33b78af6 --- /dev/null +++ b/database/migrations/2017_08_05_115800_CascadeNullValuesForDatabaseHostWhenNodeIsDeleted.php @@ -0,0 +1,30 @@ +dropForeign(['node_id']); + $table->foreign('node_id')->references('id')->on('nodes')->onDelete('set null'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('database_hosts', function (Blueprint $table) { + $table->dropForeign(['node_id']); + $table->foreign('node_id')->references('id')->on('nodes'); + }); + } +} diff --git a/database/migrations/2017_08_05_144104_AllowNegativeValuesForOverallocation.php b/database/migrations/2017_08_05_144104_AllowNegativeValuesForOverallocation.php new file mode 100644 index 000000000..77b7f984c --- /dev/null +++ b/database/migrations/2017_08_05_144104_AllowNegativeValuesForOverallocation.php @@ -0,0 +1,30 @@ +integer('disk_overallocate')->default(0)->nullable(false)->change(); + $table->integer('memory_overallocate')->default(0)->nullable(false)->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('nodes', function (Blueprint $table) { + DB::statement('ALTER TABLE nodes MODIFY disk_overallocate MEDIUMINT UNSIGNED NULL, + MODIFY memory_overallocate MEDIUMINT UNSIGNED NULL'); + }); + } +} diff --git a/database/migrations/2017_08_05_174811_SetAllocationUnqiueUsingMultipleFields.php b/database/migrations/2017_08_05_174811_SetAllocationUnqiueUsingMultipleFields.php new file mode 100644 index 000000000..f7aab7c04 --- /dev/null +++ b/database/migrations/2017_08_05_174811_SetAllocationUnqiueUsingMultipleFields.php @@ -0,0 +1,30 @@ +unique(['node_id', 'ip', 'port']); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('allocations', function (Blueprint $table) { + $table->dropForeign(['node_id']); + $table->dropUnique(['node_id', 'ip', 'port']); + $table->foreign('node_id')->references('id')->on('nodes'); + }); + } +} diff --git a/database/migrations/2017_08_15_214555_CascadeDeletionWhenAParentServiceIsDeleted.php b/database/migrations/2017_08_15_214555_CascadeDeletionWhenAParentServiceIsDeleted.php new file mode 100644 index 000000000..074f872e0 --- /dev/null +++ b/database/migrations/2017_08_15_214555_CascadeDeletionWhenAParentServiceIsDeleted.php @@ -0,0 +1,32 @@ +dropForeign(['service_id']); + + $table->foreign('service_id')->references('id')->on('services')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('service_options', function (Blueprint $table) { + $table->dropForeign(['service_id']); + + $table->foreign('service_id')->references('id')->on('services'); + }); + } +} diff --git a/database/migrations/2017_08_18_215428_RemovePackWhenParentServiceOptionIsDeleted.php b/database/migrations/2017_08_18_215428_RemovePackWhenParentServiceOptionIsDeleted.php new file mode 100644 index 000000000..1b8f1a567 --- /dev/null +++ b/database/migrations/2017_08_18_215428_RemovePackWhenParentServiceOptionIsDeleted.php @@ -0,0 +1,32 @@ +dropForeign(['option_id']); + + $table->foreign('option_id')->references('id')->on('service_options')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('packs', function (Blueprint $table) { + $table->dropForeign(['option_id']); + + $table->foreign('option_id')->references('id')->on('service_options'); + }); + } +} diff --git a/database/migrations/2017_09_10_225749_RenameTasksTableForStructureRefactor.php b/database/migrations/2017_09_10_225749_RenameTasksTableForStructureRefactor.php new file mode 100644 index 000000000..12eada73c --- /dev/null +++ b/database/migrations/2017_09_10_225749_RenameTasksTableForStructureRefactor.php @@ -0,0 +1,23 @@ +increments('id'); + $table->unsignedInteger('server_id'); + $table->string('name')->nullable(); + $table->string('cron_day_of_week'); + $table->string('cron_day_of_month'); + $table->string('cron_hour'); + $table->string('cron_minute'); + $table->boolean('is_active'); + $table->boolean('is_processing'); + $table->timestamp('last_run_at'); + $table->timestamp('next_run_at'); + $table->timestamps(); + + $table->foreign('server_id')->references('id')->on('servers')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::dropIfExists('schedules'); + } +} diff --git a/database/migrations/2017_09_10_230309_CreateNewTasksTableForSchedules.php b/database/migrations/2017_09_10_230309_CreateNewTasksTableForSchedules.php new file mode 100644 index 000000000..9c225a834 --- /dev/null +++ b/database/migrations/2017_09_10_230309_CreateNewTasksTableForSchedules.php @@ -0,0 +1,36 @@ +increments('id'); + $table->unsignedInteger('schedule_id'); + $table->unsignedInteger('sequence_id'); + $table->string('action'); + $table->text('payload'); + $table->unsignedInteger('time_offset'); + $table->boolean('is_queued'); + $table->timestamps(); + + $table->index(['schedule_id', 'sequence_id']); + $table->foreign('schedule_id')->references('id')->on('schedules')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::dropIfExists('tasks'); + } +} diff --git a/database/migrations/2017_09_11_002938_TransferOldTasksToNewScheduler.php b/database/migrations/2017_09_11_002938_TransferOldTasksToNewScheduler.php new file mode 100644 index 000000000..2a20ef10e --- /dev/null +++ b/database/migrations/2017_09_11_002938_TransferOldTasksToNewScheduler.php @@ -0,0 +1,75 @@ +get(); + + DB::beginTransaction(); + $tasks->each(function ($task) { + $schedule = DB::table('schedules')->insertGetId([ + 'server_id' => $task->server_id, + 'name' => null, + 'cron_day_of_week' => $task->day_of_week, + 'cron_day_of_month' => $task->day_of_month, + 'cron_hour' => $task->hour, + 'cron_minute' => $task->minute, + 'is_active' => (bool) $task->active, + 'is_processing' => false, + 'last_run_at' => $task->last_run, + 'next_run_at' => $task->next_run, + 'created_at' => $task->created_at, + 'updated_at' => Carbon::now()->toDateTimeString(), + ]); + + DB::table('tasks')->insert([ + 'schedule_id' => $schedule, + 'sequence_id' => 1, + 'action' => $task->action, + 'payload' => $task->data, + 'time_offset' => 0, + 'is_queued' => false, + 'updated_at' => Carbon::now()->toDateTimeString(), + 'created_at' => Carbon::now()->toDateTimeString(), + ]); + + DB::table('tasks_old')->delete($task->id); + DB::commit(); + }); + + Schema::dropIfExists('tasks_old'); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::create('tasks_old', function (Blueprint $table) { + $table->increments('id'); + $table->unsignedInteger('user_id')->nullable(); + $table->unsignedInteger('server_id'); + $table->tinyInteger('active')->default(1); + $table->string('action'); + $table->text('data'); + $table->unsignedTinyInteger('queued')->default(0); + $table->string('year')->default('*'); + $table->string('month')->default('*'); + $table->string('day_of_week')->default('*'); + $table->string('day_of_month')->default('*'); + $table->string('minute')->default('*'); + $table->timestamp('last_run')->nullable(); + $table->timestamp('next_run'); + $table->timestamps(); + }); + } +} diff --git a/database/seeds/DatabaseSeeder.php b/database/seeds/DatabaseSeeder.php index ae48c7c82..6afdea04d 100644 --- a/database/seeds/DatabaseSeeder.php +++ b/database/seeds/DatabaseSeeder.php @@ -7,8 +7,6 @@ class DatabaseSeeder extends Seeder { /** * Run the database seeds. - * - * @return void */ public function run() { diff --git a/database/seeds/MinecraftServiceTableSeeder.php b/database/seeds/MinecraftServiceTableSeeder.php index 74d777f42..7b827a6aa 100644 --- a/database/seeds/MinecraftServiceTableSeeder.php +++ b/database/seeds/MinecraftServiceTableSeeder.php @@ -85,8 +85,6 @@ EOF; /** * Run the database seeds. - * - * @return void */ public function run() { @@ -140,7 +138,7 @@ EOF; 'docker_image' => 'quay.io/pterodactyl/core:java', 'config_startup' => '{"done": ")! For help, type ", "userInteraction": [ "Go to eula.txt for more info."]}', 'config_logs' => '{"custom": false, "location": "logs/latest.log"}', - 'config_files' => '{"server.properties":{"parser": "properties", "find":{"server-ip": "0.0.0.0", "enable-query": "true", "server-port": "{{server.build.default.port}}", "query.port": "{{server.build.default.port}}"}}}', + 'config_files' => '{"server.properties":{"parser": "properties", "find":{"server-ip": "0.0.0.0", "server-port": "{{server.build.default.port}}"}}}', 'config_stop' => 'stop', 'config_from' => null, 'startup' => null, @@ -236,7 +234,7 @@ EOF; 'description' => 'For a long time, Minecraft server owners have had a dream that encompasses a free, easy, and reliable way to connect multiple Minecraft servers together. BungeeCord is the answer to said dream. Whether you are a small server wishing to string multiple game-modes together, or the owner of the ShotBow Network, BungeeCord is the ideal solution for you. With the help of BungeeCord, you will be able to unlock your community\'s full potential.', 'docker_image' => 'quay.io/pterodactyl/core:java', 'config_startup' => '{"done": "Listening on ", "userInteraction": [ "Listening on /0.0.0.0:25577"]}', - 'config_files' => '{"config.yml":{"parser": "yaml", "find":{"listeners[0].query_enabled": true, "listeners[0].query_port": "{{server.build.default.port}}", "listeners[0].host": "0.0.0.0:{{server.build.default.port}}", "servers.*.address":{"127.0.0.1": "{{config.docker.interface}}", "localhost": "{{config.docker.interface}}"}}}}', + 'config_files' => '{"config.yml":{"parser": "yaml", "find":{"listeners[0].host": "0.0.0.0:{{server.build.default.port}}", "servers.*.address":{"127.0.0.1": "{{config.docker.interface}}", "localhost": "{{config.docker.interface}}"}}}}', 'config_logs' => '{"custom": false, "location": "proxy.log.0"}', 'config_stop' => 'end', 'config_from' => null, @@ -273,7 +271,7 @@ EOF; 'docker_image' => 'quay.io/pterodactyl/core:java', 'config_startup' => '{"done": ")! For help, type ", "userInteraction": [ "Go to eula.txt for more info."]}', 'config_logs' => '{"custom": false, "location": "logs/latest.log"}', - 'config_files' => '{"server.properties":{"parser": "properties", "find":{"server-ip": "0.0.0.0", "enable-query": "true", "server-port": "{{server.build.default.port}}", "query.port": "{{server.build.default.port}}"}}}', + 'config_files' => '{"server.properties":{"parser": "properties", "find":{"server-ip": "0.0.0.0", "server-port": "{{server.build.default.port}}"}}}', 'config_stop' => 'stop', 'config_from' => null, 'startup' => 'java -Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}', diff --git a/database/seeds/RustServiceTableSeeder.php b/database/seeds/RustServiceTableSeeder.php index 681d97178..6f47773be 100644 --- a/database/seeds/RustServiceTableSeeder.php +++ b/database/seeds/RustServiceTableSeeder.php @@ -25,9 +25,12 @@ use Illuminate\Database\Seeder; use Pterodactyl\Models\Service; use Pterodactyl\Models\ServiceOption; use Pterodactyl\Models\ServiceVariable; +use Pterodactyl\Traits\Services\CreatesServiceIndex; class RustServiceTableSeeder extends Seeder { + use CreatesServiceIndex; + /** * The core service ID. * @@ -44,8 +47,6 @@ class RustServiceTableSeeder extends Seeder /** * Run the database seeds. - * - * @return void */ public function run() { @@ -63,7 +64,7 @@ class RustServiceTableSeeder extends Seeder 'name' => 'Rust', 'description' => 'The only aim in Rust is to survive. To do this you will need to overcome struggles such as hunger, thirst and cold. Build a fire. Build a shelter. Kill animals for meat. Protect yourself from other players, and kill them for meat. Create alliances with other players and form a town. Do whatever it takes to survive.', 'startup' => './RustDedicated -batchmode +server.port {{SERVER_PORT}} +server.identity "rust" +rcon.port {{RCON_PORT}} +rcon.web true +server.hostname \"{{HOSTNAME}}\" +server.level \"{{LEVEL}}\" +server.description \"{{DESCRIPTION}}\" +server.url \"{{URL}}\" +server.headerimage \"{{SERVER_IMG}}\" +server.worldsize \"{{WORLD_SIZE}}\" +server.seed \"{{SEED}}\" +server.maxplayers {{MAX_PLAYERS}} +rcon.password \"{{RCON_PASS}}\" {{ADDITIONAL_ARGS}}', - 'index_file' => Service::defaultIndexFile(), + 'index_file' => $this->getIndexScript(), ]); } diff --git a/database/seeds/SourceServiceTableSeeder.php b/database/seeds/SourceServiceTableSeeder.php index f41d1a877..fcb2a90ed 100644 --- a/database/seeds/SourceServiceTableSeeder.php +++ b/database/seeds/SourceServiceTableSeeder.php @@ -25,9 +25,12 @@ use Illuminate\Database\Seeder; use Pterodactyl\Models\Service; use Pterodactyl\Models\ServiceOption; use Pterodactyl\Models\ServiceVariable; +use Pterodactyl\Traits\Services\CreatesServiceIndex; class SourceServiceTableSeeder extends Seeder { + use CreatesServiceIndex; + /** * The core service ID. * @@ -44,8 +47,6 @@ class SourceServiceTableSeeder extends Seeder /** * Run the database seeds. - * - * @return void */ public function run() { @@ -63,7 +64,7 @@ class SourceServiceTableSeeder extends Seeder 'name' => 'Source Engine', 'description' => 'Includes support for most Source Dedicated Server games.', 'startup' => './srcds_run -game {{SRCDS_GAME}} -console -port {{SERVER_PORT}} +ip 0.0.0.0 -strictportbind -norestart', - 'index_file' => Service::defaultIndexFile(), + 'index_file' => $this->getIndexScript(), ]); } diff --git a/database/seeds/TerrariaServiceTableSeeder.php b/database/seeds/TerrariaServiceTableSeeder.php index 6d451f12b..6d19b9faf 100644 --- a/database/seeds/TerrariaServiceTableSeeder.php +++ b/database/seeds/TerrariaServiceTableSeeder.php @@ -25,9 +25,12 @@ use Illuminate\Database\Seeder; use Pterodactyl\Models\Service; use Pterodactyl\Models\ServiceOption; use Pterodactyl\Models\ServiceVariable; +use Pterodactyl\Traits\Services\CreatesServiceIndex; class TerrariaServiceTableSeeder extends Seeder { + use CreatesServiceIndex; + /** * The core service ID. * @@ -44,8 +47,6 @@ class TerrariaServiceTableSeeder extends Seeder /** * Run the database seeds. - * - * @return void */ public function run() { @@ -63,7 +64,7 @@ class TerrariaServiceTableSeeder extends Seeder 'name' => 'Terraria', 'description' => 'Terraria is a land of adventure! A land of mystery! A land that\'s yours to shape, defend, and enjoy. Your options in Terraria are limitless. Are you an action gamer with an itchy trigger finger? A master builder? A collector? An explorer? There\'s something for everyone.', 'startup' => 'mono TerrariaServer.exe -port {{SERVER_PORT}} -autocreate 2 -worldname World', - 'index_file' => Service::defaultIndexFile(), + 'index_file' => $this->getIndexScript(), ]); } diff --git a/database/seeds/VoiceServiceTableSeeder.php b/database/seeds/VoiceServiceTableSeeder.php index 1b3a05548..3d273de53 100644 --- a/database/seeds/VoiceServiceTableSeeder.php +++ b/database/seeds/VoiceServiceTableSeeder.php @@ -25,9 +25,12 @@ use Illuminate\Database\Seeder; use Pterodactyl\Models\Service; use Pterodactyl\Models\ServiceOption; use Pterodactyl\Models\ServiceVariable; +use Pterodactyl\Traits\Services\CreatesServiceIndex; class VoiceServiceTableSeeder extends Seeder { + use CreatesServiceIndex; + /** * The core service ID. * @@ -44,8 +47,6 @@ class VoiceServiceTableSeeder extends Seeder /** * Run the database seeds. - * - * @return void */ public function run() { @@ -63,7 +64,7 @@ class VoiceServiceTableSeeder extends Seeder 'name' => 'Voice Servers', 'description' => 'Voice servers such as Mumble and Teamspeak 3.', 'startup' => '', - 'index_file' => Service::defaultIndexFile(), + 'index_file' => $this->getIndexScript(), ]); } diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 000000000..ceb832d08 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,36 @@ + + + + + ./tests/Feature + + + + ./tests/Unit + + + + + ./app + + + + + + + + + + + + + + diff --git a/public/themes/pterodactyl/css/pterodactyl.css b/public/themes/pterodactyl/css/pterodactyl.css index 19251e69f..1c60da7d5 100644 --- a/public/themes/pterodactyl/css/pterodactyl.css +++ b/public/themes/pterodactyl/css/pterodactyl.css @@ -23,19 +23,44 @@ @import 'checkbox.css'; .login-page { - height: auto; + background: #10529f; +} + +.login-logo { + color: white; + font-weight: bold; +} + +.login-copyright { + color: white; +} + +.login-copyright a, .login-copyright a:hover { + color: white; + font-weight: bold; +} + +.particles-js-canvas-el { + position: absolute; } .login-box, .register-box { - width: 40%; - max-width: 500px; - margin: 7% auto; + position: absolute; + margin: -180px 0 0 -180px; + left: 50%; + top: 50%; + height: 360px; + width: 360px; + z-index: 100; } @media (max-width:768px) { - .login-box, .register-box { + .login-box { width: 90%; - margin-top: 20px + margin-top: 20px; + margin: 5%; + left: 0; + top: 0; } } @@ -282,6 +307,7 @@ tr:hover + tr.server-description { position: absolute; bottom: 5px; right: 10px; + color: white; } input.form-autocomplete-stop[readonly] { diff --git a/public/themes/pterodactyl/js/admin/new-server.js b/public/themes/pterodactyl/js/admin/new-server.js index f3de55bee..ecc0b9fb7 100644 --- a/public/themes/pterodactyl/js/admin/new-server.js +++ b/public/themes/pterodactyl/js/admin/new-server.js @@ -179,7 +179,7 @@ $('#pOptionId').on('change', function (event) { var dataAppend = ' \
\ \ - \ + \

' + item.description + '
\ Access in Startup: {{' + item.env_variable + '}}
\ Validation Rules: ' + item.rules + '

\ diff --git a/public/themes/pterodactyl/js/frontend/tasks.js b/public/themes/pterodactyl/js/frontend/tasks.js index b19e19556..8b600a268 100644 --- a/public/themes/pterodactyl/js/frontend/tasks.js +++ b/public/themes/pterodactyl/js/frontend/tasks.js @@ -18,98 +18,113 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -var Tasks = (function () { - - function initTaskFunctions() { - $('[data-action="delete-task"]').click(function (event) { - var self = $(this); - swal({ - type: 'error', - title: 'Delete Task?', - text: 'Are you sure you want to delete this task? There is no undo.', - showCancelButton: true, - allowOutsideClick: true, - closeOnConfirm: false, - confirmButtonText: 'Delete Task', - confirmButtonColor: '#d9534f', - showLoaderOnConfirm: true - }, function () { - $.ajax({ - method: 'DELETE', - url: Router.route('server.tasks.delete', { - server: Pterodactyl.server.uuidShort, - id: self.data('id'), - }), - headers: { - 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'), - } - }).done(function (data) { - swal({ - type: 'success', - title: '', - text: 'Task has been deleted.' - }); - self.parent().parent().slideUp(); - }).fail(function (jqXHR) { - console.error(jqXHR); - swal({ - type: 'error', - title: 'Whoops!', - text: 'An error occured while attempting to delete this task.' - }); - }); - }); - }); - $('[data-action="toggle-task"]').click(function (event) { - var self = $(this); - swal({ - type: 'info', - title: 'Toggle Task', - text: 'This will toggle the selected task.', - showCancelButton: true, - allowOutsideClick: true, - closeOnConfirm: false, - confirmButtonText: 'Continue', - showLoaderOnConfirm: true - }, function () { - $.ajax({ - method: 'POST', - url: Router.route('server.tasks.toggle', { - server: Pterodactyl.server.uuidShort, - id: self.data('id'), - }), - headers: { - 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'), - } - }).done(function (data) { - swal({ - type: 'success', - title: '', - text: 'Task has been toggled.' - }); - if (data.status !== 1) { - self.parent().parent().addClass('muted muted-hover'); - } else { - self.parent().parent().removeClass('muted muted-hover'); - } - }).fail(function (jqXHR) { - console.error(jqXHR); - swal({ - type: 'error', - title: 'Whoops!', - text: 'An error occured while attempting to toggle this task.' - }); - }); - }); - }); - } - - return { - init: function () { - initTaskFunctions(); +$(document).ready(function () { + $('select[name="action"]').select2(); + $('[data-action="update-field"]').on('change', function (event) { + event.preventDefault(); + var updateField = $(this).data('field'); + var selected = $(this).map(function (i, opt) { + return $(opt).val(); + }).toArray(); + if (selected.length === $(this).find('option').length) { + $('input[name=' + updateField + ']').val('*'); + } else { + $('input[name=' + updateField + ']').val(selected.join(',')); } - } + }); -})(); + $('button[data-action="add-chain"]').on('click', function () { + var clone = $('div[data-target="chain-clone"]').clone(); + clone.insertBefore('#chainLastSegment').removeAttr('data-target').removeClass('hidden'); + clone.find('select[name="chain[time_value][]"]').select2(); + clone.find('select[name="chain[time_interval][]"]').select2(); + clone.find('select[name="chain[action][]"]').select2(); + clone.find('button[data-action="remove-chain-element"]').on('click', function () { + clone.remove(); + }); + $(this).data('element', clone); + }); -Tasks.init(); + $('[data-action="delete-task"]').click(function () { + var self = $(this); + swal({ + type: 'error', + title: 'Delete Task?', + text: 'Are you sure you want to delete this task? There is no undo.', + showCancelButton: true, + allowOutsideClick: true, + closeOnConfirm: false, + confirmButtonText: 'Delete Task', + confirmButtonColor: '#d9534f', + showLoaderOnConfirm: true + }, function () { + $.ajax({ + method: 'DELETE', + url: Router.route('server.tasks.delete', { + server: Pterodactyl.server.uuidShort, + id: self.data('id'), + }), + headers: { + 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'), + } + }).done(function (data) { + swal({ + type: 'success', + title: '', + text: 'Task has been deleted.' + }); + self.parent().parent().slideUp(); + }).fail(function (jqXHR) { + console.error(jqXHR); + swal({ + type: 'error', + title: 'Whoops!', + text: 'An error occured while attempting to delete this task.' + }); + }); + }); + }); + + $('[data-action="toggle-task"]').click(function (event) { + var self = $(this); + swal({ + type: 'info', + title: 'Toggle Task', + text: 'This will toggle the selected task.', + showCancelButton: true, + allowOutsideClick: true, + closeOnConfirm: false, + confirmButtonText: 'Continue', + showLoaderOnConfirm: true + }, function () { + $.ajax({ + method: 'POST', + url: Router.route('server.tasks.toggle', { + server: Pterodactyl.server.uuidShort, + id: self.data('id'), + }), + headers: { + 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'), + } + }).done(function (data) { + swal({ + type: 'success', + title: '', + text: 'Task has been toggled.' + }); + if (data.status !== 1) { + self.parent().parent().addClass('muted muted-hover'); + } else { + self.parent().parent().removeClass('muted muted-hover'); + } + }).fail(function (jqXHR) { + console.error(jqXHR); + swal({ + type: 'error', + title: 'Whoops!', + text: 'An error occured while attempting to toggle this task.' + }); + }); + }); + }); +}); diff --git a/public/themes/pterodactyl/vendor/ace/mode-objectivec.js b/public/themes/pterodactyl/vendor/ace/mode-objectivec.js index 08c1cc564..98645e5f6 100644 --- a/public/themes/pterodactyl/vendor/ace/mode-objectivec.js +++ b/public/themes/pterodactyl/vendor/ace/mode-objectivec.js @@ -1 +1 @@ -define("ace/mode/doc_comment_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=function(){this.$rules={start:[{token:"comment.doc.tag",regex:"@[\\w\\d_]+"},s.getTagRule(),{defaultToken:"comment.doc",caseInsensitive:!0}]}};r.inherits(s,i),s.getTagRule=function(e){return{token:"comment.doc.tag.storage.type",regex:"\\b(?:TODO|FIXME|XXX|HACK)\\b"}},s.getStartRule=function(e){return{token:"comment.doc",regex:"\\/\\*(?=\\*)",next:e}},s.getEndRule=function(e){return{token:"comment.doc",regex:"\\*\\/",next:e}},t.DocCommentHighlightRules=s}),define("ace/mode/c_cpp_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/doc_comment_highlight_rules","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./doc_comment_highlight_rules").DocCommentHighlightRules,s=e("./text_highlight_rules").TextHighlightRules,o=t.cFunctions="\\b(?:hypot(?:f|l)?|s(?:scanf|ystem|nprintf|ca(?:nf|lb(?:n(?:f|l)?|ln(?:f|l)?))|i(?:n(?:h(?:f|l)?|f|l)?|gn(?:al|bit))|tr(?:s(?:tr|pn)|nc(?:py|at|mp)|c(?:spn|hr|oll|py|at|mp)|to(?:imax|d|u(?:l(?:l)?|max)|k|f|l(?:d|l)?)|error|pbrk|ftime|len|rchr|xfrm)|printf|et(?:jmp|vbuf|locale|buf)|qrt(?:f|l)?|w(?:scanf|printf)|rand)|n(?:e(?:arbyint(?:f|l)?|xt(?:toward(?:f|l)?|after(?:f|l)?))|an(?:f|l)?)|c(?:s(?:in(?:h(?:f|l)?|f|l)?|qrt(?:f|l)?)|cos(?:h(?:f)?|f|l)?|imag(?:f|l)?|t(?:ime|an(?:h(?:f|l)?|f|l)?)|o(?:s(?:h(?:f|l)?|f|l)?|nj(?:f|l)?|pysign(?:f|l)?)|p(?:ow(?:f|l)?|roj(?:f|l)?)|e(?:il(?:f|l)?|xp(?:f|l)?)|l(?:o(?:ck|g(?:f|l)?)|earerr)|a(?:sin(?:h(?:f|l)?|f|l)?|cos(?:h(?:f|l)?|f|l)?|tan(?:h(?:f|l)?|f|l)?|lloc|rg(?:f|l)?|bs(?:f|l)?)|real(?:f|l)?|brt(?:f|l)?)|t(?:ime|o(?:upper|lower)|an(?:h(?:f|l)?|f|l)?|runc(?:f|l)?|gamma(?:f|l)?|mp(?:nam|file))|i(?:s(?:space|n(?:ormal|an)|cntrl|inf|digit|u(?:nordered|pper)|p(?:unct|rint)|finite|w(?:space|c(?:ntrl|type)|digit|upper|p(?:unct|rint)|lower|al(?:num|pha)|graph|xdigit|blank)|l(?:ower|ess(?:equal|greater)?)|al(?:num|pha)|gr(?:eater(?:equal)?|aph)|xdigit|blank)|logb(?:f|l)?|max(?:div|abs))|di(?:v|fftime)|_Exit|unget(?:c|wc)|p(?:ow(?:f|l)?|ut(?:s|c(?:har)?|wc(?:har)?)|error|rintf)|e(?:rf(?:c(?:f|l)?|f|l)?|x(?:it|p(?:2(?:f|l)?|f|l|m1(?:f|l)?)?))|v(?:s(?:scanf|nprintf|canf|printf|w(?:scanf|printf))|printf|f(?:scanf|printf|w(?:scanf|printf))|w(?:scanf|printf)|a_(?:start|copy|end|arg))|qsort|f(?:s(?:canf|e(?:tpos|ek))|close|tell|open|dim(?:f|l)?|p(?:classify|ut(?:s|c|w(?:s|c))|rintf)|e(?:holdexcept|set(?:e(?:nv|xceptflag)|round)|clearexcept|testexcept|of|updateenv|r(?:aiseexcept|ror)|get(?:e(?:nv|xceptflag)|round))|flush|w(?:scanf|ide|printf|rite)|loor(?:f|l)?|abs(?:f|l)?|get(?:s|c|pos|w(?:s|c))|re(?:open|e|ad|xp(?:f|l)?)|m(?:in(?:f|l)?|od(?:f|l)?|a(?:f|l|x(?:f|l)?)?))|l(?:d(?:iv|exp(?:f|l)?)|o(?:ngjmp|cal(?:time|econv)|g(?:1(?:p(?:f|l)?|0(?:f|l)?)|2(?:f|l)?|f|l|b(?:f|l)?)?)|abs|l(?:div|abs|r(?:int(?:f|l)?|ound(?:f|l)?))|r(?:int(?:f|l)?|ound(?:f|l)?)|gamma(?:f|l)?)|w(?:scanf|c(?:s(?:s(?:tr|pn)|nc(?:py|at|mp)|c(?:spn|hr|oll|py|at|mp)|to(?:imax|d|u(?:l(?:l)?|max)|k|f|l(?:d|l)?|mbs)|pbrk|ftime|len|r(?:chr|tombs)|xfrm)|to(?:b|mb)|rtomb)|printf|mem(?:set|c(?:hr|py|mp)|move))|a(?:s(?:sert|ctime|in(?:h(?:f|l)?|f|l)?)|cos(?:h(?:f|l)?|f|l)?|t(?:o(?:i|f|l(?:l)?)|exit|an(?:h(?:f|l)?|2(?:f|l)?|f|l)?)|b(?:s|ort))|g(?:et(?:s|c(?:har)?|env|wc(?:har)?)|mtime)|r(?:int(?:f|l)?|ound(?:f|l)?|e(?:name|alloc|wind|m(?:ove|quo(?:f|l)?|ainder(?:f|l)?))|a(?:nd|ise))|b(?:search|towc)|m(?:odf(?:f|l)?|em(?:set|c(?:hr|py|mp)|move)|ktime|alloc|b(?:s(?:init|towcs|rtowcs)|towc|len|r(?:towc|len))))\\b",u=function(){var e="break|case|continue|default|do|else|for|goto|if|_Pragma|return|switch|while|catch|operator|try|throw|using",t="asm|__asm__|auto|bool|_Bool|char|_Complex|double|enum|float|_Imaginary|int|long|short|signed|struct|typedef|union|unsigned|void|class|wchar_t|template|char16_t|char32_t",n="const|extern|register|restrict|static|volatile|inline|private|protected|public|friend|explicit|virtual|export|mutable|typename|constexpr|new|delete|alignas|alignof|decltype|noexcept|thread_local",r="and|and_eq|bitand|bitor|compl|not|not_eq|or|or_eq|typeid|xor|xor_eqconst_cast|dynamic_cast|reinterpret_cast|static_cast|sizeof|namespace",s="NULL|true|false|TRUE|FALSE|nullptr",u=this.$keywords=this.createKeywordMapper({"keyword.control":e,"storage.type":t,"storage.modifier":n,"keyword.operator":r,"variable.language":"this","constant.language":s},"identifier"),a="[a-zA-Z\\$_\u00a1-\uffff][a-zA-Z\\d\\$_\u00a1-\uffff]*\\b",f=/\\(?:['"?\\abfnrtv]|[0-7]{1,3}|x[a-fA-F\d]{2}|u[a-fA-F\d]{4}U[a-fA-F\d]{8}|.)/.source;this.$rules={start:[{token:"comment",regex:"//$",next:"start"},{token:"comment",regex:"//",next:"singleLineComment"},i.getStartRule("doc-start"),{token:"comment",regex:"\\/\\*",next:"comment"},{token:"string",regex:"'(?:"+f+"|.)?'"},{token:"string.start",regex:'"',stateName:"qqstring",next:[{token:"string",regex:/\\\s*$/,next:"qqstring"},{token:"constant.language.escape",regex:f},{token:"constant.language.escape",regex:/%[^'"\\]/},{token:"string.end",regex:'"|$',next:"start"},{defaultToken:"string"}]},{token:"string.start",regex:'R"\\(',stateName:"rawString",next:[{token:"string.end",regex:'\\)"',next:"start"},{defaultToken:"string"}]},{token:"constant.numeric",regex:"0[xX][0-9a-fA-F]+(L|l|UL|ul|u|U|F|f|ll|LL|ull|ULL)?\\b"},{token:"constant.numeric",regex:"[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?(L|l|UL|ul|u|U|F|f|ll|LL|ull|ULL)?\\b"},{token:"keyword",regex:"#\\s*(?:include|import|pragma|line|define|undef)\\b",next:"directive"},{token:"keyword",regex:"#\\s*(?:endif|if|ifdef|else|elif|ifndef)\\b"},{token:"support.function.C99.c",regex:o},{token:u,regex:"[a-zA-Z_$][a-zA-Z0-9_$]*"},{token:"keyword.operator",regex:/--|\+\+|<<=|>>=|>>>=|<>|&&|\|\||\?:|[*%\/+\-&\^|~!<>=]=?/},{token:"punctuation.operator",regex:"\\?|\\:|\\,|\\;|\\."},{token:"paren.lparen",regex:"[[({]"},{token:"paren.rparen",regex:"[\\])}]"},{token:"text",regex:"\\s+"}],comment:[{token:"comment",regex:".*?\\*\\/",next:"start"},{token:"comment",regex:".+"}],singleLineComment:[{token:"comment",regex:/\\$/,next:"singleLineComment"},{token:"comment",regex:/$/,next:"start"},{defaultToken:"comment"}],directive:[{token:"constant.other.multiline",regex:/\\/},{token:"constant.other.multiline",regex:/.*\\/},{token:"constant.other",regex:"\\s*<.+?>",next:"start"},{token:"constant.other",regex:'\\s*["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]',next:"start"},{token:"constant.other",regex:"\\s*['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']",next:"start"},{token:"constant.other",regex:/[^\\\/]+/,next:"start"}]},this.embedRules(i,"doc-",[i.getEndRule("start")]),this.normalizeRules()};r.inherits(u,s),t.c_cppHighlightRules=u}),define("ace/mode/objectivec_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/doc_comment_highlight_rules","ace/mode/c_cpp_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./doc_comment_highlight_rules").DocCommentHighlightRules,s=e("./c_cpp_highlight_rules"),o=s.c_cppHighlightRules,u=function(){var e="\\\\(?:[abefnrtv'\"?\\\\]|[0-3]\\d{1,2}|[4-7]\\d?|222|x[a-zA-Z0-9]+)",t=[{regex:"\\b_cmd\\b",token:"variable.other.selector.objc"},{regex:"\\b(?:self|super)\\b",token:"variable.language.objc"}],n=new o,r=n.getRules();this.$rules={start:[{token:"comment",regex:"\\/\\/.*$"},i.getStartRule("doc-start"),{token:"comment",regex:"\\/\\*",next:"comment"},{token:["storage.type.objc","punctuation.definition.storage.type.objc","entity.name.type.objc","text","entity.other.inherited-class.objc"],regex:"(@)(interface|protocol)(?!.+;)(\\s+[A-Za-z_][A-Za-z0-9_]*)(\\s*:\\s*)([A-Za-z]+)"},{token:["storage.type.objc"],regex:"(@end)"},{token:["storage.type.objc","entity.name.type.objc","entity.other.inherited-class.objc"],regex:"(@implementation)(\\s+[A-Za-z_][A-Za-z0-9_]*)(\\s*?::\\s*(?:[A-Za-z][A-Za-z0-9]*))?"},{token:"string.begin.objc",regex:'@"',next:"constant_NSString"},{token:"storage.type.objc",regex:"\\bid\\s*<",next:"protocol_list"},{token:"keyword.control.macro.objc",regex:"\\bNS_DURING|NS_HANDLER|NS_ENDHANDLER\\b"},{token:["punctuation.definition.keyword.objc","keyword.control.exception.objc"],regex:"(@)(try|catch|finally|throw)\\b"},{token:["punctuation.definition.keyword.objc","keyword.other.objc"],regex:"(@)(defs|encode)\\b"},{token:["storage.type.id.objc","text"],regex:"(\\bid\\b)(\\s|\\n)?"},{token:"storage.type.objc",regex:"\\bIBOutlet|IBAction|BOOL|SEL|id|unichar|IMP|Class\\b"},{token:["punctuation.definition.storage.type.objc","storage.type.objc"],regex:"(@)(class|protocol)\\b"},{token:["punctuation.definition.storage.type.objc","punctuation"],regex:"(@selector)(\\s*\\()",next:"selectors"},{token:["punctuation.definition.storage.modifier.objc","storage.modifier.objc"],regex:"(@)(synchronized|public|private|protected|package)\\b"},{token:"constant.language.objc",regex:"\\bYES|NO|Nil|nil\\b"},{token:"support.variable.foundation",regex:"\\bNSApp\\b"},{token:["support.function.cocoa.leopard"],regex:"(?:\\b)(NS(?:Rect(?:ToCGRect|FromCGRect)|MakeCollectable|S(?:tringFromProtocol|ize(?:ToCGSize|FromCGSize))|Draw(?:NinePartImage|ThreePartImage)|P(?:oint(?:ToCGPoint|FromCGPoint)|rotocolFromString)|EventMaskFromType|Value))(?:\\b)"},{token:["support.function.cocoa"],regex:"(?:\\b)(NS(?:R(?:ound(?:DownToMultipleOfPageSize|UpToMultipleOfPageSize)|un(?:CriticalAlertPanel(?:RelativeToWindow)?|InformationalAlertPanel(?:RelativeToWindow)?|AlertPanel(?:RelativeToWindow)?)|e(?:set(?:MapTable|HashTable)|c(?:ycleZone|t(?:Clip(?:List)?|F(?:ill(?:UsingOperation|List(?:UsingOperation|With(?:Grays|Colors(?:UsingOperation)?))?)?|romString))|ordAllocationEvent)|turnAddress|leaseAlertPanel|a(?:dPixel|l(?:MemoryAvailable|locateCollectable))|gisterServicesProvider)|angeFromString)|Get(?:SizeAndAlignment|CriticalAlertPanel|InformationalAlertPanel|UncaughtExceptionHandler|FileType(?:s)?|WindowServerMemory|AlertPanel)|M(?:i(?:n(?:X|Y)|d(?:X|Y))|ouseInRect|a(?:p(?:Remove|Get|Member|Insert(?:IfAbsent|KnownAbsent)?)|ke(?:R(?:ect|ange)|Size|Point)|x(?:Range|X|Y)))|B(?:itsPer(?:SampleFromDepth|PixelFromDepth)|e(?:stDepth|ep|gin(?:CriticalAlertSheet|InformationalAlertSheet|AlertSheet)))|S(?:ho(?:uldRetainWithZone|w(?:sServicesMenuItem|AnimationEffect))|tringFrom(?:R(?:ect|ange)|MapTable|S(?:ize|elector)|HashTable|Class|Point)|izeFromString|e(?:t(?:ShowsServicesMenuItem|ZoneName|UncaughtExceptionHandler|FocusRingStyle)|lectorFromString|archPathForDirectoriesInDomains)|wap(?:Big(?:ShortToHost|IntToHost|DoubleToHost|FloatToHost|Long(?:ToHost|LongToHost))|Short|Host(?:ShortTo(?:Big|Little)|IntTo(?:Big|Little)|DoubleTo(?:Big|Little)|FloatTo(?:Big|Little)|Long(?:To(?:Big|Little)|LongTo(?:Big|Little)))|Int|Double|Float|L(?:ittle(?:ShortToHost|IntToHost|DoubleToHost|FloatToHost|Long(?:ToHost|LongToHost))|ong(?:Long)?)))|H(?:ighlightRect|o(?:stByteOrder|meDirectory(?:ForUser)?)|eight|ash(?:Remove|Get|Insert(?:IfAbsent|KnownAbsent)?)|FSType(?:CodeFromFileType|OfFile))|N(?:umberOfColorComponents|ext(?:MapEnumeratorPair|HashEnumeratorItem))|C(?:o(?:n(?:tainsRect|vert(?:GlyphsToPackedGlyphs|Swapped(?:DoubleToHost|FloatToHost)|Host(?:DoubleToSwapped|FloatToSwapped)))|unt(?:MapTable|HashTable|Frames|Windows(?:ForContext)?)|py(?:M(?:emoryPages|apTableWithZone)|Bits|HashTableWithZone|Object)|lorSpaceFromDepth|mpare(?:MapTables|HashTables))|lassFromString|reate(?:MapTable(?:WithZone)?|HashTable(?:WithZone)?|Zone|File(?:namePboardType|ContentsPboardType)))|TemporaryDirectory|I(?:s(?:ControllerMarker|EmptyRect|FreedObject)|n(?:setRect|crementExtraRefCount|te(?:r(?:sect(?:sRect|ionR(?:ect|ange))|faceStyleForKey)|gralRect)))|Zone(?:Realloc|Malloc|Name|Calloc|Fr(?:omPointer|ee))|O(?:penStepRootDirectory|ffsetRect)|D(?:i(?:sableScreenUpdates|videRect)|ottedFrameRect|e(?:c(?:imal(?:Round|Multiply|S(?:tring|ubtract)|Normalize|Co(?:py|mpa(?:ct|re))|IsNotANumber|Divide|Power|Add)|rementExtraRefCountWasZero)|faultMallocZone|allocate(?:MemoryPages|Object))|raw(?:Gr(?:oove|ayBezel)|B(?:itmap|utton)|ColorTiledRects|TiledRects|DarkBezel|W(?:hiteBezel|indowBackground)|LightBezel))|U(?:serName|n(?:ionR(?:ect|ange)|registerServicesProvider)|pdateDynamicServices)|Java(?:Bundle(?:Setup|Cleanup)|Setup(?:VirtualMachine)?|Needs(?:ToLoadClasses|VirtualMachine)|ClassesF(?:orBundle|romPath)|ObjectNamedInPath|ProvidesClasses)|P(?:oint(?:InRect|FromString)|erformService|lanarFromDepth|ageSize)|E(?:n(?:d(?:MapTableEnumeration|HashTableEnumeration)|umerate(?:MapTable|HashTable)|ableScreenUpdates)|qual(?:R(?:ects|anges)|Sizes|Points)|raseRect|xtraRefCount)|F(?:ileTypeForHFSTypeCode|ullUserName|r(?:ee(?:MapTable|HashTable)|ame(?:Rect(?:WithWidth(?:UsingOperation)?)?|Address)))|Wi(?:ndowList(?:ForContext)?|dth)|Lo(?:cationInRange|g(?:v|PageSize)?)|A(?:ccessibility(?:R(?:oleDescription(?:ForUIElement)?|aiseBadArgumentException)|Unignored(?:Children(?:ForOnlyChild)?|Descendant|Ancestor)|PostNotification|ActionDescription)|pplication(?:Main|Load)|vailableWindowDepths|ll(?:MapTable(?:Values|Keys)|HashTableObjects|ocate(?:MemoryPages|Collectable|Object)))))(?:\\b)"},{token:["support.class.cocoa.leopard"],regex:"(?:\\b)(NS(?:RuleEditor|G(?:arbageCollector|radient)|MapTable|HashTable|Co(?:ndition|llectionView(?:Item)?)|T(?:oolbarItemGroup|extInputClient|r(?:eeNode|ackingArea))|InvocationOperation|Operation(?:Queue)?|D(?:ictionaryController|ockTile)|P(?:ointer(?:Functions|Array)|athC(?:o(?:ntrol(?:Delegate)?|mponentCell)|ell(?:Delegate)?)|r(?:intPanelAccessorizing|edicateEditor(?:RowTemplate)?))|ViewController|FastEnumeration|Animat(?:ionContext|ablePropertyContainer)))(?:\\b)"},{token:["support.class.cocoa"],regex:"(?:\\b)(NS(?:R(?:u(?:nLoop|ler(?:Marker|View))|e(?:sponder|cursiveLock|lativeSpecifier)|an(?:domSpecifier|geSpecifier))|G(?:etCommand|lyph(?:Generator|Storage|Info)|raphicsContext)|XML(?:Node|D(?:ocument|TD(?:Node)?)|Parser|Element)|M(?:iddleSpecifier|ov(?:ie(?:View)?|eCommand)|utable(?:S(?:tring|et)|C(?:haracterSet|opying)|IndexSet|D(?:ictionary|ata)|URLRequest|ParagraphStyle|A(?:ttributedString|rray))|e(?:ssagePort(?:NameServer)?|nu(?:Item(?:Cell)?|View)?|t(?:hodSignature|adata(?:Item|Query(?:ResultGroup|AttributeValueTuple)?)))|a(?:ch(?:BootstrapServer|Port)|trix))|B(?:itmapImageRep|ox|u(?:ndle|tton(?:Cell)?)|ezierPath|rowser(?:Cell)?)|S(?:hadow|c(?:anner|r(?:ipt(?:SuiteRegistry|C(?:o(?:ercionHandler|mmand(?:Description)?)|lassDescription)|ObjectSpecifier|ExecutionContext|WhoseTest)|oll(?:er|View)|een))|t(?:epper(?:Cell)?|atus(?:Bar|Item)|r(?:ing|eam))|imple(?:HorizontalTypesetter|CString)|o(?:cketPort(?:NameServer)?|und|rtDescriptor)|p(?:e(?:cifierTest|ech(?:Recognizer|Synthesizer)|ll(?:Server|Checker))|litView)|e(?:cureTextField(?:Cell)?|t(?:Command)?|archField(?:Cell)?|rializer|gmentedC(?:ontrol|ell))|lider(?:Cell)?|avePanel)|H(?:ost|TTP(?:Cookie(?:Storage)?|URLResponse)|elpManager)|N(?:ib(?:Con(?:nector|trolConnector)|OutletConnector)?|otification(?:Center|Queue)?|u(?:ll|mber(?:Formatter)?)|etService(?:Browser)?|ameSpecifier)|C(?:ha(?:ngeSpelling|racterSet)|o(?:n(?:stantString|nection|trol(?:ler)?|ditionLock)|d(?:ing|er)|unt(?:Command|edSet)|pying|lor(?:Space|P(?:ick(?:ing(?:Custom|Default)|er)|anel)|Well|List)?|m(?:p(?:oundPredicate|arisonPredicate)|boBox(?:Cell)?))|u(?:stomImageRep|rsor)|IImageRep|ell|l(?:ipView|o(?:seCommand|neCommand)|assDescription)|a(?:ched(?:ImageRep|URLResponse)|lendar(?:Date)?)|reateCommand)|T(?:hread|ypesetter|ime(?:Zone|r)|o(?:olbar(?:Item(?:Validations)?)?|kenField(?:Cell)?)|ext(?:Block|Storage|Container|Tab(?:le(?:Block)?)?|Input|View|Field(?:Cell)?|List|Attachment(?:Cell)?)?|a(?:sk|b(?:le(?:Header(?:Cell|View)|Column|View)|View(?:Item)?))|reeController)|I(?:n(?:dex(?:S(?:pecifier|et)|Path)|put(?:Manager|S(?:tream|erv(?:iceProvider|er(?:MouseTracker)?)))|vocation)|gnoreMisspelledWords|mage(?:Rep|Cell|View)?)|O(?:ut(?:putStream|lineView)|pen(?:GL(?:Context|Pixel(?:Buffer|Format)|View)|Panel)|bj(?:CTypeSerializationCallBack|ect(?:Controller)?))|D(?:i(?:st(?:antObject(?:Request)?|ributed(?:NotificationCenter|Lock))|ctionary|rectoryEnumerator)|ocument(?:Controller)?|e(?:serializer|cimalNumber(?:Behaviors|Handler)?|leteCommand)|at(?:e(?:Components|Picker(?:Cell)?|Formatter)?|a)|ra(?:wer|ggingInfo))|U(?:ser(?:InterfaceValidations|Defaults(?:Controller)?)|RL(?:Re(?:sponse|quest)|Handle(?:Client)?|C(?:onnection|ache|redential(?:Storage)?)|Download(?:Delegate)?|Prot(?:ocol(?:Client)?|ectionSpace)|AuthenticationChallenge(?:Sender)?)?|n(?:iqueIDSpecifier|doManager|archiver))|P(?:ipe|o(?:sitionalSpecifier|pUpButton(?:Cell)?|rt(?:Message|NameServer|Coder)?)|ICTImageRep|ersistentDocument|DFImageRep|a(?:steboard|nel|ragraphStyle|geLayout)|r(?:int(?:Info|er|Operation|Panel)|o(?:cessInfo|tocolChecker|perty(?:Specifier|ListSerialization)|gressIndicator|xy)|edicate))|E(?:numerator|vent|PSImageRep|rror|x(?:ception|istsCommand|pression))|V(?:iew(?:Animation)?|al(?:idated(?:ToobarItem|UserInterfaceItem)|ue(?:Transformer)?))|Keyed(?:Unarchiver|Archiver)|Qui(?:ckDrawView|tCommand)|F(?:ile(?:Manager|Handle|Wrapper)|o(?:nt(?:Manager|Descriptor|Panel)?|rm(?:Cell|atter)))|W(?:hoseSpecifier|indow(?:Controller)?|orkspace)|L(?:o(?:c(?:k(?:ing)?|ale)|gicalTest)|evelIndicator(?:Cell)?|ayoutManager)|A(?:ssertionHandler|nimation|ctionCell|ttributedString|utoreleasePool|TSTypesetter|ppl(?:ication|e(?:Script|Event(?:Manager|Descriptor)))|ffineTransform|lert|r(?:chiver|ray(?:Controller)?))))(?:\\b)"},{token:["support.type.cocoa.leopard"],regex:"(?:\\b)(NS(?:R(?:u(?:nLoop|ler(?:Marker|View))|e(?:sponder|cursiveLock|lativeSpecifier)|an(?:domSpecifier|geSpecifier))|G(?:etCommand|lyph(?:Generator|Storage|Info)|raphicsContext)|XML(?:Node|D(?:ocument|TD(?:Node)?)|Parser|Element)|M(?:iddleSpecifier|ov(?:ie(?:View)?|eCommand)|utable(?:S(?:tring|et)|C(?:haracterSet|opying)|IndexSet|D(?:ictionary|ata)|URLRequest|ParagraphStyle|A(?:ttributedString|rray))|e(?:ssagePort(?:NameServer)?|nu(?:Item(?:Cell)?|View)?|t(?:hodSignature|adata(?:Item|Query(?:ResultGroup|AttributeValueTuple)?)))|a(?:ch(?:BootstrapServer|Port)|trix))|B(?:itmapImageRep|ox|u(?:ndle|tton(?:Cell)?)|ezierPath|rowser(?:Cell)?)|S(?:hadow|c(?:anner|r(?:ipt(?:SuiteRegistry|C(?:o(?:ercionHandler|mmand(?:Description)?)|lassDescription)|ObjectSpecifier|ExecutionContext|WhoseTest)|oll(?:er|View)|een))|t(?:epper(?:Cell)?|atus(?:Bar|Item)|r(?:ing|eam))|imple(?:HorizontalTypesetter|CString)|o(?:cketPort(?:NameServer)?|und|rtDescriptor)|p(?:e(?:cifierTest|ech(?:Recognizer|Synthesizer)|ll(?:Server|Checker))|litView)|e(?:cureTextField(?:Cell)?|t(?:Command)?|archField(?:Cell)?|rializer|gmentedC(?:ontrol|ell))|lider(?:Cell)?|avePanel)|H(?:ost|TTP(?:Cookie(?:Storage)?|URLResponse)|elpManager)|N(?:ib(?:Con(?:nector|trolConnector)|OutletConnector)?|otification(?:Center|Queue)?|u(?:ll|mber(?:Formatter)?)|etService(?:Browser)?|ameSpecifier)|C(?:ha(?:ngeSpelling|racterSet)|o(?:n(?:stantString|nection|trol(?:ler)?|ditionLock)|d(?:ing|er)|unt(?:Command|edSet)|pying|lor(?:Space|P(?:ick(?:ing(?:Custom|Default)|er)|anel)|Well|List)?|m(?:p(?:oundPredicate|arisonPredicate)|boBox(?:Cell)?))|u(?:stomImageRep|rsor)|IImageRep|ell|l(?:ipView|o(?:seCommand|neCommand)|assDescription)|a(?:ched(?:ImageRep|URLResponse)|lendar(?:Date)?)|reateCommand)|T(?:hread|ypesetter|ime(?:Zone|r)|o(?:olbar(?:Item(?:Validations)?)?|kenField(?:Cell)?)|ext(?:Block|Storage|Container|Tab(?:le(?:Block)?)?|Input|View|Field(?:Cell)?|List|Attachment(?:Cell)?)?|a(?:sk|b(?:le(?:Header(?:Cell|View)|Column|View)|View(?:Item)?))|reeController)|I(?:n(?:dex(?:S(?:pecifier|et)|Path)|put(?:Manager|S(?:tream|erv(?:iceProvider|er(?:MouseTracker)?)))|vocation)|gnoreMisspelledWords|mage(?:Rep|Cell|View)?)|O(?:ut(?:putStream|lineView)|pen(?:GL(?:Context|Pixel(?:Buffer|Format)|View)|Panel)|bj(?:CTypeSerializationCallBack|ect(?:Controller)?))|D(?:i(?:st(?:antObject(?:Request)?|ributed(?:NotificationCenter|Lock))|ctionary|rectoryEnumerator)|ocument(?:Controller)?|e(?:serializer|cimalNumber(?:Behaviors|Handler)?|leteCommand)|at(?:e(?:Components|Picker(?:Cell)?|Formatter)?|a)|ra(?:wer|ggingInfo))|U(?:ser(?:InterfaceValidations|Defaults(?:Controller)?)|RL(?:Re(?:sponse|quest)|Handle(?:Client)?|C(?:onnection|ache|redential(?:Storage)?)|Download(?:Delegate)?|Prot(?:ocol(?:Client)?|ectionSpace)|AuthenticationChallenge(?:Sender)?)?|n(?:iqueIDSpecifier|doManager|archiver))|P(?:ipe|o(?:sitionalSpecifier|pUpButton(?:Cell)?|rt(?:Message|NameServer|Coder)?)|ICTImageRep|ersistentDocument|DFImageRep|a(?:steboard|nel|ragraphStyle|geLayout)|r(?:int(?:Info|er|Operation|Panel)|o(?:cessInfo|tocolChecker|perty(?:Specifier|ListSerialization)|gressIndicator|xy)|edicate))|E(?:numerator|vent|PSImageRep|rror|x(?:ception|istsCommand|pression))|V(?:iew(?:Animation)?|al(?:idated(?:ToobarItem|UserInterfaceItem)|ue(?:Transformer)?))|Keyed(?:Unarchiver|Archiver)|Qui(?:ckDrawView|tCommand)|F(?:ile(?:Manager|Handle|Wrapper)|o(?:nt(?:Manager|Descriptor|Panel)?|rm(?:Cell|atter)))|W(?:hoseSpecifier|indow(?:Controller)?|orkspace)|L(?:o(?:c(?:k(?:ing)?|ale)|gicalTest)|evelIndicator(?:Cell)?|ayoutManager)|A(?:ssertionHandler|nimation|ctionCell|ttributedString|utoreleasePool|TSTypesetter|ppl(?:ication|e(?:Script|Event(?:Manager|Descriptor)))|ffineTransform|lert|r(?:chiver|ray(?:Controller)?))))(?:\\b)"},{token:["support.class.quartz"],regex:"(?:\\b)(C(?:I(?:Sampler|Co(?:ntext|lor)|Image(?:Accumulator)?|PlugIn(?:Registration)?|Vector|Kernel|Filter(?:Generator|Shape)?)|A(?:Renderer|MediaTiming(?:Function)?|BasicAnimation|ScrollLayer|Constraint(?:LayoutManager)?|T(?:iledLayer|extLayer|rans(?:ition|action))|OpenGLLayer|PropertyAnimation|KeyframeAnimation|Layer|A(?:nimation(?:Group)?|ction))))(?:\\b)"},{token:["support.type.quartz"],regex:"(?:\\b)(C(?:G(?:Float|Point|Size|Rect)|IFormat|AConstraintAttribute))(?:\\b)"},{token:["support.type.cocoa"],regex:"(?:\\b)(NS(?:R(?:ect(?:Edge)?|ange)|G(?:lyph(?:Relation|LayoutMode)?|radientType)|M(?:odalSession|a(?:trixMode|p(?:Table|Enumerator)))|B(?:itmapImageFileType|orderType|uttonType|ezelStyle|ackingStoreType|rowserColumnResizingType)|S(?:cr(?:oll(?:er(?:Part|Arrow)|ArrowPosition)|eenAuxiliaryOpaque)|tringEncoding|ize|ocketNativeHandle|election(?:Granularity|Direction|Affinity)|wapped(?:Double|Float)|aveOperationType)|Ha(?:sh(?:Table|Enumerator)|ndler(?:2)?)|C(?:o(?:ntrol(?:Size|Tint)|mp(?:ositingOperation|arisonResult))|ell(?:State|Type|ImagePosition|Attribute))|T(?:hreadPrivate|ypesetterGlyphInfo|i(?:ckMarkPosition|tlePosition|meInterval)|o(?:ol(?:TipTag|bar(?:SizeMode|DisplayMode))|kenStyle)|IFFCompression|ext(?:TabType|Alignment)|ab(?:State|leViewDropOperation|ViewType)|rackingRectTag)|ImageInterpolation|Zone|OpenGL(?:ContextAuxiliary|PixelFormatAuxiliary)|D(?:ocumentChangeType|atePickerElementFlags|ra(?:werState|gOperation))|UsableScrollerParts|P(?:oint|r(?:intingPageOrder|ogressIndicator(?:Style|Th(?:ickness|readInfo))))|EventType|KeyValueObservingOptions|Fo(?:nt(?:SymbolicTraits|TraitMask|Action)|cusRingType)|W(?:indow(?:OrderingMode|Depth)|orkspace(?:IconCreationOptions|LaunchOptions)|ritingDirection)|L(?:ineBreakMode|ayout(?:Status|Direction))|A(?:nimation(?:Progress|Effect)|ppl(?:ication(?:TerminateReply|DelegateReply|PrintReply)|eEventManagerSuspensionID)|ffineTransformStruct|lertStyle)))(?:\\b)"},{token:["support.constant.cocoa"],regex:"(?:\\b)(NS(?:NotFound|Ordered(?:Ascending|Descending|Same)))(?:\\b)"},{token:["support.constant.notification.cocoa.leopard"],regex:"(?:\\b)(NS(?:MenuDidBeginTracking|ViewDidUpdateTrackingAreas)?Notification)(?:\\b)"},{token:["support.constant.notification.cocoa"],regex:"(?:\\b)(NS(?:Menu(?:Did(?:RemoveItem|SendAction|ChangeItem|EndTracking|AddItem)|WillSendAction)|S(?:ystemColorsDidChange|plitView(?:DidResizeSubviews|WillResizeSubviews))|C(?:o(?:nt(?:extHelpModeDid(?:Deactivate|Activate)|rolT(?:intDidChange|extDid(?:BeginEditing|Change|EndEditing)))|lor(?:PanelColorDidChange|ListDidChange)|mboBox(?:Selection(?:IsChanging|DidChange)|Will(?:Dismiss|PopUp)))|lassDescriptionNeededForClass)|T(?:oolbar(?:DidRemoveItem|WillAddItem)|ext(?:Storage(?:DidProcessEditing|WillProcessEditing)|Did(?:BeginEditing|Change|EndEditing)|View(?:DidChange(?:Selection|TypingAttributes)|WillChangeNotifyingTextView))|ableView(?:Selection(?:IsChanging|DidChange)|ColumnDid(?:Resize|Move)))|ImageRepRegistryDidChange|OutlineView(?:Selection(?:IsChanging|DidChange)|ColumnDid(?:Resize|Move)|Item(?:Did(?:Collapse|Expand)|Will(?:Collapse|Expand)))|Drawer(?:Did(?:Close|Open)|Will(?:Close|Open))|PopUpButton(?:CellWillPopUp|WillPopUp)|View(?:GlobalFrameDidChange|BoundsDidChange|F(?:ocusDidChange|rameDidChange))|FontSetChanged|W(?:indow(?:Did(?:Resi(?:ze|gn(?:Main|Key))|M(?:iniaturize|ove)|Become(?:Main|Key)|ChangeScreen(?:|Profile)|Deminiaturize|Update|E(?:ndSheet|xpose))|Will(?:M(?:iniaturize|ove)|BeginSheet|Close))|orkspace(?:SessionDid(?:ResignActive|BecomeActive)|Did(?:Mount|TerminateApplication|Unmount|PerformFileOperation|Wake|LaunchApplication)|Will(?:Sleep|Unmount|PowerOff|LaunchApplication)))|A(?:ntialiasThresholdChanged|ppl(?:ication(?:Did(?:ResignActive|BecomeActive|Hide|ChangeScreenParameters|U(?:nhide|pdate)|FinishLaunching)|Will(?:ResignActive|BecomeActive|Hide|Terminate|U(?:nhide|pdate)|FinishLaunching))|eEventManagerWillProcessFirstEvent)))Notification)(?:\\b)"},{token:["support.constant.cocoa.leopard"],regex:"(?:\\b)(NS(?:RuleEditor(?:RowType(?:Simple|Compound)|NestingMode(?:Si(?:ngle|mple)|Compound|List))|GradientDraws(?:BeforeStartingLocation|AfterEndingLocation)|M(?:inusSetExpressionType|a(?:chPortDeallocate(?:ReceiveRight|SendRight|None)|pTable(?:StrongMemory|CopyIn|ZeroingWeakMemory|ObjectPointerPersonality)))|B(?:oxCustom|undleExecutableArchitecture(?:X86|I386|PPC(?:64)?)|etweenPredicateOperatorType|ackgroundStyle(?:Raised|Dark|L(?:ight|owered)))|S(?:tring(?:DrawingTruncatesLastVisibleLine|EncodingConversion(?:ExternalRepresentation|AllowLossy))|ubqueryExpressionType|p(?:e(?:ech(?:SentenceBoundary|ImmediateBoundary|WordBoundary)|llingState(?:GrammarFlag|SpellingFlag))|litViewDividerStyleThi(?:n|ck))|e(?:rvice(?:RequestTimedOutError|M(?:iscellaneousError|alformedServiceDictionaryError)|InvalidPasteboardDataError|ErrorM(?:inimum|aximum)|Application(?:NotFoundError|LaunchFailedError))|gmentStyle(?:Round(?:Rect|ed)|SmallSquare|Capsule|Textured(?:Rounded|Square)|Automatic)))|H(?:UDWindowMask|ashTable(?:StrongMemory|CopyIn|ZeroingWeakMemory|ObjectPointerPersonality))|N(?:oModeColorPanel|etServiceNoAutoRename)|C(?:hangeRedone|o(?:ntainsPredicateOperatorType|l(?:orRenderingIntent(?:RelativeColorimetric|Saturation|Default|Perceptual|AbsoluteColorimetric)|lectorDisabledOption))|ellHit(?:None|ContentArea|TrackableArea|EditableTextArea))|T(?:imeZoneNameStyle(?:S(?:hort(?:Standard|DaylightSaving)|tandard)|DaylightSaving)|extFieldDatePickerStyle|ableViewSelectionHighlightStyle(?:Regular|SourceList)|racking(?:Mouse(?:Moved|EnteredAndExited)|CursorUpdate|InVisibleRect|EnabledDuringMouseDrag|A(?:ssumeInside|ctive(?:In(?:KeyWindow|ActiveApp)|WhenFirstResponder|Always))))|I(?:n(?:tersectSetExpressionType|dexedColorSpaceModel)|mageScale(?:None|Proportionally(?:Down|UpOrDown)|AxesIndependently))|Ope(?:nGLPFAAllowOfflineRenderers|rationQueue(?:DefaultMaxConcurrentOperationCount|Priority(?:High|Normal|Very(?:High|Low)|Low)))|D(?:iacriticInsensitiveSearch|ownloadsDirectory)|U(?:nionSetExpressionType|TF(?:16(?:BigEndianStringEncoding|StringEncoding|LittleEndianStringEncoding)|32(?:BigEndianStringEncoding|StringEncoding|LittleEndianStringEncoding)))|P(?:ointerFunctions(?:Ma(?:chVirtualMemory|llocMemory)|Str(?:ongMemory|uctPersonality)|C(?:StringPersonality|opyIn)|IntegerPersonality|ZeroingWeakMemory|O(?:paque(?:Memory|Personality)|bjectP(?:ointerPersonality|ersonality)))|at(?:hStyle(?:Standard|NavigationBar|PopUp)|ternColorSpaceModel)|rintPanelShows(?:Scaling|Copies|Orientation|P(?:a(?:perSize|ge(?:Range|SetupAccessory))|review)))|Executable(?:RuntimeMismatchError|NotLoadableError|ErrorM(?:inimum|aximum)|L(?:inkError|oadError)|ArchitectureMismatchError)|KeyValueObservingOption(?:Initial|Prior)|F(?:i(?:ndPanelSubstringMatchType(?:StartsWith|Contains|EndsWith|FullWord)|leRead(?:TooLargeError|UnknownStringEncodingError))|orcedOrderingSearch)|Wi(?:ndow(?:BackingLocation(?:MainMemory|Default|VideoMemory)|Sharing(?:Read(?:Only|Write)|None)|CollectionBehavior(?:MoveToActiveSpace|CanJoinAllSpaces|Default))|dthInsensitiveSearch)|AggregateExpressionType))(?:\\b)"},{token:["support.constant.cocoa"],regex:"(?:\\b)(NS(?:R(?:GB(?:ModeColorPanel|ColorSpaceModel)|ight(?:Mouse(?:D(?:own(?:Mask)?|ragged(?:Mask)?)|Up(?:Mask)?)|T(?:ext(?:Movement|Alignment)|ab(?:sBezelBorder|StopType))|ArrowFunctionKey)|ound(?:RectBezelStyle|Bankers|ed(?:BezelStyle|TokenStyle|DisclosureBezelStyle)|Down|Up|Plain|Line(?:CapStyle|JoinStyle))|un(?:StoppedResponse|ContinuesResponse|AbortedResponse)|e(?:s(?:izableWindowMask|et(?:CursorRectsRunLoopOrdering|FunctionKey))|ce(?:ssedBezelStyle|iver(?:sCantHandleCommandScriptError|EvaluationScriptError))|turnTextMovement|doFunctionKey|quiredArgumentsMissingScriptError|l(?:evancyLevelIndicatorStyle|ative(?:Before|After))|gular(?:SquareBezelStyle|ControlSize)|moveTraitFontAction)|a(?:n(?:domSubelement|geDateMode)|tingLevelIndicatorStyle|dio(?:ModeMatrix|Button)))|G(?:IFFileType|lyph(?:Below|Inscribe(?:B(?:elow|ase)|Over(?:strike|Below)|Above)|Layout(?:WithPrevious|A(?:tAPoint|gainstAPoint))|A(?:ttribute(?:BidiLevel|Soft|Inscribe|Elastic)|bove))|r(?:ooveBorder|eaterThan(?:Comparison|OrEqualTo(?:Comparison|PredicateOperatorType)|PredicateOperatorType)|a(?:y(?:ModeColorPanel|ColorSpaceModel)|dient(?:None|Con(?:cave(?:Strong|Weak)|vex(?:Strong|Weak)))|phiteControlTint)))|XML(?:N(?:o(?:tationDeclarationKind|de(?:CompactEmptyElement|IsCDATA|OptionsNone|Use(?:SingleQuotes|DoubleQuotes)|Pre(?:serve(?:NamespaceOrder|C(?:haracterReferences|DATA)|DTD|Prefixes|E(?:ntities|mptyElements)|Quotes|Whitespace|A(?:ttributeOrder|ll))|ttyPrint)|ExpandEmptyElement))|amespaceKind)|CommentKind|TextKind|InvalidKind|D(?:ocument(?:X(?:MLKind|HTMLKind|Include)|HTMLKind|T(?:idy(?:XML|HTML)|extKind)|IncludeContentTypeDeclaration|Validate|Kind)|TDKind)|P(?:arser(?:GTRequiredError|XMLDeclNot(?:StartedError|FinishedError)|Mi(?:splaced(?:XMLDeclarationError|CDATAEndStringError)|xedContentDeclNot(?:StartedError|FinishedError))|S(?:t(?:andaloneValueError|ringNot(?:StartedError|ClosedError))|paceRequiredError|eparatorRequiredError)|N(?:MTOKENRequiredError|o(?:t(?:ationNot(?:StartedError|FinishedError)|WellBalancedError)|DTDError)|amespaceDeclarationError|AMERequiredError)|C(?:haracterRef(?:In(?:DTDError|PrologError|EpilogError)|AtEOFError)|o(?:nditionalSectionNot(?:StartedError|FinishedError)|mment(?:NotFinishedError|ContainsDoubleHyphenError))|DATANotFinishedError)|TagNameMismatchError|In(?:ternalError|valid(?:HexCharacterRefError|C(?:haracter(?:RefError|InEntityError|Error)|onditionalSectionError)|DecimalCharacterRefError|URIError|Encoding(?:NameError|Error)))|OutOfMemoryError|D(?:ocumentStartError|elegateAbortedParseError|OCTYPEDeclNotFinishedError)|U(?:RI(?:RequiredError|FragmentError)|n(?:declaredEntityError|parsedEntityError|knownEncodingError|finishedTagError))|P(?:CDATARequiredError|ublicIdentifierRequiredError|arsedEntityRef(?:MissingSemiError|NoNameError|In(?:Internal(?:SubsetError|Error)|PrologError|EpilogError)|AtEOFError)|r(?:ocessingInstructionNot(?:StartedError|FinishedError)|ematureDocumentEndError))|E(?:n(?:codingNotSupportedError|tity(?:Ref(?:In(?:DTDError|PrologError|EpilogError)|erence(?:MissingSemiError|WithoutNameError)|LoopError|AtEOFError)|BoundaryError|Not(?:StartedError|FinishedError)|Is(?:ParameterError|ExternalError)|ValueRequiredError))|qualExpectedError|lementContentDeclNot(?:StartedError|FinishedError)|xt(?:ernalS(?:tandaloneEntityError|ubsetNotFinishedError)|raContentError)|mptyDocumentError)|L(?:iteralNot(?:StartedError|FinishedError)|T(?:RequiredError|SlashRequiredError)|essThanSymbolInAttributeError)|Attribute(?:RedefinedError|HasNoValueError|Not(?:StartedError|FinishedError)|ListNot(?:StartedError|FinishedError)))|rocessingInstructionKind)|E(?:ntity(?:GeneralKind|DeclarationKind|UnparsedKind|P(?:ar(?:sedKind|ameterKind)|redefined))|lement(?:Declaration(?:MixedKind|UndefinedKind|E(?:lementKind|mptyKind)|Kind|AnyKind)|Kind))|Attribute(?:N(?:MToken(?:sKind|Kind)|otationKind)|CDATAKind|ID(?:Ref(?:sKind|Kind)|Kind)|DeclarationKind|En(?:tit(?:yKind|iesKind)|umerationKind)|Kind))|M(?:i(?:n(?:XEdge|iaturizableWindowMask|YEdge|uteCalendarUnit)|terLineJoinStyle|ddleSubelement|xedState)|o(?:nthCalendarUnit|deSwitchFunctionKey|use(?:Moved(?:Mask)?|E(?:ntered(?:Mask)?|ventSubtype|xited(?:Mask)?))|veToBezierPathElement|mentary(?:ChangeButton|Push(?:Button|InButton)|Light(?:Button)?))|enuFunctionKey|a(?:c(?:intoshInterfaceStyle|OSRomanStringEncoding)|tchesPredicateOperatorType|ppedRead|x(?:XEdge|YEdge))|ACHOperatingSystem)|B(?:MPFileType|o(?:ttomTabsBezelBorder|ldFontMask|rderlessWindowMask|x(?:Se(?:condary|parator)|OldStyle|Primary))|uttLineCapStyle|e(?:zelBorder|velLineJoinStyle|low(?:Bottom|Top)|gin(?:sWith(?:Comparison|PredicateOperatorType)|FunctionKey))|lueControlTint|ack(?:spaceCharacter|tabTextMovement|ingStore(?:Retained|Buffered|Nonretained)|TabCharacter|wardsSearch|groundTab)|r(?:owser(?:NoColumnResizing|UserColumnResizing|AutoColumnResizing)|eakFunctionKey))|S(?:h(?:ift(?:JISStringEncoding|KeyMask)|ow(?:ControlGlyphs|InvisibleGlyphs)|adowlessSquareBezelStyle)|y(?:s(?:ReqFunctionKey|tem(?:D(?:omainMask|efined(?:Mask)?)|FunctionKey))|mbolStringEncoding)|c(?:a(?:nnedOption|le(?:None|ToFit|Proportionally))|r(?:oll(?:er(?:NoPart|Increment(?:Page|Line|Arrow)|Decrement(?:Page|Line|Arrow)|Knob(?:Slot)?|Arrows(?:M(?:inEnd|axEnd)|None|DefaultSetting))|Wheel(?:Mask)?|LockFunctionKey)|eenChangedEventType))|t(?:opFunctionKey|r(?:ingDrawing(?:OneShot|DisableScreenFontSubstitution|Uses(?:DeviceMetrics|FontLeading|LineFragmentOrigin))|eam(?:Status(?:Reading|NotOpen|Closed|Open(?:ing)?|Error|Writing|AtEnd)|Event(?:Has(?:BytesAvailable|SpaceAvailable)|None|OpenCompleted|E(?:ndEncountered|rrorOccurred)))))|i(?:ngle(?:DateMode|UnderlineStyle)|ze(?:DownFontAction|UpFontAction))|olarisOperatingSystem|unOSOperatingSystem|pecialPageOrder|e(?:condCalendarUnit|lect(?:By(?:Character|Paragraph|Word)|i(?:ng(?:Next|Previous)|onAffinity(?:Downstream|Upstream))|edTab|FunctionKey)|gmentSwitchTracking(?:Momentary|Select(?:One|Any)))|quareLineCapStyle|witchButton|ave(?:ToOperation|Op(?:tions(?:Yes|No|Ask)|eration)|AsOperation)|mall(?:SquareBezelStyle|C(?:ontrolSize|apsFontMask)|IconButtonBezelStyle))|H(?:ighlightModeMatrix|SBModeColorPanel|o(?:ur(?:Minute(?:SecondDatePickerElementFlag|DatePickerElementFlag)|CalendarUnit)|rizontalRuler|meFunctionKey)|TTPCookieAcceptPolicy(?:Never|OnlyFromMainDocumentDomain|Always)|e(?:lp(?:ButtonBezelStyle|KeyMask|FunctionKey)|avierFontAction)|PUXOperatingSystem)|Year(?:MonthDa(?:yDatePickerElementFlag|tePickerElementFlag)|CalendarUnit)|N(?:o(?:n(?:StandardCharacterSetFontMask|ZeroWindingRule|activatingPanelMask|LossyASCIIStringEncoding)|Border|t(?:ification(?:SuspensionBehavior(?:Hold|Coalesce|D(?:eliverImmediately|rop))|NoCoalescing|CoalescingOn(?:Sender|Name)|DeliverImmediately|PostToAllSessions)|PredicateType|EqualToPredicateOperatorType)|S(?:cr(?:iptError|ollerParts)|ubelement|pecifierError)|CellMask|T(?:itle|opLevelContainersSpecifierError|abs(?:BezelBorder|NoBorder|LineBorder))|I(?:nterfaceStyle|mage)|UnderlineStyle|FontChangeAction)|u(?:ll(?:Glyph|CellType)|m(?:eric(?:Search|PadKeyMask)|berFormatter(?:Round(?:Half(?:Down|Up|Even)|Ceiling|Down|Up|Floor)|Behavior(?:10|Default)|S(?:cientificStyle|pellOutStyle)|NoStyle|CurrencyStyle|DecimalStyle|P(?:ercentStyle|ad(?:Before(?:Suffix|Prefix)|After(?:Suffix|Prefix))))))|e(?:t(?:Services(?:BadArgumentError|NotFoundError|C(?:ollisionError|ancelledError)|TimeoutError|InvalidError|UnknownError|ActivityInProgress)|workDomainMask)|wlineCharacter|xt(?:StepInterfaceStyle|FunctionKey))|EXTSTEPStringEncoding|a(?:t(?:iveShortGlyphPacking|uralTextAlignment)|rrowFontMask))|C(?:hange(?:ReadOtherContents|GrayCell(?:Mask)?|BackgroundCell(?:Mask)?|Cleared|Done|Undone|Autosaved)|MYK(?:ModeColorPanel|ColorSpaceModel)|ircular(?:BezelStyle|Slider)|o(?:n(?:stantValueExpressionType|t(?:inuousCapacityLevelIndicatorStyle|entsCellMask|ain(?:sComparison|erSpecifierError)|rol(?:Glyph|KeyMask))|densedFontMask)|lor(?:Panel(?:RGBModeMask|GrayModeMask|HSBModeMask|C(?:MYKModeMask|olorListModeMask|ustomPaletteModeMask|rayonModeMask)|WheelModeMask|AllModesMask)|ListModeColorPanel)|reServiceDirectory|m(?:p(?:osite(?:XOR|Source(?:In|O(?:ut|ver)|Atop)|Highlight|C(?:opy|lear)|Destination(?:In|O(?:ut|ver)|Atop)|Plus(?:Darker|Lighter))|ressedFontMask)|mandKeyMask))|u(?:stom(?:SelectorPredicateOperatorType|PaletteModeColorPanel)|r(?:sor(?:Update(?:Mask)?|PointingDevice)|veToBezierPathElement))|e(?:nterT(?:extAlignment|abStopType)|ll(?:State|H(?:ighlighted|as(?:Image(?:Horizontal|OnLeftOrBottom)|OverlappingImage))|ChangesContents|Is(?:Bordered|InsetButton)|Disabled|Editable|LightsBy(?:Gray|Background|Contents)|AllowsMixedState))|l(?:ipPagination|o(?:s(?:ePathBezierPathElement|ableWindowMask)|ckAndCalendarDatePickerStyle)|ear(?:ControlTint|DisplayFunctionKey|LineFunctionKey))|a(?:seInsensitive(?:Search|PredicateOption)|n(?:notCreateScriptCommandError|cel(?:Button|TextMovement))|chesDirectory|lculation(?:NoError|Overflow|DivideByZero|Underflow|LossOfPrecision)|rriageReturnCharacter)|r(?:itical(?:Request|AlertStyle)|ayonModeColorPanel))|T(?:hick(?:SquareBezelStyle|erSquareBezelStyle)|ypesetter(?:Behavior|HorizontalTabAction|ContainerBreakAction|ZeroAdvancementAction|OriginalBehavior|ParagraphBreakAction|WhitespaceAction|L(?:ineBreakAction|atestBehavior))|i(?:ckMark(?:Right|Below|Left|Above)|tledWindowMask|meZoneDatePickerElementFlag)|o(?:olbarItemVisibilityPriority(?:Standard|High|User|Low)|pTabsBezelBorder|ggleButton)|IFF(?:Compression(?:N(?:one|EXT)|CCITTFAX(?:3|4)|OldJPEG|JPEG|PackBits|LZW)|FileType)|e(?:rminate(?:Now|Cancel|Later)|xt(?:Read(?:InapplicableDocumentTypeError|WriteErrorM(?:inimum|aximum))|Block(?:M(?:i(?:nimum(?:Height|Width)|ddleAlignment)|a(?:rgin|ximum(?:Height|Width)))|B(?:o(?:ttomAlignment|rder)|aselineAlignment)|Height|TopAlignment|P(?:ercentageValueType|adding)|Width|AbsoluteValueType)|StorageEdited(?:Characters|Attributes)|CellType|ured(?:RoundedBezelStyle|BackgroundWindowMask|SquareBezelStyle)|Table(?:FixedLayoutAlgorithm|AutomaticLayoutAlgorithm)|Field(?:RoundedBezel|SquareBezel|AndStepperDatePickerStyle)|WriteInapplicableDocumentTypeError|ListPrependEnclosingMarker))|woByteGlyphPacking|ab(?:Character|TextMovement|le(?:tP(?:oint(?:Mask|EventSubtype)?|roximity(?:Mask|EventSubtype)?)|Column(?:NoResizing|UserResizingMask|AutoresizingMask)|View(?:ReverseSequentialColumnAutoresizingStyle|GridNone|S(?:olid(?:HorizontalGridLineMask|VerticalGridLineMask)|equentialColumnAutoresizingStyle)|NoColumnAutoresizing|UniformColumnAutoresizingStyle|FirstColumnOnlyAutoresizingStyle|LastColumnOnlyAutoresizingStyle)))|rackModeMatrix)|I(?:n(?:sert(?:CharFunctionKey|FunctionKey|LineFunctionKey)|t(?:Type|ernalS(?:criptError|pecifierError))|dexSubelement|validIndexSpecifierError|formational(?:Request|AlertStyle)|PredicateOperatorType)|talicFontMask|SO(?:2022JPStringEncoding|Latin(?:1StringEncoding|2StringEncoding))|dentityMappingCharacterCollection|llegalTextMovement|mage(?:R(?:ight|ep(?:MatchesDevice|LoadStatus(?:ReadingHeader|Completed|InvalidData|Un(?:expectedEOF|knownType)|WillNeedAllData)))|Below|C(?:ellType|ache(?:BySize|Never|Default|Always))|Interpolation(?:High|None|Default|Low)|O(?:nly|verlaps)|Frame(?:Gr(?:oove|ayBezel)|Button|None|Photo)|L(?:oadStatus(?:ReadError|C(?:ompleted|ancelled)|InvalidData|UnexpectedEOF)|eft)|A(?:lign(?:Right|Bottom(?:Right|Left)?|Center|Top(?:Right|Left)?|Left)|bove)))|O(?:n(?:State|eByteGlyphPacking|OffButton|lyScrollerArrows)|ther(?:Mouse(?:D(?:own(?:Mask)?|ragged(?:Mask)?)|Up(?:Mask)?)|TextMovement)|SF1OperatingSystem|pe(?:n(?:GL(?:GO(?:Re(?:setLibrary|tainRenderers)|ClearFormatCache|FormatCacheSize)|PFA(?:R(?:obust|endererID)|M(?:inimumPolicy|ulti(?:sample|Screen)|PSafe|aximumPolicy)|BackingStore|S(?:creenMask|te(?:ncilSize|reo)|ingleRenderer|upersample|ample(?:s|Buffers|Alpha))|NoRecovery|C(?:o(?:lor(?:Size|Float)|mpliant)|losestPolicy)|OffScreen|D(?:oubleBuffer|epthSize)|PixelBuffer|VirtualScreenCount|FullScreen|Window|A(?:cc(?:umSize|elerated)|ux(?:Buffers|DepthStencil)|l(?:phaSize|lRenderers))))|StepUnicodeReservedBase)|rationNotSupportedForKeyS(?:criptError|pecifierError))|ffState|KButton|rPredicateType|bjC(?:B(?:itfield|oolType)|S(?:hortType|tr(?:ingType|uctType)|electorType)|NoType|CharType|ObjectType|DoubleType|UnionType|PointerType|VoidType|FloatType|Long(?:Type|longType)|ArrayType))|D(?:i(?:s(?:c(?:losureBezelStyle|reteCapacityLevelIndicatorStyle)|playWindowRunLoopOrdering)|acriticInsensitivePredicateOption|rect(?:Selection|PredicateModifier))|o(?:c(?:ModalWindowMask|ument(?:Directory|ationDirectory))|ubleType|wn(?:TextMovement|ArrowFunctionKey))|e(?:s(?:cendingPageOrder|ktopDirectory)|cimalTabStopType|v(?:ice(?:NColorSpaceModel|IndependentModifierFlagsMask)|eloper(?:Directory|ApplicationDirectory))|fault(?:ControlTint|TokenStyle)|lete(?:Char(?:acter|FunctionKey)|FunctionKey|LineFunctionKey)|moApplicationDirectory)|a(?:yCalendarUnit|teFormatter(?:MediumStyle|Behavior(?:10|Default)|ShortStyle|NoStyle|FullStyle|LongStyle))|ra(?:wer(?:Clos(?:ingState|edState)|Open(?:ingState|State))|gOperation(?:Generic|Move|None|Copy|Delete|Private|Every|Link|All)))|U(?:ser(?:CancelledError|D(?:irectory|omainMask)|FunctionKey)|RL(?:Handle(?:NotLoaded|Load(?:Succeeded|InProgress|Failed))|CredentialPersistence(?:None|Permanent|ForSession))|n(?:scaledWindowMask|cachedRead|i(?:codeStringEncoding|talicFontMask|fiedTitleAndToolbarWindowMask)|d(?:o(?:CloseGroupingRunLoopOrdering|FunctionKey)|e(?:finedDateComponent|rline(?:Style(?:Single|None|Thick|Double)|Pattern(?:Solid|D(?:ot|ash(?:Dot(?:Dot)?)?)))))|known(?:ColorSpaceModel|P(?:ointingDevice|ageOrder)|KeyS(?:criptError|pecifierError))|boldFontMask)|tilityWindowMask|TF8StringEncoding|p(?:dateWindowsRunLoopOrdering|TextMovement|ArrowFunctionKey))|J(?:ustifiedTextAlignment|PEG(?:2000FileType|FileType)|apaneseEUC(?:GlyphPacking|StringEncoding))|P(?:o(?:s(?:t(?:Now|erFontMask|WhenIdle|ASAP)|iti(?:on(?:Replace|Be(?:fore|ginning)|End|After)|ve(?:IntType|DoubleType|FloatType)))|pUp(?:NoArrow|ArrowAt(?:Bottom|Center))|werOffEventType|rtraitOrientation)|NGFileType|ush(?:InCell(?:Mask)?|OnPushOffButton)|e(?:n(?:TipMask|UpperSideMask|PointingDevice|LowerSideMask)|riodic(?:Mask)?)|P(?:S(?:caleField|tatus(?:Title|Field)|aveButton)|N(?:ote(?:Title|Field)|ame(?:Title|Field))|CopiesField|TitleField|ImageButton|OptionsButton|P(?:a(?:perFeedButton|ge(?:Range(?:To|From)|ChoiceMatrix))|reviewButton)|LayoutButton)|lainTextTokenStyle|a(?:useFunctionKey|ragraphSeparatorCharacter|ge(?:DownFunctionKey|UpFunctionKey))|r(?:int(?:ing(?:ReplyLater|Success|Cancelled|Failure)|ScreenFunctionKey|erTable(?:NotFound|OK|Error)|FunctionKey)|o(?:p(?:ertyList(?:XMLFormat|MutableContainers(?:AndLeaves)?|BinaryFormat|Immutable|OpenStepFormat)|rietaryStringEncoding)|gressIndicator(?:BarStyle|SpinningStyle|Preferred(?:SmallThickness|Thickness|LargeThickness|AquaThickness)))|e(?:ssedTab|vFunctionKey))|L(?:HeightForm|CancelButton|TitleField|ImageButton|O(?:KButton|rientationMatrix)|UnitsButton|PaperNameButton|WidthForm))|E(?:n(?:terCharacter|d(?:sWith(?:Comparison|PredicateOperatorType)|FunctionKey))|v(?:e(?:nOddWindingRule|rySubelement)|aluatedObjectExpressionType)|qualTo(?:Comparison|PredicateOperatorType)|ra(?:serPointingDevice|CalendarUnit|DatePickerElementFlag)|x(?:clude(?:10|QuickDrawElementsIconCreationOption)|pandedFontMask|ecuteFunctionKey))|V(?:i(?:ew(?:M(?:in(?:XMargin|YMargin)|ax(?:XMargin|YMargin))|HeightSizable|NotSizable|WidthSizable)|aPanelFontAction)|erticalRuler|a(?:lidationErrorM(?:inimum|aximum)|riableExpressionType))|Key(?:SpecifierEvaluationScriptError|Down(?:Mask)?|Up(?:Mask)?|PathExpressionType|Value(?:MinusSetMutation|SetSetMutation|Change(?:Re(?:placement|moval)|Setting|Insertion)|IntersectSetMutation|ObservingOption(?:New|Old)|UnionSetMutation|ValidationError))|QTMovie(?:NormalPlayback|Looping(?:BackAndForthPlayback|Playback))|F(?:1(?:1FunctionKey|7FunctionKey|2FunctionKey|8FunctionKey|3FunctionKey|9FunctionKey|4FunctionKey|5FunctionKey|FunctionKey|0FunctionKey|6FunctionKey)|7FunctionKey|i(?:nd(?:PanelAction(?:Replace(?:A(?:ndFind|ll(?:InSelection)?))?|S(?:howFindPanel|e(?:tFindString|lectAll(?:InSelection)?))|Next|Previous)|FunctionKey)|tPagination|le(?:Read(?:No(?:SuchFileError|PermissionError)|CorruptFileError|In(?:validFileNameError|applicableStringEncodingError)|Un(?:supportedSchemeError|knownError))|HandlingPanel(?:CancelButton|OKButton)|NoSuchFileError|ErrorM(?:inimum|aximum)|Write(?:NoPermissionError|In(?:validFileNameError|applicableStringEncodingError)|OutOfSpaceError|Un(?:supportedSchemeError|knownError))|LockingError)|xedPitchFontMask)|2(?:1FunctionKey|7FunctionKey|2FunctionKey|8FunctionKey|3FunctionKey|9FunctionKey|4FunctionKey|5FunctionKey|FunctionKey|0FunctionKey|6FunctionKey)|o(?:nt(?:Mo(?:noSpaceTrait|dernSerifsClass)|BoldTrait|S(?:ymbolicClass|criptsClass|labSerifsClass|ansSerifClass)|C(?:o(?:ndensedTrait|llectionApplicationOnlyMask)|larendonSerifsClass)|TransitionalSerifsClass|I(?:ntegerAdvancementsRenderingMode|talicTrait)|O(?:ldStyleSerifsClass|rnamentalsClass)|DefaultRenderingMode|U(?:nknownClass|IOptimizedTrait)|Panel(?:S(?:hadowEffectModeMask|t(?:andardModesMask|rikethroughEffectModeMask)|izeModeMask)|CollectionModeMask|TextColorEffectModeMask|DocumentColorEffectModeMask|UnderlineEffectModeMask|FaceModeMask|All(?:ModesMask|EffectsModeMask))|ExpandedTrait|VerticalTrait|F(?:amilyClassMask|reeformSerifsClass)|Antialiased(?:RenderingMode|IntegerAdvancementsRenderingMode))|cusRing(?:Below|Type(?:None|Default|Exterior)|Only|Above)|urByteGlyphPacking|rm(?:attingError(?:M(?:inimum|aximum))?|FeedCharacter))|8FunctionKey|unction(?:ExpressionType|KeyMask)|3(?:1FunctionKey|2FunctionKey|3FunctionKey|4FunctionKey|5FunctionKey|FunctionKey|0FunctionKey)|9FunctionKey|4FunctionKey|P(?:RevertButton|S(?:ize(?:Title|Field)|etButton)|CurrentField|Preview(?:Button|Field))|l(?:oat(?:ingPointSamplesBitmapFormat|Type)|agsChanged(?:Mask)?)|axButton|5FunctionKey|6FunctionKey)|W(?:heelModeColorPanel|indow(?:s(?:NTOperatingSystem|CP125(?:1StringEncoding|2StringEncoding|3StringEncoding|4StringEncoding|0StringEncoding)|95(?:InterfaceStyle|OperatingSystem))|M(?:iniaturizeButton|ovedEventType)|Below|CloseButton|ToolbarButton|ZoomButton|Out|DocumentIconButton|ExposedEventType|Above)|orkspaceLaunch(?:NewInstance|InhibitingBackgroundOnly|Default|PreferringClassic|WithoutA(?:ctivation|ddingToRecents)|A(?:sync|nd(?:Hide(?:Others)?|Print)|llowingClassicStartup))|eek(?:day(?:CalendarUnit|OrdinalCalendarUnit)|CalendarUnit)|a(?:ntsBidiLevels|rningAlertStyle)|r(?:itingDirection(?:RightToLeft|Natural|LeftToRight)|apCalendarComponents))|L(?:i(?:stModeMatrix|ne(?:Moves(?:Right|Down|Up|Left)|B(?:order|reakBy(?:C(?:harWrapping|lipping)|Truncating(?:Middle|Head|Tail)|WordWrapping))|S(?:eparatorCharacter|weep(?:Right|Down|Up|Left))|ToBezierPathElement|DoesntMove|arSlider)|teralSearch|kePredicateOperatorType|ghterFontAction|braryDirectory)|ocalDomainMask|e(?:ssThan(?:Comparison|OrEqualTo(?:Comparison|PredicateOperatorType)|PredicateOperatorType)|ft(?:Mouse(?:D(?:own(?:Mask)?|ragged(?:Mask)?)|Up(?:Mask)?)|T(?:ext(?:Movement|Alignment)|ab(?:sBezelBorder|StopType))|ArrowFunctionKey))|a(?:yout(?:RightToLeft|NotDone|CantFit|OutOfGlyphs|Done|LeftToRight)|ndscapeOrientation)|ABColorSpaceModel)|A(?:sc(?:iiWithDoubleByteEUCGlyphPacking|endingPageOrder)|n(?:y(?:Type|PredicateModifier|EventMask)|choredSearch|imation(?:Blocking|Nonblocking(?:Threaded)?|E(?:ffect(?:DisappearingItemDefault|Poof)|ase(?:In(?:Out)?|Out))|Linear)|dPredicateType)|t(?:Bottom|tachmentCharacter|omicWrite|Top)|SCIIStringEncoding|d(?:obe(?:GB1CharacterCollection|CNS1CharacterCollection|Japan(?:1CharacterCollection|2CharacterCollection)|Korea1CharacterCollection)|dTraitFontAction|minApplicationDirectory)|uto(?:saveOperation|Pagination)|pp(?:lication(?:SupportDirectory|D(?:irectory|e(?:fined(?:Mask)?|legateReply(?:Success|Cancel|Failure)|activatedEventType))|ActivatedEventType)|KitDefined(?:Mask)?)|l(?:ternateKeyMask|pha(?:ShiftKeyMask|NonpremultipliedBitmapFormat|FirstBitmapFormat)|ert(?:SecondButtonReturn|ThirdButtonReturn|OtherReturn|DefaultReturn|ErrorReturn|FirstButtonReturn|AlternateReturn)|l(?:ScrollerParts|DomainsMask|PredicateModifier|LibrariesDirectory|ApplicationsDirectory))|rgument(?:sWrongScriptError|EvaluationScriptError)|bove(?:Bottom|Top)|WTEventType)))(?:\\b)"},{token:"support.function.C99.c",regex:s.cFunctions},{token:n.getKeywords(),regex:"[a-zA-Z_$][a-zA-Z0-9_$]*\\b"},{token:"punctuation.section.scope.begin.objc",regex:"\\[",next:"bracketed_content"},{token:"meta.function.objc",regex:"^(?:-|\\+)\\s*"}],constant_NSString:[{token:"constant.character.escape.objc",regex:e},{token:"invalid.illegal.unknown-escape.objc",regex:"\\\\."},{token:"string",regex:'[^"\\\\]+'},{token:"punctuation.definition.string.end",regex:'"',next:"start"}],protocol_list:[{token:"punctuation.section.scope.end.objc",regex:">",next:"start"},{token:"support.other.protocol.objc",regex:"\bNS(?:GlyphStorage|M(?:utableCopying|enuItem)|C(?:hangeSpelling|o(?:ding|pying|lorPicking(?:Custom|Default)))|T(?:oolbarItemValidations|ext(?:Input|AttachmentCell))|I(?:nputServ(?:iceProvider|erMouseTracker)|gnoreMisspelledWords)|Obj(?:CTypeSerializationCallBack|ect)|D(?:ecimalNumberBehaviors|raggingInfo)|U(?:serInterfaceValidations|RL(?:HandleClient|DownloadDelegate|ProtocolClient|AuthenticationChallengeSender))|Validated(?:ToobarItem|UserInterfaceItem)|Locking)\b"}],selectors:[{token:"support.function.any-method.name-of-parameter.objc",regex:"\\b(?:[a-zA-Z_:][\\w]*)+"},{token:"punctuation",regex:"\\)",next:"start"}],bracketed_content:[{token:"punctuation.section.scope.end.objc",regex:"]",next:"start"},{token:["support.function.any-method.objc"],regex:"(?:predicateWithFormat:| NSPredicate predicateWithFormat:)",next:"start"},{token:"support.function.any-method.objc",regex:"\\w+(?::|(?=]))",next:"start"}],bracketed_strings:[{token:"punctuation.section.scope.end.objc",regex:"]",next:"start"},{token:"keyword.operator.logical.predicate.cocoa",regex:"\\b(?:AND|OR|NOT|IN)\\b"},{token:["invalid.illegal.unknown-method.objc","punctuation.separator.arguments.objc"],regex:"\\b(\\w+)(:)"},{regex:"\\b(?:ALL|ANY|SOME|NONE)\\b",token:"constant.language.predicate.cocoa"},{regex:"\\b(?:NULL|NIL|SELF|TRUE|YES|FALSE|NO|FIRST|LAST|SIZE)\\b",token:"constant.language.predicate.cocoa"},{regex:"\\b(?:MATCHES|CONTAINS|BEGINSWITH|ENDSWITH|BETWEEN)\\b",token:"keyword.operator.comparison.predicate.cocoa"},{regex:"\\bC(?:ASEINSENSITIVE|I)\\b",token:"keyword.other.modifier.predicate.cocoa"},{regex:"\\b(?:ANYKEY|SUBQUERY|CAST|TRUEPREDICATE|FALSEPREDICATE)\\b",token:"keyword.other.predicate.cocoa"},{regex:e,token:"constant.character.escape.objc"},{regex:"\\\\.",token:"invalid.illegal.unknown-escape.objc"},{token:"string",regex:'[^"\\\\]'},{token:"punctuation.definition.string.end.objc",regex:'"',next:"predicates"}],comment:[{token:"comment",regex:".*?\\*\\/",next:"start"},{token:"comment",regex:".+"}],methods:[{token:"meta.function.objc",regex:"(?=\\{|#)|;",next:"start"}]};for(var u in r)this.$rules[u]?this.$rules[u].push&&this.$rules[u].push.apply(this.$rules[u],r[u]):this.$rules[u]=r[u];this.$rules.bracketed_content=this.$rules.bracketed_content.concat(this.$rules.start,t),this.embedRules(i,"doc-",[i.getEndRule("start")])};r.inherits(u,o),t.ObjectiveCHighlightRules=u}),define("ace/mode/folding/cstyle",["require","exports","module","ace/lib/oop","ace/range","ace/mode/folding/fold_mode"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("../../range").Range,s=e("./fold_mode").FoldMode,o=t.FoldMode=function(e){e&&(this.foldingStartMarker=new RegExp(this.foldingStartMarker.source.replace(/\|[^|]*?$/,"|"+e.start)),this.foldingStopMarker=new RegExp(this.foldingStopMarker.source.replace(/\|[^|]*?$/,"|"+e.end)))};r.inherits(o,s),function(){this.foldingStartMarker=/(\{|\[)[^\}\]]*$|^\s*(\/\*)/,this.foldingStopMarker=/^[^\[\{]*(\}|\])|^[\s\*]*(\*\/)/,this.singleLineBlockCommentRe=/^\s*(\/\*).*\*\/\s*$/,this.tripleStarBlockCommentRe=/^\s*(\/\*\*\*).*\*\/\s*$/,this.startRegionRe=/^\s*(\/\*|\/\/)#?region\b/,this._getFoldWidgetBase=this.getFoldWidget,this.getFoldWidget=function(e,t,n){var r=e.getLine(n);if(this.singleLineBlockCommentRe.test(r)&&!this.startRegionRe.test(r)&&!this.tripleStarBlockCommentRe.test(r))return"";var i=this._getFoldWidgetBase(e,t,n);return!i&&this.startRegionRe.test(r)?"start":i},this.getFoldWidgetRange=function(e,t,n,r){var i=e.getLine(n);if(this.startRegionRe.test(i))return this.getCommentRegionBlock(e,i,n);var s=i.match(this.foldingStartMarker);if(s){var o=s.index;if(s[1])return this.openingBracketBlock(e,s[1],n,o);var u=e.getCommentFoldRange(n,o+s[0].length,1);return u&&!u.isMultiLine()&&(r?u=this.getSectionRange(e,n):t!="all"&&(u=null)),u}if(t==="markbegin")return;var s=i.match(this.foldingStopMarker);if(s){var o=s.index+s[0].length;return s[1]?this.closingBracketBlock(e,s[1],n,o):e.getCommentFoldRange(n,o,-1)}},this.getSectionRange=function(e,t){var n=e.getLine(t),r=n.search(/\S/),s=t,o=n.length;t+=1;var u=t,a=e.getLength();while(++tf)break;var l=this.getFoldWidgetRange(e,"all",t);if(l){if(l.start.row<=s)break;if(l.isMultiLine())t=l.end.row;else if(r==f)break}u=t}return new i(s,o,u,e.getLine(u).length)},this.getCommentRegionBlock=function(e,t,n){var r=t.search(/\s*$/),s=e.getLength(),o=n,u=/^\s*(?:\/\*|\/\/|--)#?(end)?region\b/,a=1;while(++no)return new i(o,r,l,t.length)}}.call(o.prototype)}),define("ace/mode/objectivec",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/objectivec_highlight_rules","ace/mode/folding/cstyle"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./objectivec_highlight_rules").ObjectiveCHighlightRules,o=e("./folding/cstyle").FoldMode,u=function(){this.HighlightRules=s,this.foldingRules=new o,this.$behaviour=this.$defaultBehaviour};r.inherits(u,i),function(){this.lineCommentStart="//",this.blockComment={start:"/*",end:"*/"},this.$id="ace/mode/objectivec"}.call(u.prototype),t.Mode=u}) \ No newline at end of file +define("ace/mode/doc_comment_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=function(){this.$rules={start:[{token:"comment.doc.tag",regex:"@[\\w\\d_]+"},s.getTagRule(),{defaultToken:"comment.doc",caseInsensitive:!0}]}};r.inherits(s,i),s.getTagRule=function(e){return{token:"comment.doc.tag.storage.type",regex:"\\b(?:TODO|FIXME|XXX|HACK)\\b"}},s.getStartRule=function(e){return{token:"comment.doc",regex:"\\/\\*(?=\\*)",next:e}},s.getEndRule=function(e){return{token:"comment.doc",regex:"\\*\\/",next:e}},t.DocCommentHighlightRules=s}),define("ace/mode/c_cpp_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/doc_comment_highlight_rules","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./doc_comment_highlight_rules").DocCommentHighlightRules,s=e("./text_highlight_rules").TextHighlightRules,o=t.cFunctions="\\b(?:hypot(?:f|l)?|s(?:scanf|ystem|nprintf|ca(?:nf|lb(?:n(?:f|l)?|ln(?:f|l)?))|i(?:n(?:h(?:f|l)?|f|l)?|gn(?:al|bit))|tr(?:s(?:tr|pn)|nc(?:py|at|mp)|c(?:spn|hr|oll|py|at|mp)|to(?:imax|d|u(?:l(?:l)?|max)|k|f|l(?:d|l)?)|error|pbrk|ftime|len|rchr|xfrm)|printf|et(?:jmp|vbuf|locale|buf)|qrt(?:f|l)?|w(?:scanf|printf)|rand)|n(?:e(?:arbyint(?:f|l)?|xt(?:toward(?:f|l)?|after(?:f|l)?))|an(?:f|l)?)|c(?:s(?:in(?:h(?:f|l)?|f|l)?|qrt(?:f|l)?)|cos(?:h(?:f)?|f|l)?|imag(?:f|l)?|t(?:ime|an(?:h(?:f|l)?|f|l)?)|o(?:s(?:h(?:f|l)?|f|l)?|nj(?:f|l)?|pysign(?:f|l)?)|p(?:ow(?:f|l)?|roj(?:f|l)?)|e(?:il(?:f|l)?|xp(?:f|l)?)|l(?:o(?:ck|g(?:f|l)?)|earerr)|a(?:sin(?:h(?:f|l)?|f|l)?|cos(?:h(?:f|l)?|f|l)?|tan(?:h(?:f|l)?|f|l)?|lloc|rg(?:f|l)?|bs(?:f|l)?)|real(?:f|l)?|brt(?:f|l)?)|t(?:ime|o(?:upper|lower)|an(?:h(?:f|l)?|f|l)?|runc(?:f|l)?|gamma(?:f|l)?|mp(?:nam|file))|i(?:s(?:space|n(?:ormal|an)|cntrl|inf|digit|u(?:nordered|pper)|p(?:unct|rint)|finite|w(?:space|c(?:ntrl|type)|digit|upper|p(?:unct|rint)|lower|al(?:num|pha)|graph|xdigit|blank)|l(?:ower|ess(?:equal|greater)?)|al(?:num|pha)|gr(?:eater(?:equal)?|aph)|xdigit|blank)|logb(?:f|l)?|max(?:div|abs))|di(?:v|fftime)|_Exit|unget(?:c|wc)|p(?:ow(?:f|l)?|ut(?:s|c(?:har)?|wc(?:har)?)|error|rintf)|e(?:rf(?:c(?:f|l)?|f|l)?|x(?:it|p(?:2(?:f|l)?|f|l|m1(?:f|l)?)?))|v(?:s(?:scanf|nprintf|canf|printf|w(?:scanf|printf))|printf|f(?:scanf|printf|w(?:scanf|printf))|w(?:scanf|printf)|a_(?:start|copy|end|arg))|qsort|f(?:s(?:canf|e(?:tpos|ek))|close|tell|open|dim(?:f|l)?|p(?:classify|ut(?:s|c|w(?:s|c))|rintf)|e(?:holdexcept|set(?:e(?:nv|xceptflag)|round)|clearexcept|testexcept|of|updateenv|r(?:aiseexcept|ror)|get(?:e(?:nv|xceptflag)|round))|flush|w(?:scanf|ide|printf|rite)|loor(?:f|l)?|abs(?:f|l)?|get(?:s|c|pos|w(?:s|c))|re(?:open|e|ad|xp(?:f|l)?)|m(?:in(?:f|l)?|od(?:f|l)?|a(?:f|l|x(?:f|l)?)?))|l(?:d(?:iv|exp(?:f|l)?)|o(?:ngjmp|cal(?:time|econv)|g(?:1(?:p(?:f|l)?|0(?:f|l)?)|2(?:f|l)?|f|l|b(?:f|l)?)?)|abs|l(?:div|abs|r(?:int(?:f|l)?|ound(?:f|l)?))|r(?:int(?:f|l)?|ound(?:f|l)?)|gamma(?:f|l)?)|w(?:scanf|c(?:s(?:s(?:tr|pn)|nc(?:py|at|mp)|c(?:spn|hr|oll|py|at|mp)|to(?:imax|d|u(?:l(?:l)?|max)|k|f|l(?:d|l)?|mbs)|pbrk|ftime|len|r(?:chr|tombs)|xfrm)|to(?:b|mb)|rtomb)|printf|mem(?:set|c(?:hr|py|mp)|move))|a(?:s(?:sert|ctime|in(?:h(?:f|l)?|f|l)?)|cos(?:h(?:f|l)?|f|l)?|t(?:o(?:i|f|l(?:l)?)|exit|an(?:h(?:f|l)?|2(?:f|l)?|f|l)?)|b(?:s|ort))|g(?:et(?:s|c(?:har)?|env|wc(?:har)?)|mtime)|r(?:int(?:f|l)?|ound(?:f|l)?|e(?:name|alloc|wind|m(?:ove|quo(?:f|l)?|ainder(?:f|l)?))|a(?:nd|ise))|b(?:search|towc)|m(?:odf(?:f|l)?|em(?:set|c(?:hr|py|mp)|move)|ktime|alloc|b(?:s(?:init|towcs|rtowcs)|towc|len|r(?:towc|len))))\\b",u=function(){var e="break|case|continue|default|do|else|for|goto|if|_Pragma|return|switch|while|catch|operator|try|throw|using",t="asm|__asm__|auto|bool|_Bool|char|_Complex|double|enum|float|_Imaginary|int|long|short|signed|struct|typedef|union|unsigned|void|class|wchar_t|template|char16_t|char32_t",n="const|extern|register|restrict|static|volatile|inline|private|protected|public|friend|explicit|virtual|export|mutable|typename|constexpr|new|delete|alignas|alignof|decltype|noexcept|thread_local",r="and|and_eq|bitand|bitor|compl|not|not_eq|or|or_eq|typeid|xor|xor_eqconst_cast|dynamic_cast|reinterpret_cast|static_cast|sizeof|namespace",s="NULL|true|false|TRUE|FALSE|nullptr",u=this.$keywords=this.createKeywordMapper({"keyword.control":e,"storage.type":t,"storage.modifier":n,"keyword.operator":r,"variable.language":"this","constant.language":s},"identifier"),a="[a-zA-Z\\$_\u00a1-\uffff][a-zA-Z\\d\\$_\u00a1-\uffff]*\\b",f=/\\(?:['"?\\abfnrtv]|[0-7]{1,3}|x[a-fA-F\d]{2}|u[a-fA-F\d]{4}U[a-fA-F\d]{8}|.)/.source;this.$rules={start:[{token:"comment",regex:"//$",next:"start"},{token:"comment",regex:"//",next:"singleLineComment"},i.getStartRule("doc-start"),{token:"comment",regex:"\\/\\*",next:"comment"},{token:"string",regex:"'(?:"+f+"|.)?'"},{token:"string.start",regex:'"',stateName:"qqstring",next:[{token:"string",regex:/\\\s*$/,next:"qqstring"},{token:"constant.language.escape",regex:f},{token:"constant.language.escape",regex:/%[^'"\\]/},{token:"string.end",regex:'"|$',next:"start"},{defaultToken:"string"}]},{token:"string.start",regex:'R"\\(',stateName:"rawString",next:[{token:"string.end",regex:'\\)"',next:"start"},{defaultToken:"string"}]},{token:"constant.numeric",regex:"0[xX][0-9a-fA-F]+(L|l|UL|ul|u|U|F|f|ll|LL|ull|ULL)?\\b"},{token:"constant.numeric",regex:"[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?(L|l|UL|ul|u|U|F|f|ll|LL|ull|ULL)?\\b"},{token:"keyword",regex:"#\\s*(?:include|import|pragma|line|define|undef)\\b",next:"directive"},{token:"keyword",regex:"#\\s*(?:endif|if|ifdef|else|elif|ifndef)\\b"},{token:"support.function.C99.c",regex:o},{token:u,regex:"[a-zA-Z_$][a-zA-Z0-9_$]*"},{token:"keyword.operator",regex:/--|\+\+|<<=|>>=|>>>=|<>|&&|\|\||\?:|[*%\/+\-&\^|~!<>=]=?/},{token:"punctuation.operator",regex:"\\?|\\:|\\,|\\;|\\."},{token:"paren.lparen",regex:"[[({]"},{token:"paren.rparen",regex:"[\\])}]"},{token:"text",regex:"\\s+"}],comment:[{token:"comment",regex:".*?\\*\\/",next:"start"},{token:"comment",regex:".+"}],singleLineComment:[{token:"comment",regex:/\\$/,next:"singleLineComment"},{token:"comment",regex:/$/,next:"start"},{defaultToken:"comment"}],directive:[{token:"constant.other.multiline",regex:/\\/},{token:"constant.other.multiline",regex:/.*\\/},{token:"constant.other",regex:"\\s*<.+?>",next:"start"},{token:"constant.other",regex:'\\s*["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]',next:"start"},{token:"constant.other",regex:"\\s*['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']",next:"start"},{token:"constant.other",regex:/[^\\\/]+/,next:"start"}]},this.embedRules(i,"doc-",[i.getEndRule("start")]),this.normalizeRules()};r.inherits(u,s),t.c_cppHighlightRules=u}),define("ace/mode/objectivec_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/doc_comment_highlight_rules","ace/mode/c_cpp_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./doc_comment_highlight_rules").DocCommentHighlightRules,s=e("./c_cpp_highlight_rules"),o=s.c_cppHighlightRules,u=function(){var e="\\\\(?:[abefnrtv'\"?\\\\]|[0-3]\\d{1,2}|[4-7]\\d?|222|x[a-zA-Z0-9]+)",t=[{regex:"\\b_cmd\\b",token:"variable.other.selector.objc"},{regex:"\\b(?:self|super)\\b",token:"variable.language.objc"}],n=new o,r=n.getRules();this.$rules={start:[{token:"comment",regex:"\\/\\/.*$"},i.getStartRule("doc-start"),{token:"comment",regex:"\\/\\*",next:"comment"},{token:["storage.type.objc","punctuation.definition.storage.type.objc","entity.name.type.objc","text","entity.other.inherited-class.objc"],regex:"(@)(interface|protocol)(?!.+;)(\\s+[A-Za-z_][A-Za-z0-9_]*)(\\s*:\\s*)([A-Za-z]+)"},{token:["storage.type.objc"],regex:"(@end)"},{token:["storage.type.objc","entity.name.type.objc","entity.other.inherited-class.objc"],regex:"(@implementation)(\\s+[A-Za-z_][A-Za-z0-9_]*)(\\s*?::\\s*(?:[A-Za-z][A-Za-z0-9]*))?"},{token:"string.begin.objc",regex:'@"',next:"constant_NSString"},{token:"storage.type.objc",regex:"\\bid\\s*<",next:"protocol_list"},{token:"keyword.control.macro.objc",regex:"\\bNS_DURING|NS_HANDLER|NS_ENDHANDLER\\b"},{token:["punctuation.definition.keyword.objc","keyword.control.exception.objc"],regex:"(@)(try|catch|finally|throw)\\b"},{token:["punctuation.definition.keyword.objc","keyword.other.objc"],regex:"(@)(defs|encode)\\b"},{token:["storage.type.id.objc","text"],regex:"(\\bid\\b)(\\s|\\n)?"},{token:"storage.type.objc",regex:"\\bIBOutlet|IBAction|BOOL|SEL|id|unichar|IMP|Class\\b"},{token:["punctuation.definition.storage.type.objc","storage.type.objc"],regex:"(@)(class|protocol)\\b"},{token:["punctuation.definition.storage.type.objc","punctuation"],regex:"(@selector)(\\s*\\()",next:"selectors"},{token:["punctuation.definition.storage.modifier.objc","storage.modifier.objc"],regex:"(@)(synchronized|public|private|protected|package)\\b"},{token:"constant.language.objc",regex:"\\bYES|NO|Nil|nil\\b"},{token:"support.variable.foundation",regex:"\\bNSApp\\b"},{token:["support.function.cocoa.leopard"],regex:"(?:\\b)(NS(?:Rect(?:ToCGRect|FromCGRect)|MakeCollectable|S(?:tringFromProtocol|ize(?:ToCGSize|FromCGSize))|Draw(?:NinePartImage|ThreePartImage)|P(?:oint(?:ToCGPoint|FromCGPoint)|rotocolFromString)|EventMaskFromType|Value))(?:\\b)"},{token:["support.function.cocoa"],regex:"(?:\\b)(NS(?:R(?:ound(?:DownToMultipleOfPageSize|UpToMultipleOfPageSize)|un(?:CriticalAlertPanel(?:RelativeToWindow)?|InformationalAlertPanel(?:RelativeToWindow)?|AlertPanel(?:RelativeToWindow)?)|e(?:set(?:MapTable|HashTable)|c(?:ycleZone|t(?:Clip(?:List)?|F(?:ill(?:UsingOperation|List(?:UsingOperation|With(?:Grays|Colors(?:UsingOperation)?))?)?|romString))|ordAllocationEvent)|turnAddress|leaseAlertPanel|a(?:dPixel|l(?:MemoryAvailable|locateCollectable))|gisterServicesProvider)|angeFromString)|Get(?:SizeAndAlignment|CriticalAlertPanel|InformationalAlertPanel|UncaughtExceptionHandler|FileType(?:s)?|WindowServerMemory|AlertPanel)|M(?:i(?:n(?:X|Y)|d(?:X|Y))|ouseInRect|a(?:p(?:Remove|Get|Member|Insert(?:IfAbsent|KnownAbsent)?)|ke(?:R(?:ect|ange)|Size|Point)|x(?:Range|X|Y)))|B(?:itsPer(?:SampleFromDepth|PixelFromDepth)|e(?:stDepth|ep|gin(?:CriticalAlertSheet|InformationalAlertSheet|AlertSheet)))|S(?:ho(?:uldRetainWithZone|w(?:sServicesMenuItem|AnimationEffect))|tringFrom(?:R(?:ect|ange)|MapTable|S(?:ize|elector)|HashTable|Class|Point)|izeFromString|e(?:t(?:ShowsServicesMenuItem|ZoneName|UncaughtExceptionHandler|FocusRingStyle)|lectorFromString|archPathForDirectoriesInDomains)|wap(?:Big(?:ShortToHost|IntToHost|DoubleToHost|FloatToHost|Long(?:ToHost|LongToHost))|Short|Host(?:ShortTo(?:Big|Little)|IntTo(?:Big|Little)|DoubleTo(?:Big|Little)|FloatTo(?:Big|Little)|Long(?:To(?:Big|Little)|LongTo(?:Big|Little)))|Int|Double|Float|L(?:ittle(?:ShortToHost|IntToHost|DoubleToHost|FloatToHost|Long(?:ToHost|LongToHost))|ong(?:Long)?)))|H(?:ighlightRect|o(?:stByteOrder|meDirectory(?:ForUser)?)|eight|ash(?:Remove|Get|Insert(?:IfAbsent|KnownAbsent)?)|FSType(?:CodeFromFileType|OfFile))|N(?:umberOfColorComponents|ext(?:MapEnumeratorPair|HashEnumeratorItem))|C(?:o(?:n(?:tainsRect|vert(?:GlyphsToPackedGlyphs|Swapped(?:DoubleToHost|FloatToHost)|Host(?:DoubleToSwapped|FloatToSwapped)))|unt(?:MapTable|HashTable|Frames|Windows(?:ForContext)?)|py(?:M(?:emoryPages|apTableWithZone)|Bits|HashTableWithZone|Object)|lorSpaceFromDepth|mpare(?:MapTables|HashTables))|lassFromString|reate(?:MapTable(?:WithZone)?|HashTable(?:WithZone)?|Zone|File(?:namePboardType|ContentsPboardType)))|TemporaryDirectory|I(?:s(?:ControllerMarker|EmptyRect|FreedObject)|n(?:setRect|crementExtraRefCount|te(?:r(?:sect(?:sRect|ionR(?:ect|ange))|faceStyleForKey)|gralRect)))|Zone(?:Realloc|Malloc|Name|Calloc|Fr(?:omPointer|ee))|O(?:penStepRootDirectory|ffsetRect)|D(?:i(?:sableScreenUpdates|videRect)|ottedFrameRect|e(?:c(?:imal(?:Round|Multiply|S(?:tring|ubtract)|Normalize|Co(?:py|mpa(?:ct|re))|IsNotANumber|Divide|Power|Add)|rementExtraRefCountWasZero)|faultMallocZone|allocate(?:MemoryPages|Object))|raw(?:Gr(?:oove|ayBezel)|B(?:itmap|utton)|ColorTiledRects|TiledRects|DarkBezel|W(?:hiteBezel|indowBackground)|LightBezel))|U(?:serName|n(?:ionR(?:ect|ange)|registerServicesProvider)|pdateDynamicServices)|Java(?:Bundle(?:Setup|Cleanup)|Setup(?:VirtualMachine)?|Needs(?:ToLoadClasses|VirtualMachine)|ClassesF(?:orBundle|romPath)|ObjectNamedInPath|ProvidesClasses)|P(?:oint(?:InRect|FromString)|erformService|lanarFromDepth|ageSize)|E(?:n(?:d(?:MapTableEnumeration|HashTableEnumeration)|umerate(?:MapTable|HashTable)|ableScreenUpdates)|qual(?:R(?:ects|anges)|Sizes|Points)|raseRect|xtraRefCount)|F(?:ileTypeForHFSTypeCode|ullUserName|r(?:ee(?:MapTable|HashTable)|ame(?:Rect(?:WithWidth(?:UsingOperation)?)?|Address)))|Wi(?:ndowList(?:ForContext)?|dth)|Lo(?:cationInRange|g(?:v|PageSize)?)|A(?:ccessibility(?:R(?:oleDescription(?:ForUIElement)?|aiseBadArgumentException)|Unignored(?:Children(?:ForOnlyChild)?|Descendant|Ancestor)|PostNotification|ActionDescription)|pplication(?:Main|Load)|vailableWindowDepths|ll(?:MapTable(?:Values|Keys)|HashTableObjects|ocate(?:MemoryPages|Collectable|Object)))))(?:\\b)"},{token:["support.class.cocoa.leopard"],regex:"(?:\\b)(NS(?:RuleEditor|G(?:arbageCollector|radient)|MapTable|HashTable|Co(?:ndition|llectionView(?:Item)?)|T(?:oolbarItemGroup|extInputClient|r(?:eeNode|ackingArea))|InvocationOperation|Operation(?:Queue)?|D(?:ictionaryController|ockTile)|P(?:ointer(?:Functions|Array)|athC(?:o(?:ntrol(?:Delegate)?|mponentCell)|ell(?:Delegate)?)|r(?:intPanelAccessorizing|edicateEditor(?:RowTemplate)?))|ViewController|FastEnumeration|Animat(?:ionContext|ablePropertyContainer)))(?:\\b)"},{token:["support.class.cocoa"],regex:"(?:\\b)(NS(?:R(?:u(?:nLoop|ler(?:Marker|View))|e(?:sponder|cursiveLock|lativeSpecifier)|an(?:domSpecifier|geSpecifier))|G(?:etCommand|lyph(?:Generator|Storage|Info)|raphicsContext)|XML(?:Node|D(?:ocument|TD(?:Node)?)|Parser|Element)|M(?:iddleSpecifier|ov(?:ie(?:View)?|eCommand)|utable(?:S(?:tring|et)|C(?:haracterSet|opying)|IndexSet|D(?:ictionary|ata)|URLRequest|ParagraphStyle|A(?:ttributedString|rray))|e(?:ssagePort(?:NameServer)?|nu(?:Item(?:Cell)?|View)?|t(?:hodSignature|adata(?:Item|Query(?:ResultGroup|AttributeValueTuple)?)))|a(?:ch(?:BootstrapServer|Port)|trix))|B(?:itmapImageRep|ox|u(?:ndle|tton(?:Cell)?)|ezierPath|rowser(?:Cell)?)|S(?:hadow|c(?:anner|r(?:ipt(?:SuiteRegistry|C(?:o(?:ercionHandler|mmand(?:Description)?)|lassDescription)|ObjectSpecifier|ExecutionContext|WhoseTest)|oll(?:er|View)|een))|t(?:epper(?:Cell)?|atus(?:Bar|Item)|r(?:ing|eam))|imple(?:HorizontalTypesetter|CString)|o(?:cketPort(?:NameServer)?|und|rtDescriptor)|p(?:e(?:cifierTest|ech(?:Recognizer|Synthesizer)|ll(?:Server|Checker))|litView)|e(?:cureTextField(?:Cell)?|t(?:Command)?|archField(?:Cell)?|rializer|gmentedC(?:ontrol|ell))|lider(?:Cell)?|avePanel)|H(?:ost|TTP(?:Cookie(?:Storage)?|URLResponse)|elpManager)|N(?:ib(?:Con(?:nector|trolConnector)|OutletConnector)?|otification(?:Center|Queue)?|u(?:ll|mber(?:Formatter)?)|etService(?:Browser)?|ameSpecifier)|C(?:ha(?:ngeSpelling|racterSet)|o(?:n(?:stantString|nection|trol(?:ler)?|ditionLock)|d(?:ing|er)|unt(?:Command|edSet)|pying|lor(?:Space|P(?:ick(?:ing(?:Custom|Default)|er)|anel)|Well|List)?|m(?:p(?:oundPredicate|arisonPredicate)|boBox(?:Cell)?))|u(?:stomImageRep|rsor)|IImageRep|ell|l(?:ipView|o(?:seCommand|neCommand)|assDescription)|a(?:ched(?:ImageRep|URLResponse)|lendar(?:Date)?)|reateCommand)|T(?:hread|ypesetter|ime(?:Zone|r)|o(?:olbar(?:Item(?:Validations)?)?|kenField(?:Cell)?)|ext(?:Block|Storage|Container|Tab(?:le(?:Block)?)?|Input|View|Field(?:Cell)?|List|Attachment(?:Cell)?)?|a(?:sk|b(?:le(?:Header(?:Cell|View)|Column|View)|View(?:Item)?))|reeController)|I(?:n(?:dex(?:S(?:pecifier|et)|Path)|put(?:Manager|S(?:tream|erv(?:iceProvider|er(?:MouseTracker)?)))|vocation)|gnoreMisspelledWords|mage(?:Rep|Cell|View)?)|O(?:ut(?:putStream|lineView)|pen(?:GL(?:Context|Pixel(?:Buffer|Format)|View)|Panel)|bj(?:CTypeSerializationCallBack|ect(?:Controller)?))|D(?:i(?:st(?:antObject(?:Request)?|ributed(?:NotificationCenter|Lock))|ctionary|rectoryEnumerator)|ocument(?:Controller)?|e(?:serializer|cimalNumber(?:Behaviors|Handler)?|leteCommand)|at(?:e(?:Components|Picker(?:Cell)?|Formatter)?|a)|ra(?:wer|ggingInfo))|U(?:ser(?:InterfaceValidations|Defaults(?:Controller)?)|RL(?:Re(?:sponse|quest)|Handle(?:Client)?|C(?:onnection|ache|redential(?:Storage)?)|Download(?:Delegate)?|Prot(?:ocol(?:Client)?|ectionSpace)|AuthenticationChallenge(?:Sender)?)?|n(?:iqueIDSpecifier|doManager|archiver))|P(?:ipe|o(?:sitionalSpecifier|pUpButton(?:Cell)?|rt(?:Message|NameServer|Coder)?)|ICTImageRep|ersistentDocument|DFImageRep|a(?:steboard|nel|ragraphStyle|geLayout)|r(?:int(?:Info|er|Operation|Panel)|o(?:cessInfo|tocolChecker|perty(?:Specifier|ListSerialization)|gressIndicator|xy)|edicate))|E(?:numerator|vent|PSImageRep|rror|x(?:ception|istsCommand|pression))|V(?:iew(?:Animation)?|al(?:idated(?:ToobarItem|UserInterfaceItem)|ue(?:Transformer)?))|Keyed(?:Unarchiver|Archiver)|Qui(?:ckDrawView|tCommand)|F(?:ile(?:Manager|Handle|Wrapper)|o(?:nt(?:Manager|Descriptor|Panel)?|rm(?:Cell|atter)))|W(?:hoseSpecifier|indow(?:Controller)?|orkspace)|L(?:o(?:c(?:k(?:ing)?|ale)|gicalTest)|evelIndicator(?:Cell)?|ayoutManager)|A(?:ssertionHandler|nimation|ctionCell|ttributedString|utoreleasePool|TSTypesetter|ppl(?:ication|e(?:Script|Event(?:Manager|Descriptor)))|ffineTransform|lert|r(?:chiver|ray(?:Controller)?))))(?:\\b)"},{token:["support.type.cocoa.leopard"],regex:"(?:\\b)(NS(?:R(?:u(?:nLoop|ler(?:Marker|View))|e(?:sponder|cursiveLock|lativeSpecifier)|an(?:domSpecifier|geSpecifier))|G(?:etCommand|lyph(?:Generator|Storage|Info)|raphicsContext)|XML(?:Node|D(?:ocument|TD(?:Node)?)|Parser|Element)|M(?:iddleSpecifier|ov(?:ie(?:View)?|eCommand)|utable(?:S(?:tring|et)|C(?:haracterSet|opying)|IndexSet|D(?:ictionary|ata)|URLRequest|ParagraphStyle|A(?:ttributedString|rray))|e(?:ssagePort(?:NameServer)?|nu(?:Item(?:Cell)?|View)?|t(?:hodSignature|adata(?:Item|Query(?:ResultGroup|AttributeValueTuple)?)))|a(?:ch(?:BootstrapServer|Port)|trix))|B(?:itmapImageRep|ox|u(?:ndle|tton(?:Cell)?)|ezierPath|rowser(?:Cell)?)|S(?:hadow|c(?:anner|r(?:ipt(?:SuiteRegistry|C(?:o(?:ercionHandler|mmand(?:Description)?)|lassDescription)|ObjectSpecifier|ExecutionContext|WhoseTest)|oll(?:er|View)|een))|t(?:epper(?:Cell)?|atus(?:Bar|Item)|r(?:ing|eam))|imple(?:HorizontalTypesetter|CString)|o(?:cketPort(?:NameServer)?|und|rtDescriptor)|p(?:e(?:cifierTest|ech(?:Recognizer|Synthesizer)|ll(?:Server|Checker))|litView)|e(?:cureTextField(?:Cell)?|t(?:Command)?|archField(?:Cell)?|rializer|gmentedC(?:ontrol|ell))|lider(?:Cell)?|avePanel)|H(?:ost|TTP(?:Cookie(?:Storage)?|URLResponse)|elpManager)|N(?:ib(?:Con(?:nector|trolConnector)|OutletConnector)?|otification(?:Center|Queue)?|u(?:ll|mber(?:Formatter)?)|etService(?:Browser)?|ameSpecifier)|C(?:ha(?:ngeSpelling|racterSet)|o(?:n(?:stantString|nection|trol(?:ler)?|ditionLock)|d(?:ing|er)|unt(?:Command|edSet)|pying|lor(?:Space|P(?:ick(?:ing(?:Custom|Default)|er)|anel)|Well|List)?|m(?:p(?:oundPredicate|arisonPredicate)|boBox(?:Cell)?))|u(?:stomImageRep|rsor)|IImageRep|ell|l(?:ipView|o(?:seCommand|neCommand)|assDescription)|a(?:ched(?:ImageRep|URLResponse)|lendar(?:Date)?)|reateCommand)|T(?:hread|ypesetter|ime(?:Zone|r)|o(?:olbar(?:Item(?:Validations)?)?|kenField(?:Cell)?)|ext(?:Block|Storage|Container|Tab(?:le(?:Block)?)?|Input|View|Field(?:Cell)?|List|Attachment(?:Cell)?)?|a(?:sk|b(?:le(?:Header(?:Cell|View)|Column|View)|View(?:Item)?))|reeController)|I(?:n(?:dex(?:S(?:pecifier|et)|Path)|put(?:Manager|S(?:tream|erv(?:iceProvider|er(?:MouseTracker)?)))|vocation)|gnoreMisspelledWords|mage(?:Rep|Cell|View)?)|O(?:ut(?:putStream|lineView)|pen(?:GL(?:Context|Pixel(?:Buffer|Format)|View)|Panel)|bj(?:CTypeSerializationCallBack|ect(?:Controller)?))|D(?:i(?:st(?:antObject(?:Request)?|ributed(?:NotificationCenter|Lock))|ctionary|rectoryEnumerator)|ocument(?:Controller)?|e(?:serializer|cimalNumber(?:Behaviors|Handler)?|leteCommand)|at(?:e(?:Components|Picker(?:Cell)?|Formatter)?|a)|ra(?:wer|ggingInfo))|U(?:ser(?:InterfaceValidations|Defaults(?:Controller)?)|RL(?:Re(?:sponse|quest)|Handle(?:Client)?|C(?:onnection|ache|redential(?:Storage)?)|Download(?:Delegate)?|Prot(?:ocol(?:Client)?|ectionSpace)|AuthenticationChallenge(?:Sender)?)?|n(?:iqueIDSpecifier|doManager|archiver))|P(?:ipe|o(?:sitionalSpecifier|pUpButton(?:Cell)?|rt(?:Message|NameServer|Coder)?)|ICTImageRep|ersistentDocument|DFImageRep|a(?:steboard|nel|ragraphStyle|geLayout)|r(?:int(?:Info|er|Operation|Panel)|o(?:cessInfo|tocolChecker|perty(?:Specifier|ListSerialization)|gressIndicator|xy)|edicate))|E(?:numerator|vent|PSImageRep|rror|x(?:ception|istsCommand|pression))|V(?:iew(?:Animation)?|al(?:idated(?:ToobarItem|UserInterfaceItem)|ue(?:Transformer)?))|Keyed(?:Unarchiver|Archiver)|Qui(?:ckDrawView|tCommand)|F(?:ile(?:Manager|Handle|Wrapper)|o(?:nt(?:Manager|Descriptor|Panel)?|rm(?:Cell|atter)))|W(?:hoseSpecifier|indow(?:Controller)?|orkspace)|L(?:o(?:c(?:k(?:ing)?|ale)|gicalTest)|evelIndicator(?:Cell)?|ayoutManager)|A(?:ssertionHandler|nimation|ctionCell|ttributedString|utoreleasePool|TSTypesetter|ppl(?:ication|e(?:Script|Event(?:Manager|Descriptor)))|ffineTransform|lert|r(?:chiver|ray(?:Controller)?))))(?:\\b)"},{token:["support.class.quartz"],regex:"(?:\\b)(C(?:I(?:Sampler|Co(?:ntext|lor)|Image(?:Accumulator)?|PlugIn(?:Registration)?|Vector|Kernel|Filter(?:Generator|Shape)?)|A(?:Renderer|MediaTiming(?:Function)?|BasicAnimation|ScrollLayer|Constraint(?:LayoutManager)?|T(?:iledLayer|extLayer|rans(?:ition|action))|OpenGLLayer|PropertyAnimation|KeyframeAnimation|Layer|A(?:nimation(?:Group)?|ction))))(?:\\b)"},{token:["support.type.quartz"],regex:"(?:\\b)(C(?:G(?:Float|Point|Size|Rect)|IFormat|AConstraintAttribute))(?:\\b)"},{token:["support.type.cocoa"],regex:"(?:\\b)(NS(?:R(?:ect(?:Edge)?|ange)|G(?:lyph(?:Relation|LayoutMode)?|radientType)|M(?:odalSession|a(?:trixMode|p(?:Table|Enumerator)))|B(?:itmapImageFileType|orderType|uttonType|ezelStyle|ackingStoreType|rowserColumnResizingType)|S(?:cr(?:oll(?:er(?:Part|Arrow)|ArrowPosition)|eenAuxiliaryOpaque)|tringEncoding|ize|ocketNativeHandle|election(?:Granularity|Direction|Affinity)|wapped(?:Double|Float)|aveOperationType)|Ha(?:sh(?:Table|Enumerator)|ndler(?:2)?)|C(?:o(?:ntrol(?:Size|Tint)|mp(?:ositingOperation|arisonResult))|ell(?:State|Type|ImagePosition|Attribute))|T(?:hreadPrivate|ypesetterGlyphInfo|i(?:ckMarkPosition|tlePosition|meInterval)|o(?:ol(?:TipTag|bar(?:SizeMode|DisplayMode))|kenStyle)|IFFCompression|ext(?:TabType|Alignment)|ab(?:State|leViewDropOperation|ViewType)|rackingRectTag)|ImageInterpolation|Zone|OpenGL(?:ContextAuxiliary|PixelFormatAuxiliary)|D(?:ocumentChangeType|atePickerElementFlags|ra(?:werState|gOperation))|UsableScrollerParts|P(?:oint|r(?:intingPageOrder|ogressIndicator(?:Style|Th(?:ickness|readInfo))))|EventType|KeyValueObservingOptions|Fo(?:nt(?:SymbolicTraits|TraitMask|Action)|cusRingType)|W(?:indow(?:OrderingMode|Depth)|orkspace(?:IconCreationOptions|LaunchOptions)|ritingDirection)|L(?:ineBreakMode|ayout(?:Status|Direction))|A(?:nimation(?:Progress|Effect)|ppl(?:ication(?:TerminateReply|DelegateReply|PrintReply)|eEventManagerSuspensionID)|ffineTransformStruct|lertStyle)))(?:\\b)"},{token:["support.constant.cocoa"],regex:"(?:\\b)(NS(?:NotFound|Ordered(?:Ascending|Descending|Same)))(?:\\b)"},{token:["support.constant.notification.cocoa.leopard"],regex:"(?:\\b)(NS(?:MenuDidBeginTracking|ViewDidUpdateTrackingAreas)?Notification)(?:\\b)"},{token:["support.constant.notification.cocoa"],regex:"(?:\\b)(NS(?:Menu(?:Did(?:RemoveItem|SendAction|ChangeItem|EndTracking|AddItem)|WillSendAction)|S(?:ystemColorsDidChange|plitView(?:DidResizeSubviews|WillResizeSubviews))|C(?:o(?:nt(?:extHelpModeDid(?:Deactivate|Activate)|rolT(?:intDidChange|extDid(?:BeginEditing|Change|EndEditing)))|lor(?:PanelColorDidChange|ListDidChange)|mboBox(?:Selection(?:IsChanging|DidChange)|Will(?:Dismiss|PopUp)))|lassDescriptionNeededForClass)|T(?:oolbar(?:DidRemoveItem|WillAddItem)|ext(?:Storage(?:DidProcessEditing|WillProcessEditing)|Did(?:BeginEditing|Change|EndEditing)|View(?:DidChange(?:Selection|TypingAttributes)|WillChangeNotifyingTextView))|ableView(?:Selection(?:IsChanging|DidChange)|ColumnDid(?:Resize|Move)))|ImageRepRegistryDidChange|OutlineView(?:Selection(?:IsChanging|DidChange)|ColumnDid(?:Resize|Move)|Item(?:Did(?:Collapse|Expand)|Will(?:Collapse|Expand)))|Drawer(?:Did(?:Close|Open)|Will(?:Close|Open))|PopUpButton(?:CellWillPopUp|WillPopUp)|View(?:GlobalFrameDidChange|BoundsDidChange|F(?:ocusDidChange|rameDidChange))|FontSetChanged|W(?:indow(?:Did(?:Resi(?:ze|gn(?:Main|Key))|M(?:iniaturize|ove)|Become(?:Main|Key)|ChangeScreen(?:|Profile)|Deminiaturize|Update|E(?:ndSheet|xpose))|Will(?:M(?:iniaturize|ove)|BeginSheet|Close))|orkspace(?:SessionDid(?:ResignActive|BecomeActive)|Did(?:Mount|TerminateApplication|Unmount|PerformFileOperation|Wake|LaunchApplication)|Will(?:Sleep|Unmount|PowerOff|LaunchApplication)))|A(?:ntialiasThresholdChanged|ppl(?:ication(?:Did(?:ResignActive|BecomeActive|Hide|ChangeScreenParameters|U(?:nhide|pdate)|FinishLaunching)|Will(?:ResignActive|BecomeActive|Hide|Terminate|U(?:nhide|pdate)|FinishLaunching))|eEventManagerWillProcessFirstEvent)))Notification)(?:\\b)"},{token:["support.constant.cocoa.leopard"],regex:"(?:\\b)(NS(?:RuleEditor(?:RowType(?:Simple|Compound)|NestingMode(?:Si(?:ngle|mple)|Compound|List))|GradientDraws(?:BeforeStartingLocation|AfterEndingLocation)|M(?:inusSetExpressionType|a(?:chPortDeallocate(?:ReceiveRight|SendRight|None)|pTable(?:StrongMemory|CopyIn|ZeroingWeakMemory|ObjectPointerPersonality)))|B(?:oxCustom|undleExecutableArchitecture(?:X86|I386|PPC(?:64)?)|etweenPredicateOperatorType|ackgroundStyle(?:Raised|Dark|L(?:ight|owered)))|S(?:tring(?:DrawingTruncatesLastVisibleLine|EncodingConversion(?:ExternalRepresentation|AllowLossy))|ubqueryExpressionType|p(?:e(?:ech(?:SentenceBoundary|ImmediateBoundary|WordBoundary)|llingState(?:GrammarFlag|SpellingFlag))|litViewDividerStyleThi(?:n|ck))|e(?:rvice(?:RequestTimedOutError|M(?:iscellaneousError|alformedServiceDictionaryError)|InvalidPasteboardDataError|ErrorM(?:inimum|aximum)|Application(?:NotFoundError|LaunchFailedError))|gmentStyle(?:Round(?:Rect|ed)|SmallSquare|Capsule|Textured(?:Rounded|Square)|Automatic)))|H(?:UDWindowMask|ashTable(?:StrongMemory|CopyIn|ZeroingWeakMemory|ObjectPointerPersonality))|N(?:oModeColorPanel|etServiceNoAutoRename)|C(?:hangeRedone|o(?:ntainsPredicateOperatorType|l(?:orRenderingIntent(?:RelativeColorimetric|Saturation|Default|Perceptual|AbsoluteColorimetric)|lectorDisabledOption))|ellHit(?:None|ContentArea|TrackableArea|EditableTextArea))|T(?:imeZoneNameStyle(?:S(?:hort(?:Standard|DaylightSaving)|tandard)|DaylightSaving)|extFieldDatePickerStyle|ableViewSelectionHighlightStyle(?:Regular|SourceList)|racking(?:Mouse(?:Moved|EnteredAndExited)|CursorUpdate|InVisibleRect|EnabledDuringMouseDrag|A(?:ssumeInside|ctive(?:In(?:KeyWindow|ActiveApp)|WhenFirstResponder|Always))))|I(?:n(?:tersectSetExpressionType|dexedColorSpaceModel)|mageScale(?:None|Proportionally(?:Down|UpOrDown)|AxesIndependently))|Ope(?:nGLPFAAllowOfflineRenderers|rationQueue(?:DefaultMaxConcurrentOperationCount|Priority(?:High|Normal|Very(?:High|Low)|Low)))|D(?:iacriticInsensitiveSearch|ownloadsDirectory)|U(?:nionSetExpressionType|TF(?:16(?:BigEndianStringEncoding|StringEncoding|LittleEndianStringEncoding)|32(?:BigEndianStringEncoding|StringEncoding|LittleEndianStringEncoding)))|P(?:ointerFunctions(?:Ma(?:chVirtualMemory|llocMemory)|Str(?:ongMemory|uctPersonality)|C(?:StringPersonality|opyIn)|IntegerPersonality|ZeroingWeakMemory|O(?:paque(?:Memory|Personality)|bjectP(?:ointerPersonality|ersonality)))|at(?:hStyle(?:Standard|NavigationBar|PopUp)|ternColorSpaceModel)|rintPanelShows(?:Scaling|Copies|Orientation|P(?:a(?:perSize|ge(?:Range|SetupAccessory))|review)))|Executable(?:RuntimeMismatchError|NotLoadableError|ErrorM(?:inimum|aximum)|L(?:inkError|oadError)|ArchitectureMismatchError)|KeyValueObservingOption(?:Initial|Prior)|F(?:i(?:ndPanelSubstringMatchType(?:StartsWith|Contains|EndsWith|FullWord)|leRead(?:TooLargeError|UnknownStringEncodingError))|orcedOrderingSearch)|Wi(?:ndow(?:BackingLocation(?:MainMemory|Default|VideoMemory)|Sharing(?:Read(?:Only|Write)|None)|CollectionBehavior(?:MoveToActiveSpace|CanJoinAllSpaces|Default))|dthInsensitiveSearch)|AggregateExpressionType))(?:\\b)"},{token:["support.constant.cocoa"],regex:"(?:\\b)(NS(?:R(?:GB(?:ModeColorPanel|ColorSpaceModel)|ight(?:Mouse(?:D(?:own(?:Mask)?|ragged(?:Mask)?)|Up(?:Mask)?)|T(?:ext(?:Movement|Alignment)|ab(?:sBezelBorder|StopType))|ArrowFunctionKey)|ound(?:RectBezelStyle|Bankers|ed(?:BezelStyle|TokenStyle|DisclosureBezelStyle)|Down|Up|Plain|Line(?:CapStyle|JoinStyle))|un(?:StoppedResponse|ContinuesResponse|AbortedResponse)|e(?:s(?:izableWindowMask|et(?:CursorRectsRunLoopOrdering|FunctionKey))|ce(?:ssedBezelStyle|iver(?:sCantHandleCommandScriptError|EvaluationScriptError))|turnTextMovement|doFunctionKey|quiredArgumentsMissingScriptError|l(?:evancyLevelIndicatorStyle|ative(?:Before|After))|gular(?:SquareBezelStyle|ControlSize)|moveTraitFontAction)|a(?:n(?:domSubelement|geDateMode)|tingLevelIndicatorStyle|dio(?:ModeMatrix|Button)))|G(?:IFFileType|lyph(?:Below|Inscribe(?:B(?:elow|ase)|Over(?:strike|Below)|Above)|Layout(?:WithPrevious|A(?:tAPoint|gainstAPoint))|A(?:ttribute(?:BidiLevel|Soft|Inscribe|Elastic)|bove))|r(?:ooveBorder|eaterThan(?:Comparison|OrEqualTo(?:Comparison|PredicateOperatorType)|PredicateOperatorType)|a(?:y(?:ModeColorPanel|ColorSpaceModel)|dient(?:None|Con(?:cave(?:Strong|Weak)|vex(?:Strong|Weak)))|phiteControlTint)))|XML(?:N(?:o(?:tationDeclarationKind|de(?:CompactEmptyElement|IsCDATA|OptionsNone|Use(?:SingleQuotes|DoubleQuotes)|Pre(?:serve(?:NamespaceOrder|C(?:haracterReferences|DATA)|DTD|Prefixes|E(?:ntities|mptyElements)|Quotes|Whitespace|A(?:ttributeOrder|ll))|ttyPrint)|ExpandEmptyElement))|amespaceKind)|CommentKind|TextKind|InvalidKind|D(?:ocument(?:X(?:MLKind|HTMLKind|Include)|HTMLKind|T(?:idy(?:XML|HTML)|extKind)|IncludeContentTypeDeclaration|Validate|Kind)|TDKind)|P(?:arser(?:GTRequiredError|XMLDeclNot(?:StartedError|FinishedError)|Mi(?:splaced(?:XMLDeclarationError|CDATAEndStringError)|xedContentDeclNot(?:StartedError|FinishedError))|S(?:t(?:andaloneValueError|ringNot(?:StartedError|ClosedError))|paceRequiredError|eparatorRequiredError)|N(?:MTOKENRequiredError|o(?:t(?:ationNot(?:StartedError|FinishedError)|WellBalancedError)|DTDError)|amespaceDeclarationError|AMERequiredError)|C(?:haracterRef(?:In(?:DTDError|PrologError|EpilogError)|AtEOFError)|o(?:nditionalSectionNot(?:StartedError|FinishedError)|mment(?:NotFinishedError|ContainsDoubleHyphenError))|DATANotFinishedError)|TagNameMismatchError|In(?:ternalError|valid(?:HexCharacterRefError|C(?:haracter(?:RefError|InEntityError|Error)|onditionalSectionError)|DecimalCharacterRefError|URIError|Encoding(?:NameError|Error)))|OutOfMemoryError|D(?:ocumentStartError|elegateAbortedParseError|OCTYPEDeclNotFinishedError)|U(?:RI(?:RequiredError|FragmentError)|n(?:declaredEntityError|parsedEntityError|knownEncodingError|finishedTagError))|P(?:CDATARequiredError|ublicIdentifierRequiredError|arsedEntityRef(?:MissingSemiError|NoNameError|In(?:Internal(?:SubsetError|Error)|PrologError|EpilogError)|AtEOFError)|r(?:ocessingInstructionNot(?:StartedError|FinishedError)|ematureDocumentEndError))|E(?:n(?:codingNotSupportedError|tity(?:Ref(?:In(?:DTDError|PrologError|EpilogError)|erence(?:MissingSemiError|WithoutNameError)|LoopError|AtEOFError)|BoundaryError|Not(?:StartedError|FinishedError)|Is(?:ParameterError|ExternalError)|ValueRequiredError))|qualExpectedError|lementContentDeclNot(?:StartedError|FinishedError)|xt(?:ernalS(?:tandaloneEntityError|ubsetNotFinishedError)|raContentError)|mptyDocumentError)|L(?:iteralNot(?:StartedError|FinishedError)|T(?:RequiredError|SlashRequiredError)|essThanSymbolInAttributeError)|Attribute(?:RedefinedError|HasNoValueError|Not(?:StartedError|FinishedError)|ListNot(?:StartedError|FinishedError)))|rocessingInstructionKind)|E(?:ntity(?:GeneralKind|DeclarationKind|UnparsedKind|P(?:ar(?:sedKind|ameterKind)|redefined))|lement(?:Declaration(?:MixedKind|UndefinedKind|E(?:lementKind|mptyKind)|Kind|AnyKind)|Kind))|Attribute(?:N(?:MToken(?:sKind|Kind)|otationKind)|CDATAKind|ID(?:Ref(?:sKind|Kind)|Kind)|DeclarationKind|En(?:tit(?:yKind|iesKind)|umerationKind)|Kind))|M(?:i(?:n(?:XEdge|iaturizableWindowMask|YEdge|uteCalendarUnit)|terLineJoinStyle|ddleSubelement|xedState)|o(?:nthCalendarUnit|deSwitchFunctionKey|use(?:Moved(?:Mask)?|E(?:ntered(?:Mask)?|ventSubtype|xited(?:Mask)?))|veToBezierPathElement|mentary(?:ChangeButton|Push(?:Button|InButton)|Light(?:Button)?))|enuFunctionKey|a(?:c(?:intoshInterfaceStyle|OSRomanStringEncoding)|tchesPredicateOperatorType|ppedRead|x(?:XEdge|YEdge))|ACHOperatingSystem)|B(?:MPFileType|o(?:ttomTabsBezelBorder|ldFontMask|rderlessWindowMask|x(?:Se(?:condary|parator)|OldStyle|Primary))|uttLineCapStyle|e(?:zelBorder|velLineJoinStyle|low(?:Bottom|Top)|gin(?:sWith(?:Comparison|PredicateOperatorType)|FunctionKey))|lueControlTint|ack(?:spaceCharacter|tabTextMovement|ingStore(?:Retained|Buffered|Nonretained)|TabCharacter|wardsSearch|groundTab)|r(?:owser(?:NoColumnResizing|UserColumnResizing|AutoColumnResizing)|eakFunctionKey))|S(?:h(?:ift(?:JISStringEncoding|KeyMask)|ow(?:ControlGlyphs|InvisibleGlyphs)|adowlessSquareBezelStyle)|y(?:s(?:ReqFunctionKey|tem(?:D(?:omainMask|efined(?:Mask)?)|FunctionKey))|mbolStringEncoding)|c(?:a(?:nnedOption|le(?:None|ToFit|Proportionally))|r(?:oll(?:er(?:NoPart|Increment(?:Page|Line|Arrow)|Decrement(?:Page|Line|Arrow)|Knob(?:Slot)?|Arrows(?:M(?:inEnd|axEnd)|None|DefaultSetting))|Wheel(?:Mask)?|LockFunctionKey)|eenChangedEventType))|t(?:opFunctionKey|r(?:ingDrawing(?:OneShot|DisableScreenFontSubstitution|Uses(?:DeviceMetrics|FontLeading|LineFragmentOrigin))|eam(?:Status(?:Reading|NotOpen|Closed|Open(?:ing)?|Error|Writing|AtEnd)|Event(?:Has(?:BytesAvailable|SpaceAvailable)|None|OpenCompleted|E(?:ndEncountered|rrorOccurred)))))|i(?:ngle(?:DateMode|UnderlineStyle)|ze(?:DownFontAction|UpFontAction))|olarisOperatingSystem|unOSOperatingSystem|pecialPageOrder|e(?:condCalendarUnit|lect(?:By(?:Character|Paragraph|Word)|i(?:ng(?:Next|Previous)|onAffinity(?:Downstream|Upstream))|edTab|FunctionKey)|gmentSwitchTracking(?:Momentary|Select(?:One|Any)))|quareLineCapStyle|witchButton|ave(?:ToOperation|Op(?:tions(?:Yes|No|Ask)|eration)|AsOperation)|mall(?:SquareBezelStyle|C(?:ontrolSize|apsFontMask)|IconButtonBezelStyle))|H(?:ighlightModeMatrix|SBModeColorPanel|o(?:ur(?:Minute(?:SecondDatePickerElementFlag|DatePickerElementFlag)|CalendarUnit)|rizontalRuler|meFunctionKey)|TTPCookieAcceptPolicy(?:Never|OnlyFromMainDocumentDomain|Always)|e(?:lp(?:ButtonBezelStyle|KeyMask|FunctionKey)|avierFontAction)|PUXOperatingSystem)|Year(?:MonthDa(?:yDatePickerElementFlag|tePickerElementFlag)|CalendarUnit)|N(?:o(?:n(?:StandardCharacterSetFontMask|ZeroWindingRule|activatingPanelMask|LossyASCIIStringEncoding)|Border|t(?:ification(?:SuspensionBehavior(?:Hold|Coalesce|D(?:eliverImmediately|rop))|NoCoalescing|CoalescingOn(?:Sender|Name)|DeliverImmediately|PostToAllSessions)|PredicateType|EqualToPredicateOperatorType)|S(?:cr(?:iptError|ollerParts)|ubelement|pecifierError)|CellMask|T(?:itle|opLevelContainersSpecifierError|abs(?:BezelBorder|NoBorder|LineBorder))|I(?:nterfaceStyle|mage)|UnderlineStyle|FontChangeAction)|u(?:ll(?:Glyph|CellType)|m(?:eric(?:Search|PadKeyMask)|berFormatter(?:Round(?:Half(?:Down|Up|Even)|Ceiling|Down|Up|Floor)|Behavior(?:10|Default)|S(?:cientificStyle|pellOutStyle)|NoStyle|CurrencyStyle|DecimalStyle|P(?:ercentStyle|ad(?:Before(?:Suffix|Prefix)|After(?:Suffix|Prefix))))))|e(?:t(?:Service(?:BadArgumentError|NotFoundError|C(?:ollisionError|ancelledError)|TimeoutError|InvalidError|UnknownError|ActivityInProgress)|workDomainMask)|wlineCharacter|xt(?:StepInterfaceStyle|FunctionKey))|EXTSTEPStringEncoding|a(?:t(?:iveShortGlyphPacking|uralTextAlignment)|rrowFontMask))|C(?:hange(?:ReadOtherContents|GrayCell(?:Mask)?|BackgroundCell(?:Mask)?|Cleared|Done|Undone|Autosaved)|MYK(?:ModeColorPanel|ColorSpaceModel)|ircular(?:BezelStyle|Slider)|o(?:n(?:stantValueExpressionType|t(?:inuousCapacityLevelIndicatorStyle|entsCellMask|ain(?:sComparison|erSpecifierError)|rol(?:Glyph|KeyMask))|densedFontMask)|lor(?:Panel(?:RGBModeMask|GrayModeMask|HSBModeMask|C(?:MYKModeMask|olorListModeMask|ustomPaletteModeMask|rayonModeMask)|WheelModeMask|AllModesMask)|ListModeColorPanel)|reServiceDirectory|m(?:p(?:osite(?:XOR|Source(?:In|O(?:ut|ver)|Atop)|Highlight|C(?:opy|lear)|Destination(?:In|O(?:ut|ver)|Atop)|Plus(?:Darker|Lighter))|ressedFontMask)|mandKeyMask))|u(?:stom(?:SelectorPredicateOperatorType|PaletteModeColorPanel)|r(?:sor(?:Update(?:Mask)?|PointingDevice)|veToBezierPathElement))|e(?:nterT(?:extAlignment|abStopType)|ll(?:State|H(?:ighlighted|as(?:Image(?:Horizontal|OnLeftOrBottom)|OverlappingImage))|ChangesContents|Is(?:Bordered|InsetButton)|Disabled|Editable|LightsBy(?:Gray|Background|Contents)|AllowsMixedState))|l(?:ipPagination|o(?:s(?:ePathBezierPathElement|ableWindowMask)|ckAndCalendarDatePickerStyle)|ear(?:ControlTint|DisplayFunctionKey|LineFunctionKey))|a(?:seInsensitive(?:Search|PredicateOption)|n(?:notCreateScriptCommandError|cel(?:Button|TextMovement))|chesDirectory|lculation(?:NoError|Overflow|DivideByZero|Underflow|LossOfPrecision)|rriageReturnCharacter)|r(?:itical(?:Request|AlertStyle)|ayonModeColorPanel))|T(?:hick(?:SquareBezelStyle|erSquareBezelStyle)|ypesetter(?:Behavior|HorizontalTabAction|ContainerBreakAction|ZeroAdvancementAction|OriginalBehavior|ParagraphBreakAction|WhitespaceAction|L(?:ineBreakAction|atestBehavior))|i(?:ckMark(?:Right|Below|Left|Above)|tledWindowMask|meZoneDatePickerElementFlag)|o(?:olbarItemVisibilityPriority(?:Standard|High|User|Low)|pTabsBezelBorder|ggleButton)|IFF(?:Compression(?:N(?:one|EXT)|CCITTFAX(?:3|4)|OldJPEG|JPEG|PackBits|LZW)|FileType)|e(?:rminate(?:Now|Cancel|Later)|xt(?:Read(?:InapplicableDocumentTypeError|WriteErrorM(?:inimum|aximum))|Block(?:M(?:i(?:nimum(?:Height|Width)|ddleAlignment)|a(?:rgin|ximum(?:Height|Width)))|B(?:o(?:ttomAlignment|rder)|aselineAlignment)|Height|TopAlignment|P(?:ercentageValueType|adding)|Width|AbsoluteValueType)|StorageEdited(?:Characters|Attributes)|CellType|ured(?:RoundedBezelStyle|BackgroundWindowMask|SquareBezelStyle)|Table(?:FixedLayoutAlgorithm|AutomaticLayoutAlgorithm)|Field(?:RoundedBezel|SquareBezel|AndStepperDatePickerStyle)|WriteInapplicableDocumentTypeError|ListPrependEnclosingMarker))|woByteGlyphPacking|ab(?:Character|TextMovement|le(?:tP(?:oint(?:Mask|EventSubtype)?|roximity(?:Mask|EventSubtype)?)|Column(?:NoResizing|UserResizingMask|AutoresizingMask)|View(?:ReverseSequentialColumnAutoresizingStyle|GridNone|S(?:olid(?:HorizontalGridLineMask|VerticalGridLineMask)|equentialColumnAutoresizingStyle)|NoColumnAutoresizing|UniformColumnAutoresizingStyle|FirstColumnOnlyAutoresizingStyle|LastColumnOnlyAutoresizingStyle)))|rackModeMatrix)|I(?:n(?:sert(?:CharFunctionKey|FunctionKey|LineFunctionKey)|t(?:Type|ernalS(?:criptError|pecifierError))|dexSubelement|validIndexSpecifierError|formational(?:Request|AlertStyle)|PredicateOperatorType)|talicFontMask|SO(?:2022JPStringEncoding|Latin(?:1StringEncoding|2StringEncoding))|dentityMappingCharacterCollection|llegalTextMovement|mage(?:R(?:ight|ep(?:MatchesDevice|LoadStatus(?:ReadingHeader|Completed|InvalidData|Un(?:expectedEOF|knownType)|WillNeedAllData)))|Below|C(?:ellType|ache(?:BySize|Never|Default|Always))|Interpolation(?:High|None|Default|Low)|O(?:nly|verlaps)|Frame(?:Gr(?:oove|ayBezel)|Button|None|Photo)|L(?:oadStatus(?:ReadError|C(?:ompleted|ancelled)|InvalidData|UnexpectedEOF)|eft)|A(?:lign(?:Right|Bottom(?:Right|Left)?|Center|Top(?:Right|Left)?|Left)|bove)))|O(?:n(?:State|eByteGlyphPacking|OffButton|lyScrollerArrows)|ther(?:Mouse(?:D(?:own(?:Mask)?|ragged(?:Mask)?)|Up(?:Mask)?)|TextMovement)|SF1OperatingSystem|pe(?:n(?:GL(?:GO(?:Re(?:setLibrary|tainRenderers)|ClearFormatCache|FormatCacheSize)|PFA(?:R(?:obust|endererID)|M(?:inimumPolicy|ulti(?:sample|Screen)|PSafe|aximumPolicy)|BackingStore|S(?:creenMask|te(?:ncilSize|reo)|ingleRenderer|upersample|ample(?:s|Buffers|Alpha))|NoRecovery|C(?:o(?:lor(?:Size|Float)|mpliant)|losestPolicy)|OffScreen|D(?:oubleBuffer|epthSize)|PixelBuffer|VirtualScreenCount|FullScreen|Window|A(?:cc(?:umSize|elerated)|ux(?:Buffers|DepthStencil)|l(?:phaSize|lRenderers))))|StepUnicodeReservedBase)|rationNotSupportedForKeyS(?:criptError|pecifierError))|ffState|KButton|rPredicateType|bjC(?:B(?:itfield|oolType)|S(?:hortType|tr(?:ingType|uctType)|electorType)|NoType|CharType|ObjectType|DoubleType|UnionType|PointerType|VoidType|FloatType|Long(?:Type|longType)|ArrayType))|D(?:i(?:s(?:c(?:losureBezelStyle|reteCapacityLevelIndicatorStyle)|playWindowRunLoopOrdering)|acriticInsensitivePredicateOption|rect(?:Selection|PredicateModifier))|o(?:c(?:ModalWindowMask|ument(?:Directory|ationDirectory))|ubleType|wn(?:TextMovement|ArrowFunctionKey))|e(?:s(?:cendingPageOrder|ktopDirectory)|cimalTabStopType|v(?:ice(?:NColorSpaceModel|IndependentModifierFlagsMask)|eloper(?:Directory|ApplicationDirectory))|fault(?:ControlTint|TokenStyle)|lete(?:Char(?:acter|FunctionKey)|FunctionKey|LineFunctionKey)|moApplicationDirectory)|a(?:yCalendarUnit|teFormatter(?:MediumStyle|Behavior(?:10|Default)|ShortStyle|NoStyle|FullStyle|LongStyle))|ra(?:wer(?:Clos(?:ingState|edState)|Open(?:ingState|State))|gOperation(?:Generic|Move|None|Copy|Delete|Private|Every|Link|All)))|U(?:ser(?:CancelledError|D(?:irectory|omainMask)|FunctionKey)|RL(?:Handle(?:NotLoaded|Load(?:Succeeded|InProgress|Failed))|CredentialPersistence(?:None|Permanent|ForSession))|n(?:scaledWindowMask|cachedRead|i(?:codeStringEncoding|talicFontMask|fiedTitleAndToolbarWindowMask)|d(?:o(?:CloseGroupingRunLoopOrdering|FunctionKey)|e(?:finedDateComponent|rline(?:Style(?:Single|None|Thick|Double)|Pattern(?:Solid|D(?:ot|ash(?:Dot(?:Dot)?)?)))))|known(?:ColorSpaceModel|P(?:ointingDevice|ageOrder)|KeyS(?:criptError|pecifierError))|boldFontMask)|tilityWindowMask|TF8StringEncoding|p(?:dateWindowsRunLoopOrdering|TextMovement|ArrowFunctionKey))|J(?:ustifiedTextAlignment|PEG(?:2000FileType|FileType)|apaneseEUC(?:GlyphPacking|StringEncoding))|P(?:o(?:s(?:t(?:Now|erFontMask|WhenIdle|ASAP)|iti(?:on(?:Replace|Be(?:fore|ginning)|End|After)|ve(?:IntType|DoubleType|FloatType)))|pUp(?:NoArrow|ArrowAt(?:Bottom|Center))|werOffEventType|rtraitOrientation)|NGFileType|ush(?:InCell(?:Mask)?|OnPushOffButton)|e(?:n(?:TipMask|UpperSideMask|PointingDevice|LowerSideMask)|riodic(?:Mask)?)|P(?:S(?:caleField|tatus(?:Title|Field)|aveButton)|N(?:ote(?:Title|Field)|ame(?:Title|Field))|CopiesField|TitleField|ImageButton|OptionsButton|P(?:a(?:perFeedButton|ge(?:Range(?:To|From)|ChoiceMatrix))|reviewButton)|LayoutButton)|lainTextTokenStyle|a(?:useFunctionKey|ragraphSeparatorCharacter|ge(?:DownFunctionKey|UpFunctionKey))|r(?:int(?:ing(?:ReplyLater|Success|Cancelled|Failure)|ScreenFunctionKey|erTable(?:NotFound|OK|Error)|FunctionKey)|o(?:p(?:ertyList(?:XMLFormat|MutableContainers(?:AndLeaves)?|BinaryFormat|Immutable|OpenStepFormat)|rietaryStringEncoding)|gressIndicator(?:BarStyle|SpinningStyle|Preferred(?:SmallThickness|Thickness|LargeThickness|AquaThickness)))|e(?:ssedTab|vFunctionKey))|L(?:HeightForm|CancelButton|TitleField|ImageButton|O(?:KButton|rientationMatrix)|UnitsButton|PaperNameButton|WidthForm))|E(?:n(?:terCharacter|d(?:sWith(?:Comparison|PredicateOperatorType)|FunctionKey))|v(?:e(?:nOddWindingRule|rySubelement)|aluatedObjectExpressionType)|qualTo(?:Comparison|PredicateOperatorType)|ra(?:serPointingDevice|CalendarUnit|DatePickerElementFlag)|x(?:clude(?:10|QuickDrawElementsIconCreationOption)|pandedFontMask|ecuteFunctionKey))|V(?:i(?:ew(?:M(?:in(?:XMargin|YMargin)|ax(?:XMargin|YMargin))|HeightSizable|NotSizable|WidthSizable)|aPanelFontAction)|erticalRuler|a(?:lidationErrorM(?:inimum|aximum)|riableExpressionType))|Key(?:SpecifierEvaluationScriptError|Down(?:Mask)?|Up(?:Mask)?|PathExpressionType|Value(?:MinusSetMutation|SetSetMutation|Change(?:Re(?:placement|moval)|Setting|Insertion)|IntersectSetMutation|ObservingOption(?:New|Old)|UnionSetMutation|ValidationError))|QTMovie(?:NormalPlayback|Looping(?:BackAndForthPlayback|Playback))|F(?:1(?:1FunctionKey|7FunctionKey|2FunctionKey|8FunctionKey|3FunctionKey|9FunctionKey|4FunctionKey|5FunctionKey|FunctionKey|0FunctionKey|6FunctionKey)|7FunctionKey|i(?:nd(?:PanelAction(?:Replace(?:A(?:ndFind|ll(?:InSelection)?))?|S(?:howFindPanel|e(?:tFindString|lectAll(?:InSelection)?))|Next|Previous)|FunctionKey)|tPagination|le(?:Read(?:No(?:SuchFileError|PermissionError)|CorruptFileError|In(?:validFileNameError|applicableStringEncodingError)|Un(?:supportedSchemeError|knownError))|HandlingPanel(?:CancelButton|OKButton)|NoSuchFileError|ErrorM(?:inimum|aximum)|Write(?:NoPermissionError|In(?:validFileNameError|applicableStringEncodingError)|OutOfSpaceError|Un(?:supportedSchemeError|knownError))|LockingError)|xedPitchFontMask)|2(?:1FunctionKey|7FunctionKey|2FunctionKey|8FunctionKey|3FunctionKey|9FunctionKey|4FunctionKey|5FunctionKey|FunctionKey|0FunctionKey|6FunctionKey)|o(?:nt(?:Mo(?:noSpaceTrait|dernSerifsClass)|BoldTrait|S(?:ymbolicClass|criptsClass|labSerifsClass|ansSerifClass)|C(?:o(?:ndensedTrait|llectionApplicationOnlyMask)|larendonSerifsClass)|TransitionalSerifsClass|I(?:ntegerAdvancementsRenderingMode|talicTrait)|O(?:ldStyleSerifsClass|rnamentalsClass)|DefaultRenderingMode|U(?:nknownClass|IOptimizedTrait)|Panel(?:S(?:hadowEffectModeMask|t(?:andardModesMask|rikethroughEffectModeMask)|izeModeMask)|CollectionModeMask|TextColorEffectModeMask|DocumentColorEffectModeMask|UnderlineEffectModeMask|FaceModeMask|All(?:ModesMask|EffectsModeMask))|ExpandedTrait|VerticalTrait|F(?:amilyClassMask|reeformSerifsClass)|Antialiased(?:RenderingMode|IntegerAdvancementsRenderingMode))|cusRing(?:Below|Type(?:None|Default|Exterior)|Only|Above)|urByteGlyphPacking|rm(?:attingError(?:M(?:inimum|aximum))?|FeedCharacter))|8FunctionKey|unction(?:ExpressionType|KeyMask)|3(?:1FunctionKey|2FunctionKey|3FunctionKey|4FunctionKey|5FunctionKey|FunctionKey|0FunctionKey)|9FunctionKey|4FunctionKey|P(?:RevertButton|S(?:ize(?:Title|Field)|etButton)|CurrentField|Preview(?:Button|Field))|l(?:oat(?:ingPointSamplesBitmapFormat|Type)|agsChanged(?:Mask)?)|axButton|5FunctionKey|6FunctionKey)|W(?:heelModeColorPanel|indow(?:s(?:NTOperatingSystem|CP125(?:1StringEncoding|2StringEncoding|3StringEncoding|4StringEncoding|0StringEncoding)|95(?:InterfaceStyle|OperatingSystem))|M(?:iniaturizeButton|ovedEventType)|Below|CloseButton|ToolbarButton|ZoomButton|Out|DocumentIconButton|ExposedEventType|Above)|orkspaceLaunch(?:NewInstance|InhibitingBackgroundOnly|Default|PreferringClassic|WithoutA(?:ctivation|ddingToRecents)|A(?:sync|nd(?:Hide(?:Others)?|Print)|llowingClassicStartup))|eek(?:day(?:CalendarUnit|OrdinalCalendarUnit)|CalendarUnit)|a(?:ntsBidiLevels|rningAlertStyle)|r(?:itingDirection(?:RightToLeft|Natural|LeftToRight)|apCalendarComponents))|L(?:i(?:stModeMatrix|ne(?:Moves(?:Right|Down|Up|Left)|B(?:order|reakBy(?:C(?:harWrapping|lipping)|Truncating(?:Middle|Head|Tail)|WordWrapping))|S(?:eparatorCharacter|weep(?:Right|Down|Up|Left))|ToBezierPathElement|DoesntMove|arSlider)|teralSearch|kePredicateOperatorType|ghterFontAction|braryDirectory)|ocalDomainMask|e(?:ssThan(?:Comparison|OrEqualTo(?:Comparison|PredicateOperatorType)|PredicateOperatorType)|ft(?:Mouse(?:D(?:own(?:Mask)?|ragged(?:Mask)?)|Up(?:Mask)?)|T(?:ext(?:Movement|Alignment)|ab(?:sBezelBorder|StopType))|ArrowFunctionKey))|a(?:yout(?:RightToLeft|NotDone|CantFit|OutOfGlyphs|Done|LeftToRight)|ndscapeOrientation)|ABColorSpaceModel)|A(?:sc(?:iiWithDoubleByteEUCGlyphPacking|endingPageOrder)|n(?:y(?:Type|PredicateModifier|EventMask)|choredSearch|imation(?:Blocking|Nonblocking(?:Threaded)?|E(?:ffect(?:DisappearingItemDefault|Poof)|ase(?:In(?:Out)?|Out))|Linear)|dPredicateType)|t(?:Bottom|tachmentCharacter|omicWrite|Top)|SCIIStringEncoding|d(?:obe(?:GB1CharacterCollection|CNS1CharacterCollection|Japan(?:1CharacterCollection|2CharacterCollection)|Korea1CharacterCollection)|dTraitFontAction|minApplicationDirectory)|uto(?:saveOperation|Pagination)|pp(?:lication(?:SupportDirectory|D(?:irectory|e(?:fined(?:Mask)?|legateReply(?:Success|Cancel|Failure)|activatedEventType))|ActivatedEventType)|KitDefined(?:Mask)?)|l(?:ternateKeyMask|pha(?:ShiftKeyMask|NonpremultipliedBitmapFormat|FirstBitmapFormat)|ert(?:SecondButtonReturn|ThirdButtonReturn|OtherReturn|DefaultReturn|ErrorReturn|FirstButtonReturn|AlternateReturn)|l(?:ScrollerParts|DomainsMask|PredicateModifier|LibrariesDirectory|ApplicationsDirectory))|rgument(?:sWrongScriptError|EvaluationScriptError)|bove(?:Bottom|Top)|WTEventType)))(?:\\b)"},{token:"support.function.C99.c",regex:s.cFunctions},{token:n.getKeywords(),regex:"[a-zA-Z_$][a-zA-Z0-9_$]*\\b"},{token:"punctuation.section.scope.begin.objc",regex:"\\[",next:"bracketed_content"},{token:"meta.function.objc",regex:"^(?:-|\\+)\\s*"}],constant_NSString:[{token:"constant.character.escape.objc",regex:e},{token:"invalid.illegal.unknown-escape.objc",regex:"\\\\."},{token:"string",regex:'[^"\\\\]+'},{token:"punctuation.definition.string.end",regex:'"',next:"start"}],protocol_list:[{token:"punctuation.section.scope.end.objc",regex:">",next:"start"},{token:"support.other.protocol.objc",regex:"\bNS(?:GlyphStorage|M(?:utableCopying|enuItem)|C(?:hangeSpelling|o(?:ding|pying|lorPicking(?:Custom|Default)))|T(?:oolbarItemValidations|ext(?:Input|AttachmentCell))|I(?:nputServ(?:iceProvider|erMouseTracker)|gnoreMisspelledWords)|Obj(?:CTypeSerializationCallBack|ect)|D(?:ecimalNumberBehaviors|raggingInfo)|U(?:serInterfaceValidations|RL(?:HandleClient|DownloadDelegate|ProtocolClient|AuthenticationChallengeSender))|Validated(?:ToobarItem|UserInterfaceItem)|Locking)\b"}],selectors:[{token:"support.function.any-method.name-of-parameter.objc",regex:"\\b(?:[a-zA-Z_:][\\w]*)+"},{token:"punctuation",regex:"\\)",next:"start"}],bracketed_content:[{token:"punctuation.section.scope.end.objc",regex:"]",next:"start"},{token:["support.function.any-method.objc"],regex:"(?:predicateWithFormat:| NSPredicate predicateWithFormat:)",next:"start"},{token:"support.function.any-method.objc",regex:"\\w+(?::|(?=]))",next:"start"}],bracketed_strings:[{token:"punctuation.section.scope.end.objc",regex:"]",next:"start"},{token:"keyword.operator.logical.predicate.cocoa",regex:"\\b(?:AND|OR|NOT|IN)\\b"},{token:["invalid.illegal.unknown-method.objc","punctuation.separator.arguments.objc"],regex:"\\b(\\w+)(:)"},{regex:"\\b(?:ALL|ANY|SOME|NONE)\\b",token:"constant.language.predicate.cocoa"},{regex:"\\b(?:NULL|NIL|SELF|TRUE|YES|FALSE|NO|FIRST|LAST|SIZE)\\b",token:"constant.language.predicate.cocoa"},{regex:"\\b(?:MATCHES|CONTAINS|BEGINSWITH|ENDSWITH|BETWEEN)\\b",token:"keyword.operator.comparison.predicate.cocoa"},{regex:"\\bC(?:ASEINSENSITIVE|I)\\b",token:"keyword.other.modifier.predicate.cocoa"},{regex:"\\b(?:ANYKEY|SUBQUERY|CAST|TRUEPREDICATE|FALSEPREDICATE)\\b",token:"keyword.other.predicate.cocoa"},{regex:e,token:"constant.character.escape.objc"},{regex:"\\\\.",token:"invalid.illegal.unknown-escape.objc"},{token:"string",regex:'[^"\\\\]'},{token:"punctuation.definition.string.end.objc",regex:'"',next:"predicates"}],comment:[{token:"comment",regex:".*?\\*\\/",next:"start"},{token:"comment",regex:".+"}],methods:[{token:"meta.function.objc",regex:"(?=\\{|#)|;",next:"start"}]};for(var u in r)this.$rules[u]?this.$rules[u].push&&this.$rules[u].push.apply(this.$rules[u],r[u]):this.$rules[u]=r[u];this.$rules.bracketed_content=this.$rules.bracketed_content.concat(this.$rules.start,t),this.embedRules(i,"doc-",[i.getEndRule("start")])};r.inherits(u,o),t.ObjectiveCHighlightRules=u}),define("ace/mode/folding/cstyle",["require","exports","module","ace/lib/oop","ace/range","ace/mode/folding/fold_mode"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("../../range").Range,s=e("./fold_mode").FoldMode,o=t.FoldMode=function(e){e&&(this.foldingStartMarker=new RegExp(this.foldingStartMarker.source.replace(/\|[^|]*?$/,"|"+e.start)),this.foldingStopMarker=new RegExp(this.foldingStopMarker.source.replace(/\|[^|]*?$/,"|"+e.end)))};r.inherits(o,s),function(){this.foldingStartMarker=/(\{|\[)[^\}\]]*$|^\s*(\/\*)/,this.foldingStopMarker=/^[^\[\{]*(\}|\])|^[\s\*]*(\*\/)/,this.singleLineBlockCommentRe=/^\s*(\/\*).*\*\/\s*$/,this.tripleStarBlockCommentRe=/^\s*(\/\*\*\*).*\*\/\s*$/,this.startRegionRe=/^\s*(\/\*|\/\/)#?region\b/,this._getFoldWidgetBase=this.getFoldWidget,this.getFoldWidget=function(e,t,n){var r=e.getLine(n);if(this.singleLineBlockCommentRe.test(r)&&!this.startRegionRe.test(r)&&!this.tripleStarBlockCommentRe.test(r))return"";var i=this._getFoldWidgetBase(e,t,n);return!i&&this.startRegionRe.test(r)?"start":i},this.getFoldWidgetRange=function(e,t,n,r){var i=e.getLine(n);if(this.startRegionRe.test(i))return this.getCommentRegionBlock(e,i,n);var s=i.match(this.foldingStartMarker);if(s){var o=s.index;if(s[1])return this.openingBracketBlock(e,s[1],n,o);var u=e.getCommentFoldRange(n,o+s[0].length,1);return u&&!u.isMultiLine()&&(r?u=this.getSectionRange(e,n):t!="all"&&(u=null)),u}if(t==="markbegin")return;var s=i.match(this.foldingStopMarker);if(s){var o=s.index+s[0].length;return s[1]?this.closingBracketBlock(e,s[1],n,o):e.getCommentFoldRange(n,o,-1)}},this.getSectionRange=function(e,t){var n=e.getLine(t),r=n.search(/\S/),s=t,o=n.length;t+=1;var u=t,a=e.getLength();while(++tf)break;var l=this.getFoldWidgetRange(e,"all",t);if(l){if(l.start.row<=s)break;if(l.isMultiLine())t=l.end.row;else if(r==f)break}u=t}return new i(s,o,u,e.getLine(u).length)},this.getCommentRegionBlock=function(e,t,n){var r=t.search(/\s*$/),s=e.getLength(),o=n,u=/^\s*(?:\/\*|\/\/|--)#?(end)?region\b/,a=1;while(++no)return new i(o,r,l,t.length)}}.call(o.prototype)}),define("ace/mode/objectivec",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/objectivec_highlight_rules","ace/mode/folding/cstyle"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./objectivec_highlight_rules").ObjectiveCHighlightRules,o=e("./folding/cstyle").FoldMode,u=function(){this.HighlightRules=s,this.foldingRules=new o,this.$behaviour=this.$defaultBehaviour};r.inherits(u,i),function(){this.lineCommentStart="//",this.blockComment={start:"/*",end:"*/"},this.$id="ace/mode/objectivec"}.call(u.prototype),t.Mode=u}) diff --git a/public/themes/pterodactyl/vendor/adminlte/admin.min.css b/public/themes/pterodactyl/vendor/adminlte/admin.min.css index 20792ca50..4098d2a3e 100755 --- a/public/themes/pterodactyl/vendor/adminlte/admin.min.css +++ b/public/themes/pterodactyl/vendor/adminlte/admin.min.css @@ -1,7 +1,7 @@ -@import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic);/*! - * AdminLTE v2.3.8 +/*! + * AdminLTE v2.4.0 * Author: Almsaeed Studio - * Website: Almsaeed Studio + * Website: Almsaeed Studio * License: Open source - MIT * Please visit http://opensource.org/licenses/MIT for more information -!*/html,body{height:100%}.layout-boxed html,.layout-boxed body{height:100%}body{font-family:'Source Sans Pro','Helvetica Neue',Helvetica,Arial,sans-serif;font-weight:400;overflow-x:hidden;overflow-y:auto}.wrapper{height:100%;position:relative;overflow-x:hidden;overflow-y:auto}.wrapper:before,.wrapper:after{content:" ";display:table}.wrapper:after{clear:both}.layout-boxed .wrapper{max-width:1250px;margin:0 auto;min-height:100%;box-shadow:0 0 8px rgba(0,0,0,0.5);position:relative}.layout-boxed{background:url('../img/boxed-bg.jpg') repeat fixed}.content-wrapper,.right-side,.main-footer{-webkit-transition:-webkit-transform .3s ease-in-out,margin .3s ease-in-out;-moz-transition:-moz-transform .3s ease-in-out,margin .3s ease-in-out;-o-transition:-o-transform .3s ease-in-out,margin .3s ease-in-out;transition:transform .3s ease-in-out,margin .3s ease-in-out;margin-left:230px;z-index:820}.layout-top-nav .content-wrapper,.layout-top-nav .right-side,.layout-top-nav .main-footer{margin-left:0}@media (max-width:767px){.content-wrapper,.right-side,.main-footer{margin-left:0}}@media (min-width:768px){.sidebar-collapse .content-wrapper,.sidebar-collapse .right-side,.sidebar-collapse .main-footer{margin-left:0}}@media (max-width:767px){.sidebar-open .content-wrapper,.sidebar-open .right-side,.sidebar-open .main-footer{-webkit-transform:translate(230px, 0);-ms-transform:translate(230px, 0);-o-transform:translate(230px, 0);transform:translate(230px, 0)}}.content-wrapper,.right-side{min-height:100%;background-color:#ecf0f5;z-index:800}.main-footer{background:#fff;padding:15px;color:#444;border-top:1px solid #d2d6de}.fixed .main-header,.fixed .main-sidebar,.fixed .left-side{position:fixed}.fixed .main-header{top:0;right:0;left:0}.fixed .content-wrapper,.fixed .right-side{padding-top:50px}@media (max-width:767px){.fixed .content-wrapper,.fixed .right-side{padding-top:100px}}.fixed.layout-boxed .wrapper{max-width:100%}body.hold-transition .content-wrapper,body.hold-transition .right-side,body.hold-transition .main-footer,body.hold-transition .main-sidebar,body.hold-transition .left-side,body.hold-transition .main-header .navbar,body.hold-transition .main-header .logo{-webkit-transition:none;-o-transition:none;transition:none}.content{min-height:250px;padding:15px;margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:'Source Sans Pro',sans-serif}a{color:#3c8dbc}a:hover,a:active,a:focus{outline:none;text-decoration:none;color:#72afd2}.page-header{margin:10px 0 20px 0;font-size:22px}.page-header>small{color:#666;display:block;margin-top:5px}.main-header{position:relative;max-height:100px;z-index:1030}.main-header .navbar{-webkit-transition:margin-left .3s ease-in-out;-o-transition:margin-left .3s ease-in-out;transition:margin-left .3s ease-in-out;margin-bottom:0;margin-left:230px;border:none;min-height:50px;border-radius:0}.layout-top-nav .main-header .navbar{margin-left:0}.main-header #navbar-search-input.form-control{background:rgba(255,255,255,0.2);border-color:transparent}.main-header #navbar-search-input.form-control:focus,.main-header #navbar-search-input.form-control:active{border-color:rgba(0,0,0,0.1);background:rgba(255,255,255,0.9)}.main-header #navbar-search-input.form-control::-moz-placeholder{color:#ccc;opacity:1}.main-header #navbar-search-input.form-control:-ms-input-placeholder{color:#ccc}.main-header #navbar-search-input.form-control::-webkit-input-placeholder{color:#ccc}.main-header .navbar-custom-menu,.main-header .navbar-right{float:right}@media (max-width:991px){.main-header .navbar-custom-menu a,.main-header .navbar-right a{color:inherit;background:transparent}}@media (max-width:767px){.main-header .navbar-right{float:none}.navbar-collapse .main-header .navbar-right{margin:7.5px -15px}.main-header .navbar-right>li{color:inherit;border:0}}.main-header .sidebar-toggle{float:left;background-color:transparent;background-image:none;padding:15px 15px;font-family:fontAwesome}.main-header .sidebar-toggle:before{content:"\f0c9"}.main-header .sidebar-toggle:hover{color:#fff}.main-header .sidebar-toggle:focus,.main-header .sidebar-toggle:active{background:transparent}.main-header .sidebar-toggle .icon-bar{display:none}.main-header .navbar .nav>li.user>a>.fa,.main-header .navbar .nav>li.user>a>.glyphicon,.main-header .navbar .nav>li.user>a>.ion{margin-right:5px}.main-header .navbar .nav>li>a>.label{position:absolute;top:9px;right:7px;text-align:center;font-size:9px;padding:2px 3px;line-height:.9}.main-header .logo{-webkit-transition:width .3s ease-in-out;-o-transition:width .3s ease-in-out;transition:width .3s ease-in-out;display:block;float:left;height:50px;font-size:20px;line-height:50px;text-align:center;width:230px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;padding:0 15px;font-weight:300;overflow:hidden}.main-header .logo .logo-lg{display:block}.main-header .logo .logo-mini{display:none}.main-header .navbar-brand{color:#fff}.content-header{position:relative;padding:15px 15px 0 15px}.content-header>h1{margin:0;font-size:24px}.content-header>h1>small{font-size:15px;display:inline-block;padding-left:4px;font-weight:300}.content-header>.breadcrumb{float:right;background:transparent;margin-top:0;margin-bottom:0;font-size:12px;padding:7px 5px;position:absolute;top:15px;right:10px;border-radius:2px}.content-header>.breadcrumb>li>a{color:#444;text-decoration:none;display:inline-block}.content-header>.breadcrumb>li>a>.fa,.content-header>.breadcrumb>li>a>.glyphicon,.content-header>.breadcrumb>li>a>.ion{margin-right:5px}.content-header>.breadcrumb>li+li:before{content:'>\00a0'}@media (max-width:991px){.content-header>.breadcrumb{position:relative;margin-top:5px;top:0;right:0;float:none;background:#d2d6de;padding-left:10px}.content-header>.breadcrumb li:before{color:#97a0b3}}.navbar-toggle{color:#fff;border:0;margin:0;padding:15px 15px}@media (max-width:991px){.navbar-custom-menu .navbar-nav>li{float:left}.navbar-custom-menu .navbar-nav{margin:0;float:left}.navbar-custom-menu .navbar-nav>li>a{padding-top:15px;padding-bottom:15px;line-height:20px}}@media (max-width:767px){.main-header{position:relative}.main-header .logo,.main-header .navbar{width:100%;float:none}.main-header .navbar{margin:0}.main-header .navbar-custom-menu{float:right}}@media (max-width:991px){.navbar-collapse.pull-left{float:none !important}.navbar-collapse.pull-left+.navbar-custom-menu{display:block;position:absolute;top:0;right:40px}}.main-sidebar,.left-side{position:absolute;top:0;left:0;padding-top:50px;min-height:100%;width:230px;z-index:810;-webkit-transition:-webkit-transform .3s ease-in-out,width .3s ease-in-out;-moz-transition:-moz-transform .3s ease-in-out,width .3s ease-in-out;-o-transition:-o-transform .3s ease-in-out,width .3s ease-in-out;transition:transform .3s ease-in-out,width .3s ease-in-out}@media (max-width:767px){.main-sidebar,.left-side{padding-top:100px}}@media (max-width:767px){.main-sidebar,.left-side{-webkit-transform:translate(-230px, 0);-ms-transform:translate(-230px, 0);-o-transform:translate(-230px, 0);transform:translate(-230px, 0)}}@media (min-width:768px){.sidebar-collapse .main-sidebar,.sidebar-collapse .left-side{-webkit-transform:translate(-230px, 0);-ms-transform:translate(-230px, 0);-o-transform:translate(-230px, 0);transform:translate(-230px, 0)}}@media (max-width:767px){.sidebar-open .main-sidebar,.sidebar-open .left-side{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0)}}.sidebar{padding-bottom:10px}.sidebar-form input:focus{border-color:transparent}.user-panel{position:relative;width:100%;padding:10px;overflow:hidden}.user-panel:before,.user-panel:after{content:" ";display:table}.user-panel:after{clear:both}.user-panel>.image>img{width:100%;max-width:45px;height:auto}.user-panel>.info{padding:5px 5px 5px 15px;line-height:1;position:absolute;left:55px}.user-panel>.info>p{font-weight:600;margin-bottom:9px}.user-panel>.info>a{text-decoration:none;padding-right:5px;margin-top:3px;font-size:11px}.user-panel>.info>a>.fa,.user-panel>.info>a>.ion,.user-panel>.info>a>.glyphicon{margin-right:3px}.sidebar-menu{list-style:none;margin:0;padding:0}.sidebar-menu>li{position:relative;margin:0;padding:0}.sidebar-menu>li>a{padding:12px 5px 12px 15px;display:block}.sidebar-menu>li>a>.fa,.sidebar-menu>li>a>.glyphicon,.sidebar-menu>li>a>.ion{width:20px}.sidebar-menu>li .label,.sidebar-menu>li .badge{margin-right:5px}.sidebar-menu>li .badge{margin-top:3px}.sidebar-menu li.header{padding:10px 25px 10px 15px;font-size:12px}.sidebar-menu li>a>.fa-angle-left,.sidebar-menu li>a>.pull-right-container>.fa-angle-left{width:auto;height:auto;padding:0;margin-right:10px}.sidebar-menu li>a>.fa-angle-left{position:absolute;top:50%;right:10px;margin-top:-8px}.sidebar-menu li.active>a>.fa-angle-left,.sidebar-menu li.active>a>.pull-right-container>.fa-angle-left{-webkit-transform:rotate(-90deg);-ms-transform:rotate(-90deg);-o-transform:rotate(-90deg);transform:rotate(-90deg)}.sidebar-menu li.active>.treeview-menu{display:block}.sidebar-menu .treeview-menu{display:none;list-style:none;padding:0;margin:0;padding-left:5px}.sidebar-menu .treeview-menu .treeview-menu{padding-left:20px}.sidebar-menu .treeview-menu>li{margin:0}.sidebar-menu .treeview-menu>li>a{padding:5px 5px 5px 15px;display:block;font-size:14px}.sidebar-menu .treeview-menu>li>a>.fa,.sidebar-menu .treeview-menu>li>a>.glyphicon,.sidebar-menu .treeview-menu>li>a>.ion{width:20px}.sidebar-menu .treeview-menu>li>a>.pull-right-container>.fa-angle-left,.sidebar-menu .treeview-menu>li>a>.pull-right-container>.fa-angle-down,.sidebar-menu .treeview-menu>li>a>.fa-angle-left,.sidebar-menu .treeview-menu>li>a>.fa-angle-down{width:auto}@media (min-width:768px){.sidebar-mini.sidebar-collapse .content-wrapper,.sidebar-mini.sidebar-collapse .right-side,.sidebar-mini.sidebar-collapse .main-footer{margin-left:50px !important;z-index:840}.sidebar-mini.sidebar-collapse .main-sidebar{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0);width:50px !important;z-index:850}.sidebar-mini.sidebar-collapse .sidebar-menu>li{position:relative}.sidebar-mini.sidebar-collapse .sidebar-menu>li>a{margin-right:0}.sidebar-mini.sidebar-collapse .sidebar-menu>li>a>span{border-top-right-radius:4px}.sidebar-mini.sidebar-collapse .sidebar-menu>li:not(.treeview)>a>span{border-bottom-right-radius:4px}.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{padding-top:5px;padding-bottom:5px;border-bottom-right-radius:4px}.sidebar-mini.sidebar-collapse .sidebar-menu>li:hover>a>span:not(.pull-right),.sidebar-mini.sidebar-collapse .sidebar-menu>li:hover>.treeview-menu{display:block !important;position:absolute;width:180px;left:50px}.sidebar-mini.sidebar-collapse .sidebar-menu>li:hover>a>span{top:0;margin-left:-3px;padding:12px 5px 12px 20px;background-color:inherit}.sidebar-mini.sidebar-collapse .sidebar-menu>li:hover>a>.pull-right-container{position:relative!important;float:right;width:auto!important;left:180px !important;top:-22px !important;z-index:900}.sidebar-mini.sidebar-collapse .sidebar-menu>li:hover>a>.pull-right-container>.label:not(:first-of-type){display:none}.sidebar-mini.sidebar-collapse .sidebar-menu>li:hover>.treeview-menu{top:44px;margin-left:0}.sidebar-mini.sidebar-collapse .main-sidebar .user-panel>.info,.sidebar-mini.sidebar-collapse .sidebar-form,.sidebar-mini.sidebar-collapse .sidebar-menu>li>a>span,.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu,.sidebar-mini.sidebar-collapse .sidebar-menu>li>a>.pull-right,.sidebar-mini.sidebar-collapse .sidebar-menu li.header{display:none !important;-webkit-transform:translateZ(0)}.sidebar-mini.sidebar-collapse .main-header .logo{width:50px}.sidebar-mini.sidebar-collapse .main-header .logo>.logo-mini{display:block;margin-left:-15px;margin-right:-15px;font-size:18px}.sidebar-mini.sidebar-collapse .main-header .logo>.logo-lg{display:none}.sidebar-mini.sidebar-collapse .main-header .navbar{margin-left:50px}}.sidebar-menu,.main-sidebar .user-panel,.sidebar-menu>li.header{white-space:nowrap;overflow:hidden}.sidebar-menu:hover{overflow:visible}.sidebar-form,.sidebar-menu>li.header{overflow:hidden;text-overflow:clip}.sidebar-menu li>a{position:relative}.sidebar-menu li>a>.pull-right-container{position:absolute;right:10px;top:50%;margin-top:-7px}.control-sidebar-bg{position:fixed;z-index:1000;bottom:0}.control-sidebar-bg,.control-sidebar{top:0;right:-230px;width:230px;-webkit-transition:right .3s ease-in-out;-o-transition:right .3s ease-in-out;transition:right .3s ease-in-out}.control-sidebar{position:absolute;padding-top:50px;z-index:1010}@media (max-width:768px){.control-sidebar{padding-top:100px}}.control-sidebar>.tab-content{padding:10px 15px}.control-sidebar.control-sidebar-open,.control-sidebar.control-sidebar-open+.control-sidebar-bg{right:0}.control-sidebar-open .control-sidebar-bg,.control-sidebar-open .control-sidebar{right:0}@media (min-width:768px){.control-sidebar-open .content-wrapper,.control-sidebar-open .right-side,.control-sidebar-open .main-footer{margin-right:230px}}.nav-tabs.control-sidebar-tabs>li:first-of-type>a,.nav-tabs.control-sidebar-tabs>li:first-of-type>a:hover,.nav-tabs.control-sidebar-tabs>li:first-of-type>a:focus{border-left-width:0}.nav-tabs.control-sidebar-tabs>li>a{border-radius:0}.nav-tabs.control-sidebar-tabs>li>a,.nav-tabs.control-sidebar-tabs>li>a:hover{border-top:none;border-right:none;border-left:1px solid transparent;border-bottom:1px solid transparent}.nav-tabs.control-sidebar-tabs>li>a .icon{font-size:16px}.nav-tabs.control-sidebar-tabs>li.active>a,.nav-tabs.control-sidebar-tabs>li.active>a:hover,.nav-tabs.control-sidebar-tabs>li.active>a:focus,.nav-tabs.control-sidebar-tabs>li.active>a:active{border-top:none;border-right:none;border-bottom:none}@media (max-width:768px){.nav-tabs.control-sidebar-tabs{display:table}.nav-tabs.control-sidebar-tabs>li{display:table-cell}}.control-sidebar-heading{font-weight:400;font-size:16px;padding:10px 0;margin-bottom:10px}.control-sidebar-subheading{display:block;font-weight:400;font-size:14px}.control-sidebar-menu{list-style:none;padding:0;margin:0 -15px}.control-sidebar-menu>li>a{display:block;padding:10px 15px}.control-sidebar-menu>li>a:before,.control-sidebar-menu>li>a:after{content:" ";display:table}.control-sidebar-menu>li>a:after{clear:both}.control-sidebar-menu>li>a>.control-sidebar-subheading{margin-top:0}.control-sidebar-menu .menu-icon{float:left;width:35px;height:35px;border-radius:50%;text-align:center;line-height:35px}.control-sidebar-menu .menu-info{margin-left:45px;margin-top:3px}.control-sidebar-menu .menu-info>.control-sidebar-subheading{margin:0}.control-sidebar-menu .menu-info>p{margin:0;font-size:11px}.control-sidebar-menu .progress{margin:0}.control-sidebar-dark{color:#b8c7ce}.control-sidebar-dark,.control-sidebar-dark+.control-sidebar-bg{background:#222d32}.control-sidebar-dark .nav-tabs.control-sidebar-tabs{border-bottom:#1c2529}.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a{background:#181f23;color:#b8c7ce}.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:hover,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:focus{border-left-color:#141a1d;border-bottom-color:#141a1d}.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:hover,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:focus,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:active{background:#1c2529}.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:hover{color:#fff}.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li.active>a,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li.active>a:hover,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li.active>a:focus,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li.active>a:active{background:#222d32;color:#fff}.control-sidebar-dark .control-sidebar-heading,.control-sidebar-dark .control-sidebar-subheading{color:#fff}.control-sidebar-dark .control-sidebar-menu>li>a:hover{background:#1e282c}.control-sidebar-dark .control-sidebar-menu>li>a .menu-info>p{color:#b8c7ce}.control-sidebar-light{color:#5e5e5e}.control-sidebar-light,.control-sidebar-light+.control-sidebar-bg{background:#f9fafc;border-left:1px solid #d2d6de}.control-sidebar-light .nav-tabs.control-sidebar-tabs{border-bottom:#d2d6de}.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a{background:#e8ecf4;color:#444}.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:hover,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:focus{border-left-color:#d2d6de;border-bottom-color:#d2d6de}.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:hover,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:focus,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:active{background:#eff1f7}.control-sidebar-light .nav-tabs.control-sidebar-tabs>li.active>a,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li.active>a:hover,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li.active>a:focus,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li.active>a:active{background:#f9fafc;color:#111}.control-sidebar-light .control-sidebar-heading,.control-sidebar-light .control-sidebar-subheading{color:#111}.control-sidebar-light .control-sidebar-menu{margin-left:-14px}.control-sidebar-light .control-sidebar-menu>li>a:hover{background:#f4f4f5}.control-sidebar-light .control-sidebar-menu>li>a .menu-info>p{color:#5e5e5e}.dropdown-menu{box-shadow:none;border-color:#eee}.dropdown-menu>li>a{color:#777}.dropdown-menu>li>a>.glyphicon,.dropdown-menu>li>a>.fa,.dropdown-menu>li>a>.ion{margin-right:10px}.dropdown-menu>li>a:hover{background-color:#e1e3e9;color:#333}.dropdown-menu>.divider{background-color:#eee}.navbar-nav>.notifications-menu>.dropdown-menu,.navbar-nav>.messages-menu>.dropdown-menu,.navbar-nav>.tasks-menu>.dropdown-menu{width:280px;padding:0 0 0 0;margin:0;top:100%}.navbar-nav>.notifications-menu>.dropdown-menu>li,.navbar-nav>.messages-menu>.dropdown-menu>li,.navbar-nav>.tasks-menu>.dropdown-menu>li{position:relative}.navbar-nav>.notifications-menu>.dropdown-menu>li.header,.navbar-nav>.messages-menu>.dropdown-menu>li.header,.navbar-nav>.tasks-menu>.dropdown-menu>li.header{border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0;background-color:#ffffff;padding:7px 10px;border-bottom:1px solid #f4f4f4;color:#444444;font-size:14px}.navbar-nav>.notifications-menu>.dropdown-menu>li.footer>a,.navbar-nav>.messages-menu>.dropdown-menu>li.footer>a,.navbar-nav>.tasks-menu>.dropdown-menu>li.footer>a{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px;font-size:12px;background-color:#fff;padding:7px 10px;border-bottom:1px solid #eeeeee;color:#444 !important;text-align:center}@media (max-width:991px){.navbar-nav>.notifications-menu>.dropdown-menu>li.footer>a,.navbar-nav>.messages-menu>.dropdown-menu>li.footer>a,.navbar-nav>.tasks-menu>.dropdown-menu>li.footer>a{background:#fff !important;color:#444 !important}}.navbar-nav>.notifications-menu>.dropdown-menu>li.footer>a:hover,.navbar-nav>.messages-menu>.dropdown-menu>li.footer>a:hover,.navbar-nav>.tasks-menu>.dropdown-menu>li.footer>a:hover{text-decoration:none;font-weight:normal}.navbar-nav>.notifications-menu>.dropdown-menu>li .menu,.navbar-nav>.messages-menu>.dropdown-menu>li .menu,.navbar-nav>.tasks-menu>.dropdown-menu>li .menu{max-height:200px;margin:0;padding:0;list-style:none;overflow-x:hidden}.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a,.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a,.navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a{display:block;white-space:nowrap;border-bottom:1px solid #f4f4f4}.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a:hover,.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a:hover,.navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a:hover{background:#f4f4f4;text-decoration:none}.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a{color:#444444;overflow:hidden;text-overflow:ellipsis;padding:10px}.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a>.glyphicon,.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a>.fa,.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a>.ion{width:20px}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a{margin:0;padding:10px 10px}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a>div>img{margin:auto 10px auto auto;width:40px;height:40px}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a>h4{padding:0;margin:0 0 0 45px;color:#444444;font-size:15px;position:relative}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a>h4>small{color:#999999;font-size:10px;position:absolute;top:0;right:0}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a>p{margin:0 0 0 45px;font-size:12px;color:#888888}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a:before,.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a:after{content:" ";display:table}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a:after{clear:both}.navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a{padding:10px}.navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a>h3{font-size:14px;padding:0;margin:0 0 10px 0;color:#666666}.navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a>.progress{padding:0;margin:0}.navbar-nav>.user-menu>.dropdown-menu{border-top-right-radius:0;border-top-left-radius:0;padding:1px 0 0 0;border-top-width:0;width:280px}.navbar-nav>.user-menu>.dropdown-menu,.navbar-nav>.user-menu>.dropdown-menu>.user-body{border-bottom-right-radius:4px;border-bottom-left-radius:4px}.navbar-nav>.user-menu>.dropdown-menu>li.user-header{height:175px;padding:10px;text-align:center}.navbar-nav>.user-menu>.dropdown-menu>li.user-header>img{z-index:5;height:90px;width:90px;border:3px solid;border-color:transparent;border-color:rgba(255,255,255,0.2)}.navbar-nav>.user-menu>.dropdown-menu>li.user-header>p{z-index:5;color:#fff;color:rgba(255,255,255,0.8);font-size:17px;margin-top:10px}.navbar-nav>.user-menu>.dropdown-menu>li.user-header>p>small{display:block;font-size:12px}.navbar-nav>.user-menu>.dropdown-menu>.user-body{padding:15px;border-bottom:1px solid #f4f4f4;border-top:1px solid #dddddd}.navbar-nav>.user-menu>.dropdown-menu>.user-body:before,.navbar-nav>.user-menu>.dropdown-menu>.user-body:after{content:" ";display:table}.navbar-nav>.user-menu>.dropdown-menu>.user-body:after{clear:both}.navbar-nav>.user-menu>.dropdown-menu>.user-body a{color:#444 !important}@media (max-width:991px){.navbar-nav>.user-menu>.dropdown-menu>.user-body a{background:#fff !important;color:#444 !important}}.navbar-nav>.user-menu>.dropdown-menu>.user-footer{background-color:#f9f9f9;padding:10px}.navbar-nav>.user-menu>.dropdown-menu>.user-footer:before,.navbar-nav>.user-menu>.dropdown-menu>.user-footer:after{content:" ";display:table}.navbar-nav>.user-menu>.dropdown-menu>.user-footer:after{clear:both}.navbar-nav>.user-menu>.dropdown-menu>.user-footer .btn-default{color:#666666}@media (max-width:991px){.navbar-nav>.user-menu>.dropdown-menu>.user-footer .btn-default:hover{background-color:#f9f9f9}}.navbar-nav>.user-menu .user-image{float:left;width:25px;height:25px;border-radius:50%;margin-right:10px;margin-top:-2px}@media (max-width:767px){.navbar-nav>.user-menu .user-image{float:none;margin-right:0;margin-top:-8px;line-height:10px}}.open:not(.dropup)>.animated-dropdown-menu{backface-visibility:visible !important;-webkit-animation:flipInX .7s both;-o-animation:flipInX .7s both;animation:flipInX .7s both}@keyframes flipInX{0%{transform:perspective(400px) rotate3d(1, 0, 0, 90deg);transition-timing-function:ease-in;opacity:0}40%{transform:perspective(400px) rotate3d(1, 0, 0, -20deg);transition-timing-function:ease-in}60%{transform:perspective(400px) rotate3d(1, 0, 0, 10deg);opacity:1}80%{transform:perspective(400px) rotate3d(1, 0, 0, -5deg)}100%{transform:perspective(400px)}}@-webkit-keyframes flipInX{0%{-webkit-transform:perspective(400px) rotate3d(1, 0, 0, 90deg);-webkit-transition-timing-function:ease-in;opacity:0}40%{-webkit-transform:perspective(400px) rotate3d(1, 0, 0, -20deg);-webkit-transition-timing-function:ease-in}60%{-webkit-transform:perspective(400px) rotate3d(1, 0, 0, 10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotate3d(1, 0, 0, -5deg)}100%{-webkit-transform:perspective(400px)}}.navbar-custom-menu>.navbar-nav>li{position:relative}.navbar-custom-menu>.navbar-nav>li>.dropdown-menu{position:absolute;right:0;left:auto}@media (max-width:991px){.navbar-custom-menu>.navbar-nav{float:right}.navbar-custom-menu>.navbar-nav>li{position:static}.navbar-custom-menu>.navbar-nav>li>.dropdown-menu{position:absolute;right:5%;left:auto;border:1px solid #ddd;background:#fff}}.form-control{border-radius:0;box-shadow:none;border-color:#d2d6de}.form-control:focus{border-color:#3c8dbc;box-shadow:none}.form-control::-moz-placeholder,.form-control:-ms-input-placeholder,.form-control::-webkit-input-placeholder{color:#bbb;opacity:1}.form-control:not(select){-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-group.has-success label{color:#00a65a}.form-group.has-success .form-control,.form-group.has-success .input-group-addon{border-color:#00a65a;box-shadow:none}.form-group.has-success .help-block{color:#00a65a}.form-group.has-warning label{color:#f39c12}.form-group.has-warning .form-control,.form-group.has-warning .input-group-addon{border-color:#f39c12;box-shadow:none}.form-group.has-warning .help-block{color:#f39c12}.form-group.has-error label{color:#dd4b39}.form-group.has-error .form-control,.form-group.has-error .input-group-addon{border-color:#dd4b39;box-shadow:none}.form-group.has-error .help-block{color:#dd4b39}.input-group .input-group-addon{border-radius:0;border-color:#d2d6de;background-color:#fff}.btn-group-vertical .btn.btn-flat:first-of-type,.btn-group-vertical .btn.btn-flat:last-of-type{border-radius:0}.icheck>label{padding-left:0}.form-control-feedback.fa{line-height:34px}.input-lg+.form-control-feedback.fa,.input-group-lg+.form-control-feedback.fa,.form-group-lg .form-control+.form-control-feedback.fa{line-height:46px}.input-sm+.form-control-feedback.fa,.input-group-sm+.form-control-feedback.fa,.form-group-sm .form-control+.form-control-feedback.fa{line-height:30px}.progress,.progress>.progress-bar{-webkit-box-shadow:none;box-shadow:none}.progress,.progress>.progress-bar,.progress .progress-bar,.progress>.progress-bar .progress-bar{border-radius:1px}.progress.sm,.progress-sm{height:10px}.progress.sm,.progress-sm,.progress.sm .progress-bar,.progress-sm .progress-bar{border-radius:1px}.progress.xs,.progress-xs{height:7px}.progress.xs,.progress-xs,.progress.xs .progress-bar,.progress-xs .progress-bar{border-radius:1px}.progress.xxs,.progress-xxs{height:3px}.progress.xxs,.progress-xxs,.progress.xxs .progress-bar,.progress-xxs .progress-bar{border-radius:1px}.progress.vertical{position:relative;width:30px;height:200px;display:inline-block;margin-right:10px}.progress.vertical>.progress-bar{width:100%;position:absolute;bottom:0}.progress.vertical.sm,.progress.vertical.progress-sm{width:20px}.progress.vertical.xs,.progress.vertical.progress-xs{width:10px}.progress.vertical.xxs,.progress.vertical.progress-xxs{width:3px}.progress-group .progress-text{font-weight:600}.progress-group .progress-number{float:right}.table tr>td .progress{margin:0}.progress-bar-light-blue,.progress-bar-primary{background-color:#3c8dbc}.progress-striped .progress-bar-light-blue,.progress-striped .progress-bar-primary{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-green,.progress-bar-success{background-color:#00a65a}.progress-striped .progress-bar-green,.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-aqua,.progress-bar-info{background-color:#00c0ef}.progress-striped .progress-bar-aqua,.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-yellow,.progress-bar-warning{background-color:#f39c12}.progress-striped .progress-bar-yellow,.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-red,.progress-bar-danger{background-color:#dd4b39}.progress-striped .progress-bar-red,.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.small-box{border-radius:2px;position:relative;display:block;margin-bottom:20px;box-shadow:0 1px 1px rgba(0,0,0,0.1)}.small-box>.inner{padding:10px}.small-box>.small-box-footer{position:relative;text-align:center;padding:3px 0;color:#fff;color:rgba(255,255,255,0.8);display:block;z-index:10;background:rgba(0,0,0,0.1);text-decoration:none}.small-box>.small-box-footer:hover{color:#fff;background:rgba(0,0,0,0.15)}.small-box h3{font-size:38px;font-weight:bold;margin:0 0 10px 0;white-space:nowrap;padding:0}.small-box p{font-size:15px}.small-box p>small{display:block;color:#f9f9f9;font-size:13px;margin-top:5px}.small-box h3,.small-box p{z-index:5}.small-box .icon{-webkit-transition:all .3s linear;-o-transition:all .3s linear;transition:all .3s linear;position:absolute;top:-10px;right:10px;z-index:0;font-size:90px;color:rgba(0,0,0,0.15)}.small-box:hover{text-decoration:none;color:#f9f9f9}.small-box:hover .icon{font-size:95px}@media (max-width:767px){.small-box{text-align:center}.small-box .icon{display:none}.small-box p{font-size:12px}}.box{position:relative;border-radius:3px;background:#ffffff;border-top:3px solid #d2d6de;margin-bottom:20px;width:100%;box-shadow:0 1px 1px rgba(0,0,0,0.1)}.box.box-primary{border-top-color:#3c8dbc}.box.box-info{border-top-color:#00c0ef}.box.box-danger{border-top-color:#dd4b39}.box.box-warning{border-top-color:#f39c12}.box.box-success{border-top-color:#00a65a}.box.box-default{border-top-color:#d2d6de}.box.collapsed-box .box-body,.box.collapsed-box .box-footer{display:none}.box .nav-stacked>li{border-bottom:1px solid #f4f4f4;margin:0}.box .nav-stacked>li:last-of-type{border-bottom:none}.box.height-control .box-body{max-height:300px;overflow:auto}.box .border-right{border-right:1px solid #f4f4f4}.box .border-left{border-left:1px solid #f4f4f4}.box.box-solid{border-top:0}.box.box-solid>.box-header .btn.btn-default{background:transparent}.box.box-solid>.box-header .btn:hover,.box.box-solid>.box-header a:hover{background:rgba(0,0,0,0.1)}.box.box-solid.box-default{border:1px solid #d2d6de}.box.box-solid.box-default>.box-header{color:#444;background:#d2d6de;background-color:#d2d6de}.box.box-solid.box-default>.box-header a,.box.box-solid.box-default>.box-header .btn{color:#444}.box.box-solid.box-primary{border:1px solid #3c8dbc}.box.box-solid.box-primary>.box-header{color:#fff;background:#3c8dbc;background-color:#3c8dbc}.box.box-solid.box-primary>.box-header a,.box.box-solid.box-primary>.box-header .btn{color:#fff}.box.box-solid.box-info{border:1px solid #00c0ef}.box.box-solid.box-info>.box-header{color:#fff;background:#00c0ef;background-color:#00c0ef}.box.box-solid.box-info>.box-header a,.box.box-solid.box-info>.box-header .btn{color:#fff}.box.box-solid.box-danger{border:1px solid #dd4b39}.box.box-solid.box-danger>.box-header{color:#fff;background:#dd4b39;background-color:#dd4b39}.box.box-solid.box-danger>.box-header a,.box.box-solid.box-danger>.box-header .btn{color:#fff}.box.box-solid.box-warning{border:1px solid #f39c12}.box.box-solid.box-warning>.box-header{color:#fff;background:#f39c12;background-color:#f39c12}.box.box-solid.box-warning>.box-header a,.box.box-solid.box-warning>.box-header .btn{color:#fff}.box.box-solid.box-success{border:1px solid #00a65a}.box.box-solid.box-success>.box-header{color:#fff;background:#00a65a;background-color:#00a65a}.box.box-solid.box-success>.box-header a,.box.box-solid.box-success>.box-header .btn{color:#fff}.box.box-solid>.box-header>.box-tools .btn{border:0;box-shadow:none}.box.box-solid[class*='bg']>.box-header{color:#fff}.box .box-group>.box{margin-bottom:5px}.box .knob-label{text-align:center;color:#333;font-weight:100;font-size:12px;margin-bottom:0.3em}.box>.overlay,.overlay-wrapper>.overlay,.box>.loading-img,.overlay-wrapper>.loading-img{position:absolute;top:0;left:0;width:100%;height:100%}.box .overlay,.overlay-wrapper .overlay{z-index:50;background:rgba(255,255,255,0.7);border-radius:3px}.box .overlay>.fa,.overlay-wrapper .overlay>.fa{position:absolute;top:50%;left:50%;margin-left:-15px;margin-top:-15px;color:#000;font-size:30px}.box .overlay.dark,.overlay-wrapper .overlay.dark{background:rgba(0,0,0,0.5)}.box-header:before,.box-body:before,.box-footer:before,.box-header:after,.box-body:after,.box-footer:after{content:" ";display:table}.box-header:after,.box-body:after,.box-footer:after{clear:both}.box-header{color:#444;display:block;padding:10px;position:relative}.box-header.with-border{border-bottom:1px solid #f4f4f4}.collapsed-box .box-header.with-border{border-bottom:none}.box-header>.fa,.box-header>.glyphicon,.box-header>.ion,.box-header .box-title{display:inline-block;font-size:18px;margin:0;line-height:1}.box-header>.fa,.box-header>.glyphicon,.box-header>.ion{margin-right:5px}.box-header>.box-tools{position:absolute;right:10px;top:5px}.box-header>.box-tools [data-toggle="tooltip"]{position:relative}.box-header>.box-tools.pull-right .dropdown-menu{right:0;left:auto}.box-header>.box-tools .dropdown-menu>li>a{color:#444!important}.btn-box-tool{padding:5px;font-size:12px;background:transparent;color:#97a0b3}.open .btn-box-tool,.btn-box-tool:hover{color:#606c84}.btn-box-tool.btn:active{box-shadow:none}.box-body{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px;padding:10px}.no-header .box-body{border-top-right-radius:3px;border-top-left-radius:3px}.box-body>.table{margin-bottom:0}.box-body .fc{margin-top:5px}.box-body .full-width-chart{margin:-19px}.box-body.no-padding .full-width-chart{margin:-9px}.box-body .box-pane{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:3px}.box-body .box-pane-right{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:3px;border-bottom-left-radius:0}.box-footer{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px;border-top:1px solid #f4f4f4;padding:10px;background-color:#fff}.chart-legend{margin:10px 0}@media (max-width:991px){.chart-legend>li{float:left;margin-right:10px}}.box-comments{background:#f7f7f7}.box-comments .box-comment{padding:8px 0;border-bottom:1px solid #eee}.box-comments .box-comment:before,.box-comments .box-comment:after{content:" ";display:table}.box-comments .box-comment:after{clear:both}.box-comments .box-comment:last-of-type{border-bottom:0}.box-comments .box-comment:first-of-type{padding-top:0}.box-comments .box-comment img{float:left}.box-comments .comment-text{margin-left:40px;color:#555}.box-comments .username{color:#444;display:block;font-weight:600}.box-comments .text-muted{font-weight:400;font-size:12px}.todo-list{margin:0;padding:0;list-style:none;overflow:auto}.todo-list>li{border-radius:2px;padding:10px;background:#f4f4f4;margin-bottom:2px;border-left:2px solid #e6e7e8;color:#444}.todo-list>li:last-of-type{margin-bottom:0}.todo-list>li>input[type='checkbox']{margin:0 10px 0 5px}.todo-list>li .text{display:inline-block;margin-left:5px;font-weight:600}.todo-list>li .label{margin-left:10px;font-size:9px}.todo-list>li .tools{display:none;float:right;color:#dd4b39}.todo-list>li .tools>.fa,.todo-list>li .tools>.glyphicon,.todo-list>li .tools>.ion{margin-right:5px;cursor:pointer}.todo-list>li:hover .tools{display:inline-block}.todo-list>li.done{color:#999}.todo-list>li.done .text{text-decoration:line-through;font-weight:500}.todo-list>li.done .label{background:#d2d6de !important}.todo-list .danger{border-left-color:#dd4b39}.todo-list .warning{border-left-color:#f39c12}.todo-list .info{border-left-color:#00c0ef}.todo-list .success{border-left-color:#00a65a}.todo-list .primary{border-left-color:#3c8dbc}.todo-list .handle{display:inline-block;cursor:move;margin:0 5px}.chat{padding:5px 20px 5px 10px}.chat .item{margin-bottom:10px}.chat .item:before,.chat .item:after{content:" ";display:table}.chat .item:after{clear:both}.chat .item>img{width:40px;height:40px;border:2px solid transparent;border-radius:50%}.chat .item>.online{border:2px solid #00a65a}.chat .item>.offline{border:2px solid #dd4b39}.chat .item>.message{margin-left:55px;margin-top:-40px}.chat .item>.message>.name{display:block;font-weight:600}.chat .item>.attachment{border-radius:3px;background:#f4f4f4;margin-left:65px;margin-right:15px;padding:10px}.chat .item>.attachment>h4{margin:0 0 5px 0;font-weight:600;font-size:14px}.chat .item>.attachment>p,.chat .item>.attachment>.filename{font-weight:600;font-size:13px;font-style:italic;margin:0}.chat .item>.attachment:before,.chat .item>.attachment:after{content:" ";display:table}.chat .item>.attachment:after{clear:both}.box-input{max-width:200px}.modal .panel-body{color:#444}.info-box{display:block;min-height:90px;background:#fff;width:100%;box-shadow:0 1px 1px rgba(0,0,0,0.1);border-radius:2px;margin-bottom:15px}.info-box small{font-size:14px}.info-box .progress{background:rgba(0,0,0,0.2);margin:5px -10px 5px -10px;height:2px}.info-box .progress,.info-box .progress .progress-bar{border-radius:0}.info-box .progress .progress-bar{background:#fff}.info-box-icon{border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px;display:block;float:left;height:90px;width:90px;text-align:center;font-size:45px;line-height:90px;background:rgba(0,0,0,0.2)}.info-box-icon>img{max-width:100%}.info-box-content{padding:5px 10px;margin-left:90px}.info-box-number{display:block;font-weight:bold;font-size:18px}.progress-description,.info-box-text{display:block;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.info-box-text{text-transform:uppercase}.info-box-more{display:block}.progress-description{margin:0}.timeline{position:relative;margin:0 0 30px 0;padding:0;list-style:none}.timeline:before{content:'';position:absolute;top:0;bottom:0;width:4px;background:#ddd;left:31px;margin:0;border-radius:2px}.timeline>li{position:relative;margin-right:10px;margin-bottom:15px}.timeline>li:before,.timeline>li:after{content:" ";display:table}.timeline>li:after{clear:both}.timeline>li>.timeline-item{-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.1);box-shadow:0 1px 1px rgba(0,0,0,0.1);border-radius:3px;margin-top:0;background:#fff;color:#444;margin-left:60px;margin-right:15px;padding:0;position:relative}.timeline>li>.timeline-item>.time{color:#999;float:right;padding:10px;font-size:12px}.timeline>li>.timeline-item>.timeline-header{margin:0;color:#555;border-bottom:1px solid #f4f4f4;padding:10px;font-size:16px;line-height:1.1}.timeline>li>.timeline-item>.timeline-header>a{font-weight:600}.timeline>li>.timeline-item>.timeline-body,.timeline>li>.timeline-item>.timeline-footer{padding:10px}.timeline>li>.fa,.timeline>li>.glyphicon,.timeline>li>.ion{width:30px;height:30px;font-size:15px;line-height:30px;position:absolute;color:#666;background:#d2d6de;border-radius:50%;text-align:center;left:18px;top:0}.timeline>.time-label>span{font-weight:600;padding:5px;display:inline-block;background-color:#fff;border-radius:4px}.timeline-inverse>li>.timeline-item{background:#f0f0f0;border:1px solid #ddd;-webkit-box-shadow:none;box-shadow:none}.timeline-inverse>li>.timeline-item>.timeline-header{border-bottom-color:#ddd}.btn{border-radius:3px;-webkit-box-shadow:none;box-shadow:none;border:1px solid transparent}.btn.uppercase{text-transform:uppercase}.btn.btn-flat{border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;border-width:1px}.btn:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn:focus{outline:none}.btn.btn-file{position:relative;overflow:hidden}.btn.btn-file>input[type='file']{position:absolute;top:0;right:0;min-width:100%;min-height:100%;font-size:100px;text-align:right;opacity:0;filter:alpha(opacity=0);outline:none;background:white;cursor:inherit;display:block}.btn-default{background-color:#f4f4f4;color:#444;border-color:#ddd}.btn-default:hover,.btn-default:active,.btn-default.hover{background-color:#e7e7e7}.btn-primary{background-color:#3c8dbc;border-color:#367fa9}.btn-primary:hover,.btn-primary:active,.btn-primary.hover{background-color:#367fa9}.btn-success{background-color:#00a65a;border-color:#008d4c}.btn-success:hover,.btn-success:active,.btn-success.hover{background-color:#008d4c}.btn-info{background-color:#00c0ef;border-color:#00acd6}.btn-info:hover,.btn-info:active,.btn-info.hover{background-color:#00acd6}.btn-danger{background-color:#dd4b39;border-color:#d73925}.btn-danger:hover,.btn-danger:active,.btn-danger.hover{background-color:#d73925}.btn-warning{background-color:#f39c12;border-color:#e08e0b}.btn-warning:hover,.btn-warning:active,.btn-warning.hover{background-color:#e08e0b}.btn-outline{border:1px solid #fff;background:transparent;color:#fff}.btn-outline:hover,.btn-outline:focus,.btn-outline:active{color:rgba(255,255,255,0.7);border-color:rgba(255,255,255,0.7)}.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn[class*='bg-']:hover{-webkit-box-shadow:inset 0 0 100px rgba(0,0,0,0.2);box-shadow:inset 0 0 100px rgba(0,0,0,0.2)}.btn-app{border-radius:3px;position:relative;padding:15px 5px;margin:0 0 10px 10px;min-width:80px;height:60px;text-align:center;color:#666;border:1px solid #ddd;background-color:#f4f4f4;font-size:12px}.btn-app>.fa,.btn-app>.glyphicon,.btn-app>.ion{font-size:20px;display:block}.btn-app:hover{background:#f4f4f4;color:#444;border-color:#aaa}.btn-app:active,.btn-app:focus{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn-app>.badge{position:absolute;top:-3px;right:-10px;font-size:10px;font-weight:400}.callout{border-radius:3px;margin:0 0 20px 0;padding:15px 30px 15px 15px;border-left:5px solid #eee}.callout a{color:#fff;text-decoration:underline}.callout a:hover{color:#eee}.callout h4{margin-top:0;font-weight:600}.callout p:last-child{margin-bottom:0}.callout code,.callout .highlight{background-color:#fff}.callout.callout-danger{border-color:#c23321}.callout.callout-warning{border-color:#c87f0a}.callout.callout-info{border-color:#0097bc}.callout.callout-success{border-color:#00733e}.alert{border-radius:3px}.alert h4{font-weight:600}.alert .icon{margin-right:10px}.alert .close{color:#000;opacity:.2;filter:alpha(opacity=20)}.alert .close:hover{opacity:.5;filter:alpha(opacity=50)}.alert a{color:#fff;text-decoration:underline}.alert-success{border-color:#008d4c}.alert-danger,.alert-error{border-color:#d73925}.alert-warning{border-color:#e08e0b}.alert-info{border-color:#00acd6}.nav>li>a:hover,.nav>li>a:active,.nav>li>a:focus{color:#444;background:#f7f7f7}.nav-pills>li>a{border-radius:0;border-top:3px solid transparent;color:#444}.nav-pills>li>a>.fa,.nav-pills>li>a>.glyphicon,.nav-pills>li>a>.ion{margin-right:5px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{border-top-color:#3c8dbc}.nav-pills>li.active>a{font-weight:600}.nav-stacked>li>a{border-radius:0;border-top:0;border-left:3px solid transparent;color:#444}.nav-stacked>li.active>a,.nav-stacked>li.active>a:hover{background:transparent;color:#444;border-top:0;border-left-color:#3c8dbc}.nav-stacked>li.header{border-bottom:1px solid #ddd;color:#777;margin-bottom:10px;padding:5px 10px;text-transform:uppercase}.nav-tabs-custom{margin-bottom:20px;background:#fff;box-shadow:0 1px 1px rgba(0,0,0,0.1);border-radius:3px}.nav-tabs-custom>.nav-tabs{margin:0;border-bottom-color:#f4f4f4;border-top-right-radius:3px;border-top-left-radius:3px}.nav-tabs-custom>.nav-tabs>li{border-top:3px solid transparent;margin-bottom:-2px;margin-right:5px}.nav-tabs-custom>.nav-tabs>li>a{color:#444;border-radius:0}.nav-tabs-custom>.nav-tabs>li>a.text-muted{color:#999}.nav-tabs-custom>.nav-tabs>li>a,.nav-tabs-custom>.nav-tabs>li>a:hover{background:transparent;margin:0}.nav-tabs-custom>.nav-tabs>li>a:hover{color:#999}.nav-tabs-custom>.nav-tabs>li:not(.active)>a:hover,.nav-tabs-custom>.nav-tabs>li:not(.active)>a:focus,.nav-tabs-custom>.nav-tabs>li:not(.active)>a:active{border-color:transparent}.nav-tabs-custom>.nav-tabs>li.active{border-top-color:#3c8dbc}.nav-tabs-custom>.nav-tabs>li.active>a,.nav-tabs-custom>.nav-tabs>li.active:hover>a{background-color:#fff;color:#444}.nav-tabs-custom>.nav-tabs>li.active>a{border-top-color:transparent;border-left-color:#f4f4f4;border-right-color:#f4f4f4}.nav-tabs-custom>.nav-tabs>li:first-of-type{margin-left:0}.nav-tabs-custom>.nav-tabs>li:first-of-type.active>a{border-left-color:transparent}.nav-tabs-custom>.nav-tabs.pull-right{float:none !important}.nav-tabs-custom>.nav-tabs.pull-right>li{float:right}.nav-tabs-custom>.nav-tabs.pull-right>li:first-of-type{margin-right:0}.nav-tabs-custom>.nav-tabs.pull-right>li:first-of-type>a{border-left-width:1px}.nav-tabs-custom>.nav-tabs.pull-right>li:first-of-type.active>a{border-left-color:#f4f4f4;border-right-color:transparent}.nav-tabs-custom>.nav-tabs>li.header{line-height:35px;padding:0 10px;font-size:20px;color:#444}.nav-tabs-custom>.nav-tabs>li.header>.fa,.nav-tabs-custom>.nav-tabs>li.header>.glyphicon,.nav-tabs-custom>.nav-tabs>li.header>.ion{margin-right:5px}.nav-tabs-custom>.tab-content{background:#fff;padding:10px;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.nav-tabs-custom .dropdown.open>a:active,.nav-tabs-custom .dropdown.open>a:focus{background:transparent;color:#999}.nav-tabs-custom.tab-primary>.nav-tabs>li.active{border-top-color:#3c8dbc}.nav-tabs-custom.tab-info>.nav-tabs>li.active{border-top-color:#00c0ef}.nav-tabs-custom.tab-danger>.nav-tabs>li.active{border-top-color:#dd4b39}.nav-tabs-custom.tab-warning>.nav-tabs>li.active{border-top-color:#f39c12}.nav-tabs-custom.tab-success>.nav-tabs>li.active{border-top-color:#00a65a}.nav-tabs-custom.tab-default>.nav-tabs>li.active{border-top-color:#d2d6de}.pagination>li>a{background:#fafafa;color:#666}.pagination.pagination-flat>li>a{border-radius:0 !important}.products-list{list-style:none;margin:0;padding:0}.products-list>.item{border-radius:3px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.1);box-shadow:0 1px 1px rgba(0,0,0,0.1);padding:10px 0;background:#fff}.products-list>.item:before,.products-list>.item:after{content:" ";display:table}.products-list>.item:after{clear:both}.products-list .product-img{float:left}.products-list .product-img img{width:50px;height:50px}.products-list .product-info{margin-left:60px}.products-list .product-title{font-weight:600}.products-list .product-description{display:block;color:#999;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.product-list-in-box>.item{-webkit-box-shadow:none;box-shadow:none;border-radius:0;border-bottom:1px solid #f4f4f4}.product-list-in-box>.item:last-of-type{border-bottom-width:0}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{border-top:1px solid #f4f4f4}.table>thead>tr>th{border-bottom:2px solid #f4f4f4}.table tr td .progress{margin-top:5px}.table-bordered{border:1px solid #f4f4f4}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #f4f4f4}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table.no-border,.table.no-border td,.table.no-border th{border:0}table.text-center,table.text-center td,table.text-center th{text-align:center}.table.align th{text-align:left}.table.align td{text-align:right}.label-default{background-color:#d2d6de;color:#444}.direct-chat .box-body{border-bottom-right-radius:0;border-bottom-left-radius:0;position:relative;overflow-x:hidden;padding:0}.direct-chat.chat-pane-open .direct-chat-contacts{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0)}.direct-chat-messages{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0);padding:10px;height:250px;overflow:auto}.direct-chat-msg,.direct-chat-text{display:block}.direct-chat-msg{margin-bottom:10px}.direct-chat-msg:before,.direct-chat-msg:after{content:" ";display:table}.direct-chat-msg:after{clear:both}.direct-chat-messages,.direct-chat-contacts{-webkit-transition:-webkit-transform .5s ease-in-out;-moz-transition:-moz-transform .5s ease-in-out;-o-transition:-o-transform .5s ease-in-out;transition:transform .5s ease-in-out}.direct-chat-text{border-radius:5px;position:relative;padding:5px 10px;background:#d2d6de;border:1px solid #d2d6de;margin:5px 0 0 50px;color:#444}.direct-chat-text:after,.direct-chat-text:before{position:absolute;right:100%;top:15px;border:solid transparent;border-right-color:#d2d6de;content:' ';height:0;width:0;pointer-events:none}.direct-chat-text:after{border-width:5px;margin-top:-5px}.direct-chat-text:before{border-width:6px;margin-top:-6px}.right .direct-chat-text{margin-right:50px;margin-left:0}.right .direct-chat-text:after,.right .direct-chat-text:before{right:auto;left:100%;border-right-color:transparent;border-left-color:#d2d6de}.direct-chat-img{border-radius:50%;float:left;width:40px;height:40px}.right .direct-chat-img{float:right}.direct-chat-info{display:block;margin-bottom:2px;font-size:12px}.direct-chat-name{font-weight:600}.direct-chat-timestamp{color:#999}.direct-chat-contacts-open .direct-chat-contacts{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0)}.direct-chat-contacts{-webkit-transform:translate(101%, 0);-ms-transform:translate(101%, 0);-o-transform:translate(101%, 0);transform:translate(101%, 0);position:absolute;top:0;bottom:0;height:250px;width:100%;background:#222d32;color:#fff;overflow:auto}.contacts-list>li{border-bottom:1px solid rgba(0,0,0,0.2);padding:10px;margin:0}.contacts-list>li:before,.contacts-list>li:after{content:" ";display:table}.contacts-list>li:after{clear:both}.contacts-list>li:last-of-type{border-bottom:none}.contacts-list-img{border-radius:50%;width:40px;float:left}.contacts-list-info{margin-left:45px;color:#fff}.contacts-list-name,.contacts-list-status{display:block}.contacts-list-name{font-weight:600}.contacts-list-status{font-size:12px}.contacts-list-date{color:#aaa;font-weight:normal}.contacts-list-msg{color:#999}.direct-chat-danger .right>.direct-chat-text{background:#dd4b39;border-color:#dd4b39;color:#fff}.direct-chat-danger .right>.direct-chat-text:after,.direct-chat-danger .right>.direct-chat-text:before{border-left-color:#dd4b39}.direct-chat-primary .right>.direct-chat-text{background:#3c8dbc;border-color:#3c8dbc;color:#fff}.direct-chat-primary .right>.direct-chat-text:after,.direct-chat-primary .right>.direct-chat-text:before{border-left-color:#3c8dbc}.direct-chat-warning .right>.direct-chat-text{background:#f39c12;border-color:#f39c12;color:#fff}.direct-chat-warning .right>.direct-chat-text:after,.direct-chat-warning .right>.direct-chat-text:before{border-left-color:#f39c12}.direct-chat-info .right>.direct-chat-text{background:#00c0ef;border-color:#00c0ef;color:#fff}.direct-chat-info .right>.direct-chat-text:after,.direct-chat-info .right>.direct-chat-text:before{border-left-color:#00c0ef}.direct-chat-success .right>.direct-chat-text{background:#00a65a;border-color:#00a65a;color:#fff}.direct-chat-success .right>.direct-chat-text:after,.direct-chat-success .right>.direct-chat-text:before{border-left-color:#00a65a}.users-list>li{width:25%;float:left;padding:10px;text-align:center}.users-list>li img{border-radius:50%;max-width:100%;height:auto}.users-list>li>a:hover,.users-list>li>a:hover .users-list-name{color:#999}.users-list-name,.users-list-date{display:block}.users-list-name{font-weight:600;color:#444;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.users-list-date{color:#999;font-size:12px}.carousel-control.left,.carousel-control.right{background-image:none}.carousel-control>.fa{font-size:40px;position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-20px}.modal{background:rgba(0,0,0,0.3)}.modal-content{border-radius:0;-webkit-box-shadow:0 2px 3px rgba(0,0,0,0.125);box-shadow:0 2px 3px rgba(0,0,0,0.125);border:0}@media (min-width:768px){.modal-content{-webkit-box-shadow:0 2px 3px rgba(0,0,0,0.125);box-shadow:0 2px 3px rgba(0,0,0,0.125)}}.modal-header{border-bottom-color:#f4f4f4}.modal-footer{border-top-color:#f4f4f4}.modal-primary .modal-header,.modal-primary .modal-footer{border-color:#307095}.modal-warning .modal-header,.modal-warning .modal-footer{border-color:#c87f0a}.modal-info .modal-header,.modal-info .modal-footer{border-color:#0097bc}.modal-success .modal-header,.modal-success .modal-footer{border-color:#00733e}.modal-danger .modal-header,.modal-danger .modal-footer{border-color:#c23321}.box-widget{border:none;position:relative}.widget-user .widget-user-header{padding:20px;height:120px;border-top-right-radius:3px;border-top-left-radius:3px}.widget-user .widget-user-username{margin-top:0;margin-bottom:5px;font-size:25px;font-weight:300;text-shadow:0 1px 1px rgba(0,0,0,0.2)}.widget-user .widget-user-desc{margin-top:0}.widget-user .widget-user-image{position:absolute;top:65px;left:50%;margin-left:-45px}.widget-user .widget-user-image>img{width:90px;height:auto;border:3px solid #fff}.widget-user .box-footer{padding-top:30px}.widget-user-2 .widget-user-header{padding:20px;border-top-right-radius:3px;border-top-left-radius:3px}.widget-user-2 .widget-user-username{margin-top:5px;margin-bottom:5px;font-size:25px;font-weight:300}.widget-user-2 .widget-user-desc{margin-top:0}.widget-user-2 .widget-user-username,.widget-user-2 .widget-user-desc{margin-left:75px}.widget-user-2 .widget-user-image>img{width:65px;height:auto;float:left}.mailbox-messages>.table{margin:0}.mailbox-controls{padding:5px}.mailbox-controls.with-border{border-bottom:1px solid #f4f4f4}.mailbox-read-info{border-bottom:1px solid #f4f4f4;padding:10px}.mailbox-read-info h3{font-size:20px;margin:0}.mailbox-read-info h5{margin:0;padding:5px 0 0 0}.mailbox-read-time{color:#999;font-size:13px}.mailbox-read-message{padding:10px}.mailbox-attachments li{float:left;width:200px;border:1px solid #eee;margin-bottom:10px;margin-right:10px}.mailbox-attachment-name{font-weight:bold;color:#666}.mailbox-attachment-icon,.mailbox-attachment-info,.mailbox-attachment-size{display:block}.mailbox-attachment-info{padding:10px;background:#f4f4f4}.mailbox-attachment-size{color:#999;font-size:12px}.mailbox-attachment-icon{text-align:center;font-size:65px;color:#666;padding:20px 10px}.mailbox-attachment-icon.has-img{padding:0}.mailbox-attachment-icon.has-img>img{max-width:100%;height:auto}.lockscreen{background:#d2d6de}.lockscreen-logo{font-size:35px;text-align:center;margin-bottom:25px;font-weight:300}.lockscreen-logo a{color:#444}.lockscreen-wrapper{max-width:400px;margin:0 auto;margin-top:10%}.lockscreen .lockscreen-name{text-align:center;font-weight:600}.lockscreen-item{border-radius:4px;padding:0;background:#fff;position:relative;margin:10px auto 30px auto;width:290px}.lockscreen-image{border-radius:50%;position:absolute;left:-10px;top:-25px;background:#fff;padding:5px;z-index:10}.lockscreen-image>img{border-radius:50%;width:70px;height:70px}.lockscreen-credentials{margin-left:70px}.lockscreen-credentials .form-control{border:0}.lockscreen-credentials .btn{background-color:#fff;border:0;padding:0 10px}.lockscreen-footer{margin-top:10px}.login-logo,.register-logo{font-size:35px;text-align:center;margin-bottom:25px;font-weight:300}.login-logo a,.register-logo a{color:#444}.login-page,.register-page{background:#d2d6de}.login-box,.register-box{width:360px;margin:7% auto}@media (max-width:768px){.login-box,.register-box{width:90%;margin-top:20px}}.login-box-body,.register-box-body{background:#fff;padding:20px;border-top:0;color:#666}.login-box-body .form-control-feedback,.register-box-body .form-control-feedback{color:#777}.login-box-msg,.register-box-msg{margin:0;text-align:center;padding:0 20px 20px 20px}.social-auth-links{margin:10px 0}.error-page{width:600px;margin:20px auto 0 auto}@media (max-width:991px){.error-page{width:100%}}.error-page>.headline{float:left;font-size:100px;font-weight:300}@media (max-width:991px){.error-page>.headline{float:none;text-align:center}}.error-page>.error-content{margin-left:190px;display:block}@media (max-width:991px){.error-page>.error-content{margin-left:0}}.error-page>.error-content>h3{font-weight:300;font-size:25px}@media (max-width:991px){.error-page>.error-content>h3{text-align:center}}.invoice{position:relative;background:#fff;border:1px solid #f4f4f4;padding:20px;margin:10px 25px}.invoice-title{margin-top:0}.profile-user-img{margin:0 auto;width:100px;padding:3px;border:3px solid #d2d6de}.profile-username{font-size:21px;margin-top:5px}.post{border-bottom:1px solid #d2d6de;margin-bottom:15px;padding-bottom:15px;color:#666}.post:last-of-type{border-bottom:0;margin-bottom:0;padding-bottom:0}.post .user-block{margin-bottom:15px}.btn-social{position:relative;padding-left:44px;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.btn-social>:first-child{position:absolute;left:0;top:0;bottom:0;width:32px;line-height:34px;font-size:1.6em;text-align:center;border-right:1px solid rgba(0,0,0,0.2)}.btn-social.btn-lg{padding-left:61px}.btn-social.btn-lg>:first-child{line-height:45px;width:45px;font-size:1.8em}.btn-social.btn-sm{padding-left:38px}.btn-social.btn-sm>:first-child{line-height:28px;width:28px;font-size:1.4em}.btn-social.btn-xs{padding-left:30px}.btn-social.btn-xs>:first-child{line-height:20px;width:20px;font-size:1.2em}.btn-social-icon{position:relative;padding-left:44px;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;height:34px;width:34px;padding:0}.btn-social-icon>:first-child{position:absolute;left:0;top:0;bottom:0;width:32px;line-height:34px;font-size:1.6em;text-align:center;border-right:1px solid rgba(0,0,0,0.2)}.btn-social-icon.btn-lg{padding-left:61px}.btn-social-icon.btn-lg>:first-child{line-height:45px;width:45px;font-size:1.8em}.btn-social-icon.btn-sm{padding-left:38px}.btn-social-icon.btn-sm>:first-child{line-height:28px;width:28px;font-size:1.4em}.btn-social-icon.btn-xs{padding-left:30px}.btn-social-icon.btn-xs>:first-child{line-height:20px;width:20px;font-size:1.2em}.btn-social-icon>:first-child{border:none;text-align:center;width:100%}.btn-social-icon.btn-lg{height:45px;width:45px;padding-left:0;padding-right:0}.btn-social-icon.btn-sm{height:30px;width:30px;padding-left:0;padding-right:0}.btn-social-icon.btn-xs{height:22px;width:22px;padding-left:0;padding-right:0}.btn-adn{color:#fff;background-color:#d87a68;border-color:rgba(0,0,0,0.2)}.btn-adn:focus,.btn-adn.focus{color:#fff;background-color:#ce563f;border-color:rgba(0,0,0,0.2)}.btn-adn:hover{color:#fff;background-color:#ce563f;border-color:rgba(0,0,0,0.2)}.btn-adn:active,.btn-adn.active,.open>.dropdown-toggle.btn-adn{color:#fff;background-color:#ce563f;border-color:rgba(0,0,0,0.2)}.btn-adn:active,.btn-adn.active,.open>.dropdown-toggle.btn-adn{background-image:none}.btn-adn .badge{color:#d87a68;background-color:#fff}.btn-bitbucket{color:#fff;background-color:#205081;border-color:rgba(0,0,0,0.2)}.btn-bitbucket:focus,.btn-bitbucket.focus{color:#fff;background-color:#163758;border-color:rgba(0,0,0,0.2)}.btn-bitbucket:hover{color:#fff;background-color:#163758;border-color:rgba(0,0,0,0.2)}.btn-bitbucket:active,.btn-bitbucket.active,.open>.dropdown-toggle.btn-bitbucket{color:#fff;background-color:#163758;border-color:rgba(0,0,0,0.2)}.btn-bitbucket:active,.btn-bitbucket.active,.open>.dropdown-toggle.btn-bitbucket{background-image:none}.btn-bitbucket .badge{color:#205081;background-color:#fff}.btn-dropbox{color:#fff;background-color:#1087dd;border-color:rgba(0,0,0,0.2)}.btn-dropbox:focus,.btn-dropbox.focus{color:#fff;background-color:#0d6aad;border-color:rgba(0,0,0,0.2)}.btn-dropbox:hover{color:#fff;background-color:#0d6aad;border-color:rgba(0,0,0,0.2)}.btn-dropbox:active,.btn-dropbox.active,.open>.dropdown-toggle.btn-dropbox{color:#fff;background-color:#0d6aad;border-color:rgba(0,0,0,0.2)}.btn-dropbox:active,.btn-dropbox.active,.open>.dropdown-toggle.btn-dropbox{background-image:none}.btn-dropbox .badge{color:#1087dd;background-color:#fff}.btn-facebook{color:#fff;background-color:#3b5998;border-color:rgba(0,0,0,0.2)}.btn-facebook:focus,.btn-facebook.focus{color:#fff;background-color:#2d4373;border-color:rgba(0,0,0,0.2)}.btn-facebook:hover{color:#fff;background-color:#2d4373;border-color:rgba(0,0,0,0.2)}.btn-facebook:active,.btn-facebook.active,.open>.dropdown-toggle.btn-facebook{color:#fff;background-color:#2d4373;border-color:rgba(0,0,0,0.2)}.btn-facebook:active,.btn-facebook.active,.open>.dropdown-toggle.btn-facebook{background-image:none}.btn-facebook .badge{color:#3b5998;background-color:#fff}.btn-flickr{color:#fff;background-color:#ff0084;border-color:rgba(0,0,0,0.2)}.btn-flickr:focus,.btn-flickr.focus{color:#fff;background-color:#cc006a;border-color:rgba(0,0,0,0.2)}.btn-flickr:hover{color:#fff;background-color:#cc006a;border-color:rgba(0,0,0,0.2)}.btn-flickr:active,.btn-flickr.active,.open>.dropdown-toggle.btn-flickr{color:#fff;background-color:#cc006a;border-color:rgba(0,0,0,0.2)}.btn-flickr:active,.btn-flickr.active,.open>.dropdown-toggle.btn-flickr{background-image:none}.btn-flickr .badge{color:#ff0084;background-color:#fff}.btn-foursquare{color:#fff;background-color:#f94877;border-color:rgba(0,0,0,0.2)}.btn-foursquare:focus,.btn-foursquare.focus{color:#fff;background-color:#f71752;border-color:rgba(0,0,0,0.2)}.btn-foursquare:hover{color:#fff;background-color:#f71752;border-color:rgba(0,0,0,0.2)}.btn-foursquare:active,.btn-foursquare.active,.open>.dropdown-toggle.btn-foursquare{color:#fff;background-color:#f71752;border-color:rgba(0,0,0,0.2)}.btn-foursquare:active,.btn-foursquare.active,.open>.dropdown-toggle.btn-foursquare{background-image:none}.btn-foursquare .badge{color:#f94877;background-color:#fff}.btn-github{color:#fff;background-color:#444;border-color:rgba(0,0,0,0.2)}.btn-github:focus,.btn-github.focus{color:#fff;background-color:#2b2b2b;border-color:rgba(0,0,0,0.2)}.btn-github:hover{color:#fff;background-color:#2b2b2b;border-color:rgba(0,0,0,0.2)}.btn-github:active,.btn-github.active,.open>.dropdown-toggle.btn-github{color:#fff;background-color:#2b2b2b;border-color:rgba(0,0,0,0.2)}.btn-github:active,.btn-github.active,.open>.dropdown-toggle.btn-github{background-image:none}.btn-github .badge{color:#444;background-color:#fff}.btn-google{color:#fff;background-color:#dd4b39;border-color:rgba(0,0,0,0.2)}.btn-google:focus,.btn-google.focus{color:#fff;background-color:#c23321;border-color:rgba(0,0,0,0.2)}.btn-google:hover{color:#fff;background-color:#c23321;border-color:rgba(0,0,0,0.2)}.btn-google:active,.btn-google.active,.open>.dropdown-toggle.btn-google{color:#fff;background-color:#c23321;border-color:rgba(0,0,0,0.2)}.btn-google:active,.btn-google.active,.open>.dropdown-toggle.btn-google{background-image:none}.btn-google .badge{color:#dd4b39;background-color:#fff}.btn-instagram{color:#fff;background-color:#3f729b;border-color:rgba(0,0,0,0.2)}.btn-instagram:focus,.btn-instagram.focus{color:#fff;background-color:#305777;border-color:rgba(0,0,0,0.2)}.btn-instagram:hover{color:#fff;background-color:#305777;border-color:rgba(0,0,0,0.2)}.btn-instagram:active,.btn-instagram.active,.open>.dropdown-toggle.btn-instagram{color:#fff;background-color:#305777;border-color:rgba(0,0,0,0.2)}.btn-instagram:active,.btn-instagram.active,.open>.dropdown-toggle.btn-instagram{background-image:none}.btn-instagram .badge{color:#3f729b;background-color:#fff}.btn-linkedin{color:#fff;background-color:#007bb6;border-color:rgba(0,0,0,0.2)}.btn-linkedin:focus,.btn-linkedin.focus{color:#fff;background-color:#005983;border-color:rgba(0,0,0,0.2)}.btn-linkedin:hover{color:#fff;background-color:#005983;border-color:rgba(0,0,0,0.2)}.btn-linkedin:active,.btn-linkedin.active,.open>.dropdown-toggle.btn-linkedin{color:#fff;background-color:#005983;border-color:rgba(0,0,0,0.2)}.btn-linkedin:active,.btn-linkedin.active,.open>.dropdown-toggle.btn-linkedin{background-image:none}.btn-linkedin .badge{color:#007bb6;background-color:#fff}.btn-microsoft{color:#fff;background-color:#2672ec;border-color:rgba(0,0,0,0.2)}.btn-microsoft:focus,.btn-microsoft.focus{color:#fff;background-color:#125acd;border-color:rgba(0,0,0,0.2)}.btn-microsoft:hover{color:#fff;background-color:#125acd;border-color:rgba(0,0,0,0.2)}.btn-microsoft:active,.btn-microsoft.active,.open>.dropdown-toggle.btn-microsoft{color:#fff;background-color:#125acd;border-color:rgba(0,0,0,0.2)}.btn-microsoft:active,.btn-microsoft.active,.open>.dropdown-toggle.btn-microsoft{background-image:none}.btn-microsoft .badge{color:#2672ec;background-color:#fff}.btn-openid{color:#fff;background-color:#f7931e;border-color:rgba(0,0,0,0.2)}.btn-openid:focus,.btn-openid.focus{color:#fff;background-color:#da7908;border-color:rgba(0,0,0,0.2)}.btn-openid:hover{color:#fff;background-color:#da7908;border-color:rgba(0,0,0,0.2)}.btn-openid:active,.btn-openid.active,.open>.dropdown-toggle.btn-openid{color:#fff;background-color:#da7908;border-color:rgba(0,0,0,0.2)}.btn-openid:active,.btn-openid.active,.open>.dropdown-toggle.btn-openid{background-image:none}.btn-openid .badge{color:#f7931e;background-color:#fff}.btn-pinterest{color:#fff;background-color:#cb2027;border-color:rgba(0,0,0,0.2)}.btn-pinterest:focus,.btn-pinterest.focus{color:#fff;background-color:#9f191f;border-color:rgba(0,0,0,0.2)}.btn-pinterest:hover{color:#fff;background-color:#9f191f;border-color:rgba(0,0,0,0.2)}.btn-pinterest:active,.btn-pinterest.active,.open>.dropdown-toggle.btn-pinterest{color:#fff;background-color:#9f191f;border-color:rgba(0,0,0,0.2)}.btn-pinterest:active,.btn-pinterest.active,.open>.dropdown-toggle.btn-pinterest{background-image:none}.btn-pinterest .badge{color:#cb2027;background-color:#fff}.btn-reddit{color:#000;background-color:#eff7ff;border-color:rgba(0,0,0,0.2)}.btn-reddit:focus,.btn-reddit.focus{color:#000;background-color:#bcddff;border-color:rgba(0,0,0,0.2)}.btn-reddit:hover{color:#000;background-color:#bcddff;border-color:rgba(0,0,0,0.2)}.btn-reddit:active,.btn-reddit.active,.open>.dropdown-toggle.btn-reddit{color:#000;background-color:#bcddff;border-color:rgba(0,0,0,0.2)}.btn-reddit:active,.btn-reddit.active,.open>.dropdown-toggle.btn-reddit{background-image:none}.btn-reddit .badge{color:#eff7ff;background-color:#000}.btn-soundcloud{color:#fff;background-color:#f50;border-color:rgba(0,0,0,0.2)}.btn-soundcloud:focus,.btn-soundcloud.focus{color:#fff;background-color:#c40;border-color:rgba(0,0,0,0.2)}.btn-soundcloud:hover{color:#fff;background-color:#c40;border-color:rgba(0,0,0,0.2)}.btn-soundcloud:active,.btn-soundcloud.active,.open>.dropdown-toggle.btn-soundcloud{color:#fff;background-color:#c40;border-color:rgba(0,0,0,0.2)}.btn-soundcloud:active,.btn-soundcloud.active,.open>.dropdown-toggle.btn-soundcloud{background-image:none}.btn-soundcloud .badge{color:#f50;background-color:#fff}.btn-tumblr{color:#fff;background-color:#2c4762;border-color:rgba(0,0,0,0.2)}.btn-tumblr:focus,.btn-tumblr.focus{color:#fff;background-color:#1c2d3f;border-color:rgba(0,0,0,0.2)}.btn-tumblr:hover{color:#fff;background-color:#1c2d3f;border-color:rgba(0,0,0,0.2)}.btn-tumblr:active,.btn-tumblr.active,.open>.dropdown-toggle.btn-tumblr{color:#fff;background-color:#1c2d3f;border-color:rgba(0,0,0,0.2)}.btn-tumblr:active,.btn-tumblr.active,.open>.dropdown-toggle.btn-tumblr{background-image:none}.btn-tumblr .badge{color:#2c4762;background-color:#fff}.btn-twitter{color:#fff;background-color:#55acee;border-color:rgba(0,0,0,0.2)}.btn-twitter:focus,.btn-twitter.focus{color:#fff;background-color:#2795e9;border-color:rgba(0,0,0,0.2)}.btn-twitter:hover{color:#fff;background-color:#2795e9;border-color:rgba(0,0,0,0.2)}.btn-twitter:active,.btn-twitter.active,.open>.dropdown-toggle.btn-twitter{color:#fff;background-color:#2795e9;border-color:rgba(0,0,0,0.2)}.btn-twitter:active,.btn-twitter.active,.open>.dropdown-toggle.btn-twitter{background-image:none}.btn-twitter .badge{color:#55acee;background-color:#fff}.btn-vimeo{color:#fff;background-color:#1ab7ea;border-color:rgba(0,0,0,0.2)}.btn-vimeo:focus,.btn-vimeo.focus{color:#fff;background-color:#1295bf;border-color:rgba(0,0,0,0.2)}.btn-vimeo:hover{color:#fff;background-color:#1295bf;border-color:rgba(0,0,0,0.2)}.btn-vimeo:active,.btn-vimeo.active,.open>.dropdown-toggle.btn-vimeo{color:#fff;background-color:#1295bf;border-color:rgba(0,0,0,0.2)}.btn-vimeo:active,.btn-vimeo.active,.open>.dropdown-toggle.btn-vimeo{background-image:none}.btn-vimeo .badge{color:#1ab7ea;background-color:#fff}.btn-vk{color:#fff;background-color:#587ea3;border-color:rgba(0,0,0,0.2)}.btn-vk:focus,.btn-vk.focus{color:#fff;background-color:#466482;border-color:rgba(0,0,0,0.2)}.btn-vk:hover{color:#fff;background-color:#466482;border-color:rgba(0,0,0,0.2)}.btn-vk:active,.btn-vk.active,.open>.dropdown-toggle.btn-vk{color:#fff;background-color:#466482;border-color:rgba(0,0,0,0.2)}.btn-vk:active,.btn-vk.active,.open>.dropdown-toggle.btn-vk{background-image:none}.btn-vk .badge{color:#587ea3;background-color:#fff}.btn-yahoo{color:#fff;background-color:#720e9e;border-color:rgba(0,0,0,0.2)}.btn-yahoo:focus,.btn-yahoo.focus{color:#fff;background-color:#500a6f;border-color:rgba(0,0,0,0.2)}.btn-yahoo:hover{color:#fff;background-color:#500a6f;border-color:rgba(0,0,0,0.2)}.btn-yahoo:active,.btn-yahoo.active,.open>.dropdown-toggle.btn-yahoo{color:#fff;background-color:#500a6f;border-color:rgba(0,0,0,0.2)}.btn-yahoo:active,.btn-yahoo.active,.open>.dropdown-toggle.btn-yahoo{background-image:none}.btn-yahoo .badge{color:#720e9e;background-color:#fff}.fc-button{background:#f4f4f4;background-image:none;color:#444;border-color:#ddd;border-bottom-color:#ddd}.fc-button:hover,.fc-button:active,.fc-button.hover{background-color:#e9e9e9}.fc-header-title h2{font-size:15px;line-height:1.6em;color:#666;margin-left:10px}.fc-header-right{padding-right:10px}.fc-header-left{padding-left:10px}.fc-widget-header{background:#fafafa}.fc-grid{width:100%;border:0}.fc-widget-header:first-of-type,.fc-widget-content:first-of-type{border-left:0;border-right:0}.fc-widget-header:last-of-type,.fc-widget-content:last-of-type{border-right:0}.fc-toolbar{padding:10px;margin:0}.fc-day-number{font-size:20px;font-weight:300;padding-right:10px}.fc-color-picker{list-style:none;margin:0;padding:0}.fc-color-picker>li{float:left;font-size:30px;margin-right:5px;line-height:30px}.fc-color-picker>li .fa{-webkit-transition:-webkit-transform linear .3s;-moz-transition:-moz-transform linear .3s;-o-transition:-o-transform linear .3s;transition:transform linear .3s}.fc-color-picker>li .fa:hover{-webkit-transform:rotate(30deg);-ms-transform:rotate(30deg);-o-transform:rotate(30deg);transform:rotate(30deg)}#add-new-event{-webkit-transition:all linear .3s;-o-transition:all linear .3s;transition:all linear .3s}.external-event{padding:5px 10px;font-weight:bold;margin-bottom:4px;box-shadow:0 1px 1px rgba(0,0,0,0.1);text-shadow:0 1px 1px rgba(0,0,0,0.1);border-radius:3px;cursor:move}.external-event:hover{box-shadow:inset 0 0 90px rgba(0,0,0,0.2)}.select2-container--default.select2-container--focus,.select2-selection.select2-container--focus,.select2-container--default:focus,.select2-selection:focus,.select2-container--default:active,.select2-selection:active{outline:none}.select2-container--default .select2-selection--single,.select2-selection .select2-selection--single{border:1px solid #d2d6de;border-radius:0;padding:6px 12px;height:34px}.select2-container--default.select2-container--open{border-color:#3c8dbc}.select2-dropdown{border:1px solid #d2d6de;border-radius:0}.select2-container--default .select2-results__option--highlighted[aria-selected]{background-color:#3c8dbc;color:white}.select2-results__option{padding:6px 12px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--single .select2-selection__rendered{padding-left:0;padding-right:0;height:auto;margin-top:-4px}.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered{padding-right:6px;padding-left:20px}.select2-container--default .select2-selection--single .select2-selection__arrow{height:28px;right:3px}.select2-container--default .select2-selection--single .select2-selection__arrow b{margin-top:0}.select2-dropdown .select2-search__field,.select2-search--inline .select2-search__field{border:1px solid #d2d6de}.select2-dropdown .select2-search__field:focus,.select2-search--inline .select2-search__field:focus{outline:none;border:1px solid #3c8dbc}.select2-container--default .select2-results__option[aria-disabled=true]{color:#999}.select2-container--default .select2-results__option[aria-selected=true]{background-color:#ddd}.select2-container--default .select2-results__option[aria-selected=true],.select2-container--default .select2-results__option[aria-selected=true]:hover{color:#444}.select2-container--default .select2-selection--multiple{border:1px solid #d2d6de;border-radius:0}.select2-container--default .select2-selection--multiple:focus{border-color:#3c8dbc}.select2-container--default.select2-container--focus .select2-selection--multiple{border-color:#d2d6de}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#3c8dbc;border-color:#367fa9;padding:1px 10px;color:#fff}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove{margin-right:5px;color:rgba(255,255,255,0.7)}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover{color:#fff}.select2-container .select2-selection--single .select2-selection__rendered{padding-right:10px}.pad{padding:10px}.margin{margin:10px}.margin-bottom{margin-bottom:20px}.margin-bottom-none{margin-bottom:0}.margin-r-5{margin-right:5px}.inline{display:inline}.description-block{display:block;margin:10px 0;text-align:center}.description-block.margin-bottom{margin-bottom:25px}.description-block>.description-header{margin:0;padding:0;font-weight:600;font-size:16px}.description-block>.description-text{text-transform:uppercase}.bg-red,.bg-yellow,.bg-aqua,.bg-blue,.bg-light-blue,.bg-green,.bg-navy,.bg-teal,.bg-olive,.bg-lime,.bg-orange,.bg-fuchsia,.bg-purple,.bg-maroon,.bg-black,.bg-red-active,.bg-yellow-active,.bg-aqua-active,.bg-blue-active,.bg-light-blue-active,.bg-green-active,.bg-navy-active,.bg-teal-active,.bg-olive-active,.bg-lime-active,.bg-orange-active,.bg-fuchsia-active,.bg-purple-active,.bg-maroon-active,.bg-black-active,.callout.callout-danger,.callout.callout-warning,.callout.callout-info,.callout.callout-success,.alert-success,.alert-danger,.alert-error,.alert-warning,.alert-info,.label-danger,.label-info,.label-warning,.label-primary,.label-success,.modal-primary .modal-body,.modal-primary .modal-header,.modal-primary .modal-footer,.modal-warning .modal-body,.modal-warning .modal-header,.modal-warning .modal-footer,.modal-info .modal-body,.modal-info .modal-header,.modal-info .modal-footer,.modal-success .modal-body,.modal-success .modal-header,.modal-success .modal-footer,.modal-danger .modal-body,.modal-danger .modal-header,.modal-danger .modal-footer{color:#fff !important}.bg-gray{color:#000;background-color:#d2d6de !important}.bg-gray-light{background-color:#f7f7f7}.bg-black{background-color:#111 !important}.bg-red,.callout.callout-danger,.alert-danger,.alert-error,.label-danger,.modal-danger .modal-body{background-color:#dd4b39 !important}.bg-yellow,.callout.callout-warning,.alert-warning,.label-warning,.modal-warning .modal-body{background-color:#f39c12 !important}.bg-aqua,.callout.callout-info,.alert-info,.label-info,.modal-info .modal-body{background-color:#00c0ef !important}.bg-blue{background-color:#0073b7 !important}.bg-light-blue,.label-primary,.modal-primary .modal-body{background-color:#3c8dbc !important}.bg-green,.callout.callout-success,.alert-success,.label-success,.modal-success .modal-body{background-color:#00a65a !important}.bg-navy{background-color:#001f3f !important}.bg-teal{background-color:#39cccc !important}.bg-olive{background-color:#3d9970 !important}.bg-lime{background-color:#01ff70 !important}.bg-orange{background-color:#ff851b !important}.bg-fuchsia{background-color:#f012be !important}.bg-purple{background-color:#605ca8 !important}.bg-maroon{background-color:#d81b60 !important}.bg-gray-active{color:#000;background-color:#b5bbc8 !important}.bg-black-active{background-color:#000 !important}.bg-red-active,.modal-danger .modal-header,.modal-danger .modal-footer{background-color:#d33724 !important}.bg-yellow-active,.modal-warning .modal-header,.modal-warning .modal-footer{background-color:#db8b0b !important}.bg-aqua-active,.modal-info .modal-header,.modal-info .modal-footer{background-color:#00a7d0 !important}.bg-blue-active{background-color:#005384 !important}.bg-light-blue-active,.modal-primary .modal-header,.modal-primary .modal-footer{background-color:#357ca5 !important}.bg-green-active,.modal-success .modal-header,.modal-success .modal-footer{background-color:#008d4c !important}.bg-navy-active{background-color:#001a35 !important}.bg-teal-active{background-color:#30bbbb !important}.bg-olive-active{background-color:#368763 !important}.bg-lime-active{background-color:#00e765 !important}.bg-orange-active{background-color:#ff7701 !important}.bg-fuchsia-active{background-color:#db0ead !important}.bg-purple-active{background-color:#555299 !important}.bg-maroon-active{background-color:#ca195a !important}[class^="bg-"].disabled{opacity:.65;filter:alpha(opacity=65)}.text-red{color:#dd4b39 !important}.text-yellow{color:#f39c12 !important}.text-aqua{color:#00c0ef !important}.text-blue{color:#0073b7 !important}.text-black{color:#111 !important}.text-light-blue{color:#3c8dbc !important}.text-green{color:#00a65a !important}.text-gray{color:#d2d6de !important}.text-navy{color:#001f3f !important}.text-teal{color:#39cccc !important}.text-olive{color:#3d9970 !important}.text-lime{color:#01ff70 !important}.text-orange{color:#ff851b !important}.text-fuchsia{color:#f012be !important}.text-purple{color:#605ca8 !important}.text-maroon{color:#d81b60 !important}.link-muted{color:#7a869d}.link-muted:hover,.link-muted:focus{color:#606c84}.link-black{color:#666}.link-black:hover,.link-black:focus{color:#999}.hide{display:none !important}.no-border{border:0 !important}.no-padding{padding:0 !important}.no-margin{margin:0 !important}.no-shadow{box-shadow:none !important}.list-unstyled,.chart-legend,.contacts-list,.users-list,.mailbox-attachments{list-style:none;margin:0;padding:0}.list-group-unbordered>.list-group-item{border-left:0;border-right:0;border-radius:0;padding-left:0;padding-right:0}.flat{border-radius:0 !important}.text-bold,.text-bold.table td,.text-bold.table th{font-weight:700}.text-sm{font-size:12px}.jqstooltip{padding:5px !important;width:auto !important;height:auto !important}.bg-teal-gradient{background:#39cccc !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #39cccc), color-stop(1, #7adddd)) !important;background:-ms-linear-gradient(bottom, #39cccc, #7adddd) !important;background:-moz-linear-gradient(center bottom, #39cccc 0, #7adddd 100%) !important;background:-o-linear-gradient(#7adddd, #39cccc) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#7adddd', endColorstr='#39cccc', GradientType=0) !important;color:#fff}.bg-light-blue-gradient{background:#3c8dbc !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #3c8dbc), color-stop(1, #67a8ce)) !important;background:-ms-linear-gradient(bottom, #3c8dbc, #67a8ce) !important;background:-moz-linear-gradient(center bottom, #3c8dbc 0, #67a8ce 100%) !important;background:-o-linear-gradient(#67a8ce, #3c8dbc) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#67a8ce', endColorstr='#3c8dbc', GradientType=0) !important;color:#fff}.bg-blue-gradient{background:#0073b7 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #0073b7), color-stop(1, #0089db)) !important;background:-ms-linear-gradient(bottom, #0073b7, #0089db) !important;background:-moz-linear-gradient(center bottom, #0073b7 0, #0089db 100%) !important;background:-o-linear-gradient(#0089db, #0073b7) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#0089db', endColorstr='#0073b7', GradientType=0) !important;color:#fff}.bg-aqua-gradient{background:#00c0ef !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #00c0ef), color-stop(1, #14d1ff)) !important;background:-ms-linear-gradient(bottom, #00c0ef, #14d1ff) !important;background:-moz-linear-gradient(center bottom, #00c0ef 0, #14d1ff 100%) !important;background:-o-linear-gradient(#14d1ff, #00c0ef) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#14d1ff', endColorstr='#00c0ef', GradientType=0) !important;color:#fff}.bg-yellow-gradient{background:#f39c12 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #f39c12), color-stop(1, #f7bc60)) !important;background:-ms-linear-gradient(bottom, #f39c12, #f7bc60) !important;background:-moz-linear-gradient(center bottom, #f39c12 0, #f7bc60 100%) !important;background:-o-linear-gradient(#f7bc60, #f39c12) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f7bc60', endColorstr='#f39c12', GradientType=0) !important;color:#fff}.bg-purple-gradient{background:#605ca8 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #605ca8), color-stop(1, #9491c4)) !important;background:-ms-linear-gradient(bottom, #605ca8, #9491c4) !important;background:-moz-linear-gradient(center bottom, #605ca8 0, #9491c4 100%) !important;background:-o-linear-gradient(#9491c4, #605ca8) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#9491c4', endColorstr='#605ca8', GradientType=0) !important;color:#fff}.bg-green-gradient{background:#00a65a !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #00a65a), color-stop(1, #00ca6d)) !important;background:-ms-linear-gradient(bottom, #00a65a, #00ca6d) !important;background:-moz-linear-gradient(center bottom, #00a65a 0, #00ca6d 100%) !important;background:-o-linear-gradient(#00ca6d, #00a65a) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ca6d', endColorstr='#00a65a', GradientType=0) !important;color:#fff}.bg-red-gradient{background:#dd4b39 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #dd4b39), color-stop(1, #e47365)) !important;background:-ms-linear-gradient(bottom, #dd4b39, #e47365) !important;background:-moz-linear-gradient(center bottom, #dd4b39 0, #e47365 100%) !important;background:-o-linear-gradient(#e47365, #dd4b39) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#e47365', endColorstr='#dd4b39', GradientType=0) !important;color:#fff}.bg-black-gradient{background:#111 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #111), color-stop(1, #2b2b2b)) !important;background:-ms-linear-gradient(bottom, #111, #2b2b2b) !important;background:-moz-linear-gradient(center bottom, #111 0, #2b2b2b 100%) !important;background:-o-linear-gradient(#2b2b2b, #111) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#2b2b2b', endColorstr='#111111', GradientType=0) !important;color:#fff}.bg-maroon-gradient{background:#d81b60 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #d81b60), color-stop(1, #e73f7c)) !important;background:-ms-linear-gradient(bottom, #d81b60, #e73f7c) !important;background:-moz-linear-gradient(center bottom, #d81b60 0, #e73f7c 100%) !important;background:-o-linear-gradient(#e73f7c, #d81b60) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#e73f7c', endColorstr='#d81b60', GradientType=0) !important;color:#fff}.description-block .description-icon{font-size:16px}.no-pad-top{padding-top:0}.position-static{position:static !important}.list-header{font-size:15px;padding:10px 4px;font-weight:bold;color:#666}.list-seperator{height:1px;background:#f4f4f4;margin:15px 0 9px 0}.list-link>a{padding:4px;color:#777}.list-link>a:hover{color:#222}.font-light{font-weight:300}.user-block:before,.user-block:after{content:" ";display:table}.user-block:after{clear:both}.user-block img{width:40px;height:40px;float:left}.user-block .username,.user-block .description,.user-block .comment{display:block;margin-left:50px}.user-block .username{font-size:16px;font-weight:600}.user-block .description{color:#999;font-size:13px}.user-block.user-block-sm .username,.user-block.user-block-sm .description,.user-block.user-block-sm .comment{margin-left:40px}.user-block.user-block-sm .username{font-size:14px}.img-sm,.img-md,.img-lg,.box-comments .box-comment img,.user-block.user-block-sm img{float:left}.img-sm,.box-comments .box-comment img,.user-block.user-block-sm img{width:30px !important;height:30px !important}.img-sm+.img-push{margin-left:40px}.img-md{width:60px;height:60px}.img-md+.img-push{margin-left:70px}.img-lg{width:100px;height:100px}.img-lg+.img-push{margin-left:110px}.img-bordered{border:3px solid #d2d6de;padding:3px}.img-bordered-sm{border:2px solid #d2d6de;padding:2px}.attachment-block{border:1px solid #f4f4f4;padding:5px;margin-bottom:10px;background:#f7f7f7}.attachment-block .attachment-img{max-width:100px;max-height:100px;height:auto;float:left}.attachment-block .attachment-pushed{margin-left:110px}.attachment-block .attachment-heading{margin:0}.attachment-block .attachment-text{color:#555}.connectedSortable{min-height:100px}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sort-highlight{background:#f4f4f4;border:1px dashed #ddd;margin-bottom:10px}.full-opacity-hover{opacity:.65;filter:alpha(opacity=65)}.full-opacity-hover:hover{opacity:1;filter:alpha(opacity=100)}.chart{position:relative;overflow:hidden;width:100%}.chart svg,.chart canvas{width:100% !important}@media print{.no-print,.main-sidebar,.left-side,.main-header,.content-header{display:none !important}.content-wrapper,.right-side,.main-footer{margin-left:0 !important;min-height:0 !important;-webkit-transform:translate(0, 0) !important;-ms-transform:translate(0, 0) !important;-o-transform:translate(0, 0) !important;transform:translate(0, 0) !important}.fixed .content-wrapper,.fixed .right-side{padding-top:0 !important}.invoice{width:100%;border:0;margin:0;padding:0}.invoice-col{float:left;width:33.3333333%}.table-responsive{overflow:auto}.table-responsive>.table tr th,.table-responsive>.table tr td{white-space:normal !important}} \ No newline at end of file + */html,body{height:100%}.layout-boxed html,.layout-boxed body{height:100%}body{font-family:'Source Sans Pro','Helvetica Neue',Helvetica,Arial,sans-serif;font-weight:400;overflow-x:hidden;overflow-y:auto}.wrapper{height:100%;position:relative;overflow-x:hidden;overflow-y:auto}.wrapper:before,.wrapper:after{content:" ";display:table}.wrapper:after{clear:both}.layout-boxed .wrapper{max-width:1250px;margin:0 auto;min-height:100%;box-shadow:0 0 8px rgba(0,0,0,0.5);position:relative}.layout-boxed{background:url('../img/boxed-bg.jpg') repeat fixed}.content-wrapper,.main-footer{-webkit-transition:-webkit-transform .3s ease-in-out,margin .3s ease-in-out;-moz-transition:-moz-transform .3s ease-in-out,margin .3s ease-in-out;-o-transition:-o-transform .3s ease-in-out,margin .3s ease-in-out;transition:transform .3s ease-in-out,margin .3s ease-in-out;margin-left:230px;z-index:820}.layout-top-nav .content-wrapper,.layout-top-nav .main-footer{margin-left:0}@media (max-width:767px){.content-wrapper,.main-footer{margin-left:0}}@media (min-width:768px){.sidebar-collapse .content-wrapper,.sidebar-collapse .main-footer{margin-left:0}}@media (max-width:767px){.sidebar-open .content-wrapper,.sidebar-open .main-footer{-webkit-transform:translate(230px, 0);-ms-transform:translate(230px, 0);-o-transform:translate(230px, 0);transform:translate(230px, 0)}}.content-wrapper{min-height:100%;background-color:#ecf0f5;z-index:800}.main-footer{background:#fff;padding:15px;color:#444;border-top:1px solid #eaecf1}.fixed .main-header,.fixed .main-sidebar,.fixed .left-side{position:fixed}.fixed .main-header{top:0;right:0;left:0}.fixed .content-wrapper,.fixed .right-side{padding-top:50px}@media (max-width:767px){.fixed .content-wrapper,.fixed .right-side{padding-top:100px}}.fixed.layout-boxed .wrapper{max-width:100%}.fixed .wrapper{overflow:hidden}.hold-transition .content-wrapper,.hold-transition .right-side,.hold-transition .main-footer,.hold-transition .main-sidebar,.hold-transition .left-side,.hold-transition .main-header .navbar,.hold-transition .main-header .logo,.hold-transition .menu-open .fa-angle-left{-webkit-transition:none;-o-transition:none;transition:none}.content{min-height:250px;padding:15px;margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:'Source Sans Pro',sans-serif}a{color:#10529f}a:hover,a:active,a:focus{outline:none;text-decoration:none;color:#1776e5}.page-header{margin:10px 0 20px 0;font-size:22px}.page-header>small{color:#666;display:block;margin-top:5px}.main-header{position:relative;max-height:100px;z-index:1030}.main-header .navbar{-webkit-transition:margin-left .3s ease-in-out;-o-transition:margin-left .3s ease-in-out;transition:margin-left .3s ease-in-out;margin-bottom:0;margin-left:230px;border:none;min-height:50px;border-radius:0}.layout-top-nav .main-header .navbar{margin-left:0}.main-header #navbar-search-input.form-control{background:rgba(255,255,255,0.2);border-color:transparent}.main-header #navbar-search-input.form-control:focus,.main-header #navbar-search-input.form-control:active{border-color:rgba(0,0,0,0.1);background:rgba(255,255,255,0.9)}.main-header #navbar-search-input.form-control::-moz-placeholder{color:#ccc;opacity:1}.main-header #navbar-search-input.form-control:-ms-input-placeholder{color:#ccc}.main-header #navbar-search-input.form-control::-webkit-input-placeholder{color:#ccc}.main-header .navbar-custom-menu,.main-header .navbar-right{float:right}@media (max-width:991px){.main-header .navbar-custom-menu a,.main-header .navbar-right a{color:inherit;background:transparent}}@media (max-width:767px){.main-header .navbar-right{float:none}.navbar-collapse .main-header .navbar-right{margin:7.5px -15px}.main-header .navbar-right>li{color:inherit;border:0}}.main-header .sidebar-toggle{float:left;background-color:transparent;background-image:none;padding:15px 15px;font-family:fontAwesome}.main-header .sidebar-toggle:before{content:"\f0c9"}.main-header .sidebar-toggle:hover{color:#fff}.main-header .sidebar-toggle:focus,.main-header .sidebar-toggle:active{background:transparent}.main-header .sidebar-toggle .icon-bar{display:none}.main-header .navbar .nav>li.user>a>.fa,.main-header .navbar .nav>li.user>a>.glyphicon,.main-header .navbar .nav>li.user>a>.ion{margin-right:5px}.main-header .navbar .nav>li>a>.label{position:absolute;top:9px;right:7px;text-align:center;font-size:9px;padding:2px 3px;line-height:.9}.main-header .logo{-webkit-transition:width .3s ease-in-out;-o-transition:width .3s ease-in-out;transition:width .3s ease-in-out;display:block;float:left;height:50px;font-size:20px;line-height:50px;text-align:center;width:230px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;padding:0 15px;font-weight:300;overflow:hidden}.main-header .logo .logo-lg{display:block}.main-header .logo .logo-mini{display:none}.main-header .navbar-brand{color:#fff}.content-header{position:relative;padding:15px 15px 0 15px}.content-header>h1{margin:0;font-size:24px}.content-header>h1>small{font-size:15px;display:inline-block;padding-left:4px;font-weight:300}.content-header>.breadcrumb{float:right;background:transparent;margin-top:0;margin-bottom:0;font-size:12px;padding:7px 5px;position:absolute;top:15px;right:10px;border-radius:2px}.content-header>.breadcrumb>li>a{color:#444;text-decoration:none;display:inline-block}.content-header>.breadcrumb>li>a>.fa,.content-header>.breadcrumb>li>a>.glyphicon,.content-header>.breadcrumb>li>a>.ion{margin-right:5px}.content-header>.breadcrumb>li+li:before{content:'>\00a0'}@media (max-width:991px){.content-header>.breadcrumb{position:relative;margin-top:5px;top:0;right:0;float:none;background:#eaecf1;padding-left:10px}.content-header>.breadcrumb li:before{color:#adb5c8}}.navbar-toggle{color:#fff;border:0;margin:0;padding:15px 15px}@media (max-width:991px){.navbar-custom-menu .navbar-nav>li{float:left}.navbar-custom-menu .navbar-nav{margin:0;float:left}.navbar-custom-menu .navbar-nav>li>a{padding-top:15px;padding-bottom:15px;line-height:20px}}@media (max-width:767px){.main-header{position:relative}.main-header .logo,.main-header .navbar{width:100%;float:none}.main-header .navbar{margin:0}.main-header .navbar-custom-menu{float:right}}@media (max-width:991px){.navbar-collapse.pull-left{float:none !important}.navbar-collapse.pull-left+.navbar-custom-menu{display:block;position:absolute;top:0;right:40px}}.main-sidebar{position:absolute;top:0;left:0;padding-top:50px;min-height:100%;width:230px;z-index:810;-webkit-transition:-webkit-transform .3s ease-in-out,width .3s ease-in-out;-moz-transition:-moz-transform .3s ease-in-out,width .3s ease-in-out;-o-transition:-o-transform .3s ease-in-out,width .3s ease-in-out;transition:transform .3s ease-in-out,width .3s ease-in-out}@media (max-width:767px){.main-sidebar{padding-top:100px}}@media (max-width:767px){.main-sidebar{-webkit-transform:translate(-230px, 0);-ms-transform:translate(-230px, 0);-o-transform:translate(-230px, 0);transform:translate(-230px, 0)}}@media (min-width:768px){.sidebar-collapse .main-sidebar{-webkit-transform:translate(-230px, 0);-ms-transform:translate(-230px, 0);-o-transform:translate(-230px, 0);transform:translate(-230px, 0)}}@media (max-width:767px){.sidebar-open .main-sidebar{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0)}}.sidebar{padding-bottom:10px}.sidebar-form input:focus{border-color:transparent}.user-panel{position:relative;width:100%;padding:10px;overflow:hidden}.user-panel:before,.user-panel:after{content:" ";display:table}.user-panel:after{clear:both}.user-panel>.image>img{width:100%;max-width:45px;height:auto}.user-panel>.info{padding:5px 5px 5px 15px;line-height:1;position:absolute;left:55px}.user-panel>.info>p{font-weight:600;margin-bottom:9px}.user-panel>.info>a{text-decoration:none;padding-right:5px;margin-top:3px;font-size:11px}.user-panel>.info>a>.fa,.user-panel>.info>a>.ion,.user-panel>.info>a>.glyphicon{margin-right:3px}.sidebar-menu{list-style:none;margin:0;padding:0}.sidebar-menu>li{position:relative;margin:0;padding:0}.sidebar-menu>li>a{padding:12px 5px 12px 15px;display:block}.sidebar-menu>li>a>.fa,.sidebar-menu>li>a>.glyphicon,.sidebar-menu>li>a>.ion{width:20px}.sidebar-menu>li .label,.sidebar-menu>li .badge{margin-right:5px}.sidebar-menu>li .badge{margin-top:3px}.sidebar-menu li.header{padding:10px 25px 10px 15px;font-size:12px}.sidebar-menu li>a>.fa-angle-left,.sidebar-menu li>a>.pull-right-container>.fa-angle-left{width:auto;height:auto;padding:0;margin-right:10px;-webkit-transition:transform .5s ease;-o-transition:transform .5s ease;transition:transform .5s ease}.sidebar-menu li>a>.fa-angle-left{position:absolute;top:50%;right:10px;margin-top:-8px}.sidebar-menu .menu-open>a>.fa-angle-left,.sidebar-menu .menu-open>a>.pull-right-container>.fa-angle-left{-webkit-transform:rotate(-90deg);-ms-transform:rotate(-90deg);-o-transform:rotate(-90deg);transform:rotate(-90deg)}.sidebar-menu .active>.treeview-menu{display:block}@media (min-width:768px){.sidebar-mini.sidebar-collapse .content-wrapper,.sidebar-mini.sidebar-collapse .right-side,.sidebar-mini.sidebar-collapse .main-footer{margin-left:50px !important;z-index:840}.sidebar-mini.sidebar-collapse .main-sidebar{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0);width:50px !important;z-index:850}.sidebar-mini.sidebar-collapse .sidebar-menu>li{position:relative}.sidebar-mini.sidebar-collapse .sidebar-menu>li>a{margin-right:0}.sidebar-mini.sidebar-collapse .sidebar-menu>li>a>span{border-top-right-radius:4px}.sidebar-mini.sidebar-collapse .sidebar-menu>li:not(.treeview)>a>span{border-bottom-right-radius:4px}.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{padding-top:5px;padding-bottom:5px;border-bottom-right-radius:4px}.sidebar-mini.sidebar-collapse .main-sidebar .user-panel>.info,.sidebar-mini.sidebar-collapse .sidebar-form,.sidebar-mini.sidebar-collapse .sidebar-menu>li>a>span,.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu,.sidebar-mini.sidebar-collapse .sidebar-menu>li>a>.pull-right,.sidebar-mini.sidebar-collapse .sidebar-menu li.header{display:none !important;-webkit-transform:translateZ(0)}.sidebar-mini.sidebar-collapse .main-header .logo{width:50px}.sidebar-mini.sidebar-collapse .main-header .logo>.logo-mini{display:block;margin-left:-15px;margin-right:-15px;font-size:18px}.sidebar-mini.sidebar-collapse .main-header .logo>.logo-lg{display:none}.sidebar-mini.sidebar-collapse .main-header .navbar{margin-left:50px}}.sidebar-mini:not(.sidebar-mini-expand-feature).sidebar-collapse .sidebar-menu>li:hover>a>span:not(.pull-right),.sidebar-mini:not(.sidebar-mini-expand-feature).sidebar-collapse .sidebar-menu>li:hover>.treeview-menu{display:block !important;position:absolute;width:180px;left:50px}.sidebar-mini:not(.sidebar-mini-expand-feature).sidebar-collapse .sidebar-menu>li:hover>a>span{top:0;margin-left:-3px;padding:12px 5px 12px 20px;background-color:inherit}.sidebar-mini:not(.sidebar-mini-expand-feature).sidebar-collapse .sidebar-menu>li:hover>a>.pull-right-container{position:relative !important;float:right;width:auto !important;left:180px !important;top:-22px !important;z-index:900}.sidebar-mini:not(.sidebar-mini-expand-feature).sidebar-collapse .sidebar-menu>li:hover>a>.pull-right-container>.label:not(:first-of-type){display:none}.sidebar-mini:not(.sidebar-mini-expand-feature).sidebar-collapse .sidebar-menu>li:hover>.treeview-menu{top:44px;margin-left:0}.sidebar-expanded-on-hover .main-footer,.sidebar-expanded-on-hover .content-wrapper{margin-left:50px}.sidebar-expanded-on-hover .main-sidebar{box-shadow:3px 0 8px rgba(0,0,0,0.125)}.sidebar-menu,.main-sidebar .user-panel,.sidebar-menu>li.header{white-space:nowrap;overflow:hidden}.sidebar-menu:hover{overflow:visible}.sidebar-form,.sidebar-menu>li.header{overflow:hidden;text-overflow:clip}.sidebar-menu li>a{position:relative}.sidebar-menu li>a>.pull-right-container{position:absolute;right:10px;top:50%;margin-top:-7px}.control-sidebar-bg{position:fixed;z-index:1000;bottom:0}.control-sidebar-bg,.control-sidebar{top:0;right:-230px;width:230px;-webkit-transition:right .3s ease-in-out;-o-transition:right .3s ease-in-out;transition:right .3s ease-in-out}.control-sidebar{position:absolute;padding-top:50px;z-index:1010}@media (max-width:768px){.control-sidebar{padding-top:100px}}.control-sidebar>.tab-content{padding:10px 15px}.control-sidebar.control-sidebar-open,.control-sidebar.control-sidebar-open+.control-sidebar-bg{right:0}.control-sidebar-open .control-sidebar-bg,.control-sidebar-open .control-sidebar{right:0}@media (min-width:768px){.control-sidebar-open .content-wrapper,.control-sidebar-open .right-side,.control-sidebar-open .main-footer{margin-right:230px}}.fixed .control-sidebar{position:fixed;height:100%;overflow-y:auto;padding-bottom:50px}.nav-tabs.control-sidebar-tabs>li:first-of-type>a,.nav-tabs.control-sidebar-tabs>li:first-of-type>a:hover,.nav-tabs.control-sidebar-tabs>li:first-of-type>a:focus{border-left-width:0}.nav-tabs.control-sidebar-tabs>li>a{border-radius:0}.nav-tabs.control-sidebar-tabs>li>a,.nav-tabs.control-sidebar-tabs>li>a:hover{border-top:none;border-right:none;border-left:1px solid transparent;border-bottom:1px solid transparent}.nav-tabs.control-sidebar-tabs>li>a .icon{font-size:16px}.nav-tabs.control-sidebar-tabs>li.active>a,.nav-tabs.control-sidebar-tabs>li.active>a:hover,.nav-tabs.control-sidebar-tabs>li.active>a:focus,.nav-tabs.control-sidebar-tabs>li.active>a:active{border-top:none;border-right:none;border-bottom:none}@media (max-width:768px){.nav-tabs.control-sidebar-tabs{display:table}.nav-tabs.control-sidebar-tabs>li{display:table-cell}}.control-sidebar-heading{font-weight:400;font-size:16px;padding:10px 0;margin-bottom:10px}.control-sidebar-subheading{display:block;font-weight:400;font-size:14px}.control-sidebar-menu{list-style:none;padding:0;margin:0 -15px}.control-sidebar-menu>li>a{display:block;padding:10px 15px}.control-sidebar-menu>li>a:before,.control-sidebar-menu>li>a:after{content:" ";display:table}.control-sidebar-menu>li>a:after{clear:both}.control-sidebar-menu>li>a>.control-sidebar-subheading{margin-top:0}.control-sidebar-menu .menu-icon{float:left;width:35px;height:35px;border-radius:50%;text-align:center;line-height:35px}.control-sidebar-menu .menu-info{margin-left:45px;margin-top:3px}.control-sidebar-menu .menu-info>.control-sidebar-subheading{margin:0}.control-sidebar-menu .menu-info>p{margin:0;font-size:11px}.control-sidebar-menu .progress{margin:0}.control-sidebar-dark{color:#abb0c2}.control-sidebar-dark,.control-sidebar-dark+.control-sidebar-bg{background:#191b22}.control-sidebar-dark .nav-tabs.control-sidebar-tabs{border-bottom:#131419}.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a{background:#0e0f13;color:#abb0c2}.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:hover,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:focus{border-left-color:#0a0b0d;border-bottom-color:#0a0b0d}.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:hover,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:focus,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:active{background:#131419}.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:hover{color:#fff}.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li.active>a,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li.active>a:hover,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li.active>a:focus,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li.active>a:active{background:#191b22;color:#fff}.control-sidebar-dark .control-sidebar-heading,.control-sidebar-dark .control-sidebar-subheading{color:#fff}.control-sidebar-dark .control-sidebar-menu>li>a:hover{background:#15161c}.control-sidebar-dark .control-sidebar-menu>li>a .menu-info>p{color:#abb0c2}.control-sidebar-light{color:#5e5e5e}.control-sidebar-light,.control-sidebar-light+.control-sidebar-bg{background:#f9fafc;border-left:1px solid #eaecf1}.control-sidebar-light .nav-tabs.control-sidebar-tabs{border-bottom:#eaecf1}.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a{background:#e8ecf4;color:#444}.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:hover,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:focus{border-left-color:#eaecf1;border-bottom-color:#eaecf1}.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:hover,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:focus,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:active{background:#eff1f7}.control-sidebar-light .nav-tabs.control-sidebar-tabs>li.active>a,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li.active>a:hover,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li.active>a:focus,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li.active>a:active{background:#f9fafc;color:#111}.control-sidebar-light .control-sidebar-heading,.control-sidebar-light .control-sidebar-subheading{color:#111}.control-sidebar-light .control-sidebar-menu{margin-left:-14px}.control-sidebar-light .control-sidebar-menu>li>a:hover{background:#f4f4f5}.control-sidebar-light .control-sidebar-menu>li>a .menu-info>p{color:#5e5e5e}.dropdown-menu{box-shadow:none;border-color:#eee}.dropdown-menu>li>a{color:#777}.dropdown-menu>li>a>.glyphicon,.dropdown-menu>li>a>.fa,.dropdown-menu>li>a>.ion{margin-right:10px}.dropdown-menu>li>a:hover{background-color:#f9fafb;color:#333}.dropdown-menu>.divider{background-color:#eee}.navbar-nav>.notifications-menu>.dropdown-menu,.navbar-nav>.messages-menu>.dropdown-menu,.navbar-nav>.tasks-menu>.dropdown-menu{width:280px;padding:0 0 0 0;margin:0;top:100%}.navbar-nav>.notifications-menu>.dropdown-menu>li,.navbar-nav>.messages-menu>.dropdown-menu>li,.navbar-nav>.tasks-menu>.dropdown-menu>li{position:relative}.navbar-nav>.notifications-menu>.dropdown-menu>li.header,.navbar-nav>.messages-menu>.dropdown-menu>li.header,.navbar-nav>.tasks-menu>.dropdown-menu>li.header{border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0;background-color:#ffffff;padding:7px 10px;border-bottom:1px solid #f4f4f4;color:#444444;font-size:14px}.navbar-nav>.notifications-menu>.dropdown-menu>li.footer>a,.navbar-nav>.messages-menu>.dropdown-menu>li.footer>a,.navbar-nav>.tasks-menu>.dropdown-menu>li.footer>a{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px;font-size:12px;background-color:#fff;padding:7px 10px;border-bottom:1px solid #eeeeee;color:#444 !important;text-align:center}@media (max-width:991px){.navbar-nav>.notifications-menu>.dropdown-menu>li.footer>a,.navbar-nav>.messages-menu>.dropdown-menu>li.footer>a,.navbar-nav>.tasks-menu>.dropdown-menu>li.footer>a{background:#fff !important;color:#444 !important}}.navbar-nav>.notifications-menu>.dropdown-menu>li.footer>a:hover,.navbar-nav>.messages-menu>.dropdown-menu>li.footer>a:hover,.navbar-nav>.tasks-menu>.dropdown-menu>li.footer>a:hover{text-decoration:none;font-weight:normal}.navbar-nav>.notifications-menu>.dropdown-menu>li .menu,.navbar-nav>.messages-menu>.dropdown-menu>li .menu,.navbar-nav>.tasks-menu>.dropdown-menu>li .menu{max-height:200px;margin:0;padding:0;list-style:none;overflow-x:hidden}.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a,.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a,.navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a{display:block;white-space:nowrap;border-bottom:1px solid #f4f4f4}.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a:hover,.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a:hover,.navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a:hover{background:#f4f4f4;text-decoration:none}.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a{color:#444444;overflow:hidden;text-overflow:ellipsis;padding:10px}.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a>.glyphicon,.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a>.fa,.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a>.ion{width:20px}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a{margin:0;padding:10px 10px}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a>div>img{margin:auto 10px auto auto;width:40px;height:40px}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a>h4{padding:0;margin:0 0 0 45px;color:#444444;font-size:15px;position:relative}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a>h4>small{color:#999999;font-size:10px;position:absolute;top:0;right:0}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a>p{margin:0 0 0 45px;font-size:12px;color:#888888}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a:before,.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a:after{content:" ";display:table}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a:after{clear:both}.navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a{padding:10px}.navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a>h3{font-size:14px;padding:0;margin:0 0 10px 0;color:#666666}.navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a>.progress{padding:0;margin:0}.navbar-nav>.user-menu>.dropdown-menu{border-top-right-radius:0;border-top-left-radius:0;padding:1px 0 0 0;border-top-width:0;width:280px}.navbar-nav>.user-menu>.dropdown-menu,.navbar-nav>.user-menu>.dropdown-menu>.user-body{border-bottom-right-radius:4px;border-bottom-left-radius:4px}.navbar-nav>.user-menu>.dropdown-menu>li.user-header{height:175px;padding:10px;text-align:center}.navbar-nav>.user-menu>.dropdown-menu>li.user-header>img{z-index:5;height:90px;width:90px;border:3px solid;border-color:transparent;border-color:rgba(255,255,255,0.2)}.navbar-nav>.user-menu>.dropdown-menu>li.user-header>p{z-index:5;color:#fff;color:rgba(255,255,255,0.8);font-size:17px;margin-top:10px}.navbar-nav>.user-menu>.dropdown-menu>li.user-header>p>small{display:block;font-size:12px}.navbar-nav>.user-menu>.dropdown-menu>.user-body{padding:15px;border-bottom:1px solid #f4f4f4;border-top:1px solid #dddddd}.navbar-nav>.user-menu>.dropdown-menu>.user-body:before,.navbar-nav>.user-menu>.dropdown-menu>.user-body:after{content:" ";display:table}.navbar-nav>.user-menu>.dropdown-menu>.user-body:after{clear:both}.navbar-nav>.user-menu>.dropdown-menu>.user-body a{color:#444 !important}@media (max-width:991px){.navbar-nav>.user-menu>.dropdown-menu>.user-body a{background:#fff !important;color:#444 !important}}.navbar-nav>.user-menu>.dropdown-menu>.user-footer{background-color:#f9f9f9;padding:10px}.navbar-nav>.user-menu>.dropdown-menu>.user-footer:before,.navbar-nav>.user-menu>.dropdown-menu>.user-footer:after{content:" ";display:table}.navbar-nav>.user-menu>.dropdown-menu>.user-footer:after{clear:both}.navbar-nav>.user-menu>.dropdown-menu>.user-footer .btn-default{color:#666666}@media (max-width:991px){.navbar-nav>.user-menu>.dropdown-menu>.user-footer .btn-default:hover{background-color:#f9f9f9}}.navbar-nav>.user-menu .user-image{float:left;width:25px;height:25px;border-radius:50%;margin-right:10px;margin-top:-2px}@media (max-width:767px){.navbar-nav>.user-menu .user-image{float:none;margin-right:0;margin-top:-8px;line-height:10px}}.open:not(.dropup)>.animated-dropdown-menu{backface-visibility:visible !important;-webkit-animation:flipInX .7s both;-o-animation:flipInX .7s both;animation:flipInX .7s both}@keyframes flipInX{0%{transform:perspective(400px) rotate3d(1, 0, 0, 90deg);transition-timing-function:ease-in;opacity:0}40%{transform:perspective(400px) rotate3d(1, 0, 0, -20deg);transition-timing-function:ease-in}60%{transform:perspective(400px) rotate3d(1, 0, 0, 10deg);opacity:1}80%{transform:perspective(400px) rotate3d(1, 0, 0, -5deg)}100%{transform:perspective(400px)}}@-webkit-keyframes flipInX{0%{-webkit-transform:perspective(400px) rotate3d(1, 0, 0, 90deg);-webkit-transition-timing-function:ease-in;opacity:0}40%{-webkit-transform:perspective(400px) rotate3d(1, 0, 0, -20deg);-webkit-transition-timing-function:ease-in}60%{-webkit-transform:perspective(400px) rotate3d(1, 0, 0, 10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotate3d(1, 0, 0, -5deg)}100%{-webkit-transform:perspective(400px)}}.navbar-custom-menu>.navbar-nav>li{position:relative}.navbar-custom-menu>.navbar-nav>li>.dropdown-menu{position:absolute;right:0;left:auto}@media (max-width:991px){.navbar-custom-menu>.navbar-nav{float:right}.navbar-custom-menu>.navbar-nav>li{position:static}.navbar-custom-menu>.navbar-nav>li>.dropdown-menu{position:absolute;right:5%;left:auto;border:1px solid #ddd;background:#fff}}.form-control{border-radius:0;box-shadow:none;border-color:#eaecf1}.form-control:focus{border-color:#10529f;box-shadow:none}.form-control::-moz-placeholder,.form-control:-ms-input-placeholder,.form-control::-webkit-input-placeholder{color:#bbb;opacity:1}.form-control:not(select){-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-group.has-success label{color:#00a65a}.form-group.has-success .form-control,.form-group.has-success .input-group-addon{border-color:#00a65a;box-shadow:none}.form-group.has-success .help-block{color:#00a65a}.form-group.has-warning label{color:#f39c12}.form-group.has-warning .form-control,.form-group.has-warning .input-group-addon{border-color:#f39c12;box-shadow:none}.form-group.has-warning .help-block{color:#f39c12}.form-group.has-error label{color:#dd4b39}.form-group.has-error .form-control,.form-group.has-error .input-group-addon{border-color:#dd4b39;box-shadow:none}.form-group.has-error .help-block{color:#dd4b39}.input-group .input-group-addon{border-radius:0;border-color:#eaecf1;background-color:#fff}.btn-group-vertical .btn.btn-flat:first-of-type,.btn-group-vertical .btn.btn-flat:last-of-type{border-radius:0}.icheck>label{padding-left:0}.form-control-feedback.fa{line-height:34px}.input-lg+.form-control-feedback.fa,.input-group-lg+.form-control-feedback.fa,.form-group-lg .form-control+.form-control-feedback.fa{line-height:46px}.input-sm+.form-control-feedback.fa,.input-group-sm+.form-control-feedback.fa,.form-group-sm .form-control+.form-control-feedback.fa{line-height:30px}.progress,.progress>.progress-bar{-webkit-box-shadow:none;box-shadow:none}.progress,.progress>.progress-bar,.progress .progress-bar,.progress>.progress-bar .progress-bar{border-radius:1px}.progress.sm,.progress-sm{height:10px}.progress.sm,.progress-sm,.progress.sm .progress-bar,.progress-sm .progress-bar{border-radius:1px}.progress.xs,.progress-xs{height:7px}.progress.xs,.progress-xs,.progress.xs .progress-bar,.progress-xs .progress-bar{border-radius:1px}.progress.xxs,.progress-xxs{height:3px}.progress.xxs,.progress-xxs,.progress.xxs .progress-bar,.progress-xxs .progress-bar{border-radius:1px}.progress.vertical{position:relative;width:30px;height:200px;display:inline-block;margin-right:10px}.progress.vertical>.progress-bar{width:100%;position:absolute;bottom:0}.progress.vertical.sm,.progress.vertical.progress-sm{width:20px}.progress.vertical.xs,.progress.vertical.progress-xs{width:10px}.progress.vertical.xxs,.progress.vertical.progress-xxs{width:3px}.progress-group .progress-text{font-weight:600}.progress-group .progress-number{float:right}.table tr>td .progress{margin:0}.progress-bar-light-blue,.progress-bar-primary{background-color:#10529f}.progress-striped .progress-bar-light-blue,.progress-striped .progress-bar-primary{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-green,.progress-bar-success{background-color:#00a65a}.progress-striped .progress-bar-green,.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-aqua,.progress-bar-info{background-color:#00c0ef}.progress-striped .progress-bar-aqua,.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-yellow,.progress-bar-warning{background-color:#f39c12}.progress-striped .progress-bar-yellow,.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-red,.progress-bar-danger{background-color:#dd4b39}.progress-striped .progress-bar-red,.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.small-box{border-radius:2px;position:relative;display:block;margin-bottom:20px;box-shadow:0 1px 1px rgba(0,0,0,0.1)}.small-box>.inner{padding:10px}.small-box>.small-box-footer{position:relative;text-align:center;padding:3px 0;color:#fff;color:rgba(255,255,255,0.8);display:block;z-index:10;background:rgba(0,0,0,0.1);text-decoration:none}.small-box>.small-box-footer:hover{color:#fff;background:rgba(0,0,0,0.15)}.small-box h3{font-size:38px;font-weight:bold;margin:0 0 10px 0;white-space:nowrap;padding:0}.small-box p{font-size:15px}.small-box p>small{display:block;color:#f9f9f9;font-size:13px;margin-top:5px}.small-box h3,.small-box p{z-index:5}.small-box .icon{-webkit-transition:all .3s linear;-o-transition:all .3s linear;transition:all .3s linear;position:absolute;top:-10px;right:10px;z-index:0;font-size:90px;color:rgba(0,0,0,0.15)}.small-box:hover{text-decoration:none;color:#f9f9f9}.small-box:hover .icon{font-size:95px}@media (max-width:767px){.small-box{text-align:center}.small-box .icon{display:none}.small-box p{font-size:12px}}.box{position:relative;border-radius:3px;background:#ffffff;border-top:3px solid #d2d6de;margin-bottom:20px;width:100%;box-shadow:0 1px 1px rgba(0,0,0,0.1)}.box.box-primary{border-top-color:#10529f}.box.box-info{border-top-color:#00c0ef}.box.box-danger{border-top-color:#dd4b39}.box.box-warning{border-top-color:#f39c12}.box.box-success{border-top-color:#00a65a}.box.box-default{border-top-color:#eaecf1}.box.collapsed-box .box-body,.box.collapsed-box .box-footer{display:none}.box .nav-stacked>li{border-bottom:1px solid #f4f4f4;margin:0}.box .nav-stacked>li:last-of-type{border-bottom:none}.box.height-control .box-body{max-height:300px;overflow:auto}.box .border-right{border-right:1px solid #f4f4f4}.box .border-left{border-left:1px solid #f4f4f4}.box.box-solid{border-top:0}.box.box-solid>.box-header .btn.btn-default{background:transparent}.box.box-solid>.box-header .btn:hover,.box.box-solid>.box-header a:hover{background:rgba(0,0,0,0.1)}.box.box-solid.box-default{border:1px solid #eaecf1}.box.box-solid.box-default>.box-header{color:#444;background:#eaecf1;background-color:#eaecf1}.box.box-solid.box-default>.box-header a,.box.box-solid.box-default>.box-header .btn{color:#444}.box.box-solid.box-primary{border:1px solid #10529f}.box.box-solid.box-primary>.box-header{color:#fff;background:#10529f;background-color:#10529f}.box.box-solid.box-primary>.box-header a,.box.box-solid.box-primary>.box-header .btn{color:#fff}.box.box-solid.box-info{border:1px solid #00c0ef}.box.box-solid.box-info>.box-header{color:#fff;background:#00c0ef;background-color:#00c0ef}.box.box-solid.box-info>.box-header a,.box.box-solid.box-info>.box-header .btn{color:#fff}.box.box-solid.box-danger{border:1px solid #dd4b39}.box.box-solid.box-danger>.box-header{color:#fff;background:#dd4b39;background-color:#dd4b39}.box.box-solid.box-danger>.box-header a,.box.box-solid.box-danger>.box-header .btn{color:#fff}.box.box-solid.box-warning{border:1px solid #f39c12}.box.box-solid.box-warning>.box-header{color:#fff;background:#f39c12;background-color:#f39c12}.box.box-solid.box-warning>.box-header a,.box.box-solid.box-warning>.box-header .btn{color:#fff}.box.box-solid.box-success{border:1px solid #00a65a}.box.box-solid.box-success>.box-header{color:#fff;background:#00a65a;background-color:#00a65a}.box.box-solid.box-success>.box-header a,.box.box-solid.box-success>.box-header .btn{color:#fff}.box.box-solid>.box-header>.box-tools .btn{border:0;box-shadow:none}.box.box-solid[class*='bg']>.box-header{color:#fff}.box .box-group>.box{margin-bottom:5px}.box .knob-label{text-align:center;color:#333;font-weight:100;font-size:12px;margin-bottom:0.3em}.box>.overlay,.overlay-wrapper>.overlay,.box>.loading-img,.overlay-wrapper>.loading-img{position:absolute;top:0;left:0;width:100%;height:100%}.box .overlay,.overlay-wrapper .overlay{z-index:50;background:rgba(255,255,255,0.7);border-radius:3px}.box .overlay>.fa,.overlay-wrapper .overlay>.fa{position:absolute;top:50%;left:50%;margin-left:-15px;margin-top:-15px;color:#000;font-size:30px}.box .overlay.dark,.overlay-wrapper .overlay.dark{background:rgba(0,0,0,0.5)}.box-header:before,.box-body:before,.box-footer:before,.box-header:after,.box-body:after,.box-footer:after{content:" ";display:table}.box-header:after,.box-body:after,.box-footer:after{clear:both}.box-header{color:#444;display:block;padding:10px;position:relative}.box-header.with-border{border-bottom:1px solid #f4f4f4}.collapsed-box .box-header.with-border{border-bottom:none}.box-header>.fa,.box-header>.glyphicon,.box-header>.ion,.box-header .box-title{display:inline-block;font-size:18px;margin:0;line-height:1}.box-header>.fa,.box-header>.glyphicon,.box-header>.ion{margin-right:5px}.box-header>.box-tools{position:absolute;right:10px;top:5px}.box-header>.box-tools [data-toggle="tooltip"]{position:relative}.box-header>.box-tools.pull-right .dropdown-menu{right:0;left:auto}.box-header>.box-tools .dropdown-menu>li>a{color:#444!important}.btn-box-tool{padding:5px;font-size:12px;background:transparent;color:#97a0b3}.open .btn-box-tool,.btn-box-tool:hover{color:#606c84}.btn-box-tool.btn:active{box-shadow:none}.box-body{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px;padding:10px}.no-header .box-body{border-top-right-radius:3px;border-top-left-radius:3px}.box-body>.table{margin-bottom:0}.box-body .fc{margin-top:5px}.box-body .full-width-chart{margin:-19px}.box-body.no-padding .full-width-chart{margin:-9px}.box-body .box-pane{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:3px}.box-body .box-pane-right{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:3px;border-bottom-left-radius:0}.box-footer{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px;border-top:1px solid #f4f4f4;padding:10px;background-color:#fff}.chart-legend{margin:10px 0}@media (max-width:991px){.chart-legend>li{float:left;margin-right:10px}}.box-comments{background:#f7f7f7}.box-comments .box-comment{padding:8px 0;border-bottom:1px solid #eee}.box-comments .box-comment:before,.box-comments .box-comment:after{content:" ";display:table}.box-comments .box-comment:after{clear:both}.box-comments .box-comment:last-of-type{border-bottom:0}.box-comments .box-comment:first-of-type{padding-top:0}.box-comments .box-comment img{float:left}.box-comments .comment-text{margin-left:40px;color:#555}.box-comments .username{color:#444;display:block;font-weight:600}.box-comments .text-muted{font-weight:400;font-size:12px}.todo-list{margin:0;padding:0;list-style:none;overflow:auto}.todo-list>li{border-radius:2px;padding:10px;background:#f4f4f4;margin-bottom:2px;border-left:2px solid #e6e7e8;color:#444}.todo-list>li:last-of-type{margin-bottom:0}.todo-list>li>input[type='checkbox']{margin:0 10px 0 5px}.todo-list>li .text{display:inline-block;margin-left:5px;font-weight:600}.todo-list>li .label{margin-left:10px;font-size:9px}.todo-list>li .tools{display:none;float:right;color:#dd4b39}.todo-list>li .tools>.fa,.todo-list>li .tools>.glyphicon,.todo-list>li .tools>.ion{margin-right:5px;cursor:pointer}.todo-list>li:hover .tools{display:inline-block}.todo-list>li.done{color:#999}.todo-list>li.done .text{text-decoration:line-through;font-weight:500}.todo-list>li.done .label{background:#eaecf1 !important}.todo-list .danger{border-left-color:#dd4b39}.todo-list .warning{border-left-color:#f39c12}.todo-list .info{border-left-color:#00c0ef}.todo-list .success{border-left-color:#00a65a}.todo-list .primary{border-left-color:#10529f}.todo-list .handle{display:inline-block;cursor:move;margin:0 5px}.chat{padding:5px 20px 5px 10px}.chat .item{margin-bottom:10px}.chat .item:before,.chat .item:after{content:" ";display:table}.chat .item:after{clear:both}.chat .item>img{width:40px;height:40px;border:2px solid transparent;border-radius:50%}.chat .item>.online{border:2px solid #00a65a}.chat .item>.offline{border:2px solid #dd4b39}.chat .item>.message{margin-left:55px;margin-top:-40px}.chat .item>.message>.name{display:block;font-weight:600}.chat .item>.attachment{border-radius:3px;background:#f4f4f4;margin-left:65px;margin-right:15px;padding:10px}.chat .item>.attachment>h4{margin:0 0 5px 0;font-weight:600;font-size:14px}.chat .item>.attachment>p,.chat .item>.attachment>.filename{font-weight:600;font-size:13px;font-style:italic;margin:0}.chat .item>.attachment:before,.chat .item>.attachment:after{content:" ";display:table}.chat .item>.attachment:after{clear:both}.box-input{max-width:200px}.modal .panel-body{color:#444}.info-box{display:block;min-height:90px;background:#fff;width:100%;box-shadow:0 1px 1px rgba(0,0,0,0.1);border-radius:2px;margin-bottom:15px}.info-box small{font-size:14px}.info-box .progress{background:rgba(0,0,0,0.2);margin:5px -10px 5px -10px;height:2px}.info-box .progress,.info-box .progress .progress-bar{border-radius:0}.info-box .progress .progress-bar{background:#fff}.info-box-icon{border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px;display:block;float:left;height:90px;width:90px;text-align:center;font-size:45px;line-height:90px;background:rgba(0,0,0,0.2)}.info-box-icon>img{max-width:100%}.info-box-content{padding:5px 10px;margin-left:90px}.info-box-number{display:block;font-weight:bold;font-size:18px}.progress-description,.info-box-text{display:block;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.info-box-text{text-transform:uppercase}.info-box-more{display:block}.progress-description{margin:0}.timeline{position:relative;margin:0 0 30px 0;padding:0;list-style:none}.timeline:before{content:'';position:absolute;top:0;bottom:0;width:4px;background:#ddd;left:31px;margin:0;border-radius:2px}.timeline>li{position:relative;margin-right:10px;margin-bottom:15px}.timeline>li:before,.timeline>li:after{content:" ";display:table}.timeline>li:after{clear:both}.timeline>li>.timeline-item{-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.1);box-shadow:0 1px 1px rgba(0,0,0,0.1);border-radius:3px;margin-top:0;background:#fff;color:#444;margin-left:60px;margin-right:15px;padding:0;position:relative}.timeline>li>.timeline-item>.time{color:#999;float:right;padding:10px;font-size:12px}.timeline>li>.timeline-item>.timeline-header{margin:0;color:#555;border-bottom:1px solid #f4f4f4;padding:10px;font-size:16px;line-height:1.1}.timeline>li>.timeline-item>.timeline-header>a{font-weight:600}.timeline>li>.timeline-item>.timeline-body,.timeline>li>.timeline-item>.timeline-footer{padding:10px}.timeline>li>.fa,.timeline>li>.glyphicon,.timeline>li>.ion{width:30px;height:30px;font-size:15px;line-height:30px;position:absolute;color:#666;background:#eaecf1;border-radius:50%;text-align:center;left:18px;top:0}.timeline>.time-label>span{font-weight:600;padding:5px;display:inline-block;background-color:#fff;border-radius:4px}.timeline-inverse>li>.timeline-item{background:#f0f0f0;border:1px solid #ddd;-webkit-box-shadow:none;box-shadow:none}.timeline-inverse>li>.timeline-item>.timeline-header{border-bottom-color:#ddd}.btn{border-radius:3px;-webkit-box-shadow:none;box-shadow:none;border:1px solid transparent}.btn.uppercase{text-transform:uppercase}.btn.btn-flat{border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;border-width:1px}.btn:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn:focus{outline:none}.btn.btn-file{position:relative;overflow:hidden}.btn.btn-file>input[type='file']{position:absolute;top:0;right:0;min-width:100%;min-height:100%;font-size:100px;text-align:right;opacity:0;filter:alpha(opacity=0);outline:none;background:white;cursor:inherit;display:block}.btn-default{background-color:#f4f4f4;color:#444;border-color:#ddd}.btn-default:hover,.btn-default:active,.btn-default.hover{background-color:#e7e7e7}.btn-primary{background-color:#10529f;border-color:#0e4688}.btn-primary:hover,.btn-primary:active,.btn-primary.hover{background-color:#0e4688}.btn-success{background-color:#00a65a;border-color:#008d4c}.btn-success:hover,.btn-success:active,.btn-success.hover{background-color:#008d4c}.btn-info{background-color:#00c0ef;border-color:#00acd6}.btn-info:hover,.btn-info:active,.btn-info.hover{background-color:#00acd6}.btn-danger{background-color:#dd4b39;border-color:#d73925}.btn-danger:hover,.btn-danger:active,.btn-danger.hover{background-color:#d73925}.btn-warning{background-color:#f39c12;border-color:#e08e0b}.btn-warning:hover,.btn-warning:active,.btn-warning.hover{background-color:#e08e0b}.btn-outline{border:1px solid #fff;background:transparent;color:#fff}.btn-outline:hover,.btn-outline:focus,.btn-outline:active{color:rgba(255,255,255,0.7);border-color:rgba(255,255,255,0.7)}.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn[class*='bg-']:hover{-webkit-box-shadow:inset 0 0 100px rgba(0,0,0,0.2);box-shadow:inset 0 0 100px rgba(0,0,0,0.2)}.btn-app{border-radius:3px;position:relative;padding:15px 5px;margin:0 0 10px 10px;min-width:80px;height:60px;text-align:center;color:#666;border:1px solid #ddd;background-color:#f4f4f4;font-size:12px}.btn-app>.fa,.btn-app>.glyphicon,.btn-app>.ion{font-size:20px;display:block}.btn-app:hover{background:#f4f4f4;color:#444;border-color:#aaa}.btn-app:active,.btn-app:focus{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn-app>.badge{position:absolute;top:-3px;right:-10px;font-size:10px;font-weight:400}.callout{border-radius:3px;margin:0 0 20px 0;padding:15px 30px 15px 15px;border-left:5px solid #eee}.callout a{color:#fff;text-decoration:underline}.callout a:hover{color:#eee}.callout h4{margin-top:0;font-weight:600}.callout p:last-child{margin-bottom:0}.callout code,.callout .highlight{background-color:#fff}.callout.callout-danger{border-color:#c23321}.callout.callout-warning{border-color:#c87f0a}.callout.callout-info{border-color:#0097bc}.callout.callout-success{border-color:#00733e}.alert{border-radius:3px}.alert h4{font-weight:600}.alert .icon{margin-right:10px}.alert .close{color:#000;opacity:.2;filter:alpha(opacity=20)}.alert .close:hover{opacity:.5;filter:alpha(opacity=50)}.alert a{color:#fff;text-decoration:underline}.alert-success{border-color:#008d4c}.alert-danger,.alert-error{border-color:#d73925}.alert-warning{border-color:#e08e0b}.alert-info{border-color:#00acd6}.nav>li>a:hover,.nav>li>a:active,.nav>li>a:focus{color:#444;background:#f7f7f7}.nav-pills>li>a{border-radius:0;border-top:3px solid transparent;color:#444}.nav-pills>li>a>.fa,.nav-pills>li>a>.glyphicon,.nav-pills>li>a>.ion{margin-right:5px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{border-top-color:#10529f}.nav-pills>li.active>a{font-weight:600}.nav-stacked>li>a{border-radius:0;border-top:0;border-left:3px solid transparent;color:#444}.nav-stacked>li.active>a,.nav-stacked>li.active>a:hover{background:transparent;color:#444;border-top:0;border-left-color:#10529f}.nav-stacked>li.header{border-bottom:1px solid #ddd;color:#777;margin-bottom:10px;padding:5px 10px;text-transform:uppercase}.nav-tabs-custom{margin-bottom:20px;background:#fff;box-shadow:0 1px 1px rgba(0,0,0,0.1);border-radius:3px}.nav-tabs-custom>.nav-tabs{margin:0;border-bottom-color:#f4f4f4;border-top-right-radius:3px;border-top-left-radius:3px}.nav-tabs-custom>.nav-tabs>li{border-top:3px solid transparent;margin-bottom:-2px;margin-right:5px}.nav-tabs-custom>.nav-tabs>li.disabled>a{color:#777}.nav-tabs-custom>.nav-tabs>li>a{color:#444;border-radius:0}.nav-tabs-custom>.nav-tabs>li>a.text-muted{color:#999}.nav-tabs-custom>.nav-tabs>li>a,.nav-tabs-custom>.nav-tabs>li>a:hover{background:transparent;margin:0}.nav-tabs-custom>.nav-tabs>li>a:hover{color:#999}.nav-tabs-custom>.nav-tabs>li:not(.active)>a:hover,.nav-tabs-custom>.nav-tabs>li:not(.active)>a:focus,.nav-tabs-custom>.nav-tabs>li:not(.active)>a:active{border-color:transparent}.nav-tabs-custom>.nav-tabs>li.active{border-top-color:#10529f}.nav-tabs-custom>.nav-tabs>li.active>a,.nav-tabs-custom>.nav-tabs>li.active:hover>a{background-color:#fff;color:#444}.nav-tabs-custom>.nav-tabs>li.active>a{border-top-color:transparent;border-left-color:#f4f4f4;border-right-color:#f4f4f4}.nav-tabs-custom>.nav-tabs>li:first-of-type{margin-left:0}.nav-tabs-custom>.nav-tabs>li:first-of-type.active>a{border-left-color:transparent}.nav-tabs-custom>.nav-tabs.pull-right{float:none !important}.nav-tabs-custom>.nav-tabs.pull-right>li{float:right}.nav-tabs-custom>.nav-tabs.pull-right>li:first-of-type{margin-right:0}.nav-tabs-custom>.nav-tabs.pull-right>li:first-of-type>a{border-left-width:1px}.nav-tabs-custom>.nav-tabs.pull-right>li:first-of-type.active>a{border-left-color:#f4f4f4;border-right-color:transparent}.nav-tabs-custom>.nav-tabs>li.header{line-height:35px;padding:0 10px;font-size:20px;color:#444}.nav-tabs-custom>.nav-tabs>li.header>.fa,.nav-tabs-custom>.nav-tabs>li.header>.glyphicon,.nav-tabs-custom>.nav-tabs>li.header>.ion{margin-right:5px}.nav-tabs-custom>.tab-content{background:#fff;padding:10px;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.nav-tabs-custom .dropdown.open>a:active,.nav-tabs-custom .dropdown.open>a:focus{background:transparent;color:#999}.nav-tabs-custom.tab-primary>.nav-tabs>li.active{border-top-color:#10529f}.nav-tabs-custom.tab-info>.nav-tabs>li.active{border-top-color:#00c0ef}.nav-tabs-custom.tab-danger>.nav-tabs>li.active{border-top-color:#dd4b39}.nav-tabs-custom.tab-warning>.nav-tabs>li.active{border-top-color:#f39c12}.nav-tabs-custom.tab-success>.nav-tabs>li.active{border-top-color:#00a65a}.nav-tabs-custom.tab-default>.nav-tabs>li.active{border-top-color:#eaecf1}.pagination>li>a{background:#fafafa;color:#666}.pagination.pagination-flat>li>a{border-radius:0 !important}.products-list{list-style:none;margin:0;padding:0}.products-list>.item{border-radius:3px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.1);box-shadow:0 1px 1px rgba(0,0,0,0.1);padding:10px 0;background:#fff}.products-list>.item:before,.products-list>.item:after{content:" ";display:table}.products-list>.item:after{clear:both}.products-list .product-img{float:left}.products-list .product-img img{width:50px;height:50px}.products-list .product-info{margin-left:60px}.products-list .product-title{font-weight:600}.products-list .product-description{display:block;color:#999;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.product-list-in-box>.item{-webkit-box-shadow:none;box-shadow:none;border-radius:0;border-bottom:1px solid #f4f4f4}.product-list-in-box>.item:last-of-type{border-bottom-width:0}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{border-top:1px solid #f4f4f4}.table>thead>tr>th{border-bottom:2px solid #f4f4f4}.table tr td .progress{margin-top:5px}.table-bordered{border:1px solid #f4f4f4}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #f4f4f4}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table.no-border,.table.no-border td,.table.no-border th{border:0}table.text-center,table.text-center td,table.text-center th{text-align:center}.table.align th{text-align:left}.table.align td{text-align:right}.label-default{background-color:#eaecf1;color:#444}.direct-chat .box-body{border-bottom-right-radius:0;border-bottom-left-radius:0;position:relative;overflow-x:hidden;padding:0}.direct-chat.chat-pane-open .direct-chat-contacts{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0)}.direct-chat-messages{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0);padding:10px;height:250px;overflow:auto}.direct-chat-msg,.direct-chat-text{display:block}.direct-chat-msg{margin-bottom:10px}.direct-chat-msg:before,.direct-chat-msg:after{content:" ";display:table}.direct-chat-msg:after{clear:both}.direct-chat-messages,.direct-chat-contacts{-webkit-transition:-webkit-transform .5s ease-in-out;-moz-transition:-moz-transform .5s ease-in-out;-o-transition:-o-transform .5s ease-in-out;transition:transform .5s ease-in-out}.direct-chat-text{border-radius:5px;position:relative;padding:5px 10px;background:#eaecf1;border:1px solid #eaecf1;margin:5px 0 0 50px;color:#444}.direct-chat-text:after,.direct-chat-text:before{position:absolute;right:100%;top:15px;border:solid transparent;border-right-color:#eaecf1;content:' ';height:0;width:0;pointer-events:none}.direct-chat-text:after{border-width:5px;margin-top:-5px}.direct-chat-text:before{border-width:6px;margin-top:-6px}.right .direct-chat-text{margin-right:50px;margin-left:0}.right .direct-chat-text:after,.right .direct-chat-text:before{right:auto;left:100%;border-right-color:transparent;border-left-color:#eaecf1}.direct-chat-img{border-radius:50%;float:left;width:40px;height:40px}.right .direct-chat-img{float:right}.direct-chat-info{display:block;margin-bottom:2px;font-size:12px}.direct-chat-name{font-weight:600}.direct-chat-timestamp{color:#999}.direct-chat-contacts-open .direct-chat-contacts{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0)}.direct-chat-contacts{-webkit-transform:translate(101%, 0);-ms-transform:translate(101%, 0);-o-transform:translate(101%, 0);transform:translate(101%, 0);position:absolute;top:0;bottom:0;height:250px;width:100%;background:#222d32;color:#fff;overflow:auto}.contacts-list>li{border-bottom:1px solid rgba(0,0,0,0.2);padding:10px;margin:0}.contacts-list>li:before,.contacts-list>li:after{content:" ";display:table}.contacts-list>li:after{clear:both}.contacts-list>li:last-of-type{border-bottom:none}.contacts-list-img{border-radius:50%;width:40px;float:left}.contacts-list-info{margin-left:45px;color:#fff}.contacts-list-name,.contacts-list-status{display:block}.contacts-list-name{font-weight:600}.contacts-list-status{font-size:12px}.contacts-list-date{color:#aaa;font-weight:normal}.contacts-list-msg{color:#999}.direct-chat-danger .right>.direct-chat-text{background:#dd4b39;border-color:#dd4b39;color:#fff}.direct-chat-danger .right>.direct-chat-text:after,.direct-chat-danger .right>.direct-chat-text:before{border-left-color:#dd4b39}.direct-chat-primary .right>.direct-chat-text{background:#10529f;border-color:#10529f;color:#fff}.direct-chat-primary .right>.direct-chat-text:after,.direct-chat-primary .right>.direct-chat-text:before{border-left-color:#10529f}.direct-chat-warning .right>.direct-chat-text{background:#f39c12;border-color:#f39c12;color:#fff}.direct-chat-warning .right>.direct-chat-text:after,.direct-chat-warning .right>.direct-chat-text:before{border-left-color:#f39c12}.direct-chat-info .right>.direct-chat-text{background:#00c0ef;border-color:#00c0ef;color:#fff}.direct-chat-info .right>.direct-chat-text:after,.direct-chat-info .right>.direct-chat-text:before{border-left-color:#00c0ef}.direct-chat-success .right>.direct-chat-text{background:#00a65a;border-color:#00a65a;color:#fff}.direct-chat-success .right>.direct-chat-text:after,.direct-chat-success .right>.direct-chat-text:before{border-left-color:#00a65a}.users-list>li{width:25%;float:left;padding:10px;text-align:center}.users-list>li img{border-radius:50%;max-width:100%;height:auto}.users-list>li>a:hover,.users-list>li>a:hover .users-list-name{color:#999}.users-list-name,.users-list-date{display:block}.users-list-name{font-weight:600;color:#444;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.users-list-date{color:#999;font-size:12px}.carousel-control.left,.carousel-control.right{background-image:none}.carousel-control>.fa{font-size:40px;position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-20px}.modal{background:rgba(0,0,0,0.3)}.modal-content{border-radius:0;-webkit-box-shadow:0 2px 3px rgba(0,0,0,0.125);box-shadow:0 2px 3px rgba(0,0,0,0.125);border:0}@media (min-width:768px){.modal-content{-webkit-box-shadow:0 2px 3px rgba(0,0,0,0.125);box-shadow:0 2px 3px rgba(0,0,0,0.125)}}.modal-header{border-bottom-color:#f4f4f4}.modal-footer{border-top-color:#f4f4f4}.modal-primary .modal-header,.modal-primary .modal-footer{border-color:#0b3a71}.modal-warning .modal-header,.modal-warning .modal-footer{border-color:#c87f0a}.modal-info .modal-header,.modal-info .modal-footer{border-color:#0097bc}.modal-success .modal-header,.modal-success .modal-footer{border-color:#00733e}.modal-danger .modal-header,.modal-danger .modal-footer{border-color:#c23321}.box-widget{border:none;position:relative}.widget-user .widget-user-header{padding:20px;height:120px;border-top-right-radius:3px;border-top-left-radius:3px}.widget-user .widget-user-username{margin-top:0;margin-bottom:5px;font-size:25px;font-weight:300;text-shadow:0 1px 1px rgba(0,0,0,0.2)}.widget-user .widget-user-desc{margin-top:0}.widget-user .widget-user-image{position:absolute;top:65px;left:50%;margin-left:-45px}.widget-user .widget-user-image>img{width:90px;height:auto;border:3px solid #fff}.widget-user .box-footer{padding-top:30px}.widget-user-2 .widget-user-header{padding:20px;border-top-right-radius:3px;border-top-left-radius:3px}.widget-user-2 .widget-user-username{margin-top:5px;margin-bottom:5px;font-size:25px;font-weight:300}.widget-user-2 .widget-user-desc{margin-top:0}.widget-user-2 .widget-user-username,.widget-user-2 .widget-user-desc{margin-left:75px}.widget-user-2 .widget-user-image>img{width:65px;height:auto;float:left}.treeview-menu{display:none;list-style:none;padding:0;margin:0;padding-left:5px}.treeview-menu .treeview-menu{padding-left:20px}.treeview-menu>li{margin:0}.treeview-menu>li>a{padding:5px 5px 5px 15px;display:block;font-size:14px}.treeview-menu>li>a>.fa,.treeview-menu>li>a>.glyphicon,.treeview-menu>li>a>.ion{width:20px}.treeview-menu>li>a>.pull-right-container>.fa-angle-left,.treeview-menu>li>a>.pull-right-container>.fa-angle-down,.treeview-menu>li>a>.fa-angle-left,.treeview-menu>li>a>.fa-angle-down{width:auto}.mailbox-messages>.table{margin:0}.mailbox-controls{padding:5px}.mailbox-controls.with-border{border-bottom:1px solid #f4f4f4}.mailbox-read-info{border-bottom:1px solid #f4f4f4;padding:10px}.mailbox-read-info h3{font-size:20px;margin:0}.mailbox-read-info h5{margin:0;padding:5px 0 0 0}.mailbox-read-time{color:#999;font-size:13px}.mailbox-read-message{padding:10px}.mailbox-attachments li{float:left;width:200px;border:1px solid #eee;margin-bottom:10px;margin-right:10px}.mailbox-attachment-name{font-weight:bold;color:#666}.mailbox-attachment-icon,.mailbox-attachment-info,.mailbox-attachment-size{display:block}.mailbox-attachment-info{padding:10px;background:#f4f4f4}.mailbox-attachment-size{color:#999;font-size:12px}.mailbox-attachment-icon{text-align:center;font-size:65px;color:#666;padding:20px 10px}.mailbox-attachment-icon.has-img{padding:0}.mailbox-attachment-icon.has-img>img{max-width:100%;height:auto}.lockscreen{background:#eaecf1}.lockscreen-logo{font-size:35px;text-align:center;margin-bottom:25px;font-weight:300}.lockscreen-logo a{color:#444}.lockscreen-wrapper{max-width:400px;margin:0 auto;margin-top:10%}.lockscreen .lockscreen-name{text-align:center;font-weight:600}.lockscreen-item{border-radius:4px;padding:0;background:#fff;position:relative;margin:10px auto 30px auto;width:290px}.lockscreen-image{border-radius:50%;position:absolute;left:-10px;top:-25px;background:#fff;padding:5px;z-index:10}.lockscreen-image>img{border-radius:50%;width:70px;height:70px}.lockscreen-credentials{margin-left:70px}.lockscreen-credentials .form-control{border:0}.lockscreen-credentials .btn{background-color:#fff;border:0;padding:0 10px}.lockscreen-footer{margin-top:10px}.login-logo,.register-logo{font-size:35px;text-align:center;margin-bottom:25px;font-weight:300}.login-logo a,.register-logo a{color:#444}.login-page,.register-page{background:#eaecf1}.login-box,.register-box{width:360px;margin:7% auto}@media (max-width:768px){.login-box,.register-box{width:90%;margin-top:20px}}.login-box-body,.register-box-body{background:#fff;padding:20px;border-top:0;color:#666}.login-box-body .form-control-feedback,.register-box-body .form-control-feedback{color:#777}.login-box-msg,.register-box-msg{margin:0;text-align:center;padding:0 20px 20px 20px}.social-auth-links{margin:10px 0}.error-page{width:600px;margin:20px auto 0 auto}@media (max-width:991px){.error-page{width:100%}}.error-page>.headline{float:left;font-size:100px;font-weight:300}@media (max-width:991px){.error-page>.headline{float:none;text-align:center}}.error-page>.error-content{margin-left:190px;display:block}@media (max-width:991px){.error-page>.error-content{margin-left:0}}.error-page>.error-content>h3{font-weight:300;font-size:25px}@media (max-width:991px){.error-page>.error-content>h3{text-align:center}}.invoice{position:relative;background:#fff;border:1px solid #f4f4f4;padding:20px;margin:10px 25px}.invoice-title{margin-top:0}.profile-user-img{margin:0 auto;width:100px;padding:3px;border:3px solid #eaecf1}.profile-username{font-size:21px;margin-top:5px}.post{border-bottom:1px solid #eaecf1;margin-bottom:15px;padding-bottom:15px;color:#666}.post:last-of-type{border-bottom:0;margin-bottom:0;padding-bottom:0}.post .user-block{margin-bottom:15px}.btn-social{position:relative;padding-left:44px;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.btn-social>:first-child{position:absolute;left:0;top:0;bottom:0;width:32px;line-height:34px;font-size:1.6em;text-align:center;border-right:1px solid rgba(0,0,0,0.2)}.btn-social.btn-lg{padding-left:61px}.btn-social.btn-lg>:first-child{line-height:45px;width:45px;font-size:1.8em}.btn-social.btn-sm{padding-left:38px}.btn-social.btn-sm>:first-child{line-height:28px;width:28px;font-size:1.4em}.btn-social.btn-xs{padding-left:30px}.btn-social.btn-xs>:first-child{line-height:20px;width:20px;font-size:1.2em}.btn-social-icon{position:relative;padding-left:44px;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;height:34px;width:34px;padding:0}.btn-social-icon>:first-child{position:absolute;left:0;top:0;bottom:0;width:32px;line-height:34px;font-size:1.6em;text-align:center;border-right:1px solid rgba(0,0,0,0.2)}.btn-social-icon.btn-lg{padding-left:61px}.btn-social-icon.btn-lg>:first-child{line-height:45px;width:45px;font-size:1.8em}.btn-social-icon.btn-sm{padding-left:38px}.btn-social-icon.btn-sm>:first-child{line-height:28px;width:28px;font-size:1.4em}.btn-social-icon.btn-xs{padding-left:30px}.btn-social-icon.btn-xs>:first-child{line-height:20px;width:20px;font-size:1.2em}.btn-social-icon>:first-child{border:none;text-align:center;width:100%}.btn-social-icon.btn-lg{height:45px;width:45px;padding-left:0;padding-right:0}.btn-social-icon.btn-sm{height:30px;width:30px;padding-left:0;padding-right:0}.btn-social-icon.btn-xs{height:22px;width:22px;padding-left:0;padding-right:0}.btn-adn{color:#fff;background-color:#d87a68;border-color:rgba(0,0,0,0.2)}.btn-adn:focus,.btn-adn.focus{color:#fff;background-color:#ce563f;border-color:rgba(0,0,0,0.2)}.btn-adn:hover{color:#fff;background-color:#ce563f;border-color:rgba(0,0,0,0.2)}.btn-adn:active,.btn-adn.active,.open>.dropdown-toggle.btn-adn{color:#fff;background-color:#ce563f;border-color:rgba(0,0,0,0.2)}.btn-adn:active,.btn-adn.active,.open>.dropdown-toggle.btn-adn{background-image:none}.btn-adn .badge{color:#d87a68;background-color:#fff}.btn-bitbucket{color:#fff;background-color:#205081;border-color:rgba(0,0,0,0.2)}.btn-bitbucket:focus,.btn-bitbucket.focus{color:#fff;background-color:#163758;border-color:rgba(0,0,0,0.2)}.btn-bitbucket:hover{color:#fff;background-color:#163758;border-color:rgba(0,0,0,0.2)}.btn-bitbucket:active,.btn-bitbucket.active,.open>.dropdown-toggle.btn-bitbucket{color:#fff;background-color:#163758;border-color:rgba(0,0,0,0.2)}.btn-bitbucket:active,.btn-bitbucket.active,.open>.dropdown-toggle.btn-bitbucket{background-image:none}.btn-bitbucket .badge{color:#205081;background-color:#fff}.btn-dropbox{color:#fff;background-color:#1087dd;border-color:rgba(0,0,0,0.2)}.btn-dropbox:focus,.btn-dropbox.focus{color:#fff;background-color:#0d6aad;border-color:rgba(0,0,0,0.2)}.btn-dropbox:hover{color:#fff;background-color:#0d6aad;border-color:rgba(0,0,0,0.2)}.btn-dropbox:active,.btn-dropbox.active,.open>.dropdown-toggle.btn-dropbox{color:#fff;background-color:#0d6aad;border-color:rgba(0,0,0,0.2)}.btn-dropbox:active,.btn-dropbox.active,.open>.dropdown-toggle.btn-dropbox{background-image:none}.btn-dropbox .badge{color:#1087dd;background-color:#fff}.btn-facebook{color:#fff;background-color:#3b5998;border-color:rgba(0,0,0,0.2)}.btn-facebook:focus,.btn-facebook.focus{color:#fff;background-color:#2d4373;border-color:rgba(0,0,0,0.2)}.btn-facebook:hover{color:#fff;background-color:#2d4373;border-color:rgba(0,0,0,0.2)}.btn-facebook:active,.btn-facebook.active,.open>.dropdown-toggle.btn-facebook{color:#fff;background-color:#2d4373;border-color:rgba(0,0,0,0.2)}.btn-facebook:active,.btn-facebook.active,.open>.dropdown-toggle.btn-facebook{background-image:none}.btn-facebook .badge{color:#3b5998;background-color:#fff}.btn-flickr{color:#fff;background-color:#ff0084;border-color:rgba(0,0,0,0.2)}.btn-flickr:focus,.btn-flickr.focus{color:#fff;background-color:#cc006a;border-color:rgba(0,0,0,0.2)}.btn-flickr:hover{color:#fff;background-color:#cc006a;border-color:rgba(0,0,0,0.2)}.btn-flickr:active,.btn-flickr.active,.open>.dropdown-toggle.btn-flickr{color:#fff;background-color:#cc006a;border-color:rgba(0,0,0,0.2)}.btn-flickr:active,.btn-flickr.active,.open>.dropdown-toggle.btn-flickr{background-image:none}.btn-flickr .badge{color:#ff0084;background-color:#fff}.btn-foursquare{color:#fff;background-color:#f94877;border-color:rgba(0,0,0,0.2)}.btn-foursquare:focus,.btn-foursquare.focus{color:#fff;background-color:#f71752;border-color:rgba(0,0,0,0.2)}.btn-foursquare:hover{color:#fff;background-color:#f71752;border-color:rgba(0,0,0,0.2)}.btn-foursquare:active,.btn-foursquare.active,.open>.dropdown-toggle.btn-foursquare{color:#fff;background-color:#f71752;border-color:rgba(0,0,0,0.2)}.btn-foursquare:active,.btn-foursquare.active,.open>.dropdown-toggle.btn-foursquare{background-image:none}.btn-foursquare .badge{color:#f94877;background-color:#fff}.btn-github{color:#fff;background-color:#444;border-color:rgba(0,0,0,0.2)}.btn-github:focus,.btn-github.focus{color:#fff;background-color:#2b2b2b;border-color:rgba(0,0,0,0.2)}.btn-github:hover{color:#fff;background-color:#2b2b2b;border-color:rgba(0,0,0,0.2)}.btn-github:active,.btn-github.active,.open>.dropdown-toggle.btn-github{color:#fff;background-color:#2b2b2b;border-color:rgba(0,0,0,0.2)}.btn-github:active,.btn-github.active,.open>.dropdown-toggle.btn-github{background-image:none}.btn-github .badge{color:#444;background-color:#fff}.btn-google{color:#fff;background-color:#dd4b39;border-color:rgba(0,0,0,0.2)}.btn-google:focus,.btn-google.focus{color:#fff;background-color:#c23321;border-color:rgba(0,0,0,0.2)}.btn-google:hover{color:#fff;background-color:#c23321;border-color:rgba(0,0,0,0.2)}.btn-google:active,.btn-google.active,.open>.dropdown-toggle.btn-google{color:#fff;background-color:#c23321;border-color:rgba(0,0,0,0.2)}.btn-google:active,.btn-google.active,.open>.dropdown-toggle.btn-google{background-image:none}.btn-google .badge{color:#dd4b39;background-color:#fff}.btn-instagram{color:#fff;background-color:#3f729b;border-color:rgba(0,0,0,0.2)}.btn-instagram:focus,.btn-instagram.focus{color:#fff;background-color:#305777;border-color:rgba(0,0,0,0.2)}.btn-instagram:hover{color:#fff;background-color:#305777;border-color:rgba(0,0,0,0.2)}.btn-instagram:active,.btn-instagram.active,.open>.dropdown-toggle.btn-instagram{color:#fff;background-color:#305777;border-color:rgba(0,0,0,0.2)}.btn-instagram:active,.btn-instagram.active,.open>.dropdown-toggle.btn-instagram{background-image:none}.btn-instagram .badge{color:#3f729b;background-color:#fff}.btn-linkedin{color:#fff;background-color:#007bb6;border-color:rgba(0,0,0,0.2)}.btn-linkedin:focus,.btn-linkedin.focus{color:#fff;background-color:#005983;border-color:rgba(0,0,0,0.2)}.btn-linkedin:hover{color:#fff;background-color:#005983;border-color:rgba(0,0,0,0.2)}.btn-linkedin:active,.btn-linkedin.active,.open>.dropdown-toggle.btn-linkedin{color:#fff;background-color:#005983;border-color:rgba(0,0,0,0.2)}.btn-linkedin:active,.btn-linkedin.active,.open>.dropdown-toggle.btn-linkedin{background-image:none}.btn-linkedin .badge{color:#007bb6;background-color:#fff}.btn-microsoft{color:#fff;background-color:#2672ec;border-color:rgba(0,0,0,0.2)}.btn-microsoft:focus,.btn-microsoft.focus{color:#fff;background-color:#125acd;border-color:rgba(0,0,0,0.2)}.btn-microsoft:hover{color:#fff;background-color:#125acd;border-color:rgba(0,0,0,0.2)}.btn-microsoft:active,.btn-microsoft.active,.open>.dropdown-toggle.btn-microsoft{color:#fff;background-color:#125acd;border-color:rgba(0,0,0,0.2)}.btn-microsoft:active,.btn-microsoft.active,.open>.dropdown-toggle.btn-microsoft{background-image:none}.btn-microsoft .badge{color:#2672ec;background-color:#fff}.btn-openid{color:#fff;background-color:#f7931e;border-color:rgba(0,0,0,0.2)}.btn-openid:focus,.btn-openid.focus{color:#fff;background-color:#da7908;border-color:rgba(0,0,0,0.2)}.btn-openid:hover{color:#fff;background-color:#da7908;border-color:rgba(0,0,0,0.2)}.btn-openid:active,.btn-openid.active,.open>.dropdown-toggle.btn-openid{color:#fff;background-color:#da7908;border-color:rgba(0,0,0,0.2)}.btn-openid:active,.btn-openid.active,.open>.dropdown-toggle.btn-openid{background-image:none}.btn-openid .badge{color:#f7931e;background-color:#fff}.btn-pinterest{color:#fff;background-color:#cb2027;border-color:rgba(0,0,0,0.2)}.btn-pinterest:focus,.btn-pinterest.focus{color:#fff;background-color:#9f191f;border-color:rgba(0,0,0,0.2)}.btn-pinterest:hover{color:#fff;background-color:#9f191f;border-color:rgba(0,0,0,0.2)}.btn-pinterest:active,.btn-pinterest.active,.open>.dropdown-toggle.btn-pinterest{color:#fff;background-color:#9f191f;border-color:rgba(0,0,0,0.2)}.btn-pinterest:active,.btn-pinterest.active,.open>.dropdown-toggle.btn-pinterest{background-image:none}.btn-pinterest .badge{color:#cb2027;background-color:#fff}.btn-reddit{color:#000;background-color:#eff7ff;border-color:rgba(0,0,0,0.2)}.btn-reddit:focus,.btn-reddit.focus{color:#000;background-color:#bcddff;border-color:rgba(0,0,0,0.2)}.btn-reddit:hover{color:#000;background-color:#bcddff;border-color:rgba(0,0,0,0.2)}.btn-reddit:active,.btn-reddit.active,.open>.dropdown-toggle.btn-reddit{color:#000;background-color:#bcddff;border-color:rgba(0,0,0,0.2)}.btn-reddit:active,.btn-reddit.active,.open>.dropdown-toggle.btn-reddit{background-image:none}.btn-reddit .badge{color:#eff7ff;background-color:#000}.btn-soundcloud{color:#fff;background-color:#f50;border-color:rgba(0,0,0,0.2)}.btn-soundcloud:focus,.btn-soundcloud.focus{color:#fff;background-color:#c40;border-color:rgba(0,0,0,0.2)}.btn-soundcloud:hover{color:#fff;background-color:#c40;border-color:rgba(0,0,0,0.2)}.btn-soundcloud:active,.btn-soundcloud.active,.open>.dropdown-toggle.btn-soundcloud{color:#fff;background-color:#c40;border-color:rgba(0,0,0,0.2)}.btn-soundcloud:active,.btn-soundcloud.active,.open>.dropdown-toggle.btn-soundcloud{background-image:none}.btn-soundcloud .badge{color:#f50;background-color:#fff}.btn-tumblr{color:#fff;background-color:#2c4762;border-color:rgba(0,0,0,0.2)}.btn-tumblr:focus,.btn-tumblr.focus{color:#fff;background-color:#1c2d3f;border-color:rgba(0,0,0,0.2)}.btn-tumblr:hover{color:#fff;background-color:#1c2d3f;border-color:rgba(0,0,0,0.2)}.btn-tumblr:active,.btn-tumblr.active,.open>.dropdown-toggle.btn-tumblr{color:#fff;background-color:#1c2d3f;border-color:rgba(0,0,0,0.2)}.btn-tumblr:active,.btn-tumblr.active,.open>.dropdown-toggle.btn-tumblr{background-image:none}.btn-tumblr .badge{color:#2c4762;background-color:#fff}.btn-twitter{color:#fff;background-color:#55acee;border-color:rgba(0,0,0,0.2)}.btn-twitter:focus,.btn-twitter.focus{color:#fff;background-color:#2795e9;border-color:rgba(0,0,0,0.2)}.btn-twitter:hover{color:#fff;background-color:#2795e9;border-color:rgba(0,0,0,0.2)}.btn-twitter:active,.btn-twitter.active,.open>.dropdown-toggle.btn-twitter{color:#fff;background-color:#2795e9;border-color:rgba(0,0,0,0.2)}.btn-twitter:active,.btn-twitter.active,.open>.dropdown-toggle.btn-twitter{background-image:none}.btn-twitter .badge{color:#55acee;background-color:#fff}.btn-vimeo{color:#fff;background-color:#1ab7ea;border-color:rgba(0,0,0,0.2)}.btn-vimeo:focus,.btn-vimeo.focus{color:#fff;background-color:#1295bf;border-color:rgba(0,0,0,0.2)}.btn-vimeo:hover{color:#fff;background-color:#1295bf;border-color:rgba(0,0,0,0.2)}.btn-vimeo:active,.btn-vimeo.active,.open>.dropdown-toggle.btn-vimeo{color:#fff;background-color:#1295bf;border-color:rgba(0,0,0,0.2)}.btn-vimeo:active,.btn-vimeo.active,.open>.dropdown-toggle.btn-vimeo{background-image:none}.btn-vimeo .badge{color:#1ab7ea;background-color:#fff}.btn-vk{color:#fff;background-color:#587ea3;border-color:rgba(0,0,0,0.2)}.btn-vk:focus,.btn-vk.focus{color:#fff;background-color:#466482;border-color:rgba(0,0,0,0.2)}.btn-vk:hover{color:#fff;background-color:#466482;border-color:rgba(0,0,0,0.2)}.btn-vk:active,.btn-vk.active,.open>.dropdown-toggle.btn-vk{color:#fff;background-color:#466482;border-color:rgba(0,0,0,0.2)}.btn-vk:active,.btn-vk.active,.open>.dropdown-toggle.btn-vk{background-image:none}.btn-vk .badge{color:#587ea3;background-color:#fff}.btn-yahoo{color:#fff;background-color:#720e9e;border-color:rgba(0,0,0,0.2)}.btn-yahoo:focus,.btn-yahoo.focus{color:#fff;background-color:#500a6f;border-color:rgba(0,0,0,0.2)}.btn-yahoo:hover{color:#fff;background-color:#500a6f;border-color:rgba(0,0,0,0.2)}.btn-yahoo:active,.btn-yahoo.active,.open>.dropdown-toggle.btn-yahoo{color:#fff;background-color:#500a6f;border-color:rgba(0,0,0,0.2)}.btn-yahoo:active,.btn-yahoo.active,.open>.dropdown-toggle.btn-yahoo{background-image:none}.btn-yahoo .badge{color:#720e9e;background-color:#fff}.fc-button{background:#f4f4f4;background-image:none;color:#444;border-color:#ddd;border-bottom-color:#ddd}.fc-button:hover,.fc-button:active,.fc-button.hover{background-color:#e9e9e9}.fc-header-title h2{font-size:15px;line-height:1.6em;color:#666;margin-left:10px}.fc-header-right{padding-right:10px}.fc-header-left{padding-left:10px}.fc-widget-header{background:#fafafa}.fc-grid{width:100%;border:0}.fc-widget-header:first-of-type,.fc-widget-content:first-of-type{border-left:0;border-right:0}.fc-widget-header:last-of-type,.fc-widget-content:last-of-type{border-right:0}.fc-toolbar{padding:10px;margin:0}.fc-day-number{font-size:20px;font-weight:300;padding-right:10px}.fc-color-picker{list-style:none;margin:0;padding:0}.fc-color-picker>li{float:left;font-size:30px;margin-right:5px;line-height:30px}.fc-color-picker>li .fa{-webkit-transition:-webkit-transform linear .3s;-moz-transition:-moz-transform linear .3s;-o-transition:-o-transform linear .3s;transition:transform linear .3s}.fc-color-picker>li .fa:hover{-webkit-transform:rotate(30deg);-ms-transform:rotate(30deg);-o-transform:rotate(30deg);transform:rotate(30deg)}#add-new-event{-webkit-transition:all linear .3s;-o-transition:all linear .3s;transition:all linear .3s}.external-event{padding:5px 10px;font-weight:bold;margin-bottom:4px;box-shadow:0 1px 1px rgba(0,0,0,0.1);text-shadow:0 1px 1px rgba(0,0,0,0.1);border-radius:3px;cursor:move}.external-event:hover{box-shadow:inset 0 0 90px rgba(0,0,0,0.2)}.select2-container--default.select2-container--focus,.select2-selection.select2-container--focus,.select2-container--default:focus,.select2-selection:focus,.select2-container--default:active,.select2-selection:active{outline:none}.select2-container--default .select2-selection--single,.select2-selection .select2-selection--single{border:1px solid #eaecf1;border-radius:0;padding:6px 12px;height:34px}.select2-container--default.select2-container--open{border-color:#10529f}.select2-dropdown{border:1px solid #eaecf1;border-radius:0}.select2-container--default .select2-results__option--highlighted[aria-selected]{background-color:#10529f;color:white}.select2-results__option{padding:6px 12px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--single .select2-selection__rendered{padding-left:0;padding-right:0;height:auto;margin-top:-4px}.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered{padding-right:6px;padding-left:20px}.select2-container--default .select2-selection--single .select2-selection__arrow{height:28px;right:3px}.select2-container--default .select2-selection--single .select2-selection__arrow b{margin-top:0}.select2-dropdown .select2-search__field,.select2-search--inline .select2-search__field{border:1px solid #eaecf1}.select2-dropdown .select2-search__field:focus,.select2-search--inline .select2-search__field:focus{outline:none}.select2-container--default.select2-container--focus .select2-selection--multiple,.select2-container--default .select2-search--dropdown .select2-search__field{border-color:#10529f !important}.select2-container--default .select2-results__option[aria-disabled=true]{color:#999}.select2-container--default .select2-results__option[aria-selected=true]{background-color:#ddd}.select2-container--default .select2-results__option[aria-selected=true],.select2-container--default .select2-results__option[aria-selected=true]:hover{color:#444}.select2-container--default .select2-selection--multiple{border:1px solid #eaecf1;border-radius:0}.select2-container--default .select2-selection--multiple:focus{border-color:#10529f}.select2-container--default.select2-container--focus .select2-selection--multiple{border-color:#eaecf1}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#10529f;border-color:#0e4688;padding:1px 10px;color:#fff}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove{margin-right:5px;color:rgba(255,255,255,0.7)}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover{color:#fff}.select2-container .select2-selection--single .select2-selection__rendered{padding-right:10px}.box .datepicker-inline,.box .datepicker-inline .datepicker-days,.box .datepicker-inline>table,.box .datepicker-inline .datepicker-days>table{width:100%}.box .datepicker-inline td:hover,.box .datepicker-inline .datepicker-days td:hover,.box .datepicker-inline>table td:hover,.box .datepicker-inline .datepicker-days>table td:hover{background-color:rgba(255,255,255,0.3)}.box .datepicker-inline td.day.old,.box .datepicker-inline .datepicker-days td.day.old,.box .datepicker-inline>table td.day.old,.box .datepicker-inline .datepicker-days>table td.day.old,.box .datepicker-inline td.day.new,.box .datepicker-inline .datepicker-days td.day.new,.box .datepicker-inline>table td.day.new,.box .datepicker-inline .datepicker-days>table td.day.new{color:#777}.pad{padding:10px}.margin{margin:10px}.margin-bottom{margin-bottom:20px}.margin-bottom-none{margin-bottom:0}.margin-r-5{margin-right:5px}.inline{display:inline}.description-block{display:block;margin:10px 0;text-align:center}.description-block.margin-bottom{margin-bottom:25px}.description-block>.description-header{margin:0;padding:0;font-weight:600;font-size:16px}.description-block>.description-text{text-transform:uppercase}.bg-red,.bg-yellow,.bg-aqua,.bg-blue,.bg-light-blue,.bg-green,.bg-navy,.bg-teal,.bg-olive,.bg-lime,.bg-orange,.bg-fuchsia,.bg-purple,.bg-maroon,.bg-black,.bg-red-active,.bg-yellow-active,.bg-aqua-active,.bg-blue-active,.bg-light-blue-active,.bg-green-active,.bg-navy-active,.bg-teal-active,.bg-olive-active,.bg-lime-active,.bg-orange-active,.bg-fuchsia-active,.bg-purple-active,.bg-maroon-active,.bg-black-active,.callout.callout-danger,.callout.callout-warning,.callout.callout-info,.callout.callout-success,.alert-success,.alert-danger,.alert-error,.alert-warning,.alert-info,.label-danger,.label-info,.label-warning,.label-primary,.label-success,.modal-primary .modal-body,.modal-primary .modal-header,.modal-primary .modal-footer,.modal-warning .modal-body,.modal-warning .modal-header,.modal-warning .modal-footer,.modal-info .modal-body,.modal-info .modal-header,.modal-info .modal-footer,.modal-success .modal-body,.modal-success .modal-header,.modal-success .modal-footer,.modal-danger .modal-body,.modal-danger .modal-header,.modal-danger .modal-footer{color:#fff !important}.bg-gray{color:#000;background-color:#eaecf1 !important}.bg-gray-light{background-color:#f7f7f7}.bg-black{background-color:#111 !important}.bg-red,.callout.callout-danger,.alert-danger,.alert-error,.label-danger,.modal-danger .modal-body{background-color:#dd4b39 !important}.bg-yellow,.callout.callout-warning,.alert-warning,.label-warning,.modal-warning .modal-body{background-color:#f39c12 !important}.bg-aqua,.callout.callout-info,.alert-info,.label-info,.modal-info .modal-body{background-color:#00c0ef !important}.bg-blue{background-color:#10529f !important}.bg-light-blue,.label-primary,.modal-primary .modal-body{background-color:#10529f !important}.bg-green,.callout.callout-success,.alert-success,.label-success,.modal-success .modal-body{background-color:#00a65a !important}.bg-navy{background-color:#001f3f !important}.bg-teal{background-color:#39cccc !important}.bg-olive{background-color:#3d9970 !important}.bg-lime{background-color:#01ff70 !important}.bg-orange{background-color:#ff851b !important}.bg-fuchsia{background-color:#f012be !important}.bg-purple{background-color:#605ca8 !important}.bg-maroon{background-color:#d81b60 !important}.bg-gray-active{color:#000;background-color:#cbd0dd !important}.bg-black-active{background-color:#000 !important}.bg-red-active,.modal-danger .modal-header,.modal-danger .modal-footer{background-color:#d33724 !important}.bg-yellow-active,.modal-warning .modal-header,.modal-warning .modal-footer{background-color:#db8b0b !important}.bg-aqua-active,.modal-info .modal-header,.modal-info .modal-footer{background-color:#00a7d0 !important}.bg-blue-active{background-color:#0b3a71 !important}.bg-light-blue-active,.modal-primary .modal-header,.modal-primary .modal-footer{background-color:#0d4483 !important}.bg-green-active,.modal-success .modal-header,.modal-success .modal-footer{background-color:#008d4c !important}.bg-navy-active{background-color:#001a35 !important}.bg-teal-active{background-color:#30bbbb !important}.bg-olive-active{background-color:#368763 !important}.bg-lime-active{background-color:#00e765 !important}.bg-orange-active{background-color:#ff7701 !important}.bg-fuchsia-active{background-color:#db0ead !important}.bg-purple-active{background-color:#555299 !important}.bg-maroon-active{background-color:#ca195a !important}[class^="bg-"].disabled{opacity:.65;filter:alpha(opacity=65)}.text-red{color:#dd4b39 !important}.text-yellow{color:#f39c12 !important}.text-aqua{color:#00c0ef !important}.text-blue{color:#10529f !important}.text-black{color:#111 !important}.text-light-blue{color:#10529f !important}.text-green{color:#00a65a !important}.text-gray{color:#eaecf1 !important}.text-navy{color:#001f3f !important}.text-teal{color:#39cccc !important}.text-olive{color:#3d9970 !important}.text-lime{color:#01ff70 !important}.text-orange{color:#ff851b !important}.text-fuchsia{color:#f012be !important}.text-purple{color:#605ca8 !important}.text-maroon{color:#d81b60 !important}.link-muted{color:#8e99b4}.link-muted:hover,.link-muted:focus{color:#707d9f}.link-black{color:#666}.link-black:hover,.link-black:focus{color:#999}.hide{display:none !important}.no-border{border:0 !important}.no-padding{padding:0 !important}.no-margin{margin:0 !important}.no-shadow{box-shadow:none !important}.list-unstyled,.chart-legend,.contacts-list,.users-list,.mailbox-attachments{list-style:none;margin:0;padding:0}.list-group-unbordered>.list-group-item{border-left:0;border-right:0;border-radius:0;padding-left:0;padding-right:0}.flat{border-radius:0 !important}.text-bold,.text-bold.table td,.text-bold.table th{font-weight:700}.text-sm{font-size:12px}.jqstooltip{padding:5px !important;width:auto !important;height:auto !important}.bg-teal-gradient{background:#39cccc !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #39cccc), color-stop(1, #7adddd)) !important;background:-ms-linear-gradient(bottom, #39cccc, #7adddd) !important;background:-moz-linear-gradient(center bottom, #39cccc 0, #7adddd 100%) !important;background:-o-linear-gradient(#7adddd, #39cccc) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#7adddd', endColorstr='#39cccc', GradientType=0) !important;color:#fff}.bg-light-blue-gradient{background:#10529f !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #10529f), color-stop(1, #166fd7)) !important;background:-ms-linear-gradient(bottom, #10529f, #166fd7) !important;background:-moz-linear-gradient(center bottom, #10529f 0, #166fd7 100%) !important;background:-o-linear-gradient(#166fd7, #10529f) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#166fd7', endColorstr='#10529f', GradientType=0) !important;color:#fff}.bg-blue-gradient{background:#10529f !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #10529f), color-stop(1, #1363bf)) !important;background:-ms-linear-gradient(bottom, #10529f, #1363bf) !important;background:-moz-linear-gradient(center bottom, #10529f 0, #1363bf 100%) !important;background:-o-linear-gradient(#1363bf, #10529f) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#1363bf', endColorstr='#10529f', GradientType=0) !important;color:#fff}.bg-aqua-gradient{background:#00c0ef !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #00c0ef), color-stop(1, #14d1ff)) !important;background:-ms-linear-gradient(bottom, #00c0ef, #14d1ff) !important;background:-moz-linear-gradient(center bottom, #00c0ef 0, #14d1ff 100%) !important;background:-o-linear-gradient(#14d1ff, #00c0ef) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#14d1ff', endColorstr='#00c0ef', GradientType=0) !important;color:#fff}.bg-yellow-gradient{background:#f39c12 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #f39c12), color-stop(1, #f7bc60)) !important;background:-ms-linear-gradient(bottom, #f39c12, #f7bc60) !important;background:-moz-linear-gradient(center bottom, #f39c12 0, #f7bc60 100%) !important;background:-o-linear-gradient(#f7bc60, #f39c12) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f7bc60', endColorstr='#f39c12', GradientType=0) !important;color:#fff}.bg-purple-gradient{background:#605ca8 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #605ca8), color-stop(1, #9491c4)) !important;background:-ms-linear-gradient(bottom, #605ca8, #9491c4) !important;background:-moz-linear-gradient(center bottom, #605ca8 0, #9491c4 100%) !important;background:-o-linear-gradient(#9491c4, #605ca8) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#9491c4', endColorstr='#605ca8', GradientType=0) !important;color:#fff}.bg-green-gradient{background:#00a65a !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #00a65a), color-stop(1, #00ca6d)) !important;background:-ms-linear-gradient(bottom, #00a65a, #00ca6d) !important;background:-moz-linear-gradient(center bottom, #00a65a 0, #00ca6d 100%) !important;background:-o-linear-gradient(#00ca6d, #00a65a) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ca6d', endColorstr='#00a65a', GradientType=0) !important;color:#fff}.bg-red-gradient{background:#dd4b39 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #dd4b39), color-stop(1, #e47365)) !important;background:-ms-linear-gradient(bottom, #dd4b39, #e47365) !important;background:-moz-linear-gradient(center bottom, #dd4b39 0, #e47365 100%) !important;background:-o-linear-gradient(#e47365, #dd4b39) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#e47365', endColorstr='#dd4b39', GradientType=0) !important;color:#fff}.bg-black-gradient{background:#111 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #111), color-stop(1, #2b2b2b)) !important;background:-ms-linear-gradient(bottom, #111, #2b2b2b) !important;background:-moz-linear-gradient(center bottom, #111 0, #2b2b2b 100%) !important;background:-o-linear-gradient(#2b2b2b, #111) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#2b2b2b', endColorstr='#111111', GradientType=0) !important;color:#fff}.bg-maroon-gradient{background:#d81b60 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #d81b60), color-stop(1, #e73f7c)) !important;background:-ms-linear-gradient(bottom, #d81b60, #e73f7c) !important;background:-moz-linear-gradient(center bottom, #d81b60 0, #e73f7c 100%) !important;background:-o-linear-gradient(#e73f7c, #d81b60) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#e73f7c', endColorstr='#d81b60', GradientType=0) !important;color:#fff}.description-block .description-icon{font-size:16px}.no-pad-top{padding-top:0}.position-static{position:static !important}.list-header{font-size:15px;padding:10px 4px;font-weight:bold;color:#666}.list-seperator{height:1px;background:#f4f4f4;margin:15px 0 9px 0}.list-link>a{padding:4px;color:#777}.list-link>a:hover{color:#222}.font-light{font-weight:300}.user-block:before,.user-block:after{content:" ";display:table}.user-block:after{clear:both}.user-block img{width:40px;height:40px;float:left}.user-block .username,.user-block .description,.user-block .comment{display:block;margin-left:50px}.user-block .username{font-size:16px;font-weight:600}.user-block .description{color:#999;font-size:13px}.user-block.user-block-sm .username,.user-block.user-block-sm .description,.user-block.user-block-sm .comment{margin-left:40px}.user-block.user-block-sm .username{font-size:14px}.img-sm,.img-md,.img-lg,.box-comments .box-comment img,.user-block.user-block-sm img{float:left}.img-sm,.box-comments .box-comment img,.user-block.user-block-sm img{width:30px !important;height:30px !important}.img-sm+.img-push{margin-left:40px}.img-md{width:60px;height:60px}.img-md+.img-push{margin-left:70px}.img-lg{width:100px;height:100px}.img-lg+.img-push{margin-left:110px}.img-bordered{border:3px solid #eaecf1;padding:3px}.img-bordered-sm{border:2px solid #eaecf1;padding:2px}.attachment-block{border:1px solid #f4f4f4;padding:5px;margin-bottom:10px;background:#f7f7f7}.attachment-block .attachment-img{max-width:100px;max-height:100px;height:auto;float:left}.attachment-block .attachment-pushed{margin-left:110px}.attachment-block .attachment-heading{margin:0}.attachment-block .attachment-text{color:#555}.connectedSortable{min-height:100px}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sort-highlight{background:#f4f4f4;border:1px dashed #ddd;margin-bottom:10px}.full-opacity-hover{opacity:.65;filter:alpha(opacity=65)}.full-opacity-hover:hover{opacity:1;filter:alpha(opacity=100)}.chart{position:relative;overflow:hidden;width:100%}.chart svg,.chart canvas{width:100% !important}@media print{.no-print,.main-sidebar,.left-side,.main-header,.content-header{display:none !important}.content-wrapper,.right-side,.main-footer{margin-left:0 !important;min-height:0 !important;-webkit-transform:translate(0, 0) !important;-ms-transform:translate(0, 0) !important;-o-transform:translate(0, 0) !important;transform:translate(0, 0) !important}.fixed .content-wrapper,.fixed .right-side{padding-top:0 !important}.invoice{width:100%;border:0;margin:0;padding:0}.invoice-col{float:left;width:33.3333333%}.table-responsive{overflow:auto}.table-responsive>.table tr th,.table-responsive>.table tr td{white-space:normal !important}} diff --git a/public/themes/pterodactyl/vendor/adminlte/app.min.js b/public/themes/pterodactyl/vendor/adminlte/app.min.js index 7efb107e1..c779c0c43 100755 --- a/public/themes/pterodactyl/vendor/adminlte/app.min.js +++ b/public/themes/pterodactyl/vendor/adminlte/app.min.js @@ -1,13 +1,14 @@ /*! AdminLTE app.js - * ================ - * Main JS application file for AdminLTE v2. This file - * should be included in all pages. It controls some layout - * options and implements exclusive AdminLTE plugins. - * - * @Author Almsaeed Studio - * @Support - * @Email - * @version 2.3.8 - * @license MIT - */ -function _init(){"use strict";$.AdminLTE.layout={activate:function(){var a=this;a.fix(),a.fixSidebar(),$("body, html, .wrapper").css("height","auto"),$(window,".wrapper").resize(function(){a.fix(),a.fixSidebar()})},fix:function(){$(".layout-boxed > .wrapper").css("overflow","hidden");var a=$(".main-footer").outerHeight()||0,b=$(".main-header").outerHeight()+a,c=$(window).height(),d=$(".sidebar").height()||0;if($("body").hasClass("fixed"))$(".content-wrapper, .right-side").css("min-height",c-a);else{var e;c>=d?($(".content-wrapper, .right-side").css("min-height",c-b),e=c-b):($(".content-wrapper, .right-side").css("min-height",d),e=d);var f=$($.AdminLTE.options.controlSidebarOptions.selector);"undefined"!=typeof f&&f.height()>e&&$(".content-wrapper, .right-side").css("min-height",f.height())}},fixSidebar:function(){return $("body").hasClass("fixed")?("undefined"==typeof $.fn.slimScroll&&window.console&&window.console.error("Error: the fixed layout requires the slimscroll plugin!"),void($.AdminLTE.options.sidebarSlimScroll&&"undefined"!=typeof $.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"})))):void("undefined"!=typeof $.fn.slimScroll&&$(".sidebar").slimScroll({destroy:!0}).height("auto"))}},$.AdminLTE.pushMenu={activate:function(a){var b=$.AdminLTE.options.screenSizes;$(document).on("click",a,function(a){a.preventDefault(),$(window).width()>b.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()<=b.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 a=this,b=$.AdminLTE.options.screenSizes.sm-1;$(".main-sidebar").hover(function(){$("body").hasClass("sidebar-mini")&&$("body").hasClass("sidebar-collapse")&&$(window).width()>b&&a.expand()},function(){$("body").hasClass("sidebar-mini")&&$("body").hasClass("sidebar-expanded-on-hover")&&$(window).width()>b&&a.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(a){var b=this,c=$.AdminLTE.options.animationSpeed;$(document).off("click",a+" li a").on("click",a+" li a",function(a){var d=$(this),e=d.next();if(e.is(".treeview-menu")&&e.is(":visible")&&!$("body").hasClass("sidebar-collapse"))e.slideUp(c,function(){e.removeClass("menu-open")}),e.parent("li").removeClass("active");else if(e.is(".treeview-menu")&&!e.is(":visible")){var f=d.parents("ul").first(),g=f.find("ul:visible").slideUp(c);g.removeClass("menu-open");var h=d.parent("li");e.slideDown(c,function(){e.addClass("menu-open"),f.find("li.active").removeClass("active"),h.addClass("active"),b.layout.fix()})}e.is(".treeview-menu")&&a.preventDefault()})},$.AdminLTE.controlSidebar={activate:function(){var a=this,b=$.AdminLTE.options.controlSidebarOptions,c=$(b.selector),d=$(b.toggleBtnSelector);d.on("click",function(d){d.preventDefault(),c.hasClass("control-sidebar-open")||$("body").hasClass("control-sidebar-open")?a.close(c,b.slide):a.open(c,b.slide)});var e=$(".control-sidebar-bg");a._fix(e),$("body").hasClass("fixed")?a._fixForFixed(c):$(".content-wrapper, .right-side").height() .box-body, > .box-footer, > form >.box-body, > form > .box-footer");c.hasClass("collapsed-box")?(a.children(":first").removeClass(b.icons.open).addClass(b.icons.collapse),d.slideDown(b.animationSpeed,function(){c.removeClass("collapsed-box")})):(a.children(":first").removeClass(b.icons.collapse).addClass(b.icons.open),d.slideUp(b.animationSpeed,function(){c.addClass("collapsed-box")}))},remove:function(a){var b=a.parents(".box").first();b.slideUp(this.animationSpeed)}}}if("undefined"==typeof jQuery)throw new Error("AdminLTE requires jQuery");$.AdminLTE={},$.AdminLTE.options={navbarMenuSlimscroll:!0,navbarMenuSlimscrollWidth:"3px",navbarMenuHeight:"200px",animationSpeed:500,sidebarToggleSelector:"[data-toggle='offcanvas']",sidebarPushMenu:!0,sidebarSlimScroll:!0,sidebarExpandOnHover:!1,enableBoxRefresh:!0,enableBSToppltip:!0,BSTooltipSelector:"[data-toggle='tooltip']",enableFastclick:!1,enableControlTreeView:!0,enableControlSidebar:!0,controlSidebarOptions:{toggleBtnSelector:"[data-action='control-sidebar']",selector:".control-sidebar",slide:!0},enableBoxWidget:!0,boxWidgetOptions:{boxWidgetIcons:{collapse:"fa-minus",open:"fa-plus",remove:"fa-times"},boxWidgetSelectors:{remove:'[data-widget="remove"]',collapse:'[data-widget="collapse"]'}},directChat:{enable:!0,contactToggleSelector:'[data-widget="chat-pane-toggle"]'},colors:{lightBlue:"#3c8dbc",red:"#f56954",green:"#00a65a",aqua:"#00c0ef",yellow:"#f39c12",blue:"#0073b7",navy:"#001F3F",teal:"#39CCCC",olive:"#3D9970",lime:"#01FF70",orange:"#FF851B",fuchsia:"#F012BE",purple:"#8E24AA",maroon:"#D81B60",black:"#222222",gray:"#d2d6de"},screenSizes:{xs:480,sm:768,md:992,lg:1200}},$(function(){"use strict";$("body").removeClass("hold-transition"),"undefined"!=typeof AdminLTEOptions&&$.extend(!0,$.AdminLTE.options,AdminLTEOptions);var a=$.AdminLTE.options;_init(),$.AdminLTE.layout.activate(),a.enableControlTreeView&&$.AdminLTE.tree(".sidebar"),a.enableControlSidebar&&$.AdminLTE.controlSidebar.activate(),a.navbarMenuSlimscroll&&"undefined"!=typeof $.fn.slimscroll&&$(".navbar .menu").slimscroll({height:a.navbarMenuHeight,alwaysVisible:!1,size:a.navbarMenuSlimscrollWidth}).css("width","100%"),a.sidebarPushMenu&&$.AdminLTE.pushMenu.activate(a.sidebarToggleSelector),a.enableBSToppltip&&$("body").tooltip({selector:a.BSTooltipSelector,container:"body"}),a.enableBoxWidget&&$.AdminLTE.boxWidget.activate(),a.enableFastclick&&"undefined"!=typeof FastClick&&FastClick.attach(document.body),a.directChat.enable&&$(document).on("click",a.directChat.contactToggleSelector,function(){var a=$(this).parents(".direct-chat").first();a.toggleClass("direct-chat-contacts-open")}),$('.btn-group[data-toggle="btn-toggle"]').each(function(){var a=$(this);$(this).find(".btn").on("click",function(b){a.find(".btn.active").removeClass("active"),$(this).addClass("active"),b.preventDefault()})})}),function(a){"use strict";a.fn.boxRefresh=function(b){function c(a){a.append(f),e.onLoadStart.call(a)}function d(a){a.find(f).remove(),e.onLoadDone.call(a)}var e=a.extend({trigger:".refresh-btn",source:"",onLoadStart:function(a){return a},onLoadDone:function(a){return a}},b),f=a('
');return this.each(function(){if(""===e.source)return void(window.console&&window.console.log("Please specify a source first - boxRefresh()"));var b=a(this),f=b.find(e.trigger).first();f.on("click",function(a){a.preventDefault(),c(b),b.find(".box-body").load(e.source,function(){d(b)})})})}}(jQuery),function(a){"use strict";a.fn.activateBox=function(){a.AdminLTE.boxWidget.activate(this)},a.fn.toggleBox=function(){var b=a(a.AdminLTE.boxWidget.selectors.collapse,this);a.AdminLTE.boxWidget.collapse(b)},a.fn.removeBox=function(){var b=a(a.AdminLTE.boxWidget.selectors.remove,this);a.AdminLTE.boxWidget.remove(b)}}(jQuery),function(a){"use strict";a.fn.todolist=function(b){var c=a.extend({onCheck:function(a){return a},onUncheck:function(a){return a}},b);return this.each(function(){"undefined"!=typeof a.fn.iCheck?(a("input",this).on("ifChecked",function(){var b=a(this).parents("li").first();b.toggleClass("done"),c.onCheck.call(b)}),a("input",this).on("ifUnchecked",function(){var b=a(this).parents("li").first();b.toggleClass("done"),c.onUncheck.call(b)})):a("input",this).on("change",function(){var b=a(this).parents("li").first();b.toggleClass("done"),a("input",b).is(":checked")?c.onCheck.call(b):c.onUncheck.call(b)})})}}(jQuery); \ No newline at end of file +* ================ +* Main JS application file for AdminLTE v2. This file +* should be included in all pages. It controls some layout +* options and implements exclusive AdminLTE plugins. +* +* @Author Almsaeed Studio +* @Support +* @Email +* @version 2.4.0 +* @repository git://github.com/almasaeed2010/AdminLTE.git +* @license MIT +*/ +if("undefined"==typeof jQuery)throw new Error("AdminLTE requires jQuery");+function(a){"use strict";function b(b){return this.each(function(){var e=a(this),g=e.data(c);if(!g){var h=a.extend({},d,e.data(),"object"==typeof b&&b);e.data(c,g=new f(e,h))}if("string"==typeof g){if(void 0===g[b])throw new Error("No method named "+b);g[b]()}})}var c="lte.boxrefresh",d={source:"",params:{},trigger:".refresh-btn",content:".box-body",loadInContent:!0,responseType:"",overlayTemplate:'
',onLoadStart:function(){},onLoadDone:function(a){return a}},e={data:'[data-widget="box-refresh"]'},f=function(b,c){if(this.element=b,this.options=c,this.$overlay=a(c.overlay),""===c.source)throw new Error("Source url was not defined. Please specify a url in your BoxRefresh source option.");this._setUpListeners(),this.load()};f.prototype.load=function(){this._addOverlay(),this.options.onLoadStart.call(a(this)),a.get(this.options.source,this.options.params,function(b){this.options.loadInContent&&a(this.options.content).html(b),this.options.onLoadDone.call(a(this),b),this._removeOverlay()}.bind(this),""!==this.options.responseType&&this.options.responseType)},f.prototype._setUpListeners=function(){a(this.element).on("click",e.trigger,function(a){a&&a.preventDefault(),this.load()}.bind(this))},f.prototype._addOverlay=function(){a(this.element).append(this.$overlay)},f.prototype._removeOverlay=function(){a(this.element).remove(this.$overlay)};var g=a.fn.boxRefresh;a.fn.boxRefresh=b,a.fn.boxRefresh.Constructor=f,a.fn.boxRefresh.noConflict=function(){return a.fn.boxRefresh=g,this},a(window).on("load",function(){a(e.data).each(function(){b.call(a(this))})})}(jQuery),function(a){"use strict";function b(b){return this.each(function(){var e=a(this),f=e.data(c);if(!f){var g=a.extend({},d,e.data(),"object"==typeof b&&b);e.data(c,f=new h(e,g))}if("string"==typeof b){if(void 0===f[b])throw new Error("No method named "+b);f[b]()}})}var c="lte.boxwidget",d={animationSpeed:500,collapseTrigger:'[data-widget="collapse"]',removeTrigger:'[data-widget="remove"]',collapseIcon:"fa-minus",expandIcon:"fa-plus",removeIcon:"fa-times"},e={data:".box",collapsed:".collapsed-box",body:".box-body",footer:".box-footer",tools:".box-tools"},f={collapsed:"collapsed-box"},g={collapsed:"collapsed.boxwidget",expanded:"expanded.boxwidget",removed:"removed.boxwidget"},h=function(a,b){this.element=a,this.options=b,this._setUpListeners()};h.prototype.toggle=function(){a(this.element).is(e.collapsed)?this.expand():this.collapse()},h.prototype.expand=function(){var b=a.Event(g.expanded),c=this.options.collapseIcon,d=this.options.expandIcon;a(this.element).removeClass(f.collapsed),a(this.element).find(e.tools).find("."+d).removeClass(d).addClass(c),a(this.element).find(e.body+", "+e.footer).slideDown(this.options.animationSpeed,function(){a(this.element).trigger(b)}.bind(this))},h.prototype.collapse=function(){var b=a.Event(g.collapsed),c=this.options.collapseIcon,d=this.options.expandIcon;a(this.element).find(e.tools).find("."+c).removeClass(c).addClass(d),a(this.element).find(e.body+", "+e.footer).slideUp(this.options.animationSpeed,function(){a(this.element).addClass(f.collapsed),a(this.element).trigger(b)}.bind(this))},h.prototype.remove=function(){var b=a.Event(g.removed);a(this.element).slideUp(this.options.animationSpeed,function(){a(this.element).trigger(b),a(this.element).remove()}.bind(this))},h.prototype._setUpListeners=function(){var b=this;a(this.element).on("click",this.options.collapseTrigger,function(a){a&&a.preventDefault(),b.toggle()}),a(this.element).on("click",this.options.removeTrigger,function(a){a&&a.preventDefault(),b.remove()})};var i=a.fn.boxWidget;a.fn.boxWidget=b,a.fn.boxWidget.Constructor=h,a.fn.boxWidget.noConflict=function(){return a.fn.boxWidget=i,this},a(window).on("load",function(){a(e.data).each(function(){b.call(a(this))})})}(jQuery),function(a){"use strict";function b(b){return this.each(function(){var e=a(this),f=e.data(c);if(!f){var g=a.extend({},d,e.data(),"object"==typeof b&&b);e.data(c,f=new h(e,g))}"string"==typeof b&&f.toggle()})}var c="lte.controlsidebar",d={slide:!0},e={sidebar:".control-sidebar",data:'[data-toggle="control-sidebar"]',open:".control-sidebar-open",bg:".control-sidebar-bg",wrapper:".wrapper",content:".content-wrapper",boxed:".layout-boxed"},f={open:"control-sidebar-open",fixed:"fixed"},g={collapsed:"collapsed.controlsidebar",expanded:"expanded.controlsidebar"},h=function(a,b){this.element=a,this.options=b,this.hasBindedResize=!1,this.init()};h.prototype.init=function(){a(this.element).is(e.data)||a(this).on("click",this.toggle),this.fix(),a(window).resize(function(){this.fix()}.bind(this))},h.prototype.toggle=function(b){b&&b.preventDefault(),this.fix(),a(e.sidebar).is(e.open)||a("body").is(e.open)?this.collapse():this.expand()},h.prototype.expand=function(){this.options.slide?a(e.sidebar).addClass(f.open):a("body").addClass(f.open),a(this.element).trigger(a.Event(g.expanded))},h.prototype.collapse=function(){a("body, "+e.sidebar).removeClass(f.open),a(this.element).trigger(a.Event(g.collapsed))},h.prototype.fix=function(){a("body").is(e.boxed)&&this._fixForBoxed(a(e.bg))},h.prototype._fixForBoxed=function(b){b.css({position:"absolute",height:a(e.wrapper).height()})};var i=a.fn.controlSidebar;a.fn.controlSidebar=b,a.fn.controlSidebar.Constructor=h,a.fn.controlSidebar.noConflict=function(){return a.fn.controlSidebar=i,this},a(document).on("click",e.data,function(c){c&&c.preventDefault(),b.call(a(this),"toggle")})}(jQuery),function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data(c);e||d.data(c,e=new f(d)),"string"==typeof b&&e.toggle(d)})}var c="lte.directchat",d={data:'[data-widget="chat-pane-toggle"]',box:".direct-chat"},e={open:"direct-chat-contacts-open"},f=function(a){this.element=a};f.prototype.toggle=function(a){a.parents(d.box).first().toggleClass(e.open)};var g=a.fn.directChat;a.fn.directChat=b,a.fn.directChat.Constructor=f,a.fn.directChat.noConflict=function(){return a.fn.directChat=g,this},a(document).on("click",d.data,function(c){c&&c.preventDefault(),b.call(a(this),"toggle")})}(jQuery),function(a){"use strict";function b(b){return this.each(function(){var e=a(this),f=e.data(c);if(!f){var h=a.extend({},d,e.data(),"object"==typeof b&&b);e.data(c,f=new g(h))}if("string"==typeof b){if(void 0===f[b])throw new Error("No method named "+b);f[b]()}})}var c="lte.layout",d={slimscroll:!0,resetHeight:!0},e={wrapper:".wrapper",contentWrapper:".content-wrapper",layoutBoxed:".layout-boxed",mainFooter:".main-footer",mainHeader:".main-header",sidebar:".sidebar",controlSidebar:".control-sidebar",fixed:".fixed",sidebarMenu:".sidebar-menu",logo:".main-header .logo"},f={fixed:"fixed",holdTransition:"hold-transition"},g=function(a){this.options=a,this.bindedResize=!1,this.activate()};g.prototype.activate=function(){this.fix(),this.fixSidebar(),a("body").removeClass(f.holdTransition),this.options.resetHeight&&a("body, html, "+e.wrapper).css({height:"auto","min-height":"100%"}),this.bindedResize||(a(window).resize(function(){this.fix(),this.fixSidebar(),a(e.logo+", "+e.sidebar).one("webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend",function(){this.fix(),this.fixSidebar()}.bind(this))}.bind(this)),this.bindedResize=!0),a(e.sidebarMenu).on("expanded.tree",function(){this.fix(),this.fixSidebar()}.bind(this)),a(e.sidebarMenu).on("collapsed.tree",function(){this.fix(),this.fixSidebar()}.bind(this))},g.prototype.fix=function(){a(e.layoutBoxed+" > "+e.wrapper).css("overflow","hidden");var b=a(e.mainFooter).outerHeight()||0,c=a(e.mainHeader).outerHeight()+b,d=a(window).height(),g=a(e.sidebar).height()||0;if(a("body").hasClass(f.fixed))a(e.contentWrapper).css("min-height",d-b);else{var h;d>=g?(a(e.contentWrapper).css("min-height",d-c),h=d-c):(a(e.contentWrapper).css("min-height",g),h=g);var i=a(e.controlSidebar);void 0!==i&&i.height()>h&&a(e.contentWrapper).css("min-height",i.height())}},g.prototype.fixSidebar=function(){if(!a("body").hasClass(f.fixed))return void(void 0!==a.fn.slimScroll&&a(e.sidebar).slimScroll({destroy:!0}).height("auto"));this.options.slimscroll&&void 0!==a.fn.slimScroll&&(a(e.sidebar).slimScroll({destroy:!0}).height("auto"),a(e.sidebar).slimScroll({height:a(window).height()-a(e.mainHeader).height()+"px",color:"rgba(0,0,0,0.2)",size:"3px"}))};var h=a.fn.layout;a.fn.layout=b,a.fn.layout.Constuctor=g,a.fn.layout.noConflict=function(){return a.fn.layout=h,this},a(window).on("load",function(){b.call(a("body"))})}(jQuery),function(a){"use strict";function b(b){return this.each(function(){var e=a(this),f=e.data(c);if(!f){var g=a.extend({},d,e.data(),"object"==typeof b&&b);e.data(c,f=new h(g))}"toggle"==b&&f.toggle()})}var c="lte.pushmenu",d={collapseScreenSize:767,expandOnHover:!1,expandTransitionDelay:200},e={collapsed:".sidebar-collapse",open:".sidebar-open",mainSidebar:".main-sidebar",contentWrapper:".content-wrapper",searchInput:".sidebar-form .form-control",button:'[data-toggle="push-menu"]',mini:".sidebar-mini",expanded:".sidebar-expanded-on-hover",layoutFixed:".fixed"},f={collapsed:"sidebar-collapse",open:"sidebar-open",mini:"sidebar-mini",expanded:"sidebar-expanded-on-hover",expandFeature:"sidebar-mini-expand-feature",layoutFixed:"fixed"},g={expanded:"expanded.pushMenu",collapsed:"collapsed.pushMenu"},h=function(a){this.options=a,this.init()};h.prototype.init=function(){(this.options.expandOnHover||a("body").is(e.mini+e.layoutFixed))&&(this.expandOnHover(),a("body").addClass(f.expandFeature)),a(e.contentWrapper).click(function(){a(window).width()<=this.options.collapseScreenSize&&a("body").hasClass(f.open)&&this.close()}.bind(this)),a(e.searchInput).click(function(a){a.stopPropagation()})},h.prototype.toggle=function(){var b=a(window).width(),c=!a("body").hasClass(f.collapsed);b<=this.options.collapseScreenSize&&(c=a("body").hasClass(f.open)),c?this.close():this.open()},h.prototype.open=function(){a(window).width()>this.options.collapseScreenSize?a("body").removeClass(f.collapsed).trigger(a.Event(g.expanded)):a("body").addClass(f.open).trigger(a.Event(g.expanded))},h.prototype.close=function(){a(window).width()>this.options.collapseScreenSize?a("body").addClass(f.collapsed).trigger(a.Event(g.collapsed)):a("body").removeClass(f.open+" "+f.collapsed).trigger(a.Event(g.collapsed))},h.prototype.expandOnHover=function(){a(e.mainSidebar).hover(function(){a("body").is(e.mini+e.collapsed)&&a(window).width()>this.options.collapseScreenSize&&this.expand()}.bind(this),function(){a("body").is(e.expanded)&&this.collapse()}.bind(this))},h.prototype.expand=function(){setTimeout(function(){a("body").removeClass(f.collapsed).addClass(f.expanded)},this.options.expandTransitionDelay)},h.prototype.collapse=function(){setTimeout(function(){a("body").removeClass(f.expanded).addClass(f.collapsed)},this.options.expandTransitionDelay)};var i=a.fn.pushMenu;a.fn.pushMenu=b,a.fn.pushMenu.Constructor=h,a.fn.pushMenu.noConflict=function(){return a.fn.pushMenu=i,this},a(document).on("click",e.button,function(c){c.preventDefault(),b.call(a(this),"toggle")}),a(window).on("load",function(){b.call(a(e.button))})}(jQuery),function(a){"use strict";function b(b){return this.each(function(){var e=a(this),f=e.data(c);if(!f){var h=a.extend({},d,e.data(),"object"==typeof b&&b);e.data(c,f=new g(e,h))}if("string"==typeof f){if(void 0===f[b])throw new Error("No method named "+b);f[b]()}})}var c="lte.todolist",d={onCheck:function(a){return a},onUnCheck:function(a){return a}},e={data:'[data-widget="todo-list"]'},f={done:"done"},g=function(a,b){this.element=a,this.options=b,this._setUpListeners()};g.prototype.toggle=function(a){if(a.parents(e.li).first().toggleClass(f.done),!a.prop("checked"))return void this.unCheck(a);this.check(a)},g.prototype.check=function(a){this.options.onCheck.call(a)},g.prototype.unCheck=function(a){this.options.onUnCheck.call(a)},g.prototype._setUpListeners=function(){var b=this;a(this.element).on("change ifChanged","input:checkbox",function(){b.toggle(a(this))})};var h=a.fn.todoList;a.fn.todoList=b,a.fn.todoList.Constructor=g,a.fn.todoList.noConflict=function(){return a.fn.todoList=h,this},a(window).on("load",function(){a(e.data).each(function(){b.call(a(this))})})}(jQuery),function(a){"use strict";function b(b){return this.each(function(){var e=a(this);if(!e.data(c)){var f=a.extend({},d,e.data(),"object"==typeof b&&b);e.data(c,new h(e,f))}})}var c="lte.tree",d={animationSpeed:500,accordion:!0,followLink:!1,trigger:".treeview a"},e={tree:".tree",treeview:".treeview",treeviewMenu:".treeview-menu",open:".menu-open, .active",li:"li",data:'[data-widget="tree"]',active:".active"},f={open:"menu-open",tree:"tree"},g={collapsed:"collapsed.tree",expanded:"expanded.tree"},h=function(b,c){this.element=b,this.options=c,a(this.element).addClass(f.tree),a(e.treeview+e.active,this.element).addClass(f.open),this._setUpListeners()};h.prototype.toggle=function(a,b){var c=a.next(e.treeviewMenu),d=a.parent(),g=d.hasClass(f.open);d.is(e.treeview)&&(this.options.followLink&&"#"!=a.attr("href")||b.preventDefault(),g?this.collapse(c,d):this.expand(c,d))},h.prototype.expand=function(b,c){var d=a.Event(g.expanded);if(this.options.accordion){var h=c.siblings(e.open),i=h.children(e.treeviewMenu);this.collapse(i,h)}c.addClass(f.open),b.slideDown(this.options.animationSpeed,function(){a(this.element).trigger(d)}.bind(this))},h.prototype.collapse=function(b,c){var d=a.Event(g.collapsed);b.find(e.open).removeClass(f.open),c.removeClass(f.open),b.slideUp(this.options.animationSpeed,function(){b.find(e.open+" > "+e.treeview).slideUp(),a(this.element).trigger(d)}.bind(this))},h.prototype._setUpListeners=function(){var b=this;a(this.element).on("click",this.options.trigger,function(c){b.toggle(a(this),c)})};var i=a.fn.tree;a.fn.tree=b,a.fn.tree.Constructor=h,a.fn.tree.noConflict=function(){return a.fn.tree=i,this},a(window).on("load",function(){a(e.data).each(function(){b.call(a(this))})})}(jQuery); diff --git a/public/themes/pterodactyl/vendor/adminlte/colors/skin-blue.min.css b/public/themes/pterodactyl/vendor/adminlte/colors/skin-blue.min.css index 44524fe38..cdbef24bf 100755 --- a/public/themes/pterodactyl/vendor/adminlte/colors/skin-blue.min.css +++ b/public/themes/pterodactyl/vendor/adminlte/colors/skin-blue.min.css @@ -1 +1 @@ -.skin-blue .main-header .navbar{background-color:#3c8dbc}.skin-blue .main-header .navbar .nav>li>a{color:#fff}.skin-blue .main-header .navbar .nav>li>a:hover,.skin-blue .main-header .navbar .nav>li>a:active,.skin-blue .main-header .navbar .nav>li>a:focus,.skin-blue .main-header .navbar .nav .open>a,.skin-blue .main-header .navbar .nav .open>a:hover,.skin-blue .main-header .navbar .nav .open>a:focus,.skin-blue .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-blue .main-header .navbar .sidebar-toggle{color:#fff}.skin-blue .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-blue .main-header .navbar .sidebar-toggle{color:#fff}.skin-blue .main-header .navbar .sidebar-toggle:hover{background-color:#367fa9}@media (max-width:767px){.skin-blue .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-blue .main-header .navbar .dropdown-menu li a{color:#fff}.skin-blue .main-header .navbar .dropdown-menu li a:hover{background:#367fa9}}.skin-blue .main-header .logo{background-color:#367fa9;color:#fff;border-bottom:0 solid transparent}.skin-blue .main-header .logo:hover{background-color:#357ca5}.skin-blue .main-header li.user-header{background-color:#3c8dbc}.skin-blue .content-header{background:transparent}.skin-blue .wrapper,.skin-blue .main-sidebar,.skin-blue .left-side{background-color:#222d32}.skin-blue .user-panel>.info,.skin-blue .user-panel>.info>a{color:#fff}.skin-blue .sidebar-menu>li.header{color:#4b646f;background:#1a2226}.skin-blue .sidebar-menu>li>a{border-left:3px solid transparent}.skin-blue .sidebar-menu>li:hover>a,.skin-blue .sidebar-menu>li.active>a{color:#fff;background:#1e282c;border-left-color:#3c8dbc}.skin-blue .sidebar-menu>li>.treeview-menu{margin:0 1px;background:#2c3b41}.skin-blue .sidebar a{color:#b8c7ce}.skin-blue .sidebar a:hover{text-decoration:none}.skin-blue .treeview-menu>li>a{color:#8aa4af}.skin-blue .treeview-menu>li.active>a,.skin-blue .treeview-menu>li>a:hover{color:#fff}.skin-blue .sidebar-form{border-radius:3px;border:1px solid #374850;margin:10px 10px}.skin-blue .sidebar-form input[type="text"],.skin-blue .sidebar-form .btn{box-shadow:none;background-color:#374850;border:1px solid transparent;height:35px}.skin-blue .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-blue .sidebar-form input[type="text"]:focus,.skin-blue .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-blue .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-blue .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}.skin-blue.layout-top-nav .main-header>.logo{background-color:#3c8dbc;color:#fff;border-bottom:0 solid transparent}.skin-blue.layout-top-nav .main-header>.logo:hover{background-color:#3b8ab8} \ No newline at end of file +.skin-blue .main-header .navbar{background-color:#10529f}.skin-blue .main-header .navbar .nav>li>a{color:#fff}.skin-blue .main-header .navbar .nav>li>a:hover,.skin-blue .main-header .navbar .nav>li>a:active,.skin-blue .main-header .navbar .nav>li>a:focus,.skin-blue .main-header .navbar .nav .open>a,.skin-blue .main-header .navbar .nav .open>a:hover,.skin-blue .main-header .navbar .nav .open>a:focus,.skin-blue .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-blue .main-header .navbar .sidebar-toggle{color:#fff}.skin-blue .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-blue .main-header .navbar .sidebar-toggle{color:#fff}.skin-blue .main-header .navbar .sidebar-toggle:hover{background-color:#0e4688}@media (max-width:767px){.skin-blue .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-blue .main-header .navbar .dropdown-menu li a{color:#fff}.skin-blue .main-header .navbar .dropdown-menu li a:hover{background:#0e4688}}.skin-blue .main-header .logo{background-color:#0e4688;color:#fff;border-bottom:0 solid transparent}.skin-blue .main-header .logo:hover{background-color:#0d4483}.skin-blue .main-header li.user-header{background-color:#10529f}.skin-blue .content-header{background:transparent}.skin-blue .wrapper,.skin-blue .main-sidebar,.skin-blue .left-side{background-color:#191b22}.skin-blue .user-panel>.info,.skin-blue .user-panel>.info>a{color:#fff}.skin-blue .sidebar-menu>li.header{color:#444a5d;background:#101216}.skin-blue .sidebar-menu>li>a{border-left:3px solid transparent}.skin-blue .sidebar-menu>li:hover>a,.skin-blue .sidebar-menu>li.active>a,.skin-blue .sidebar-menu>li.menu-open>a{color:#fff;background:#15161c}.skin-blue .sidebar-menu>li.active>a{border-left-color:#10529f}.skin-blue .sidebar-menu>li>.treeview-menu{margin:0 1px;background:#242731}.skin-blue .sidebar a{color:#abb0c2}.skin-blue .sidebar a:hover{text-decoration:none}.skin-blue .sidebar-menu .treeview-menu>li>a{color:#7f87a1}.skin-blue .sidebar-menu .treeview-menu>li.active>a,.skin-blue .sidebar-menu .treeview-menu>li>a:hover{color:#fff}.skin-blue .sidebar-form{border-radius:3px;border:1px solid #2f323f;margin:10px 10px}.skin-blue .sidebar-form input[type="text"],.skin-blue .sidebar-form .btn{box-shadow:none;background-color:#2f323f;border:1px solid transparent;height:35px}.skin-blue .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-blue .sidebar-form input[type="text"]:focus,.skin-blue .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-blue .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-blue .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}.skin-blue.layout-top-nav .main-header>.logo{background-color:#10529f;color:#fff;border-bottom:0 solid transparent}.skin-blue.layout-top-nav .main-header>.logo:hover{background-color:#10509a} diff --git a/public/themes/pterodactyl/vendor/particlesjs/particles.json b/public/themes/pterodactyl/vendor/particlesjs/particles.json new file mode 100644 index 000000000..077ccae76 --- /dev/null +++ b/public/themes/pterodactyl/vendor/particlesjs/particles.json @@ -0,0 +1,110 @@ +{ + "particles": { + "number": { + "value": 80, + "density": { + "enable": true, + "value_area": 800 + } + }, + "color": { + "value": "#ffffff" + }, + "shape": { + "type": "circle", + "stroke": { + "width": 0, + "color": "#000000" + }, + "polygon": { + "nb_sides": 5 + }, + "image": { + "src": "img/github.svg", + "width": 100, + "height": 100 + } + }, + "opacity": { + "value": 0.40246529723245905, + "random": false, + "anim": { + "enable": false, + "speed": 1, + "opacity_min": 0.1, + "sync": false + } + }, + "size": { + "value": 1, + "random": true, + "anim": { + "enable": false, + "speed": 40, + "size_min": 0.1, + "sync": false + } + }, + "line_linked": { + "enable": true, + "distance": 150, + "color": "#ffffff", + "opacity": 0.4, + "width": 1 + }, + "move": { + "enable": true, + "speed": 5, + "direction": "none", + "random": false, + "straight": false, + "out_mode": "out", + "bounce": false, + "attract": { + "enable": false, + "rotateX": 600, + "rotateY": 1200 + } + } + }, + "interactivity": { + "detect_on": "canvas", + "events": { + "onhover": { + "enable": true, + "mode": "repulse" + }, + "onclick": { + "enable": false, + "mode": "repulse" + }, + "resize": true + }, + "modes": { + "grab": { + "distance": 400, + "line_linked": { + "opacity": 1 + } + }, + "bubble": { + "distance": 400, + "size": 40, + "duration": 2, + "opacity": 8, + "speed": 3 + }, + "repulse": { + "distance": 200, + "duration": 0.4 + }, + "push": { + "particles_nb": 4 + }, + "remove": { + "particles_nb": 2 + } + } + }, + "retina_detect": true +} diff --git a/public/themes/pterodactyl/vendor/particlesjs/particles.min.js b/public/themes/pterodactyl/vendor/particlesjs/particles.min.js new file mode 100644 index 000000000..1b204b7bd --- /dev/null +++ b/public/themes/pterodactyl/vendor/particlesjs/particles.min.js @@ -0,0 +1,9 @@ +/* ----------------------------------------------- +/* Author : Vincent Garreau - vincentgarreau.com +/* MIT license: http://opensource.org/licenses/MIT +/* Demo / Generator : vincentgarreau.com/particles.js +/* GitHub : github.com/VincentGarreau/particles.js +/* How to use? : Check the GitHub README +/* v2.0.0 +/* ----------------------------------------------- */ +function hexToRgb(e){var a=/^#?([a-f\d])([a-f\d])([a-f\d])$/i;e=e.replace(a,function(e,a,t,i){return a+a+t+t+i+i});var t=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(e);return t?{r:parseInt(t[1],16),g:parseInt(t[2],16),b:parseInt(t[3],16)}:null}function clamp(e,a,t){return Math.min(Math.max(e,a),t)}function isInArray(e,a){return a.indexOf(e)>-1}var pJS=function(e,a){var t=document.querySelector("#"+e+" > .particles-js-canvas-el");this.pJS={canvas:{el:t,w:t.offsetWidth,h:t.offsetHeight},particles:{number:{value:400,density:{enable:!0,value_area:800}},color:{value:"#fff"},shape:{type:"circle",stroke:{width:0,color:"#ff0000"},polygon:{nb_sides:5},image:{src:"",width:100,height:100}},opacity:{value:1,random:!1,anim:{enable:!1,speed:2,opacity_min:0,sync:!1}},size:{value:20,random:!1,anim:{enable:!1,speed:20,size_min:0,sync:!1}},line_linked:{enable:!0,distance:100,color:"#fff",opacity:1,width:1},move:{enable:!0,speed:2,direction:"none",random:!1,straight:!1,out_mode:"out",bounce:!1,attract:{enable:!1,rotateX:3e3,rotateY:3e3}},array:[]},interactivity:{detect_on:"canvas",events:{onhover:{enable:!0,mode:"grab"},onclick:{enable:!0,mode:"push"},resize:!0},modes:{grab:{distance:100,line_linked:{opacity:1}},bubble:{distance:200,size:80,duration:.4},repulse:{distance:200,duration:.4},push:{particles_nb:4},remove:{particles_nb:2}},mouse:{}},retina_detect:!1,fn:{interact:{},modes:{},vendors:{}},tmp:{}};var i=this.pJS;a&&Object.deepExtend(i,a),i.tmp.obj={size_value:i.particles.size.value,size_anim_speed:i.particles.size.anim.speed,move_speed:i.particles.move.speed,line_linked_distance:i.particles.line_linked.distance,line_linked_width:i.particles.line_linked.width,mode_grab_distance:i.interactivity.modes.grab.distance,mode_bubble_distance:i.interactivity.modes.bubble.distance,mode_bubble_size:i.interactivity.modes.bubble.size,mode_repulse_distance:i.interactivity.modes.repulse.distance},i.fn.retinaInit=function(){i.retina_detect&&window.devicePixelRatio>1?(i.canvas.pxratio=window.devicePixelRatio,i.tmp.retina=!0):(i.canvas.pxratio=1,i.tmp.retina=!1),i.canvas.w=i.canvas.el.offsetWidth*i.canvas.pxratio,i.canvas.h=i.canvas.el.offsetHeight*i.canvas.pxratio,i.particles.size.value=i.tmp.obj.size_value*i.canvas.pxratio,i.particles.size.anim.speed=i.tmp.obj.size_anim_speed*i.canvas.pxratio,i.particles.move.speed=i.tmp.obj.move_speed*i.canvas.pxratio,i.particles.line_linked.distance=i.tmp.obj.line_linked_distance*i.canvas.pxratio,i.interactivity.modes.grab.distance=i.tmp.obj.mode_grab_distance*i.canvas.pxratio,i.interactivity.modes.bubble.distance=i.tmp.obj.mode_bubble_distance*i.canvas.pxratio,i.particles.line_linked.width=i.tmp.obj.line_linked_width*i.canvas.pxratio,i.interactivity.modes.bubble.size=i.tmp.obj.mode_bubble_size*i.canvas.pxratio,i.interactivity.modes.repulse.distance=i.tmp.obj.mode_repulse_distance*i.canvas.pxratio},i.fn.canvasInit=function(){i.canvas.ctx=i.canvas.el.getContext("2d")},i.fn.canvasSize=function(){i.canvas.el.width=i.canvas.w,i.canvas.el.height=i.canvas.h,i&&i.interactivity.events.resize&&window.addEventListener("resize",function(){i.canvas.w=i.canvas.el.offsetWidth,i.canvas.h=i.canvas.el.offsetHeight,i.tmp.retina&&(i.canvas.w*=i.canvas.pxratio,i.canvas.h*=i.canvas.pxratio),i.canvas.el.width=i.canvas.w,i.canvas.el.height=i.canvas.h,i.particles.move.enable||(i.fn.particlesEmpty(),i.fn.particlesCreate(),i.fn.particlesDraw(),i.fn.vendors.densityAutoParticles()),i.fn.vendors.densityAutoParticles()})},i.fn.canvasPaint=function(){i.canvas.ctx.fillRect(0,0,i.canvas.w,i.canvas.h)},i.fn.canvasClear=function(){i.canvas.ctx.clearRect(0,0,i.canvas.w,i.canvas.h)},i.fn.particle=function(e,a,t){if(this.radius=(i.particles.size.random?Math.random():1)*i.particles.size.value,i.particles.size.anim.enable&&(this.size_status=!1,this.vs=i.particles.size.anim.speed/100,i.particles.size.anim.sync||(this.vs=this.vs*Math.random())),this.x=t?t.x:Math.random()*i.canvas.w,this.y=t?t.y:Math.random()*i.canvas.h,this.x>i.canvas.w-2*this.radius?this.x=this.x-this.radius:this.x<2*this.radius&&(this.x=this.x+this.radius),this.y>i.canvas.h-2*this.radius?this.y=this.y-this.radius:this.y<2*this.radius&&(this.y=this.y+this.radius),i.particles.move.bounce&&i.fn.vendors.checkOverlap(this,t),this.color={},"object"==typeof e.value)if(e.value instanceof Array){var s=e.value[Math.floor(Math.random()*i.particles.color.value.length)];this.color.rgb=hexToRgb(s)}else void 0!=e.value.r&&void 0!=e.value.g&&void 0!=e.value.b&&(this.color.rgb={r:e.value.r,g:e.value.g,b:e.value.b}),void 0!=e.value.h&&void 0!=e.value.s&&void 0!=e.value.l&&(this.color.hsl={h:e.value.h,s:e.value.s,l:e.value.l});else"random"==e.value?this.color.rgb={r:Math.floor(256*Math.random())+0,g:Math.floor(256*Math.random())+0,b:Math.floor(256*Math.random())+0}:"string"==typeof e.value&&(this.color=e,this.color.rgb=hexToRgb(this.color.value));this.opacity=(i.particles.opacity.random?Math.random():1)*i.particles.opacity.value,i.particles.opacity.anim.enable&&(this.opacity_status=!1,this.vo=i.particles.opacity.anim.speed/100,i.particles.opacity.anim.sync||(this.vo=this.vo*Math.random()));var n={};switch(i.particles.move.direction){case"top":n={x:0,y:-1};break;case"top-right":n={x:.5,y:-.5};break;case"right":n={x:1,y:-0};break;case"bottom-right":n={x:.5,y:.5};break;case"bottom":n={x:0,y:1};break;case"bottom-left":n={x:-.5,y:1};break;case"left":n={x:-1,y:0};break;case"top-left":n={x:-.5,y:-.5};break;default:n={x:0,y:0}}i.particles.move.straight?(this.vx=n.x,this.vy=n.y,i.particles.move.random&&(this.vx=this.vx*Math.random(),this.vy=this.vy*Math.random())):(this.vx=n.x+Math.random()-.5,this.vy=n.y+Math.random()-.5),this.vx_i=this.vx,this.vy_i=this.vy;var r=i.particles.shape.type;if("object"==typeof r){if(r instanceof Array){var c=r[Math.floor(Math.random()*r.length)];this.shape=c}}else this.shape=r;if("image"==this.shape){var o=i.particles.shape;this.img={src:o.image.src,ratio:o.image.width/o.image.height},this.img.ratio||(this.img.ratio=1),"svg"==i.tmp.img_type&&void 0!=i.tmp.source_svg&&(i.fn.vendors.createSvgImg(this),i.tmp.pushing&&(this.img.loaded=!1))}},i.fn.particle.prototype.draw=function(){function e(){i.canvas.ctx.drawImage(r,a.x-t,a.y-t,2*t,2*t/a.img.ratio)}var a=this;if(void 0!=a.radius_bubble)var t=a.radius_bubble;else var t=a.radius;if(void 0!=a.opacity_bubble)var s=a.opacity_bubble;else var s=a.opacity;if(a.color.rgb)var n="rgba("+a.color.rgb.r+","+a.color.rgb.g+","+a.color.rgb.b+","+s+")";else var n="hsla("+a.color.hsl.h+","+a.color.hsl.s+"%,"+a.color.hsl.l+"%,"+s+")";switch(i.canvas.ctx.fillStyle=n,i.canvas.ctx.beginPath(),a.shape){case"circle":i.canvas.ctx.arc(a.x,a.y,t,0,2*Math.PI,!1);break;case"edge":i.canvas.ctx.rect(a.x-t,a.y-t,2*t,2*t);break;case"triangle":i.fn.vendors.drawShape(i.canvas.ctx,a.x-t,a.y+t/1.66,2*t,3,2);break;case"polygon":i.fn.vendors.drawShape(i.canvas.ctx,a.x-t/(i.particles.shape.polygon.nb_sides/3.5),a.y-t/.76,2.66*t/(i.particles.shape.polygon.nb_sides/3),i.particles.shape.polygon.nb_sides,1);break;case"star":i.fn.vendors.drawShape(i.canvas.ctx,a.x-2*t/(i.particles.shape.polygon.nb_sides/4),a.y-t/1.52,2*t*2.66/(i.particles.shape.polygon.nb_sides/3),i.particles.shape.polygon.nb_sides,2);break;case"image":if("svg"==i.tmp.img_type)var r=a.img.obj;else var r=i.tmp.img_obj;r&&e()}i.canvas.ctx.closePath(),i.particles.shape.stroke.width>0&&(i.canvas.ctx.strokeStyle=i.particles.shape.stroke.color,i.canvas.ctx.lineWidth=i.particles.shape.stroke.width,i.canvas.ctx.stroke()),i.canvas.ctx.fill()},i.fn.particlesCreate=function(){for(var e=0;e=i.particles.opacity.value&&(a.opacity_status=!1),a.opacity+=a.vo):(a.opacity<=i.particles.opacity.anim.opacity_min&&(a.opacity_status=!0),a.opacity-=a.vo),a.opacity<0&&(a.opacity=0)),i.particles.size.anim.enable&&(1==a.size_status?(a.radius>=i.particles.size.value&&(a.size_status=!1),a.radius+=a.vs):(a.radius<=i.particles.size.anim.size_min&&(a.size_status=!0),a.radius-=a.vs),a.radius<0&&(a.radius=0)),"bounce"==i.particles.move.out_mode)var s={x_left:a.radius,x_right:i.canvas.w,y_top:a.radius,y_bottom:i.canvas.h};else var s={x_left:-a.radius,x_right:i.canvas.w+a.radius,y_top:-a.radius,y_bottom:i.canvas.h+a.radius};switch(a.x-a.radius>i.canvas.w?(a.x=s.x_left,a.y=Math.random()*i.canvas.h):a.x+a.radius<0&&(a.x=s.x_right,a.y=Math.random()*i.canvas.h),a.y-a.radius>i.canvas.h?(a.y=s.y_top,a.x=Math.random()*i.canvas.w):a.y+a.radius<0&&(a.y=s.y_bottom,a.x=Math.random()*i.canvas.w),i.particles.move.out_mode){case"bounce":a.x+a.radius>i.canvas.w?a.vx=-a.vx:a.x-a.radius<0&&(a.vx=-a.vx),a.y+a.radius>i.canvas.h?a.vy=-a.vy:a.y-a.radius<0&&(a.vy=-a.vy)}if(isInArray("grab",i.interactivity.events.onhover.mode)&&i.fn.modes.grabParticle(a),(isInArray("bubble",i.interactivity.events.onhover.mode)||isInArray("bubble",i.interactivity.events.onclick.mode))&&i.fn.modes.bubbleParticle(a),(isInArray("repulse",i.interactivity.events.onhover.mode)||isInArray("repulse",i.interactivity.events.onclick.mode))&&i.fn.modes.repulseParticle(a),i.particles.line_linked.enable||i.particles.move.attract.enable)for(var n=e+1;n0){var c=i.particles.line_linked.color_rgb_line;i.canvas.ctx.strokeStyle="rgba("+c.r+","+c.g+","+c.b+","+r+")",i.canvas.ctx.lineWidth=i.particles.line_linked.width,i.canvas.ctx.beginPath(),i.canvas.ctx.moveTo(e.x,e.y),i.canvas.ctx.lineTo(a.x,a.y),i.canvas.ctx.stroke(),i.canvas.ctx.closePath()}}},i.fn.interact.attractParticles=function(e,a){var t=e.x-a.x,s=e.y-a.y,n=Math.sqrt(t*t+s*s);if(n<=i.particles.line_linked.distance){var r=t/(1e3*i.particles.move.attract.rotateX),c=s/(1e3*i.particles.move.attract.rotateY);e.vx-=r,e.vy-=c,a.vx+=r,a.vy+=c}},i.fn.interact.bounceParticles=function(e,a){var t=e.x-a.x,i=e.y-a.y,s=Math.sqrt(t*t+i*i),n=e.radius+a.radius;n>=s&&(e.vx=-e.vx,e.vy=-e.vy,a.vx=-a.vx,a.vy=-a.vy)},i.fn.modes.pushParticles=function(e,a){i.tmp.pushing=!0;for(var t=0;e>t;t++)i.particles.array.push(new i.fn.particle(i.particles.color,i.particles.opacity.value,{x:a?a.pos_x:Math.random()*i.canvas.w,y:a?a.pos_y:Math.random()*i.canvas.h})),t==e-1&&(i.particles.move.enable||i.fn.particlesDraw(),i.tmp.pushing=!1)},i.fn.modes.removeParticles=function(e){i.particles.array.splice(0,e),i.particles.move.enable||i.fn.particlesDraw()},i.fn.modes.bubbleParticle=function(e){function a(){e.opacity_bubble=e.opacity,e.radius_bubble=e.radius}function t(a,t,s,n,c){if(a!=t)if(i.tmp.bubble_duration_end){if(void 0!=s){var o=n-p*(n-a)/i.interactivity.modes.bubble.duration,l=a-o;d=a+l,"size"==c&&(e.radius_bubble=d),"opacity"==c&&(e.opacity_bubble=d)}}else if(r<=i.interactivity.modes.bubble.distance){if(void 0!=s)var v=s;else var v=n;if(v!=a){var d=n-p*(n-a)/i.interactivity.modes.bubble.duration;"size"==c&&(e.radius_bubble=d),"opacity"==c&&(e.opacity_bubble=d)}}else"size"==c&&(e.radius_bubble=void 0),"opacity"==c&&(e.opacity_bubble=void 0)}if(i.interactivity.events.onhover.enable&&isInArray("bubble",i.interactivity.events.onhover.mode)){var s=e.x-i.interactivity.mouse.pos_x,n=e.y-i.interactivity.mouse.pos_y,r=Math.sqrt(s*s+n*n),c=1-r/i.interactivity.modes.bubble.distance;if(r<=i.interactivity.modes.bubble.distance){if(c>=0&&"mousemove"==i.interactivity.status){if(i.interactivity.modes.bubble.size!=i.particles.size.value)if(i.interactivity.modes.bubble.size>i.particles.size.value){var o=e.radius+i.interactivity.modes.bubble.size*c;o>=0&&(e.radius_bubble=o)}else{var l=e.radius-i.interactivity.modes.bubble.size,o=e.radius-l*c;o>0?e.radius_bubble=o:e.radius_bubble=0}if(i.interactivity.modes.bubble.opacity!=i.particles.opacity.value)if(i.interactivity.modes.bubble.opacity>i.particles.opacity.value){var v=i.interactivity.modes.bubble.opacity*c;v>e.opacity&&v<=i.interactivity.modes.bubble.opacity&&(e.opacity_bubble=v)}else{var v=e.opacity-(i.particles.opacity.value-i.interactivity.modes.bubble.opacity)*c;v=i.interactivity.modes.bubble.opacity&&(e.opacity_bubble=v)}}}else a();"mouseleave"==i.interactivity.status&&a()}else if(i.interactivity.events.onclick.enable&&isInArray("bubble",i.interactivity.events.onclick.mode)){if(i.tmp.bubble_clicking){var s=e.x-i.interactivity.mouse.click_pos_x,n=e.y-i.interactivity.mouse.click_pos_y,r=Math.sqrt(s*s+n*n),p=((new Date).getTime()-i.interactivity.mouse.click_time)/1e3;p>i.interactivity.modes.bubble.duration&&(i.tmp.bubble_duration_end=!0),p>2*i.interactivity.modes.bubble.duration&&(i.tmp.bubble_clicking=!1,i.tmp.bubble_duration_end=!1)}i.tmp.bubble_clicking&&(t(i.interactivity.modes.bubble.size,i.particles.size.value,e.radius_bubble,e.radius,"size"),t(i.interactivity.modes.bubble.opacity,i.particles.opacity.value,e.opacity_bubble,e.opacity,"opacity"))}},i.fn.modes.repulseParticle=function(e){function a(){var a=Math.atan2(d,p);if(e.vx=u*Math.cos(a),e.vy=u*Math.sin(a),"bounce"==i.particles.move.out_mode){var t={x:e.x+e.vx,y:e.y+e.vy};t.x+e.radius>i.canvas.w?e.vx=-e.vx:t.x-e.radius<0&&(e.vx=-e.vx),t.y+e.radius>i.canvas.h?e.vy=-e.vy:t.y-e.radius<0&&(e.vy=-e.vy)}}if(i.interactivity.events.onhover.enable&&isInArray("repulse",i.interactivity.events.onhover.mode)&&"mousemove"==i.interactivity.status){var t=e.x-i.interactivity.mouse.pos_x,s=e.y-i.interactivity.mouse.pos_y,n=Math.sqrt(t*t+s*s),r={x:t/n,y:s/n},c=i.interactivity.modes.repulse.distance,o=100,l=clamp(1/c*(-1*Math.pow(n/c,2)+1)*c*o,0,50),v={x:e.x+r.x*l,y:e.y+r.y*l};"bounce"==i.particles.move.out_mode?(v.x-e.radius>0&&v.x+e.radius0&&v.y+e.radius=m&&a()}else 0==i.tmp.repulse_clicking&&(e.vx=e.vx_i,e.vy=e.vy_i)},i.fn.modes.grabParticle=function(e){if(i.interactivity.events.onhover.enable&&"mousemove"==i.interactivity.status){var a=e.x-i.interactivity.mouse.pos_x,t=e.y-i.interactivity.mouse.pos_y,s=Math.sqrt(a*a+t*t);if(s<=i.interactivity.modes.grab.distance){var n=i.interactivity.modes.grab.line_linked.opacity-s/(1/i.interactivity.modes.grab.line_linked.opacity)/i.interactivity.modes.grab.distance;if(n>0){var r=i.particles.line_linked.color_rgb_line;i.canvas.ctx.strokeStyle="rgba("+r.r+","+r.g+","+r.b+","+n+")",i.canvas.ctx.lineWidth=i.particles.line_linked.width,i.canvas.ctx.beginPath(),i.canvas.ctx.moveTo(e.x,e.y),i.canvas.ctx.lineTo(i.interactivity.mouse.pos_x,i.interactivity.mouse.pos_y),i.canvas.ctx.stroke(),i.canvas.ctx.closePath()}}}},i.fn.vendors.eventsListeners=function(){"window"==i.interactivity.detect_on?i.interactivity.el=window:i.interactivity.el=i.canvas.el,(i.interactivity.events.onhover.enable||i.interactivity.events.onclick.enable)&&(i.interactivity.el.addEventListener("mousemove",function(e){if(i.interactivity.el==window)var a=e.clientX,t=e.clientY;else var a=e.offsetX||e.clientX,t=e.offsetY||e.clientY;i.interactivity.mouse.pos_x=a,i.interactivity.mouse.pos_y=t,i.tmp.retina&&(i.interactivity.mouse.pos_x*=i.canvas.pxratio,i.interactivity.mouse.pos_y*=i.canvas.pxratio),i.interactivity.status="mousemove"}),i.interactivity.el.addEventListener("mouseleave",function(e){i.interactivity.mouse.pos_x=null,i.interactivity.mouse.pos_y=null,i.interactivity.status="mouseleave"})),i.interactivity.events.onclick.enable&&i.interactivity.el.addEventListener("click",function(){if(i.interactivity.mouse.click_pos_x=i.interactivity.mouse.pos_x,i.interactivity.mouse.click_pos_y=i.interactivity.mouse.pos_y,i.interactivity.mouse.click_time=(new Date).getTime(),i.interactivity.events.onclick.enable)switch(i.interactivity.events.onclick.mode){case"push":i.particles.move.enable?i.fn.modes.pushParticles(i.interactivity.modes.push.particles_nb,i.interactivity.mouse):1==i.interactivity.modes.push.particles_nb?i.fn.modes.pushParticles(i.interactivity.modes.push.particles_nb,i.interactivity.mouse):i.interactivity.modes.push.particles_nb>1&&i.fn.modes.pushParticles(i.interactivity.modes.push.particles_nb);break;case"remove":i.fn.modes.removeParticles(i.interactivity.modes.remove.particles_nb);break;case"bubble":i.tmp.bubble_clicking=!0;break;case"repulse":i.tmp.repulse_clicking=!0,i.tmp.repulse_count=0,i.tmp.repulse_finish=!1,setTimeout(function(){i.tmp.repulse_clicking=!1},1e3*i.interactivity.modes.repulse.duration)}})},i.fn.vendors.densityAutoParticles=function(){if(i.particles.number.density.enable){var e=i.canvas.el.width*i.canvas.el.height/1e3;i.tmp.retina&&(e/=2*i.canvas.pxratio);var a=e*i.particles.number.value/i.particles.number.density.value_area,t=i.particles.array.length-a;0>t?i.fn.modes.pushParticles(Math.abs(t)):i.fn.modes.removeParticles(t)}},i.fn.vendors.checkOverlap=function(e,a){for(var t=0;tv;v++)e.lineTo(i,0),e.translate(i,0),e.rotate(l);e.fill(),e.restore()},i.fn.vendors.exportImg=function(){window.open(i.canvas.el.toDataURL("image/png"),"_blank")},i.fn.vendors.loadImg=function(e){if(i.tmp.img_error=void 0,""!=i.particles.shape.image.src)if("svg"==e){var a=new XMLHttpRequest;a.open("GET",i.particles.shape.image.src),a.onreadystatechange=function(e){4==a.readyState&&(200==a.status?(i.tmp.source_svg=e.currentTarget.response,i.fn.vendors.checkBeforeDraw()):(console.log("Error pJS - Image not found"),i.tmp.img_error=!0))},a.send()}else{var t=new Image;t.addEventListener("load",function(){i.tmp.img_obj=t,i.fn.vendors.checkBeforeDraw()}),t.src=i.particles.shape.image.src}else console.log("Error pJS - No image.src"),i.tmp.img_error=!0},i.fn.vendors.draw=function(){"image"==i.particles.shape.type?"svg"==i.tmp.img_type?i.tmp.count_svg>=i.particles.number.value?(i.fn.particlesDraw(),i.particles.move.enable?i.fn.drawAnimFrame=requestAnimFrame(i.fn.vendors.draw):cancelRequestAnimFrame(i.fn.drawAnimFrame)):i.tmp.img_error||(i.fn.drawAnimFrame=requestAnimFrame(i.fn.vendors.draw)):void 0!=i.tmp.img_obj?(i.fn.particlesDraw(),i.particles.move.enable?i.fn.drawAnimFrame=requestAnimFrame(i.fn.vendors.draw):cancelRequestAnimFrame(i.fn.drawAnimFrame)):i.tmp.img_error||(i.fn.drawAnimFrame=requestAnimFrame(i.fn.vendors.draw)):(i.fn.particlesDraw(),i.particles.move.enable?i.fn.drawAnimFrame=requestAnimFrame(i.fn.vendors.draw):cancelRequestAnimFrame(i.fn.drawAnimFrame))},i.fn.vendors.checkBeforeDraw=function(){"image"==i.particles.shape.type?"svg"==i.tmp.img_type&&void 0==i.tmp.source_svg?i.tmp.checkAnimFrame=requestAnimFrame(check):(cancelRequestAnimFrame(i.tmp.checkAnimFrame),i.tmp.img_error||(i.fn.vendors.init(),i.fn.vendors.draw())):(i.fn.vendors.init(),i.fn.vendors.draw())},i.fn.vendors.init=function(){i.fn.retinaInit(),i.fn.canvasInit(),i.fn.canvasSize(),i.fn.canvasPaint(),i.fn.particlesCreate(),i.fn.vendors.densityAutoParticles(),i.particles.line_linked.color_rgb_line=hexToRgb(i.particles.line_linked.color)},i.fn.vendors.start=function(){isInArray("image",i.particles.shape.type)?(i.tmp.img_type=i.particles.shape.image.src.substr(i.particles.shape.image.src.length-3),i.fn.vendors.loadImg(i.tmp.img_type)):i.fn.vendors.checkBeforeDraw()},i.fn.vendors.eventsListeners(),i.fn.vendors.start()};Object.deepExtend=function(e,a){for(var t in a)a[t]&&a[t].constructor&&a[t].constructor===Object?(e[t]=e[t]||{},arguments.callee(e[t],a[t])):e[t]=a[t];return e},window.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(e){window.setTimeout(e,1e3/60)}}(),window.cancelRequestAnimFrame=function(){return window.cancelAnimationFrame||window.webkitCancelRequestAnimationFrame||window.mozCancelRequestAnimationFrame||window.oCancelRequestAnimationFrame||window.msCancelRequestAnimationFrame||clearTimeout}(),window.pJSDom=[],window.particlesJS=function(e,a){"string"!=typeof e&&(a=e,e="particles-js"),e||(e="particles-js");var t=document.getElementById(e),i="particles-js-canvas-el",s=t.getElementsByClassName(i);if(s.length)for(;s.length>0;)t.removeChild(s[0]);var n=document.createElement("canvas");n.className=i,n.style.width="100%",n.style.height="100%";var r=document.getElementById(e).appendChild(n);null!=r&&pJSDom.push(new pJS(e,a))},window.particlesJS.load=function(e,a,t){var i=new XMLHttpRequest;i.open("GET",a),i.onreadystatechange=function(a){if(4==i.readyState)if(200==i.status){var s=JSON.parse(a.currentTarget.response);window.particlesJS(e,s),t&&t()}else console.log("Error pJS - XMLHttpRequest status: "+i.status),console.log("Error pJS - File config not found")},i.send()}; diff --git a/resources/lang/en/admin/node.php b/resources/lang/en/admin/node.php new file mode 100644 index 000000000..8e685a8f8 --- /dev/null +++ b/resources/lang/en/admin/node.php @@ -0,0 +1,38 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +return [ + 'validation' => [ + 'fqdn_not_resolvable' => 'The FQDN or IP address provided does not resolve to a valid IP address.', + 'fqdn_required_for_ssl' => 'A fully qualified domain name that resolves to a public IP address is required in order to use SSL for this node.', + ], + 'notices' => [ + 'allocations_added' => 'Allocations have successfully been added to this node.', + 'node_deleted' => 'Node has been successfully removed from the panel.', + 'location_required' => 'You must have at least one location configured before you can add a node to this panel.', + 'node_created' => 'Successfully created new node. You can automatically configure the daemon on this machine by visiting the \'Configuration\' tab. Before you can add any servers you must first allocate at least one IP address and port.', + 'node_updated' => 'Node information has been updated. If any daemon settings were changed you will need to reboot it for those changes to take effect.', + 'unallocated_deleted' => 'Deleted all unallocatred ports for :ip.', + ], +]; diff --git a/resources/lang/en/admin/pack.php b/resources/lang/en/admin/pack.php new file mode 100644 index 000000000..d41cfd4d5 --- /dev/null +++ b/resources/lang/en/admin/pack.php @@ -0,0 +1,31 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +return [ + 'notices' => [ + 'pack_updated' => 'Pack has been successfully updated.', + 'pack_deleted' => 'Successfully deleted the pack ":name" from the system.', + 'pack_created' => 'A new pack was successfully created on the system and is now available for deployment to servers.', + ], +]; diff --git a/resources/lang/en/admin/server.php b/resources/lang/en/admin/server.php new file mode 100644 index 000000000..6cf48223a --- /dev/null +++ b/resources/lang/en/admin/server.php @@ -0,0 +1,45 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +return [ + 'exceptions' => [ + 'no_new_default_allocation' => 'You are attempting to delete the default allocation for this server but there is no fallback allocation to use.', + 'marked_as_failed' => 'This server was marked as having failed a previous installation. Current status cannot be toggled in this state.', + 'bad_variable' => 'There was a validation error with the :name variable.', + 'daemon_exception' => 'There was an exception while attempting to communicate with the daemon resulting in a HTTP/:code response code. This exception has been logged.', + 'default_allocation_not_found' => 'The requested default allocation was not found in this server\'s allocations.', + ], + 'alerts' => [ + 'startup_changed' => 'The startup configuration for this server has been updated. If this server\'s service or option was changed a reinstall will be occuring now.', + 'server_deleted' => 'Server has successfully been deleted from the system.', + 'server_created' => 'Server was successfully created on the panel. Please allow the daemon a few minutes to completely install this server.', + 'build_updated' => 'The build details for this server have been updated. Some changes may require a restart to take effect.', + 'suspension_toggled' => 'Server suspension status has been changed to :status.', + 'rebuild_on_boot' => 'This server has been marked as requiring a Docker Container rebuild. This will happen the next time the server is started.', + 'install_toggled' => 'The installation status for this server has been toggled.', + 'server_reinstalled' => 'This server has been queued for a reinstallation beginning now.', + 'details_updated' => 'Server details have been successfully updated.', + 'docker_image_updated' => 'Successfully changed the default Docker image to use for this server. A reboot is required to apply this change.', + ], +]; diff --git a/resources/lang/en/admin/services.php b/resources/lang/en/admin/services.php new file mode 100644 index 000000000..91c851f32 --- /dev/null +++ b/resources/lang/en/admin/services.php @@ -0,0 +1,47 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +return [ + 'notices' => [ + 'service_created' => 'A new service, :name, has been successfully created.', + 'service_deleted' => 'Successfully deleted the requested service from the Panel.', + 'service_updated' => 'Successfully updated the service configuration options.', + 'functions_updated' => 'The service functions file has been updated. You will need to reboot your Nodes in order for these changes to be applied.', + ], + 'options' => [ + 'notices' => [ + 'option_deleted' => 'Successfully deleted the requested service option from the Panel.', + 'option_updated' => 'Service option configuration has been updated successfully.', + 'script_updated' => 'Service option install script has been updated and will run whenever servers are installed.', + 'option_created' => 'New service option was created successfully. You will need to restart any running daemons to apply this new service.', + ], + ], + 'variables' => [ + 'notices' => [ + 'variable_deleted' => 'The variable ":variable" has been deleted and will no longer be available to servers once rebuilt.', + 'variable_updated' => 'The variable ":variable" has been updated. You will need to rebuild any servers using this variable in order to apply changes.', + 'variable_created' => 'New variable has successfully been created and assigned to this service option.', + ], + ], +]; diff --git a/resources/lang/en/admin/user.php b/resources/lang/en/admin/user.php new file mode 100644 index 000000000..b8d38d323 --- /dev/null +++ b/resources/lang/en/admin/user.php @@ -0,0 +1,33 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +return [ + 'exceptions' => [ + 'user_has_servers' => 'Cannot delete a user with active servers attached to their account. Please delete their server\'s before continuing.', + ], + 'notices' => [ + 'account_created' => 'Account has been created successfully.', + 'account_updated' => 'Account has been successfully updated.', + ], +]; diff --git a/resources/lang/en/base.php b/resources/lang/en/base.php index 15554fd14..9c7bd9d0d 100644 --- a/resources/lang/en/base.php +++ b/resources/lang/en/base.php @@ -33,6 +33,7 @@ return [ 'header_sub' => 'Manage your API access keys.', 'list' => 'API Keys', 'create_new' => 'Create New API key', + 'keypair_created' => 'An API Key-Pair has been generated. Your API secret token is :token. Please take note of this key as it will not be displayed again.', ], 'new' => [ 'header' => 'New API Key', @@ -173,7 +174,7 @@ return [ 'service_header' => 'Service Control', 'service' => [ 'list' => [ - 'title' => 'List Services', + 'title' => 'List Service', 'desc' => 'Allows listing of all services configured on the system.', ], 'view' => [ @@ -207,6 +208,8 @@ return [ ], ], 'account' => [ + 'details_updated' => 'Your account details have been successfully updated.', + 'invalid_password' => 'The password provided for your account was not valid.', 'header' => 'Your Account', 'header_sub' => 'Manage your account details.', 'update_pass' => 'Update Password', @@ -219,10 +222,9 @@ return [ 'last_name' => 'Last Name', 'update_identitity' => 'Update Identity', 'username_help' => 'Your username must be unique to your account, and may only contain the following characters: :requirements.', - 'invalid_pass' => 'The password provided was not valid for this account.', - 'exception' => 'An error occurred while attempting to update your account.', ], 'security' => [ + 'session_mgmt_disabled' => 'Your host has not enabled the ability to manage account sessions via this interface.', 'header' => 'Account Security', 'header_sub' => 'Control active sessions and 2-Factor Authentication.', 'sessions' => 'Active Sessions', @@ -234,5 +236,6 @@ return [ 'enable_2fa' => 'Enable 2-Factor Authentication', '2fa_qr' => 'Confgure 2FA on Your Device', '2fa_checkpoint_help' => 'Use the 2FA application on your phone to take a picture of the QR code to the left, or manually enter the code under it. Once you have done so, generate a token and enter it below.', + '2fa_disable_error' => 'The 2FA token provided was not valid. Protection has not been disabled for this account.', ], ]; diff --git a/resources/lang/en/exceptions.php b/resources/lang/en/exceptions.php new file mode 100644 index 000000000..59950e2c2 --- /dev/null +++ b/resources/lang/en/exceptions.php @@ -0,0 +1,65 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +return [ + 'daemon_connection_failed' => 'There was an exception while attempting to communicate with the daemon resulting in a HTTP/:code response code. This exception has been logged.', + 'node' => [ + 'servers_attached' => 'A node must have no servers linked to it in order to be deleted.', + 'daemon_off_config_updated' => 'The daemon configuration has been updated, however there was an error encountered while attempting to automatically update the configuration file on the Daemon. You will need to manually update the configuration file (core.json) for the daemon to apply these changes. The daemon responded with a HTTP/:code response code and the error has been logged.', + ], + 'allocations' => [ + 'too_many_ports' => 'Adding more than 1000 ports at a single time is not supported. Please use a smaller range.', + 'invalid_mapping' => 'The mapping provided for :port was invalid and could not be processed.', + 'cidr_out_of_range' => 'CIDR notation only allows masks between /25 and /32.', + ], + 'service' => [ + 'delete_has_servers' => 'A service with active servers attached to it cannot be deleted from the Panel.', + 'options' => [ + 'delete_has_servers' => 'A service option with active servers attached to it cannot be deleted from the Panel.', + 'invalid_copy_id' => 'The service option selected for copying a script from either does not exist, or is copying a script itself.', + 'must_be_child' => 'The "Copy Settings From" directive for this option must be a child option for the selected service.', + ], + 'variables' => [ + 'env_not_unique' => 'The environment variable :name must be unique to this service option.', + 'reserved_name' => 'The environment variable :name is protected and cannot be assigned to a variable.', + ], + ], + 'packs' => [ + 'delete_has_servers' => 'Cannot delete a pack that is attached to active servers.', + 'update_has_servers' => 'Cannot modify the associated option ID when servers are currently attached to a pack.', + 'invalid_upload' => 'The file provided does not appear to be valid.', + 'invalid_mime' => 'The file provided does not meet the required type :type', + 'unreadable' => 'The archive provided could not be opened by the server.', + 'zip_extraction' => 'An exception was encountered while attempting to extract the archive provided onto the server.', + 'invalid_archive_exception' => 'The pack archive provided appears to be missing a required archive.tar.gz or import.json file in the base directory.', + ], + 'subusers' => [ + 'editing_self' => 'Editing your own subuser account is not permitted.', + 'user_is_owner' => 'You cannot add the server owner as a subuser for this server.', + 'subuser_exists' => 'A user with that email address is already assigned as a subuser for this server.', + ], + 'databases' => [ + 'delete_has_databases' => 'Cannot delete a database host server that has active databases linked to it.', + ], +]; diff --git a/resources/lang/en/pagination.php b/resources/lang/en/pagination.php index fcab34b25..ecac3aa33 100644 --- a/resources/lang/en/pagination.php +++ b/resources/lang/en/pagination.php @@ -1,7 +1,6 @@ '« Previous', - 'next' => 'Next »', - + 'next' => 'Next »', ]; diff --git a/resources/lang/en/server.php b/resources/lang/en/server.php index bb852965a..8e7503243 100644 --- a/resources/lang/en/server.php +++ b/resources/lang/en/server.php @@ -19,6 +19,7 @@ return [ 'new' => [ 'header' => 'New Task', 'header_sub' => 'Create a new scheduled task for this server.', + 'task_name' => 'Task Name', 'day_of_week' => 'Day of Week', 'custom' => 'Custom Value', 'day_of_month' => 'Day of Month', @@ -33,9 +34,16 @@ return [ 'sat' => 'Saturday', 'submit' => 'Create Task', 'type' => 'Task Type', + 'chain_then' => 'Then, After', + 'chain_do' => 'Do', + 'chain_arguments' => 'With Arguments', 'payload' => 'Task Payload', 'payload_help' => 'For example, if you selected Send Command enter the command here. If you selected Send Power Option put the power action here (e.g. restart).', ], + 'edit' => [ + 'header' => 'Manage Task', + 'submit' => 'Update Task', + ], ], 'users' => [ 'header' => 'Manage Users', @@ -44,6 +52,8 @@ return [ 'list' => 'Accounts with Access', 'add' => 'Add New Subuser', 'update' => 'Update Subuser', + 'user_assigned' => 'Successfully assigned a new subuser to this server.', + 'user_updated' => 'Successfully updated permissions.', 'edit' => [ 'header' => 'Edit Subuser', 'header_sub' => 'Modify user\'s access to server.', @@ -203,6 +213,10 @@ return [ ], ], 'files' => [ + 'exceptions' => [ + 'invalid_mime' => 'This type of file cannot be edited via the Panel\'s built-in editor.', + 'max_size' => 'This file is too large to edit via the Panel\'s built-in editor.', + ], 'header' => 'File Manager', 'header_sub' => 'Manage all of your files directly from the web.', 'loading' => 'Loading initial file structure, this could take a few seconds.', diff --git a/resources/lang/en/strings.php b/resources/lang/en/strings.php index 864cdf2e2..8cdb400c1 100644 --- a/resources/lang/en/strings.php +++ b/resources/lang/en/strings.php @@ -71,4 +71,7 @@ return [ 'admin' => 'Admin', 'subuser' => 'Subuser', 'captcha_invalid' => 'The provided captcha is invalid.', + 'child_tasks' => 'Child Tasks', + 'seconds' => 'Seconds', + 'minutes' => 'Minutes', ]; diff --git a/resources/lang/en/validation.php b/resources/lang/en/validation.php index 9608bc25b..d6b784673 100644 --- a/resources/lang/en/validation.php +++ b/resources/lang/en/validation.php @@ -1,7 +1,6 @@ 'The :attribute must be accepted.', - 'active_url' => 'The :attribute is not a valid URL.', - 'after' => 'The :attribute must be a date after :date.', - 'after_or_equal' => 'The :attribute must be a date after or equal to :date.', - 'alpha' => 'The :attribute may only contain letters.', - 'alpha_dash' => 'The :attribute may only contain letters, numbers, and dashes.', - 'alpha_num' => 'The :attribute may only contain letters and numbers.', - 'array' => 'The :attribute must be an array.', - 'before' => 'The :attribute must be a date before :date.', - 'before_or_equal' => 'The :attribute must be a date before or equal to :date.', - 'between' => [ + 'accepted' => 'The :attribute must be accepted.', + 'active_url' => 'The :attribute is not a valid URL.', + 'after' => 'The :attribute must be a date after :date.', + 'after_or_equal' => 'The :attribute must be a date after or equal to :date.', + 'alpha' => 'The :attribute may only contain letters.', + 'alpha_dash' => 'The :attribute may only contain letters, numbers, and dashes.', + 'alpha_num' => 'The :attribute may only contain letters and numbers.', + 'array' => 'The :attribute must be an array.', + 'before' => 'The :attribute must be a date before :date.', + 'before_or_equal' => 'The :attribute must be a date before or equal to :date.', + 'between' => [ 'numeric' => 'The :attribute must be between :min and :max.', - 'file' => 'The :attribute must be between :min and :max kilobytes.', - 'string' => 'The :attribute must be between :min and :max characters.', - 'array' => 'The :attribute must have between :min and :max items.', + 'file' => 'The :attribute must be between :min and :max kilobytes.', + 'string' => 'The :attribute must be between :min and :max characters.', + 'array' => 'The :attribute must have between :min and :max items.', ], - 'boolean' => 'The :attribute field must be true or false.', - 'confirmed' => 'The :attribute confirmation does not match.', - 'date' => 'The :attribute is not a valid date.', - 'date_format' => 'The :attribute does not match the format :format.', - 'different' => 'The :attribute and :other must be different.', - 'digits' => 'The :attribute must be :digits digits.', - 'digits_between' => 'The :attribute must be between :min and :max digits.', - 'dimensions' => 'The :attribute has invalid image dimensions.', - 'distinct' => 'The :attribute field has a duplicate value.', - 'email' => 'The :attribute must be a valid email address.', - 'exists' => 'The selected :attribute is invalid.', - 'file' => 'The :attribute must be a file.', - 'filled' => 'The :attribute field is required.', - 'image' => 'The :attribute must be an image.', - 'in' => 'The selected :attribute is invalid.', - 'in_array' => 'The :attribute field does not exist in :other.', - 'integer' => 'The :attribute must be an integer.', - 'ip' => 'The :attribute must be a valid IP address.', - 'json' => 'The :attribute must be a valid JSON string.', - 'max' => [ + 'boolean' => 'The :attribute field must be true or false.', + 'confirmed' => 'The :attribute confirmation does not match.', + 'date' => 'The :attribute is not a valid date.', + 'date_format' => 'The :attribute does not match the format :format.', + 'different' => 'The :attribute and :other must be different.', + 'digits' => 'The :attribute must be :digits digits.', + 'digits_between' => 'The :attribute must be between :min and :max digits.', + 'dimensions' => 'The :attribute has invalid image dimensions.', + 'distinct' => 'The :attribute field has a duplicate value.', + 'email' => 'The :attribute must be a valid email address.', + 'exists' => 'The selected :attribute is invalid.', + 'file' => 'The :attribute must be a file.', + 'filled' => 'The :attribute field is required.', + 'image' => 'The :attribute must be an image.', + 'in' => 'The selected :attribute is invalid.', + 'in_array' => 'The :attribute field does not exist in :other.', + 'integer' => 'The :attribute must be an integer.', + 'ip' => 'The :attribute must be a valid IP address.', + 'json' => 'The :attribute must be a valid JSON string.', + 'max' => [ 'numeric' => 'The :attribute may not be greater than :max.', - 'file' => 'The :attribute may not be greater than :max kilobytes.', - 'string' => 'The :attribute may not be greater than :max characters.', - 'array' => 'The :attribute may not have more than :max items.', + 'file' => 'The :attribute may not be greater than :max kilobytes.', + 'string' => 'The :attribute may not be greater than :max characters.', + 'array' => 'The :attribute may not have more than :max items.', ], - 'mimes' => 'The :attribute must be a file of type: :values.', - 'mimetypes' => 'The :attribute must be a file of type: :values.', - 'min' => [ + 'mimes' => 'The :attribute must be a file of type: :values.', + 'mimetypes' => 'The :attribute must be a file of type: :values.', + 'min' => [ 'numeric' => 'The :attribute must be at least :min.', - 'file' => 'The :attribute must be at least :min kilobytes.', - 'string' => 'The :attribute must be at least :min characters.', - 'array' => 'The :attribute must have at least :min items.', + 'file' => 'The :attribute must be at least :min kilobytes.', + 'string' => 'The :attribute must be at least :min characters.', + 'array' => 'The :attribute must have at least :min items.', ], - 'not_in' => 'The selected :attribute is invalid.', - 'numeric' => 'The :attribute must be a number.', - 'present' => 'The :attribute field must be present.', - 'regex' => 'The :attribute format is invalid.', - 'required' => 'The :attribute field is required.', - 'required_if' => 'The :attribute field is required when :other is :value.', - 'required_unless' => 'The :attribute field is required unless :other is in :values.', - 'required_with' => 'The :attribute field is required when :values is present.', - 'required_with_all' => 'The :attribute field is required when :values is present.', - 'required_without' => 'The :attribute field is required when :values is not present.', + 'not_in' => 'The selected :attribute is invalid.', + 'numeric' => 'The :attribute must be a number.', + 'present' => 'The :attribute field must be present.', + 'regex' => 'The :attribute format is invalid.', + 'required' => 'The :attribute field is required.', + 'required_if' => 'The :attribute field is required when :other is :value.', + 'required_unless' => 'The :attribute field is required unless :other is in :values.', + 'required_with' => 'The :attribute field is required when :values is present.', + 'required_with_all' => 'The :attribute field is required when :values is present.', + 'required_without' => 'The :attribute field is required when :values is not present.', 'required_without_all' => 'The :attribute field is required when none of :values are present.', - 'same' => 'The :attribute and :other must match.', - 'size' => [ + 'same' => 'The :attribute and :other must match.', + 'size' => [ 'numeric' => 'The :attribute must be :size.', - 'file' => 'The :attribute must be :size kilobytes.', - 'string' => 'The :attribute must be :size characters.', - 'array' => 'The :attribute must contain :size items.', + 'file' => 'The :attribute must be :size kilobytes.', + 'string' => 'The :attribute must be :size characters.', + 'array' => 'The :attribute must contain :size items.', ], - 'string' => 'The :attribute must be a string.', - 'timezone' => 'The :attribute must be a valid zone.', - 'unique' => 'The :attribute has already been taken.', - 'uploaded' => 'The :attribute failed to upload.', - 'url' => 'The :attribute format is invalid.', + 'string' => 'The :attribute must be a string.', + 'timezone' => 'The :attribute must be a valid zone.', + 'unique' => 'The :attribute has already been taken.', + 'uploaded' => 'The :attribute failed to upload.', + 'url' => 'The :attribute format is invalid.', /* |-------------------------------------------------------------------------- @@ -115,5 +114,4 @@ return [ */ 'attributes' => [], - ]; diff --git a/resources/themes/pterodactyl/admin/databases/index.blade.php b/resources/themes/pterodactyl/admin/databases/index.blade.php index e8e0c21ca..be805d608 100644 --- a/resources/themes/pterodactyl/admin/databases/index.blade.php +++ b/resources/themes/pterodactyl/admin/databases/index.blade.php @@ -117,7 +117,7 @@
+
@@ -83,7 +83,7 @@ @foreach($locations as $location) @endforeach @@ -229,15 +229,10 @@
- - + +

This is the default Docker container that will be used to run this server.

-
- - -

If you would like to use a custom Docker container please enter it here, otherwise leave empty.

-
diff --git a/resources/themes/pterodactyl/admin/servers/view/database.blade.php b/resources/themes/pterodactyl/admin/servers/view/database.blade.php index 562b890e0..c074ad2e0 100644 --- a/resources/themes/pterodactyl/admin/servers/view/database.blade.php +++ b/resources/themes/pterodactyl/admin/servers/view/database.blade.php @@ -91,8 +91,8 @@
- - @foreach($hosts as $host) @endforeach @@ -107,8 +107,8 @@
- - + +

This should reflect the IP address that connections are allowed from. Uses standard MySQL notation. If unsure leave as %.

diff --git a/resources/themes/pterodactyl/admin/servers/view/details.blade.php b/resources/themes/pterodactyl/admin/servers/view/details.blade.php index 8519a16c2..886df32df 100644 --- a/resources/themes/pterodactyl/admin/servers/view/details.blade.php +++ b/resources/themes/pterodactyl/admin/servers/view/details.blade.php @@ -89,6 +89,7 @@
@@ -102,13 +103,14 @@
- +

The docker image to use for this server. The default image for this service and option combination is {{ $server->option->docker_image }}.

diff --git a/resources/themes/pterodactyl/admin/servers/view/startup.blade.php b/resources/themes/pterodactyl/admin/servers/view/startup.blade.php index c83b48cb0..32ad9d2a8 100644 --- a/resources/themes/pterodactyl/admin/servers/view/startup.blade.php +++ b/resources/themes/pterodactyl/admin/servers/view/startup.blade.php @@ -97,7 +97,7 @@ @foreach($services as $service) @endforeach @@ -125,30 +125,7 @@
-
- @foreach($server->option->variables as $variable) -
-
-
-

{{ $variable->name }}

-
-
- -

{{ $variable->description }}

-

- @if($variable->required)Required@elseOptional@endif - @if($variable->user_viewable)Visible@elseHidden@endif - @if($variable->user_editable)Editable@elseLocked@endif -

-
- -
-
- @endforeach -
+
@@ -183,7 +160,6 @@ $('#pServiceId').on('change', function (event) { $('#pOptionId').html('').select2({ data: $.map(_.get(Pterodactyl.services, $(this).val() + '.options', []), function (item) { - console.log(item); return { id: item.id, text: item.name, @@ -205,7 +181,7 @@ } $('#pPackId').html('').select2({ - data: [{ id: 0, text: 'No Service Pack' }].concat( + data: [{ id: '', text: 'No Service Pack' }].concat( $.map(_.get(objectChain, 'packs', []), function (item, i) { return { id: item.id, @@ -213,11 +189,15 @@ }; }) ), - }).val('{{ is_null($server->pack_id) ? 0 : $server->pack_id }}'); + }); + + @if(! is_null($server->pack_id)) + $('#pPackId').val({{ $server->pack_id }}); + @endif $('#appendVariablesTo').html(''); $.each(_.get(objectChain, 'variables', []), function (i, item) { - var setValue = _.get(Pterodactyl.server_variables, 'env_' + item.id + '.value', item.default_value); + var setValue = _.get(Pterodactyl.server_variables, item.env_variable, item.default_value); var isRequired = (item.required === 1) ? 'Required ' : ''; var dataAppend = ' \
\ @@ -226,7 +206,7 @@

' + isRequired + item.name + '

\
\
\ - \ + \

' + item.description + '

\
\ diff --git a/resources/themes/pterodactyl/admin/services/index.blade.php b/resources/themes/pterodactyl/admin/services/index.blade.php index a943355e7..a60ffc225 100644 --- a/resources/themes/pterodactyl/admin/services/index.blade.php +++ b/resources/themes/pterodactyl/admin/services/index.blade.php @@ -20,14 +20,14 @@ @extends('layouts.admin') @section('title') - Services + Service @endsection @section('content-header') -

ServicesAll services currently available on this system.

+

ServiceAll services currently available on this system.

@endsection @@ -36,7 +36,7 @@
-

Configured Services

+

Configured Service

diff --git a/resources/themes/pterodactyl/admin/services/new.blade.php b/resources/themes/pterodactyl/admin/services/new.blade.php index aa3bd0b33..6697aa987 100644 --- a/resources/themes/pterodactyl/admin/services/new.blade.php +++ b/resources/themes/pterodactyl/admin/services/new.blade.php @@ -27,7 +27,7 @@

New ServiceConfigure a new service to deploy to all nodes.

@endsection @@ -64,7 +64,7 @@
-

Services are downloaded by the daemon and stored in a folder using this name. The storage location is /srv/daemon/services/{NAME} by default.

+

Service are downloaded by the daemon and stored in a folder using this name. The storage location is /srv/daemon/services/{NAME} by default.

diff --git a/resources/themes/pterodactyl/admin/services/options/new.blade.php b/resources/themes/pterodactyl/admin/services/options/new.blade.php index b69f539b9..80e611018 100644 --- a/resources/themes/pterodactyl/admin/services/options/new.blade.php +++ b/resources/themes/pterodactyl/admin/services/options/new.blade.php @@ -20,14 +20,14 @@ @extends('layouts.admin') @section('title') - Services → New Option + Service → New Option @endsection @section('content-header')

New OptionCreate a new service option to assign to servers.

@endsection @@ -146,7 +146,7 @@ $('#pConfigFrom').select2(); }); $('#pServiceId').on('change', function (event) { - $('#pConfigFrom').html('').select2({ + $('#pConfigFrom').html('').select2({ data: $.map(_.get(Pterodactyl.services, $(this).val() + '.options', []), function (item) { return { id: item.id, diff --git a/resources/themes/pterodactyl/admin/services/options/scripts.blade.php b/resources/themes/pterodactyl/admin/services/options/scripts.blade.php index f94f9f9a0..0d1881dd5 100644 --- a/resources/themes/pterodactyl/admin/services/options/scripts.blade.php +++ b/resources/themes/pterodactyl/admin/services/options/scripts.blade.php @@ -20,14 +20,14 @@ @extends('layouts.admin') @section('title') - Services → Option: {{ $option->name }} → Scripts + Service → Option: {{ $option->name }} → Scripts @endsection @section('content-header')

{{ $option->name }}Manage install and upgrade scripts for this service option.

diff --git a/resources/themes/pterodactyl/admin/services/options/variables.blade.php b/resources/themes/pterodactyl/admin/services/options/variables.blade.php index dcc8882ca..975745dfe 100644 --- a/resources/themes/pterodactyl/admin/services/options/variables.blade.php +++ b/resources/themes/pterodactyl/admin/services/options/variables.blade.php @@ -27,7 +27,7 @@

{{ $option->name }}Managing variables for this service option.

diff --git a/resources/themes/pterodactyl/admin/services/options/view.blade.php b/resources/themes/pterodactyl/admin/services/options/view.blade.php index 3d3025808..b30cb6efb 100644 --- a/resources/themes/pterodactyl/admin/services/options/view.blade.php +++ b/resources/themes/pterodactyl/admin/services/options/view.blade.php @@ -20,14 +20,14 @@ @extends('layouts.admin') @section('title') - Services → Option: {{ $option->name }} + Service → Option: {{ $option->name }} @endsection @section('content-header')

{{ $option->name }}{{ str_limit($option->description, 50) }}

@@ -143,10 +143,10 @@
diff --git a/resources/themes/pterodactyl/admin/services/view.blade.php b/resources/themes/pterodactyl/admin/services/view.blade.php index 64cf367c2..852a46aaf 100644 --- a/resources/themes/pterodactyl/admin/services/view.blade.php +++ b/resources/themes/pterodactyl/admin/services/view.blade.php @@ -20,14 +20,14 @@ @extends('layouts.admin') @section('title') - Services → {{ $service->name }} + Service → {{ $service->name }} @endsection @section('content-header')

{{ $service->name }}{{ str_limit($service->description, 50) }}

@endsection @@ -71,7 +71,7 @@
-

Services are downloaded by the daemon and stored in a folder using this name. The storage location is /srv/daemon/services/{NAME} by default.

+

Service are downloaded by the daemon and stored in a folder using this name. The storage location is /srv/daemon/services/{NAME} by default.

@@ -84,8 +84,8 @@
diff --git a/resources/themes/pterodactyl/admin/users/view.blade.php b/resources/themes/pterodactyl/admin/users/view.blade.php index 39461e462..3e23bb26d 100644 --- a/resources/themes/pterodactyl/admin/users/view.blade.php +++ b/resources/themes/pterodactyl/admin/users/view.blade.php @@ -68,6 +68,7 @@ diff --git a/resources/themes/pterodactyl/base/api/new.blade.php b/resources/themes/pterodactyl/base/api/new.blade.php index a7a487c3a..aeef9d24b 100644 --- a/resources/themes/pterodactyl/base/api/new.blade.php +++ b/resources/themes/pterodactyl/base/api/new.blade.php @@ -110,35 +110,37 @@ @endif @endforeach -
- @foreach($permissions['admin'] as $block => $perms) -
-
-
-

@lang('base.api.permissions.admin.' . $block . '_header')

-
-
- @foreach($perms as $permission) -
-
- - + @if(Auth::user()->root_admin) +
+ @foreach($permissions['admin'] as $block => $perms) +
+
+
+

@lang('base.api.permissions.admin.' . $block . '_header')

+
+
+ @foreach($perms as $permission) +
+
+ + +
+

@lang('base.api.permissions.admin.' . $block . '.' . $permission . '.desc')

-

@lang('base.api.permissions.admin.' . $block . '.' . $permission . '.desc')

-
- @endforeach + @endforeach +
-
- @if ($loop->iteration % 3 === 0) -
- @endif - @if ($loop->iteration % 2 === 0) -
- @endif - @endforeach -
+ @if ($loop->iteration % 3 === 0) +
+ @endif + @if ($loop->iteration % 2 === 0) +
+ @endif + @endforeach +
+ @endif @endsection diff --git a/resources/themes/pterodactyl/base/security.blade.php b/resources/themes/pterodactyl/base/security.blade.php index 666a790bc..8f3908db5 100644 --- a/resources/themes/pterodactyl/base/security.blade.php +++ b/resources/themes/pterodactyl/base/security.blade.php @@ -39,30 +39,36 @@

@lang('base.security.sessions')

-
- - - - - - - - - @foreach($sessions as $session) + @if(!is_null($sessions)) +
+
@lang('strings.id')@lang('strings.ip')@lang('strings.last_activity')
+ - - - - + + + + - @endforeach - -
{{ substr($session->id, 0, 6) }}{{ $session->ip_address }}{{ Carbon::createFromTimestamp($session->last_activity)->diffForHumans() }} - - - - @lang('strings.id')@lang('strings.ip')@lang('strings.last_activity')
-
+ @foreach($sessions as $session) + + {{ substr($session->id, 0, 6) }} + {{ $session->ip_address }} + {{ Carbon::createFromTimestamp($session->last_activity)->diffForHumans() }} + + + + + + + @endforeach + + +
+ @else +
+

@lang('base.security.session_mgmt_disabled')

+
+ @endif
diff --git a/resources/themes/pterodactyl/layouts/admin.blade.php b/resources/themes/pterodactyl/layouts/admin.blade.php index e568ef0a4..b86896936 100644 --- a/resources/themes/pterodactyl/layouts/admin.blade.php +++ b/resources/themes/pterodactyl/layouts/admin.blade.php @@ -61,7 +61,7 @@ {{ Settings::get('company', 'Pterodactyl') }}