Begin updating UI
This commit is contained in:
parent
493c5888a3
commit
ae671e6b19
22 changed files with 182 additions and 102 deletions
|
@ -9,7 +9,6 @@
|
|||
|
||||
namespace Pterodactyl\Console\Commands\Environment;
|
||||
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
use Pterodactyl\Traits\Commands\EnvironmentWriterTrait;
|
||||
|
@ -38,6 +37,7 @@ class AppSettingsCommand extends Command
|
|||
* @var string
|
||||
*/
|
||||
protected $signature = 'p:environment:setup
|
||||
{--author= : The email that services created on this instance should be linked to.}
|
||||
{--url= : The URL that this Panel is running on.}
|
||||
{--timezone= : The timezone to use for Panel times.}
|
||||
{--cache= : The cache driver backend to use.}
|
||||
|
@ -72,9 +72,10 @@ class AppSettingsCommand extends Command
|
|||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (is_null($this->config->get('pterodactyl.service.author'))) {
|
||||
$this->variables['SERVICE_AUTHOR'] = Uuid::uuid4()->toString();
|
||||
}
|
||||
$this->output->comment(trans('command/messages.environment.app.author_help'));
|
||||
$this->variables['SERVICE_AUTHOR'] = $this->option('author') ?? $this->ask(
|
||||
trans('command/messages.environment.app.author'), $this->config->get('pterodactyl.service.author', 'undefined@unknown-author.com')
|
||||
);
|
||||
|
||||
$this->output->comment(trans('command/messages.environment.app.app_url_help'));
|
||||
$this->variables['APP_URL'] = $this->option('url') ?? $this->ask(
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
|
||||
namespace Pterodactyl\Contracts\Repository;
|
||||
|
||||
use Pterodactyl\Models\Service;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
interface ServiceRepositoryInterface extends RepositoryInterface
|
||||
{
|
||||
/**
|
||||
|
@ -17,7 +20,15 @@ interface ServiceRepositoryInterface extends RepositoryInterface
|
|||
* @param int $id
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function getWithOptions($id = null);
|
||||
public function getWithOptions(int $id = null): Collection;
|
||||
|
||||
/**
|
||||
* Return a service or all services and the count of options, packs, and servers for that service.
|
||||
*
|
||||
* @param int|null $id
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function getWithCounts(int $id = null): Collection;
|
||||
|
||||
/**
|
||||
* Return a service along with its associated options and the servers relation on those options.
|
||||
|
@ -25,5 +36,5 @@ interface ServiceRepositoryInterface extends RepositoryInterface
|
|||
* @param int $id
|
||||
* @return mixed
|
||||
*/
|
||||
public function getWithOptionServers($id);
|
||||
public function getWithOptionServers(int $id): Service;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,9 @@
|
|||
|
||||
namespace Pterodactyl\Http\Controllers\Admin;
|
||||
|
||||
use Illuminate\View\View;
|
||||
use Pterodactyl\Models\Service;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Prologue\Alerts\AlertsMessageBag;
|
||||
use Pterodactyl\Http\Controllers\Controller;
|
||||
use Pterodactyl\Services\Services\ServiceUpdateService;
|
||||
|
@ -46,6 +48,15 @@ class ServiceController extends Controller
|
|||
*/
|
||||
protected $updateService;
|
||||
|
||||
/**
|
||||
* ServiceController constructor.
|
||||
*
|
||||
* @param \Prologue\Alerts\AlertsMessageBag $alert
|
||||
* @param \Pterodactyl\Services\Services\ServiceCreationService $creationService
|
||||
* @param \Pterodactyl\Services\Services\ServiceDeletionService $deletionService
|
||||
* @param \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface $repository
|
||||
* @param \Pterodactyl\Services\Services\ServiceUpdateService $updateService
|
||||
*/
|
||||
public function __construct(
|
||||
AlertsMessageBag $alert,
|
||||
ServiceCreationService $creationService,
|
||||
|
@ -65,10 +76,10 @@ class ServiceController extends Controller
|
|||
*
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function index()
|
||||
public function index(): View
|
||||
{
|
||||
return view('admin.services.index', [
|
||||
'services' => $this->repository->getWithOptions(),
|
||||
'services' => $this->repository->getWithCounts(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -77,7 +88,7 @@ class ServiceController extends Controller
|
|||
*
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function create()
|
||||
public function create(): View
|
||||
{
|
||||
return view('admin.services.new');
|
||||
}
|
||||
|
@ -88,7 +99,7 @@ class ServiceController extends Controller
|
|||
* @param int $service
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function view($service)
|
||||
public function view(int $service): View
|
||||
{
|
||||
return view('admin.services.view', [
|
||||
'service' => $this->repository->getWithOptionServers($service),
|
||||
|
@ -101,7 +112,7 @@ class ServiceController extends Controller
|
|||
* @param \Pterodactyl\Models\Service $service
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function viewFunctions(Service $service)
|
||||
public function viewFunctions(Service $service): View
|
||||
{
|
||||
return view('admin.services.functions', ['service' => $service]);
|
||||
}
|
||||
|
@ -114,7 +125,7 @@ class ServiceController extends Controller
|
|||
*
|
||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
*/
|
||||
public function store(ServiceFormRequest $request)
|
||||
public function store(ServiceFormRequest $request): RedirectResponse
|
||||
{
|
||||
$service = $this->creationService->handle($request->normalize());
|
||||
$this->alert->success(trans('admin/services.notices.service_created', ['name' => $service->name]))->flash();
|
||||
|
@ -132,7 +143,7 @@ class ServiceController extends Controller
|
|||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
*/
|
||||
public function update(ServiceFormRequest $request, Service $service)
|
||||
public function update(ServiceFormRequest $request, Service $service): RedirectResponse
|
||||
{
|
||||
$this->updateService->handle($service->id, $request->normalize());
|
||||
$this->alert->success(trans('admin/services.notices.service_updated'))->flash();
|
||||
|
@ -150,7 +161,7 @@ class ServiceController extends Controller
|
|||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
*/
|
||||
public function updateFunctions(ServiceFunctionsFormRequest $request, Service $service)
|
||||
public function updateFunctions(ServiceFunctionsFormRequest $request, Service $service): RedirectResponse
|
||||
{
|
||||
$this->updateService->handle($service->id, $request->normalize());
|
||||
$this->alert->success(trans('admin/services.notices.functions_updated'))->flash();
|
||||
|
@ -166,7 +177,7 @@ class ServiceController extends Controller
|
|||
*
|
||||
* @throws \Pterodactyl\Exceptions\Service\HasActiveServersException
|
||||
*/
|
||||
public function destroy(Service $service)
|
||||
public function destroy(Service $service): RedirectResponse
|
||||
{
|
||||
$this->deletionService->handle($service->id);
|
||||
$this->alert->success(trans('admin/services.notices.service_deleted'))->flash();
|
||||
|
|
|
@ -13,7 +13,12 @@ use Illuminate\Foundation\Http\FormRequest;
|
|||
|
||||
abstract class AdminFormRequest extends FormRequest
|
||||
{
|
||||
abstract public function rules();
|
||||
/**
|
||||
* The rules to apply to the incoming form request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract public function rules(): array;
|
||||
|
||||
/**
|
||||
* Determine if the user is an admin and has permission to access this
|
||||
|
@ -21,7 +26,7 @@ abstract class AdminFormRequest extends FormRequest
|
|||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
public function authorize(): bool
|
||||
{
|
||||
if (is_null($this->user())) {
|
||||
return false;
|
||||
|
@ -37,7 +42,7 @@ abstract class AdminFormRequest extends FormRequest
|
|||
* @param array $only
|
||||
* @return array
|
||||
*/
|
||||
public function normalize($only = [])
|
||||
public function normalize($only = []): array
|
||||
{
|
||||
return array_merge(
|
||||
$this->only($only),
|
||||
|
|
|
@ -16,22 +16,12 @@ class ServiceFormRequest extends AdminFormRequest
|
|||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
public function rules(): array
|
||||
{
|
||||
$rules = [
|
||||
return [
|
||||
'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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,12 @@ class Service extends Model implements CleansAttributes, ValidableContract
|
|||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = ['name', 'author', 'description', 'folder', 'startup', 'index_file'];
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
'startup',
|
||||
'index_file',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
|
@ -40,7 +45,6 @@ class Service extends Model implements CleansAttributes, ValidableContract
|
|||
'author' => 'required',
|
||||
'name' => 'required',
|
||||
'description' => 'sometimes',
|
||||
'folder' => 'required',
|
||||
'startup' => 'sometimes',
|
||||
'index_file' => 'required',
|
||||
];
|
||||
|
@ -49,10 +53,9 @@ class Service extends Model implements CleansAttributes, ValidableContract
|
|||
* @var array
|
||||
*/
|
||||
protected static $dataIntegrityRules = [
|
||||
'author' => 'string|size:36',
|
||||
'author' => 'email',
|
||||
'name' => 'string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'folder' => 'string|max:255|regex:/^[\w.-]{1,50}$/|unique:services,folder',
|
||||
'startup' => 'nullable|string',
|
||||
'index_file' => 'string',
|
||||
];
|
||||
|
@ -74,12 +77,7 @@ class Service extends Model implements CleansAttributes, ValidableContract
|
|||
*/
|
||||
public function packs()
|
||||
{
|
||||
return $this->hasManyThrough(
|
||||
Pack::class,
|
||||
ServiceOption::class,
|
||||
'service_id',
|
||||
'option_id'
|
||||
);
|
||||
return $this->hasManyThrough(Pack::class, ServiceOption::class, 'service_id', 'option_id');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -31,7 +31,22 @@ class ServiceOption extends Model implements CleansAttributes, ValidableContract
|
|||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $guarded = ['id', 'created_at', 'updated_at'];
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
'docker_image',
|
||||
'config_files',
|
||||
'config_startup',
|
||||
'config_logs',
|
||||
'config_stop',
|
||||
'config_from',
|
||||
'startup',
|
||||
'script_is_privileged',
|
||||
'script_install',
|
||||
'script_entry',
|
||||
'script_container',
|
||||
'copy_script_from',
|
||||
];
|
||||
|
||||
/**
|
||||
* Cast values to correct type.
|
||||
|
@ -40,7 +55,9 @@ class ServiceOption extends Model implements CleansAttributes, ValidableContract
|
|||
*/
|
||||
protected $casts = [
|
||||
'service_id' => 'integer',
|
||||
'config_from' => 'integer',
|
||||
'script_is_privileged' => 'boolean',
|
||||
'copy_script_from' => 'integer',
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -48,6 +65,7 @@ class ServiceOption extends Model implements CleansAttributes, ValidableContract
|
|||
*/
|
||||
protected static $applicationRules = [
|
||||
'service_id' => 'required',
|
||||
'author' => 'required',
|
||||
'name' => 'required',
|
||||
'description' => 'required',
|
||||
'tag' => 'required',
|
||||
|
@ -64,13 +82,14 @@ class ServiceOption extends Model implements CleansAttributes, ValidableContract
|
|||
* @var array
|
||||
*/
|
||||
protected static $dataIntegrityRules = [
|
||||
'service_id' => 'numeric|exists:services,id',
|
||||
'service_id' => 'bail|numeric|exists:services,id',
|
||||
'author' => 'email',
|
||||
'name' => 'string|max:255',
|
||||
'description' => 'string',
|
||||
'tag' => 'alpha_num|max:60|unique:service_options,tag',
|
||||
'tag' => 'bail|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_from' => 'bail|nullable|numeric|exists:service_options,id',
|
||||
'config_stop' => 'nullable|string|max:255',
|
||||
'config_startup' => 'nullable|json',
|
||||
'config_logs' => 'nullable|json',
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
|
||||
namespace Pterodactyl\Repositories\Eloquent;
|
||||
|
||||
use Webmozart\Assert\Assert;
|
||||
use Pterodactyl\Models\Service;
|
||||
use Illuminate\Support\Collection;
|
||||
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
|
||||
use Pterodactyl\Contracts\Repository\ServiceRepositoryInterface;
|
||||
|
||||
|
@ -27,16 +27,14 @@ class ServiceRepository extends EloquentRepository implements ServiceRepositoryI
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getWithOptions($id = null)
|
||||
public function getWithOptions(int $id = null): Collection
|
||||
{
|
||||
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();
|
||||
throw new RecordNotFoundException;
|
||||
}
|
||||
|
||||
return $instance;
|
||||
|
@ -48,15 +46,33 @@ class ServiceRepository extends EloquentRepository implements ServiceRepositoryI
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getWithOptionServers($id)
|
||||
public function getWithCounts(int $id = null): Collection
|
||||
{
|
||||
Assert::numeric($id, 'First argument passed to getWithOptionServers must be numeric, received %s.');
|
||||
$instance = $this->getBuilder()->withCount(['options', 'packs', 'servers']);
|
||||
|
||||
$instance = $this->getBuilder()->with('options.servers')->find($id, $this->getColumns());
|
||||
if (! $instance) {
|
||||
throw new RecordNotFoundException();
|
||||
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(int $id): Service
|
||||
{
|
||||
$instance = $this->getBuilder()->with('options.servers')->find($id, $this->getColumns());
|
||||
if (! $instance) {
|
||||
throw new RecordNotFoundException;
|
||||
}
|
||||
|
||||
/* @var Service $instance */
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ class InstallScriptUpdateService
|
|||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
* @throws \Pterodactyl\Exceptions\Service\ServiceOption\InvalidCopyFromException
|
||||
*/
|
||||
public function handle($option, array $data)
|
||||
public function handle($option, array $data): void
|
||||
{
|
||||
if (! $option instanceof ServiceOption) {
|
||||
$option = $this->repository->find($option);
|
||||
|
|
|
@ -9,11 +9,19 @@
|
|||
|
||||
namespace Pterodactyl\Services\Services\Options;
|
||||
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Pterodactyl\Models\ServiceOption;
|
||||
use Illuminate\Contracts\Config\Repository as ConfigRepository;
|
||||
use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface;
|
||||
use Pterodactyl\Exceptions\Service\ServiceOption\NoParentConfigurationFoundException;
|
||||
|
||||
class OptionCreationService
|
||||
{
|
||||
/**
|
||||
* @var \Illuminate\Contracts\Config\Repository
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface
|
||||
*/
|
||||
|
@ -22,10 +30,12 @@ class OptionCreationService
|
|||
/**
|
||||
* CreationService constructor.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Config\Repository $config
|
||||
* @param \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface $repository
|
||||
*/
|
||||
public function __construct(ServiceOptionRepositoryInterface $repository)
|
||||
public function __construct(ConfigRepository $config, ServiceOptionRepositoryInterface $repository)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
|
@ -38,7 +48,7 @@ class OptionCreationService
|
|||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
* @throws \Pterodactyl\Exceptions\Service\ServiceOption\NoParentConfigurationFoundException
|
||||
*/
|
||||
public function handle(array $data)
|
||||
public function handle(array $data): ServiceOption
|
||||
{
|
||||
if (! is_null(array_get($data, 'config_from'))) {
|
||||
$results = $this->repository->findCountWhere([
|
||||
|
@ -53,6 +63,14 @@ class OptionCreationService
|
|||
$data['config_from'] = null;
|
||||
}
|
||||
|
||||
return $this->repository->create($data);
|
||||
if (count($parts = explode(':', array_get($data, 'tag'))) > 1) {
|
||||
$data['tag'] = $this->config->get('pterodactyl.service.author') . ':' . trim(array_pop($parts));
|
||||
} else {
|
||||
$data['tag'] = $this->config->get('pterodactyl.service.author') . ':' . trim(array_get($data, 'tag'));
|
||||
}
|
||||
|
||||
return $this->repository->create(array_merge($data, [
|
||||
'uuid' => Uuid::uuid4()->toString(),
|
||||
]), true, true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
|
||||
namespace Pterodactyl\Services\Services\Options;
|
||||
|
||||
use Webmozart\Assert\Assert;
|
||||
use Pterodactyl\Exceptions\Service\HasActiveServersException;
|
||||
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
|
||||
use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface;
|
||||
|
@ -50,10 +49,8 @@ class OptionDeletionService
|
|||
* @throws \Pterodactyl\Exceptions\Service\HasActiveServersException
|
||||
* @throws \Pterodactyl\Exceptions\Service\ServiceOption\HasChildrenException
|
||||
*/
|
||||
public function handle($option)
|
||||
public function handle(int $option): int
|
||||
{
|
||||
Assert::integerish($option, 'First argument passed to handle must be integer, received %s.');
|
||||
|
||||
$servers = $this->serverRepository->findCountWhere([['option_id', '=', $option]]);
|
||||
if ($servers > 0) {
|
||||
throw new HasActiveServersException(trans('exceptions.service.options.delete_has_servers'));
|
||||
|
|
|
@ -40,7 +40,7 @@ class OptionUpdateService
|
|||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
* @throws \Pterodactyl\Exceptions\Service\ServiceOption\NoParentConfigurationFoundException
|
||||
*/
|
||||
public function handle($option, array $data)
|
||||
public function handle($option, array $data): void
|
||||
{
|
||||
if (! $option instanceof ServiceOption) {
|
||||
$option = $this->repository->find($option);
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
|
||||
namespace Pterodactyl\Services\Services;
|
||||
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Pterodactyl\Models\Service;
|
||||
use Pterodactyl\Traits\Services\CreatesServiceIndex;
|
||||
use Illuminate\Contracts\Config\Repository as ConfigRepository;
|
||||
use Pterodactyl\Contracts\Repository\ServiceRepositoryInterface;
|
||||
|
@ -49,16 +51,15 @@ class ServiceCreationService
|
|||
*
|
||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
*/
|
||||
public function handle(array $data)
|
||||
public function handle(array $data): Service
|
||||
{
|
||||
return $this->repository->create(array_merge([
|
||||
return $this->repository->create([
|
||||
'uuid' => Uuid::uuid4()->toString(),
|
||||
'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(),
|
||||
]));
|
||||
], true, true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ class ServiceDeletionService
|
|||
*
|
||||
* @throws \Pterodactyl\Exceptions\Service\HasActiveServersException
|
||||
*/
|
||||
public function handle($service)
|
||||
public function handle(int $service): int
|
||||
{
|
||||
$count = $this->serverRepository->findCountWhere([['service_id', '=', $service]]);
|
||||
if ($count > 0) {
|
||||
|
|
|
@ -36,7 +36,7 @@ class ServiceUpdateService
|
|||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
*/
|
||||
public function handle($service, array $data)
|
||||
public function handle(int $service, array $data): void
|
||||
{
|
||||
if (! is_null(array_get($data, 'author'))) {
|
||||
unset($data['author']);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue