More repository/service/refactor changes

This commit is contained in:
Dane Everitt 2017-08-12 15:29:01 -05:00
parent 2c77d5c44d
commit b8d7d99096
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
44 changed files with 977 additions and 669 deletions

View file

@ -26,5 +26,28 @@ namespace Pterodactyl\Contracts\Repository;
interface ServiceOptionRepositoryInterface extends RepositoryInterface 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);
} }

View file

@ -0,0 +1,30 @@
<?php
/*
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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
{
//
}

View file

@ -5,7 +5,12 @@ namespace Pterodactyl\Exceptions;
use Exception; use Exception;
use Prologue\Alerts\Facades\Alert; use Prologue\Alerts\Facades\Alert;
use Illuminate\Auth\AuthenticationException; 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 Pterodactyl\Exceptions\Model\DataValidationException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
class Handler extends ExceptionHandler class Handler extends ExceptionHandler
@ -16,15 +21,15 @@ class Handler extends ExceptionHandler
* @var array * @var array
*/ */
protected $dontReport = [ protected $dontReport = [
\Illuminate\Auth\AuthenticationException::class, AuthenticationException::class,
\Illuminate\Auth\Access\AuthorizationException::class, AuthorizationException::class,
\Symfony\Component\HttpKernel\Exception\HttpException::class,
\Illuminate\Database\Eloquent\ModelNotFoundException::class,
\Illuminate\Session\TokenMismatchException::class,
\Illuminate\Validation\ValidationException::class,
DisplayException::class, DisplayException::class,
DisplayValidationException::class,
DataValidationException::class, DataValidationException::class,
DisplayValidationException::class,
HttpException::class,
ModelNotFoundException::class,
TokenMismatchException::class,
ValidationException::class,
]; ];
/** /**

View file

@ -24,9 +24,7 @@
namespace Pterodactyl\Exceptions\Repository; namespace Pterodactyl\Exceptions\Repository;
use Illuminate\Database\Eloquent\ModelNotFoundException; class RecordNotFoundException extends \Exception
class RecordNotFoundException extends ModelNotFoundException
{ {
// //
} }

View file

@ -22,7 +22,7 @@
* SOFTWARE. * SOFTWARE.
*/ */
namespace Pterodactyl\Exceptions\Services\Servers; namespace Pterodactyl\Exceptions\Services\Server;
use Exception; use Exception;

View file

@ -0,0 +1,30 @@
<?php
/*
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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\Services\ServiceOption;
class HasActiveServersException extends \Exception
{
//
}

View file

@ -0,0 +1,30 @@
<?php
/*
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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\Services\ServiceOption;
class InvalidCopyFromException extends \Exception
{
//
}

View file

@ -0,0 +1,30 @@
<?php
/*
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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\Services\ServiceOption;
class NoParentConfigurationFoundException extends \Exception
{
//
}

View file

@ -0,0 +1,32 @@
<?php
/*
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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\Services\ServiceVariable;
use Exception;
class ReservedVariableNameException extends Exception
{
//
}

View file

@ -24,26 +24,22 @@
namespace Pterodactyl\Http\Controllers\Admin; namespace Pterodactyl\Http\Controllers\Admin;
use Log;
use Alert;
use Prologue\Alerts\AlertsMessageBag;
use Pterodactyl\Contracts\Repository\ServiceRepositoryInterface;
use Pterodactyl\Http\Requests\Admin\OptionVariableFormRequest;
use Pterodactyl\Http\Requests\Admin\ServiceOptionFormRequest;
use Pterodactyl\Services\Services\Options\CreationService;
use Pterodactyl\Services\Services\Variables\VariableCreationService;
use Route;
use Javascript; use Javascript;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Pterodactyl\Models\Service; use Prologue\Alerts\AlertsMessageBag;
use Pterodactyl\Models\ServiceOption; use Pterodactyl\Models\ServiceOption;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Http\Controllers\Controller; 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\Http\Requests\Admin\Service\EditOptionScript;
use Pterodactyl\Http\Requests\Admin\Service\StoreOptionVariable; 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\Services\ServiceOption\InvalidCopyFromException;
use Pterodactyl\Exceptions\Services\ServiceOption\HasActiveServersException;
use Pterodactyl\Exceptions\Services\ServiceOption\NoParentConfigurationFoundException;
class OptionController extends Controller class OptionController extends Controller
{ {
@ -53,9 +49,24 @@ class OptionController extends Controller
protected $alert; protected $alert;
/** /**
* @var \Pterodactyl\Services\Services\Options\CreationService * @var \Pterodactyl\Services\Services\Options\InstallScriptUpdateService
*/ */
protected $creationService; 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 * @var \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface
@ -63,28 +74,37 @@ class OptionController extends Controller
protected $serviceRepository; protected $serviceRepository;
/** /**
* @var \Pterodactyl\Services\Services\Variables\VariableCreationService * @var \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface
*/ */
protected $variableCreationService; protected $serviceOptionRepository;
/** /**
* OptionController constructor. * OptionController constructor.
* *
* @param \Prologue\Alerts\AlertsMessageBag $alert * @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\ServiceRepositoryInterface $serviceRepository
* @param \Pterodactyl\Services\Services\Options\CreationService $creationService * @param \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface $serviceOptionRepository
* @param \Pterodactyl\Services\Services\Variables\VariableCreationService $variableCreationService
*/ */
public function __construct( public function __construct(
AlertsMessageBag $alert, AlertsMessageBag $alert,
InstallScriptUpdateService $installScriptUpdateService,
OptionCreationService $optionCreationService,
OptionDeletionService $optionDeletionService,
OptionUpdateService $optionUpdateService,
ServiceRepositoryInterface $serviceRepository, ServiceRepositoryInterface $serviceRepository,
CreationService $creationService, ServiceOptionRepositoryInterface $serviceOptionRepository
VariableCreationService $variableCreationService
) { ) {
$this->alert = $alert; $this->alert = $alert;
$this->creationService = $creationService; $this->installScriptUpdateService = $installScriptUpdateService;
$this->optionCreationService = $optionCreationService;
$this->optionDeletionService = $optionDeletionService;
$this->optionUpdateService = $optionUpdateService;
$this->serviceRepository = $serviceRepository; $this->serviceRepository = $serviceRepository;
$this->variableCreationService = $variableCreationService; $this->serviceOptionRepository = $serviceOptionRepository;
} }
/** /**
@ -103,77 +123,77 @@ class OptionController extends Controller
/** /**
* Handle adding a new service option. * Handle adding a new service option.
* *
* @param \Pterodactyl\Http\Requests\Admin\ServiceOptionFormRequest $request * @param \Pterodactyl\Http\Requests\Admin\Service\ServiceOptionFormRequest $request
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse
* *
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Model\DataValidationException
*/ */
public function store(ServiceOptionFormRequest $request) public function store(ServiceOptionFormRequest $request)
{ {
$option = $this->creationService->handle($request->normalize()); try {
$option = $this->optionCreationService->handle($request->normalize());
$this->alert->success(trans('admin/services.options.notices.option_created'))->flash(); $this->alert->success(trans('admin/services.options.notices.option_created'))->flash();
} catch (NoParentConfigurationFoundException $exception) {
$this->alert->danger($exception->getMessage())->flash();
return redirect()->back()->withInput();
}
return redirect()->route('admin.services.option.view', $option->id); 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 \Pterodactyl\Http\Requests\Admin\OptionVariableFormRequest $request
* @param \Pterodactyl\Models\ServiceOption $option * @param \Pterodactyl\Models\ServiceOption $option
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse
*/ */
public function createVariable(OptionVariableFormRequest $request, ServiceOption $option) public function delete(ServiceOption $option)
{ {
$this->variableCreationService->handle($option->id, $request->normalize()); try {
$this->alert->success(trans('admin/services.variables.notices.variable_created'))->flash(); $this->optionDeletionService->handle($option->id);
$this->alert->success()->flash();
} catch (HasActiveServersException $exception) {
$this->alert->danger($exception->getMessage())->flash();
return redirect()->route('admin.services.option.variables', $option->id); return redirect()->route('admin.services.option.view', $option->id);
}
return redirect()->route('admin.services.view', $option->service_id);
} }
/** /**
* Display option overview page. * Display option overview page.
* *
* @param \Illuminate\Http\Request $request * @param \Pterodactyl\Models\ServiceOption $option
* @param int $id
* @return \Illuminate\View\View * @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)]); return view('admin.services.options.view', ['option' => $option]);
}
/**
* 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), ]);
} }
/** /**
* Display script management page for an option. * Display script management page for an option.
* *
* @param Request $request * @param int $option
* @param int $id
* @return \Illuminate\View\View * @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', [ return view('admin.services.options.scripts', [
'copyFromOptions' => ServiceOption::whereNull('copy_script_from')->where([ 'copyFromOptions' => $copyOptions,
['service_id', $option->service_id], 'relyOnScript' => $relyScript,
['id', '!=', $option->id],
])->get(),
'relyOnScript' => ServiceOption::where('copy_script_from', $option->id)->get(),
'option' => $option, 'option' => $option,
]); ]);
} }
@ -182,91 +202,43 @@ class OptionController extends Controller
* Handles POST when editing a configration for a service option. * Handles POST when editing a configration for a service option.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param int $id * @param \Pterodactyl\Models\ServiceOption $option
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse
*/
public function editConfiguration(Request $request, $id)
{
$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();
}
return redirect()->route('admin.services.option.view', $id);
}
/**
* Handles POST when editing a configration for a service option.
* *
* @param \Pterodactyl\Http\Requests\Admin\Service\StoreOptionVariable $request * @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @param int $option * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @param int $variable
* @return \Illuminate\Http\RedirectResponse
*/ */
public function editVariable(StoreOptionVariable $request, $option, $variable) public function editConfiguration(Request $request, ServiceOption $option)
{ {
$repo = new VariableRepository;
try { try {
if ($request->input('action') !== 'delete') { $this->optionUpdateService->handle($option, $request->all());
$variable = $repo->update($variable, $request->normalize()); $this->alert->success(trans('admin/services.options.notices.option_updated'))->flash();
Alert::success("The service variable '{$variable->name}' has been updated.")->flash(); } catch (NoParentConfigurationFoundException $exception) {
} else { dd('hodor');
$repo->delete($variable); $this->alert->danger($exception->getMessage())->flash();
Alert::success('That service variable has been deleted.')->flash();
}
} 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); return redirect()->route('admin.services.option.view', $option->id);
} }
/** /**
* Handles POST when updating script for a service option. * Handles POST when updating script for a service option.
* *
* @param \Pterodactyl\Http\Requests\Admin\Service\EditOptionScript $request * @param \Pterodactyl\Http\Requests\Admin\Service\EditOptionScript $request
* @param \Pterodactyl\Models\ServiceOption $option
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
*/ */
public function updateScripts(EditOptionScript $request) public function updateScripts(EditOptionScript $request, ServiceOption $option)
{ {
try { try {
$this->repository->scripts($request->normalize()); $this->installScriptUpdateService->handle($option, $request->normalize());
$this->alert->success(trans('admin/services.options.notices.script_updated'))->flash();
Alert::success('Successfully updated option scripts to be run when servers are installed.')->flash(); } catch (InvalidCopyFromException $exception) {
} catch (DisplayException $ex) { $this->alert->danger($exception->getMessage())->flash();
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.scripts', $id); return redirect()->route('admin.services.option.scripts', $option->id);
} }
} }

View file

@ -0,0 +1,147 @@
<?php
/*
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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\Http\Controllers\Controller;
use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface;
use Pterodactyl\Http\Requests\Admin\OptionVariableFormRequest;
use Pterodactyl\Models\ServiceOption;
use Pterodactyl\Models\ServiceVariable;
use Pterodactyl\Repositories\Eloquent\ServiceVariableRepository;
use Pterodactyl\Services\Services\Variables\VariableCreationService;
use Pterodactyl\Services\Services\Variables\VariableUpdateService;
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\Services\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\Services\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);
}
}

View file

@ -50,7 +50,12 @@ class OptionVariableFormRequest extends AdminFormRequest
*/ */
public function withValidator($validator) public function withValidator($validator)
{ {
$validator->sometimes('default_value', $this->input('rules') ?? null, function ($input) { $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; return $input->default_value;
}); });
} }

View file

@ -22,21 +22,18 @@
* SOFTWARE. * SOFTWARE.
*/ */
namespace Pterodactyl\Http\Requests\Admin; namespace Pterodactyl\Http\Requests\Admin\Service;
use Pterodactyl\Models\ServiceOption; use Pterodactyl\Models\ServiceOption;
use Pterodactyl\Http\Requests\Admin\AdminFormRequest;
class ServiceOptionFormRequest extends AdminFormRequest class ServiceOptionFormRequest extends AdminFormRequest
{ {
/** /**
* @return array * {@inheritdoc}
*/ */
public function rules() public function rules()
{ {
if ($this->method() === 'PATCH') {
return ServiceOption::getUpdateRulesForId($this->route()->parameter('option')->id);
}
return ServiceOption::getCreateRules(); return ServiceOption::getCreateRules();
} }
} }

View file

@ -27,9 +27,10 @@ namespace Pterodactyl\Models;
use Sofa\Eloquence\Eloquence; use Sofa\Eloquence\Eloquence;
use Sofa\Eloquence\Validable; use Sofa\Eloquence\Validable;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Sofa\Eloquence\Contracts\CleansAttributes;
use Sofa\Eloquence\Contracts\Validable as ValidableContract; use Sofa\Eloquence\Contracts\Validable as ValidableContract;
class APIKey extends Model implements ValidableContract class APIKey extends Model implements CleansAttributes, ValidableContract
{ {
use Eloquence, Validable; use Eloquence, Validable;

View file

@ -27,9 +27,10 @@ namespace Pterodactyl\Models;
use Sofa\Eloquence\Eloquence; use Sofa\Eloquence\Eloquence;
use Sofa\Eloquence\Validable; use Sofa\Eloquence\Validable;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Sofa\Eloquence\Contracts\CleansAttributes;
use Sofa\Eloquence\Contracts\Validable as ValidableContract; use Sofa\Eloquence\Contracts\Validable as ValidableContract;
class APIPermission extends Model implements ValidableContract class APIPermission extends Model implements CleansAttributes, ValidableContract
{ {
use Eloquence, Validable; use Eloquence, Validable;

View file

@ -27,9 +27,10 @@ namespace Pterodactyl\Models;
use Sofa\Eloquence\Eloquence; use Sofa\Eloquence\Eloquence;
use Sofa\Eloquence\Validable; use Sofa\Eloquence\Validable;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Sofa\Eloquence\Contracts\CleansAttributes;
use Sofa\Eloquence\Contracts\Validable as ValidableContract; use Sofa\Eloquence\Contracts\Validable as ValidableContract;
class Allocation extends Model implements ValidableContract class Allocation extends Model implements CleansAttributes, ValidableContract
{ {
use Eloquence, Validable; use Eloquence, Validable;

View file

@ -27,9 +27,10 @@ namespace Pterodactyl\Models;
use Sofa\Eloquence\Eloquence; use Sofa\Eloquence\Eloquence;
use Sofa\Eloquence\Validable; use Sofa\Eloquence\Validable;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Sofa\Eloquence\Contracts\CleansAttributes;
use Sofa\Eloquence\Contracts\Validable as ValidableContract; use Sofa\Eloquence\Contracts\Validable as ValidableContract;
class Database extends Model implements ValidableContract class Database extends Model implements CleansAttributes, ValidableContract
{ {
use Eloquence, Validable; use Eloquence, Validable;

View file

@ -27,9 +27,10 @@ namespace Pterodactyl\Models;
use Sofa\Eloquence\Eloquence; use Sofa\Eloquence\Eloquence;
use Sofa\Eloquence\Validable; use Sofa\Eloquence\Validable;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Sofa\Eloquence\Contracts\CleansAttributes;
use Sofa\Eloquence\Contracts\Validable as ValidableContract; use Sofa\Eloquence\Contracts\Validable as ValidableContract;
class DatabaseHost extends Model implements ValidableContract class DatabaseHost extends Model implements CleansAttributes, ValidableContract
{ {
use Eloquence, Validable; use Eloquence, Validable;

View file

@ -27,9 +27,10 @@ namespace Pterodactyl\Models;
use Sofa\Eloquence\Eloquence; use Sofa\Eloquence\Eloquence;
use Sofa\Eloquence\Validable; use Sofa\Eloquence\Validable;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Sofa\Eloquence\Contracts\CleansAttributes;
use Sofa\Eloquence\Contracts\Validable as ValidableContract; use Sofa\Eloquence\Contracts\Validable as ValidableContract;
class Location extends Model implements ValidableContract class Location extends Model implements CleansAttributes, ValidableContract
{ {
use Eloquence, Validable; use Eloquence, Validable;

View file

@ -29,9 +29,10 @@ use Sofa\Eloquence\Eloquence;
use Sofa\Eloquence\Validable; use Sofa\Eloquence\Validable;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
use Sofa\Eloquence\Contracts\CleansAttributes;
use Sofa\Eloquence\Contracts\Validable as ValidableContract; use Sofa\Eloquence\Contracts\Validable as ValidableContract;
class Node extends Model implements ValidableContract class Node extends Model implements CleansAttributes, ValidableContract
{ {
use Eloquence, Notifiable, Validable; use Eloquence, Notifiable, Validable;

View file

@ -33,9 +33,10 @@ use Sofa\Eloquence\Eloquence;
use Sofa\Eloquence\Validable; use Sofa\Eloquence\Validable;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
use Sofa\Eloquence\Contracts\CleansAttributes;
use Sofa\Eloquence\Contracts\Validable as ValidableContract; use Sofa\Eloquence\Contracts\Validable as ValidableContract;
class Server extends Model implements ValidableContract class Server extends Model implements CleansAttributes, ValidableContract
{ {
use Eloquence, Notifiable, Validable; use Eloquence, Notifiable, Validable;

View file

@ -27,9 +27,10 @@ namespace Pterodactyl\Models;
use Sofa\Eloquence\Eloquence; use Sofa\Eloquence\Eloquence;
use Sofa\Eloquence\Validable; use Sofa\Eloquence\Validable;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Sofa\Eloquence\Contracts\CleansAttributes;
use Sofa\Eloquence\Contracts\Validable as ValidableContract; use Sofa\Eloquence\Contracts\Validable as ValidableContract;
class ServiceOption extends Model implements ValidableContract class ServiceOption extends Model implements CleansAttributes, ValidableContract
{ {
use Eloquence, Validable; use Eloquence, Validable;

View file

@ -27,16 +27,17 @@ namespace Pterodactyl\Models;
use Sofa\Eloquence\Eloquence; use Sofa\Eloquence\Eloquence;
use Sofa\Eloquence\Validable; use Sofa\Eloquence\Validable;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Sofa\Eloquence\Contracts\CleansAttributes;
use Sofa\Eloquence\Contracts\Validable as ValidableContract; use Sofa\Eloquence\Contracts\Validable as ValidableContract;
class ServiceVariable extends Model implements ValidableContract class ServiceVariable extends Model implements CleansAttributes, ValidableContract
{ {
use Eloquence, Validable; use Eloquence, Validable;
/** /**
* Reserved environment variable names. * Reserved environment variable names.
* *
* @var array * @var string
*/ */
const RESERVED_ENV_NAMES = 'SERVER_MEMORY,SERVER_IP,SERVER_PORT,ENV,HOME,USER,STARTUP,SERVER_UUID,UUID'; const RESERVED_ENV_NAMES = 'SERVER_MEMORY,SERVER_IP,SERVER_PORT,ENV,HOME,USER,STARTUP,SERVER_UUID,UUID';

View file

@ -32,6 +32,7 @@ use Illuminate\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Exceptions\DisplayException;
use Sofa\Eloquence\Contracts\CleansAttributes;
use Illuminate\Auth\Passwords\CanResetPassword; use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Foundation\Auth\Access\Authorizable; use Illuminate\Foundation\Auth\Access\Authorizable;
use Sofa\Eloquence\Contracts\Validable as ValidableContract; use Sofa\Eloquence\Contracts\Validable as ValidableContract;
@ -40,7 +41,12 @@ use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification; use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification;
class User extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract, ValidableContract class User extends Model implements
AuthenticatableContract,
AuthorizableContract,
CanResetPasswordContract,
CleansAttributes,
ValidableContract
{ {
use Authenticatable, Authorizable, CanResetPassword, Eloquence, Notifiable, Validable; use Authenticatable, Authorizable, CanResetPassword, Eloquence, Notifiable, Validable;
@ -125,6 +131,7 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
/** /**
* Rules verifying that the data passed in forms is valid and meets application logic rules. * Rules verifying that the data passed in forms is valid and meets application logic rules.
*
* @var array * @var array
*/ */
protected static $applicationRules = [ protected static $applicationRules = [

View file

@ -25,7 +25,9 @@
namespace Pterodactyl\Providers; namespace Pterodactyl\Providers;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Pterodactyl\Contracts\Repository\ServiceVariableRepositoryInterface;
use Pterodactyl\Repositories\Eloquent\NodeRepository; use Pterodactyl\Repositories\Eloquent\NodeRepository;
use Pterodactyl\Repositories\Eloquent\ServiceVariableRepository;
use Pterodactyl\Repositories\Eloquent\UserRepository; use Pterodactyl\Repositories\Eloquent\UserRepository;
use Pterodactyl\Repositories\Eloquent\ApiKeyRepository; use Pterodactyl\Repositories\Eloquent\ApiKeyRepository;
use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Repositories\Eloquent\ServerRepository;
@ -76,6 +78,7 @@ class RepositoryServiceProvider extends ServiceProvider
$this->app->bind(ServerVariableRepositoryInterface::class, ServerVariableRepository::class); $this->app->bind(ServerVariableRepositoryInterface::class, ServerVariableRepository::class);
$this->app->bind(ServiceRepositoryInterface::class, ServiceRepository::class); $this->app->bind(ServiceRepositoryInterface::class, ServiceRepository::class);
$this->app->bind(ServiceOptionRepositoryInterface::class, ServiceOptionRepository::class); $this->app->bind(ServiceOptionRepositoryInterface::class, ServiceOptionRepository::class);
$this->app->bind(ServiceVariableRepositoryInterface::class, ServiceVariableRepository::class);
$this->app->bind(UserRepositoryInterface::class, UserRepository::class); $this->app->bind(UserRepositoryInterface::class, UserRepository::class);
// Daemon Repositories // Daemon Repositories

View file

@ -24,8 +24,9 @@
namespace Pterodactyl\Repositories\Eloquent; namespace Pterodactyl\Repositories\Eloquent;
use Illuminate\Database\Query\Expression; use Webmozart\Assert\Assert;
use Pterodactyl\Repository\Repository; use Pterodactyl\Repository\Repository;
use Illuminate\Database\Query\Expression;
use Pterodactyl\Contracts\Repository\RepositoryInterface; use Pterodactyl\Contracts\Repository\RepositoryInterface;
use Pterodactyl\Exceptions\Model\DataValidationException; use Pterodactyl\Exceptions\Model\DataValidationException;
use Pterodactyl\Exceptions\Repository\RecordNotFoundException; use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
@ -48,6 +49,9 @@ abstract class EloquentRepository extends Repository implements RepositoryInterf
*/ */
public function create(array $fields, $validate = true, $force = false) public function create(array $fields, $validate = true, $force = false)
{ {
Assert::boolean($validate, 'Second argument passed to create should be boolean, recieved %s.');
Assert::boolean($force, 'Third argument passed to create should be boolean, received %s.');
$instance = $this->getBuilder()->newModelInstance(); $instance = $this->getBuilder()->newModelInstance();
if ($force) { if ($force) {
@ -73,6 +77,8 @@ abstract class EloquentRepository extends Repository implements RepositoryInterf
*/ */
public function find($id) public function find($id)
{ {
Assert::integer($id, 'First argument passed to find should be integer, received %s.');
$instance = $this->getBuilder()->find($id, $this->getColumns()); $instance = $this->getBuilder()->find($id, $this->getColumns());
if (! $instance) { if (! $instance) {
@ -119,6 +125,9 @@ abstract class EloquentRepository extends Repository implements RepositoryInterf
*/ */
public function delete($id, $destroy = false) public function delete($id, $destroy = false)
{ {
Assert::integer($id, 'First argument passed to delete should be integer, received %s.');
Assert::boolean($destroy, 'Second argument passed to delete should be boolean, received %s.');
$instance = $this->getBuilder()->where($this->getModel()->getKeyName(), $id); $instance = $this->getBuilder()->where($this->getModel()->getKeyName(), $id);
return ($destroy) ? $instance->forceDelete() : $instance->delete(); return ($destroy) ? $instance->forceDelete() : $instance->delete();
@ -129,6 +138,8 @@ abstract class EloquentRepository extends Repository implements RepositoryInterf
*/ */
public function deleteWhere(array $attributes, $force = false) public function deleteWhere(array $attributes, $force = false)
{ {
Assert::boolean($force, 'Second argument passed to deleteWhere should be boolean, received %s.');
$instance = $this->getBuilder()->where($attributes); $instance = $this->getBuilder()->where($attributes);
return ($force) ? $instance->forceDelete() : $instance->delete(); return ($force) ? $instance->forceDelete() : $instance->delete();
@ -139,6 +150,10 @@ abstract class EloquentRepository extends Repository implements RepositoryInterf
*/ */
public function update($id, array $fields, $validate = true, $force = false) public function update($id, array $fields, $validate = true, $force = false)
{ {
Assert::integer($id, 'First argument passed to update expected to be integer, received %s.');
Assert::boolean($validate, 'Third argument passed to update should be boolean, received %s.');
Assert::boolean($force, 'Fourth argument passed to update should be boolean, received %s.');
$instance = $this->getBuilder()->where('id', $id)->first(); $instance = $this->getBuilder()->where('id', $id)->first();
if (! $instance) { if (! $instance) {
@ -167,6 +182,8 @@ abstract class EloquentRepository extends Repository implements RepositoryInterf
*/ */
public function updateWhereIn($column, array $values, array $fields) public function updateWhereIn($column, array $values, array $fields)
{ {
Assert::stringNotEmpty($column, 'First argument passed to updateWhereIn expected to be a string, received %s.');
return $this->getBuilder()->whereIn($column, $values)->update($fields); return $this->getBuilder()->whereIn($column, $values)->update($fields);
} }
@ -238,6 +255,9 @@ abstract class EloquentRepository extends Repository implements RepositoryInterf
*/ */
public function updateOrCreate(array $where, array $fields, $validate = true, $force = false) public function updateOrCreate(array $where, array $fields, $validate = true, $force = false)
{ {
Assert::boolean($validate, 'Third argument passed to updateOrCreate should be boolean, received %s.');
Assert::boolean($force, 'Fourth argument passed to updateOrCreate should be boolean, received %s.');
$instance = $this->withColumns('id')->findWhere($where)->first(); $instance = $this->withColumns('id')->findWhere($where)->first();
if (! $instance) { if (! $instance) {

View file

@ -36,4 +36,31 @@ class ServiceOptionRepository extends EloquentRepository implements ServiceOptio
{ {
return ServiceOption::class; 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();
}
} }

View file

@ -22,26 +22,18 @@
* SOFTWARE. * SOFTWARE.
*/ */
namespace Pterodactyl\Http\Requests\Admin\Service; namespace Pterodactyl\Repositories\Eloquent;
use Pterodactyl\Http\Requests\Admin\AdminFormRequest; use Pterodactyl\Models\ServiceVariable;
use Pterodactyl\Contracts\Repository\ServiceVariableRepositoryInterface;
class StoreOptionVariable extends AdminFormRequest class ServiceVariableRepository extends EloquentRepository implements ServiceVariableRepositoryInterface
{ {
/** /**
* Set the rules to be used for data passed to the request. * {@inheritdoc}
*
* @return array
*/ */
public function rules() public function model()
{ {
return [ return ServiceVariable::class;
'name' => 'required|string|min:1|max:255',
'description' => 'nullable|string',
'env_variable' => 'required|regex:/^[\w]{1,255}$/',
'rules' => 'bail|required|string',
'default_value' => explode('|', $this->input('rules')),
'options' => 'sometimes|required|array',
];
} }
} }

View file

@ -1,241 +0,0 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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 InvalidArgumentException;
use Pterodactyl\Models\ServiceOption;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Exceptions\DisplayValidationException;
class OptionRepository
{
/**
* Store the requested service option.
*
* @var \Pterodactyl\Models\ServiceOption
*/
protected $model;
/**
* OptionRepository constructor.
*
* @param null|int|\Pterodactyl\Models\ServiceOption $option
*/
public function __construct($option = null)
{
if (is_null($option)) {
return;
}
if ($option instanceof ServiceOption) {
$this->model = $option;
} else {
if (! is_numeric($option)) {
throw new InvalidArgumentException(
sprintf('Variable passed to constructor must be integer or instance of \Pterodactyl\Models\ServiceOption.')
);
}
$this->model = ServiceOption::findOrFail($option);
}
}
/**
* Return the eloquent model for the given repository.
*
* @return null|\Pterodactyl\Models\ServiceOption
*/
public function getModel()
{
return $this->model;
}
/**
* Update the currently assigned model by re-initalizing the class.
*
* @param null|int|\Pterodactyl\Models\ServiceOption $option
* @return $this
*/
public function setModel($option)
{
self::__construct($option);
return $this;
}
/**
* 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 $this->setModel(ServiceOption::create($data))->getModel();
}
/**
* Deletes a service option from the system.
*
* @param int $id
* @return void
*
* @throws \Exception
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Throwable
*/
public function delete($id)
{
$this->model->load('variables', 'servers');
if ($this->model->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 array $data
*
* @throws \Pterodactyl\Exceptions\DisplayException
*/
public function scripts(array $data)
{
$data['script_install'] = empty($data['script_install']) ? null : $data['script_install'];
if (isset($data['copy_script_from']) && ! empty($data['copy_script_from'])) {
$select = ServiceOption::whereNull('copy_script_from')
->where('id', $data['copy_script_from'])
->where('service_id', $this->model->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;
}
$this->model->fill($data)->save();
}
}

View file

@ -1,170 +0,0 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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);
$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;
}
}

View file

@ -0,0 +1,77 @@
<?php
/*
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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\Services\ServiceOption\InvalidCopyFromException;
class InstallScriptUpdateService
{
/**
* @var \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface
*/
protected $serviceOptionRepository;
/**
* InstallScriptUpdateService constructor.
*
* @param \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface $serviceOptionRepository
*/
public function __construct(ServiceOptionRepositoryInterface $serviceOptionRepository)
{
$this->serviceOptionRepository = $serviceOptionRepository;
}
/**
* 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\Services\ServiceOption\InvalidCopyFromException
*/
public function handle($option, array $data)
{
if (! $option instanceof ServiceOption) {
$option = $this->serviceOptionRepository->find($option);
}
if (! is_null(array_get($data, 'copy_script_from'))) {
if (! $this->serviceOptionRepository->isCopiableScript(array_get($data, 'copy_script_from'), $option->service_id)) {
throw new InvalidCopyFromException(trans('admin/exceptions.service.options.invalid_copy_id'));
}
}
$this->serviceOptionRepository->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'),
]);
}
}

View file

@ -24,10 +24,10 @@
namespace Pterodactyl\Services\Services\Options; namespace Pterodactyl\Services\Services\Options;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface; use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface;
use Pterodactyl\Exceptions\Services\ServiceOption\NoParentConfigurationFoundException;
class CreationService class OptionCreationService
{ {
/** /**
* @var \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface * @var \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface
@ -50,8 +50,8 @@ class CreationService
* @param array $data * @param array $data
* @return \Pterodactyl\Models\ServiceOption * @return \Pterodactyl\Models\ServiceOption
* *
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Services\ServiceOption\NoParentConfigurationFoundException
*/ */
public function handle(array $data) public function handle(array $data)
{ {
@ -62,7 +62,7 @@ class CreationService
]); ]);
if ($results !== 1) { if ($results !== 1) {
throw new DisplayException(trans('admin/exceptions.service.options.must_be_child')); throw new NoParentConfigurationFoundException(trans('admin/exceptions.service.options.must_be_child'));
} }
} else { } else {
$data['config_from'] = null; $data['config_from'] = null;

View file

@ -0,0 +1,77 @@
<?php
/*
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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\ServerRepositoryInterface;
use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface;
use Pterodactyl\Exceptions\Services\ServiceOption\HasActiveServersException;
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\Services\ServiceOption\HasActiveServersException
*/
public function handle($option)
{
$servers = $this->serverRepository->findCountWhere([
['option_id', '=', $option],
]);
if ($servers > 0) {
throw new HasActiveServersException(trans('admin/exceptions.service.options.delete_has_servers'));
}
return $this->repository->delete($option);
}
}

View file

@ -0,0 +1,77 @@
<?php
/*
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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\Services\ServiceOption\NoParentConfigurationFoundException;
use Pterodactyl\Models\ServiceOption;
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\Services\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('admin/exceptions.service.options.must_be_child'));
}
}
$this->repository->withoutFresh()->update($option->id, $data);
}
}

View file

@ -25,6 +25,10 @@
namespace Pterodactyl\Services\Services\Variables; namespace Pterodactyl\Services\Services\Variables;
use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface; use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface;
use Pterodactyl\Contracts\Repository\ServiceVariableRepositoryInterface;
use Pterodactyl\Exceptions\Services\ServiceVariable\ReservedVariableNameException;
use Pterodactyl\Models\ServiceOption;
use Pterodactyl\Models\ServiceVariable;
class VariableCreationService class VariableCreationService
{ {
@ -33,20 +37,45 @@ class VariableCreationService
*/ */
protected $serviceOptionRepository; protected $serviceOptionRepository;
public function __construct(ServiceOptionRepositoryInterface $serviceOptionRepository) /**
{ * @var \Pterodactyl\Contracts\Repository\ServiceVariableRepositoryInterface
*/
protected $serviceVariableRepository;
public function __construct(
ServiceOptionRepositoryInterface $serviceOptionRepository,
ServiceVariableRepositoryInterface $serviceVariableRepository
) {
$this->serviceOptionRepository = $serviceOptionRepository; $this->serviceOptionRepository = $serviceOptionRepository;
$this->serviceVariableRepository = $serviceVariableRepository;
} }
/** /**
* Create a new variable for a given service option. * Create a new variable for a given service option.
* *
* @param int $optionId * @param int $option
* @param array $data * @param array $data
* @return \Pterodactyl\Models\ServiceVariable * @return \Pterodactyl\Models\ServiceVariable
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Services\ServiceVariable\ReservedVariableNameException
*/ */
public function handle($optionId, array $data) public function handle($option, array $data)
{ {
$option = $this->serviceOptionRepository->find($optionId); 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.'));
}
$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));
} }
} }

View file

@ -0,0 +1,88 @@
<?php
/*
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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\Services\ServiceVariable\ReservedVariableNameException;
class VariableUpdateService
{
/**
* @var \Pterodactyl\Contracts\Repository\ServiceVariableRepositoryInterface
*/
protected $serviceVariableRepository;
public function __construct(ServiceVariableRepositoryInterface $serviceVariableRepository)
{
$this->serviceVariableRepository = $serviceVariableRepository;
}
/**
* Update a specific service variable.
*
* @param int|\Pterodactyl\Models\ServiceVariable $variable
* @param array $data
* @return \Pterodactyl\Models\ServiceVariable
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Services\ServiceVariable\ReservedVariableNameException
*/
public function handle($variable, array $data)
{
if (! $variable instanceof ServiceVariable) {
$variable = $this->serviceVariableRepository->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('admin/exceptions.service.variables.reserved_name', [
'name' => array_get($data, 'env_variable'),
]));
}
$search = $this->serviceVariableRepository->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('admin/exceptions.service.variables.env_not_unique', [
'name' => array_get($data, 'env_variable'),
]));
}
}
$options = array_get($data, 'options', []);
return $this->serviceVariableRepository->update($variable->id, array_merge([
'user_viewable' => in_array('user_viewable', $options, $variable->user_viewable),
'user_editable' => in_array('user_editable', $options, $variable->user_editable),
], $data));
}
}

View file

@ -38,6 +38,7 @@
"sofa/eloquence": "5.4.1", "sofa/eloquence": "5.4.1",
"spatie/laravel-fractal": "4.0.1", "spatie/laravel-fractal": "4.0.1",
"watson/validating": "3.0.1", "watson/validating": "3.0.1",
"webmozart/assert": "^1.2",
"webpatser/laravel-uuid": "2.0.1" "webpatser/laravel-uuid": "2.0.1"
}, },
"require-dev": { "require-dev": {

102
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "d4f8198c8d3d27408b5be1a525e8ad4b", "content-hash": "76f4864c9d8653bb8a6be22a115b7489",
"packages": [ "packages": [
{ {
"name": "aws/aws-sdk-php", "name": "aws/aws-sdk-php",
@ -3821,6 +3821,56 @@
], ],
"time": "2016-10-31T21:53:17+00:00" "time": "2016-10-31T21:53:17+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", "name": "webpatser/laravel-uuid",
"version": "2.0.1", "version": "2.0.1",
@ -6027,56 +6077,6 @@
"description": "Symfony Yaml Component", "description": "Symfony Yaml Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2017-07-23T12:43:26+00:00" "time": "2017-07-23T12:43:26+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"
} }
], ],
"aliases": [], "aliases": [],

View file

@ -35,7 +35,13 @@ return [
], ],
'service' => [ 'service' => [
'options' => [ 'options' => [
'must_be_child' => 'The "Configuration From" directive for this option must be a child option for the selected service.', '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.',
], ],
], ],
]; ];

View file

@ -25,11 +25,15 @@
return [ return [
'options' => [ 'options' => [
'notices' => [ 'notices' => [
'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.', 'option_created' => 'New service option was created successfully. You will need to restart any running daemons to apply this new service.',
], ],
], ],
'variables' => [ 'variables' => [
'notices' => [ '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.', 'variable_created' => 'New variable has successfully been created and assigned to this service option.',
], ],
], ],

View file

@ -92,8 +92,8 @@
</div> </div>
<div class="box-footer"> <div class="box-footer">
{!! csrf_field() !!} {!! csrf_field() !!}
<button class="btn btn-sm btn-danger pull-left muted muted-hover" data-action="delete" name="action" value="delete" type="submit"><i class="fa fa-trash-o"></i></button> <button class="btn btn-sm btn-danger pull-left muted muted-hover" data-action="delete" name="_method" value="DELETE" type="submit"><i class="fa fa-trash-o"></i></button>
<button class="btn btn-sm btn-primary pull-right" name="action" value="save" type="submit">Save</button> <button class="btn btn-sm btn-primary pull-right" name="_method" value="PATCH" type="submit">Save</button>
</div> </div>
</form> </form>
</div> </div>

View file

@ -143,10 +143,10 @@
</div> </div>
<div class="box-footer"> <div class="box-footer">
{!! csrf_field() !!} {!! csrf_field() !!}
<button id="deleteButton" type="submit" name="action" value="delete" class="btn btn-danger btn-sm muted muted-hover"> <button id="deleteButton" type="submit" name="_method" value="DELETE" class="btn btn-danger btn-sm muted muted-hover">
<i class="fa fa-trash-o"></i> <i class="fa fa-trash-o"></i>
</button> </button>
<button type="submit" name="action" value="edit" class="btn btn-primary btn-sm pull-right">Edit Service</button> <button type="submit" name="_method" value="PATCH" class="btn btn-primary btn-sm pull-right">Edit Service</button>
</div> </div>
</div> </div>
</div> </div>

View file

@ -84,8 +84,8 @@
</div> </div>
<div class="box-footer"> <div class="box-footer">
{!! csrf_field() !!} {!! csrf_field() !!}
<button id="deleteButton" type="input" name="action" value="delete" class="btn btn-sm btn-danger muted muted-hover"><i class="fa fa-trash-o"></i></button> <button id="deleteButton" type="submit" name="_method" value="DELETE" class="btn btn-sm btn-danger muted muted-hover"><i class="fa fa-trash-o"></i></button>
<button type="input" class="btn btn-primary btn-sm pull-right">Edit Service</button> <button type="submit" name="_method" value="PATCH" class="btn btn-primary btn-sm pull-right">Edit Service</button>
</div> </div>
</div> </div>
</div> </div>

View file

@ -170,19 +170,21 @@ Route::group(['prefix' => 'services'], function () {
Route::get('/view/{id}/functions', 'ServiceController@viewFunctions')->name('admin.services.view.functions'); Route::get('/view/{id}/functions', 'ServiceController@viewFunctions')->name('admin.services.view.functions');
Route::get('/option/new', 'OptionController@create')->name('admin.services.option.new'); Route::get('/option/new', 'OptionController@create')->name('admin.services.option.new');
Route::get('/option/{option}', 'OptionController@viewConfiguration')->name('admin.services.option.view'); Route::get('/option/{option}', 'OptionController@viewConfiguration')->name('admin.services.option.view');
Route::get('/option/{option}/variables', 'OptionController@viewVariables')->name('admin.services.option.variables'); Route::get('/option/{option}/variables', 'VariableController@view')->name('admin.services.option.variables');
Route::get('/option/{option}/scripts', 'OptionController@viewScripts')->name('admin.services.option.scripts'); Route::get('/option/{option}/scripts', 'OptionController@viewScripts')->name('admin.services.option.scripts');
Route::post('/new', 'ServiceController@store'); Route::post('/new', 'ServiceController@store');
Route::post('/view/{option}', 'ServiceController@edit');
Route::post('/option/new', 'OptionController@store'); Route::post('/option/new', 'OptionController@store');
Route::post('/option/{option}/scripts', 'OptionController@updateScripts'); Route::post('/option/{option}/variables', 'VariableController@store');
Route::post('/option/{option}/variables', 'OptionController@createVariable');
Route::post('/option/{option}/variables/{variable}', 'OptionController@editVariable')->name('admin.services.option.variables.edit');
Route::patch('/view/{option}', 'ServiceController@edit');
Route::patch('/option/{option}', 'OptionController@editConfiguration'); Route::patch('/option/{option}', 'OptionController@editConfiguration');
Route::patch('/option/{option}/scripts', 'OptionController@updateScripts');
Route::patch('/option/{option}/variables/{variable}', 'VariableController@update')->name('admin.services.option.variables.edit');
Route::delete('/view/{id}', 'ServiceController@delete'); Route::delete('/view/{id}', 'ServiceController@delete');
Route::delete('/option/{option}', 'OptionController@delete');
Route::delete('/option/{option}/variables/{variable}', 'VariableController@delete');
}); });
/* /*