Custom user fields #711 (#772)

Custom user fields during registration and profile edit #711
This commit is contained in:
Nabeel S 2020-08-11 17:48:51 -04:00 committed by GitHub
parent 3739cc8e91
commit 3ebf4f2924
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 740 additions and 107 deletions

View File

@ -16,7 +16,7 @@ abstract class Listener
public function subscribe(Dispatcher $events): void public function subscribe(Dispatcher $events): void
{ {
foreach (static::$callbacks as $klass => $cb) { foreach (static::$callbacks as $klass => $cb) {
$events->listen($klass, get_class($this).'@'.$cb); $events->listen($klass, static::class.'@'.$cb);
} }
} }
} }

View File

@ -9,7 +9,7 @@ use Hashids\Hashids;
$factory->define(App\Models\Airline::class, function (Faker $faker) { $factory->define(App\Models\Airline::class, function (Faker $faker) {
return [ return [
'id' => null, 'id' => null,
'icao' => function (array $apt) use ($faker) { 'icao' => function (array $apt) {
$hashids = new Hashids(microtime(), 5); $hashids = new Hashids(microtime(), 5);
$mt = str_replace('.', '', microtime(true)); $mt = str_replace('.', '', microtime(true));

View File

@ -0,0 +1,49 @@
<?php
use App\Contracts\Model;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUserFields extends Migration
{
/**
* Add two tables for holding user fields and the values
*/
public function up()
{
/*
* Hold a master list of fields
*/
Schema::create('user_fields', function (Blueprint $table) {
$table->increments('id');
$table->string('name', 200);
$table->text('description')->nullable();
$table->boolean('show_on_registration')->default(false)->nullable();
$table->boolean('required')->default(false)->nullable();
$table->boolean('private')->default(false)->nullable();
$table->boolean('active')->default(true)->nullable();
$table->timestamps();
});
/*
* The values for the actual fields
*/
Schema::create('user_field_values', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('user_field_id');
$table->string('user_id', Model::ID_MAX_LENGTH);
$table->text('value')->nullable();
$table->timestamps();
$table->index(['user_field_id', 'user_id']);
});
}
/**
* @return void
*/
public function down()
{
}
}

View File

@ -69,3 +69,26 @@ role_user:
- user_id: 3 - user_id: 3
role_id: 2 role_id: 2
user_type: App\Models\User user_type: App\Models\User
user_fields:
- id: 1
name: 'VATSIM ID'
show_on_registration: true
required: false
private: false
- id: 2
name: 'Referral'
description: 'Who referred you'
show_on_registration: true
required: false
private: true
user_field_values:
- id: 1
user_field_id: 1
user_id: 1
value: 'my vatsim id'
- id: 2
user_field_id: 2
user_id: 1
value: 'Nobody did'

View File

@ -158,11 +158,12 @@ class UserController extends Controller
*/ */
public function edit($id) public function edit($id)
{ {
$user = $this->userRepo->findWithoutFail($id); $user = $this->userRepo
->with(['fields', 'rank'])
->findWithoutFail($id);
if (empty($user)) { if (empty($user)) {
Flash::error('User not found'); Flash::error('User not found');
return redirect(route('admin.users.index')); return redirect(route('admin.users.index'));
} }

View File

@ -0,0 +1,154 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Contracts\Controller;
use App\Repositories\UserFieldRepository;
use Illuminate\Http\Request;
use Laracasts\Flash\Flash;
use Prettus\Repository\Criteria\RequestCriteria;
class UserFieldController extends Controller
{
/** @var \App\Repositories\UserFieldRepository */
private $userFieldRepo;
/**
* @param UserFieldRepository $userFieldRepo
*/
public function __construct(UserFieldRepository $userFieldRepo)
{
$this->userFieldRepo = $userFieldRepo;
}
/**
* Display a listing of the UserField.
*
* @param Request $request
*
* @throws \Prettus\Repository\Exceptions\RepositoryException
*
* @return mixed
*/
public function index(Request $request)
{
$this->userFieldRepo->pushCriteria(new RequestCriteria($request));
$fields = $this->userFieldRepo->all();
return view('admin.userfields.index', ['fields' => $fields]);
}
/**
* Show the form for creating a new UserField.
*/
public function create()
{
return view('admin.userfields.create');
}
/**
* Store a newly created UserField in storage.
*
* @param Request $request
*
* @throws \Prettus\Validator\Exceptions\ValidatorException
*
* @return mixed
*/
public function store(Request $request)
{
$this->userFieldRepo->create($request->all());
Flash::success('Field added successfully.');
return redirect(route('admin.userfields.index'));
}
/**
* Display the specified UserField.
*
* @param int $id
*
* @return mixed
*/
public function show($id)
{
$field = $this->userFieldRepo->findWithoutFail($id);
if (empty($field)) {
Flash::error('Flight field not found');
return redirect(route('admin.userfields.index'));
}
return view('admin.userfields.show', ['field' => $field]);
}
/**
* Show the form for editing the specified UserField.
*
* @param int $id
*
* @return mixed
*/
public function edit($id)
{
$field = $this->userFieldRepo->findWithoutFail($id);
if (empty($field)) {
Flash::error('Field not found');
return redirect(route('admin.userfields.index'));
}
return view('admin.userfields.edit', ['field' => $field]);
}
/**
* Update the specified UserField in storage.
*
* @param $id
* @param Request $request
*
* @throws \Prettus\Validator\Exceptions\ValidatorException
*
* @return mixed
*/
public function update($id, Request $request)
{
$field = $this->userFieldRepo->findWithoutFail($id);
if (empty($field)) {
Flash::error('UserField not found');
return redirect(route('admin.userfields.index'));
}
$this->userFieldRepo->update($request->all(), $id);
Flash::success('Field updated successfully.');
return redirect(route('admin.userfields.index'));
}
/**
* Remove the specified UserField from storage.
*
* @param int $id
*
* @return mixed
*/
public function destroy($id)
{
$field = $this->userFieldRepo->findWithoutFail($id);
if (empty($field)) {
Flash::error('Field not found');
return redirect(route('admin.userfields.index'));
}
if ($this->userFieldRepo->isInUse($id)) {
Flash::error('This field cannot be deleted, it is in use. Deactivate it instead');
return redirect(route('admin.userfields.index'));
}
$this->userFieldRepo->delete($id);
Flash::success('Field deleted successfully.');
return redirect(route('admin.userfields.index'));
}
}

View File

@ -3,19 +3,20 @@
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use App\Contracts\Controller; use App\Contracts\Controller;
use App\Http\Requests\CreateUserRequest;
use App\Models\Enums\UserState; use App\Models\Enums\UserState;
use App\Models\User; use App\Models\User;
use App\Models\UserField;
use App\Models\UserFieldValue;
use App\Repositories\AirlineRepository; use App\Repositories\AirlineRepository;
use App\Repositories\AirportRepository; use App\Repositories\AirportRepository;
use App\Services\UserService; use App\Services\UserService;
use App\Support\Countries; use App\Support\Countries;
use App\Support\Timezonelist; use App\Support\Timezonelist;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Auth\RegistersUsers; use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
class RegisterController extends Controller class RegisterController extends Controller
{ {
@ -61,12 +62,14 @@ class RegisterController extends Controller
{ {
$airports = $this->airportRepo->selectBoxList(false, setting('pilots.home_hubs_only')); $airports = $this->airportRepo->selectBoxList(false, setting('pilots.home_hubs_only'));
$airlines = $this->airlineRepo->selectBoxList(); $airlines = $this->airlineRepo->selectBoxList();
$userFields = UserField::where(['show_on_registration' => true])->get();
return view('auth.register', [ return view('auth.register', [
'airports' => $airports, 'airports' => $airports,
'airlines' => $airlines, 'airlines' => $airlines,
'countries' => Countries::getSelectList(), 'countries' => Countries::getSelectList(),
'timezones' => Timezonelist::toArray(), 'timezones' => Timezonelist::toArray(),
'userFields' => $userFields,
]); ]);
} }
@ -88,6 +91,12 @@ class RegisterController extends Controller
'toc_accepted' => 'accepted', 'toc_accepted' => 'accepted',
]; ];
// Dynamically add the required fields
$userFields = UserField::where(['show_on_registration' => true, 'required' => true])->get();
foreach ($userFields as $field) {
$rules['field_'.$field->slug] = 'required';
}
if (config('captcha.enabled')) { if (config('captcha.enabled')) {
$rules['g-recaptcha-response'] = 'required|captcha'; $rules['g-recaptcha-response'] = 'required|captcha';
} }
@ -119,6 +128,15 @@ class RegisterController extends Controller
Log::info('User registered: ', $user->toArray()); Log::info('User registered: ', $user->toArray());
$userFields = UserField::all();
foreach ($userFields as $field) {
$field_name = 'field_'.$field->slug;
UserFieldValue::updateOrCreate([
'user_field_id' => $field->id,
'user_id' => $user->id,
], ['value' => $opts[$field_name]]);
}
return $user; return $user;
} }
@ -131,8 +149,10 @@ class RegisterController extends Controller
* *
* @return mixed * @return mixed
*/ */
public function register(CreateUserRequest $request) public function register(Request $request)
{ {
$this->validator($request->all())->validate();
$user = $this->create($request->all()); $user = $this->create($request->all());
if ($user->state === UserState::PENDING) { if ($user->state === UserState::PENDING) {
return view('auth.pending'); return view('auth.pending');

View File

@ -4,6 +4,8 @@ namespace App\Http\Controllers\Frontend;
use App\Contracts\Controller; use App\Contracts\Controller;
use App\Models\User; use App\Models\User;
use App\Models\UserField;
use App\Models\UserFieldValue;
use App\Repositories\AirlineRepository; use App\Repositories\AirlineRepository;
use App\Repositories\AirportRepository; use App\Repositories\AirportRepository;
use App\Repositories\UserRepository; use App\Repositories\UserRepository;
@ -63,16 +65,22 @@ class ProfileController extends Controller
*/ */
public function index() public function index()
{ {
/** @var User $user */
$user = Auth::user();
if (setting('pilots.home_hubs_only')) { if (setting('pilots.home_hubs_only')) {
$airports = $this->airportRepo->findWhere(['hub' => true]); $airports = $this->airportRepo->findWhere(['hub' => true]);
} else { } else {
$airports = $this->airportRepo->all(); $airports = $this->airportRepo->all();
} }
$userFields = $this->userRepo->getUserFields($user);
return view('profile.index', [ return view('profile.index', [
'acars' => $this->acarsEnabled(), 'acars' => $this->acarsEnabled(),
'user' => Auth::user(), 'user' => $user,
'airports' => $airports, 'airports' => $airports,
'userFields' => $userFields,
]); ]);
} }
@ -83,10 +91,12 @@ class ProfileController extends Controller
*/ */
public function show($id) public function show($id)
{ {
$user = User::where('id', $id)->first(); $user = User::with(['fields', 'fields.field'])
->where('id', $id)
->first();
if (empty($user)) { if (empty($user)) {
Flash::error('User not found!'); Flash::error('User not found!');
return redirect(route('frontend.dashboard.index')); return redirect(route('frontend.dashboard.index'));
} }
@ -109,22 +119,27 @@ class ProfileController extends Controller
*/ */
public function edit(Request $request) public function edit(Request $request)
{ {
$user = User::where('id', Auth::user()->id)->first(); /** @var \App\Models\User $user */
$user = User::with(['fields', 'fields.field'])
->where('id', Auth::user()->id)
->first();
if (empty($user)) { if (empty($user)) {
Flash::error('User not found!'); Flash::error('User not found!');
return redirect(route('frontend.dashboard.index')); return redirect(route('frontend.dashboard.index'));
} }
$airlines = $this->airlineRepo->selectBoxList(); $airlines = $this->airlineRepo->selectBoxList();
$airports = $this->airportRepo->selectBoxList(false, setting('pilots.home_hubs_only')); $airports = $this->airportRepo->selectBoxList(false, setting('pilots.home_hubs_only'));
$userFields = $this->userRepo->getUserFields($user);
return view('profile.edit', [ return view('profile.edit', [
'user' => $user, 'user' => $user,
'airlines' => $airlines, 'airlines' => $airlines,
'airports' => $airports, 'airports' => $airports,
'countries' => Countries::getSelectList(), 'countries' => Countries::getSelectList(),
'timezones' => Timezonelist::toArray(), 'timezones' => Timezonelist::toArray(),
'userFields' => $userFields,
]); ]);
} }
@ -140,13 +155,20 @@ class ProfileController extends Controller
$id = Auth::user()->id; $id = Auth::user()->id;
$user = $this->userRepo->findWithoutFail($id); $user = $this->userRepo->findWithoutFail($id);
$validator = Validator::make($request->toArray(), [ $rules = [
'name' => 'required', 'name' => 'required',
'email' => 'required|unique:users,email,'.$id, 'email' => 'required|unique:users,email,'.$id,
'airline_id' => 'required', 'airline_id' => 'required',
'password' => 'confirmed', 'password' => 'confirmed',
'avatar' => 'nullable|mimes:jpeg,png,jpg', 'avatar' => 'nullable|mimes:jpeg,png,jpg',
]); ];
$userFields = UserField::where(['show_on_registration' => true, 'required' => true])->get();
foreach ($userFields as $field) {
$rules['field_'.$field->slug] = 'required';
}
$validator = Validator::make($request->toArray(), $rules);
if ($validator->fails()) { if ($validator->fails()) {
Log::info('validator failed for user '.$user->ident); Log::info('validator failed for user '.$user->ident);
@ -167,6 +189,7 @@ class ProfileController extends Controller
if (isset($req_data['avatar']) !== null) { if (isset($req_data['avatar']) !== null) {
Storage::delete($user->avatar); Storage::delete($user->avatar);
} }
if ($request->hasFile('avatar')) { if ($request->hasFile('avatar')) {
$avatar = $request->file('avatar'); $avatar = $request->file('avatar');
$file_name = $user->ident.'.'.$avatar->getClientOriginalExtension(); $file_name = $user->ident.'.'.$avatar->getClientOriginalExtension();
@ -190,6 +213,16 @@ class ProfileController extends Controller
$this->userRepo->update($req_data, $id); $this->userRepo->update($req_data, $id);
// Save all of the user fields
$userFields = UserField::all();
foreach ($userFields as $field) {
$field_name = 'field_'.$field->slug;
UserFieldValue::updateOrCreate([
'user_field_id' => $field->id,
'user_id' => $id,
], ['value' => $request->get($field_name)]);
}
Flash::success('Profile updated successfully!'); Flash::success('Profile updated successfully!');
return redirect(route('frontend.profile.index')); return redirect(route('frontend.profile.index'));

View File

@ -9,14 +9,14 @@ use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str; use Illuminate\Support\Str;
/** /**
* @property string $name * @property string $name
* @property string $description * @property string $description
* @property string $disk * @property string $disk
* @property string $path * @property string $path
* @property bool $public * @property bool $public
* @property int $download_count * @property int $download_count
* @property string $url * @property string $url
* @property string $filename * @property string $filename
*/ */
class File extends Model class File extends Model
{ {

View File

@ -14,11 +14,11 @@ use Carbon\Carbon;
* Holds various journals, depending on the morphed_type and morphed_id columns * Holds various journals, depending on the morphed_type and morphed_id columns
* *
* @property mixed id * @property mixed id
* @property Money $balance * @property Money $balance
* @property string $currency * @property string $currency
* @property Carbon $updated_at * @property Carbon $updated_at
* @property Carbon $post_date * @property Carbon $post_date
* @property Carbon $created_at * @property Carbon $created_at
* @property \App\Models\Enums\JournalType type * @property \App\Models\Enums\JournalType type
* @property mixed morphed_type * @property mixed morphed_type
* @property mixed morphed_id * @property mixed morphed_id

View File

@ -13,11 +13,11 @@ use Carbon\Carbon;
/** /**
* Class Ledger * Class Ledger
* *
* @property Money $balance * @property Money $balance
* @property string $currency * @property string $currency
* @property Carbon $updated_at * @property Carbon $updated_at
* @property Carbon $post_date * @property Carbon $post_date
* @property Carbon $created_at * @property Carbon $created_at
*/ */
class Ledger extends Model class Ledger extends Model
{ {

View File

@ -6,10 +6,10 @@ use App\Contracts\Model;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
/** /**
* @property string $id The Simbrief OFP ID * @property string $id The Simbrief OFP ID
* @property int $user_id The user that generated this * @property int $user_id The user that generated this
* @property string $flight_id Optional, if attached to a flight, removed if attached to PIREP * @property string $flight_id Optional, if attached to a flight, removed if attached to PIREP
* @property string $pirep_id Optional, if attached to a PIREP, removed if attached to flight * @property string $pirep_id Optional, if attached to a PIREP, removed if attached to flight
* @property string $acars_xml * @property string $acars_xml
* @property string $ofp_xml * @property string $ofp_xml
* @property string $ofp_html * @property string $ofp_html

View File

@ -10,32 +10,33 @@ use Illuminate\Notifications\Notifiable;
use Laratrust\Traits\LaratrustUserTrait; use Laratrust\Traits\LaratrustUserTrait;
/** /**
* @property int id * @property int id
* @property int pilot_id * @property int pilot_id
* @property int airline_id * @property int airline_id
* @property string name * @property string name
* @property string name_private Only first name, rest are initials * @property string name_private Only first name, rest are initials
* @property string email * @property string email
* @property string password * @property string password
* @property string api_key * @property string api_key
* @property mixed timezone * @property mixed timezone
* @property string ident * @property string ident
* @property string curr_airport_id * @property string curr_airport_id
* @property string home_airport_id * @property string home_airport_id
* @property string avatar * @property string avatar
* @property Airline airline * @property Airline airline
* @property Flight[] flights * @property Flight[] flights
* @property int flight_time * @property int flight_time
* @property int transfer_time * @property int transfer_time
* @property string remember_token * @property string remember_token
* @property \Carbon\Carbon created_at * @property \Carbon\Carbon created_at
* @property \Carbon\Carbon updated_at * @property \Carbon\Carbon updated_at
* @property Rank rank * @property Rank rank
* @property Journal journal * @property Journal journal
* @property int rank_id * @property int rank_id
* @property int state * @property int state
* @property bool opt_in * @property bool opt_in
* @property string last_pirep_id * @property string last_pirep_id
* @property UserFieldValue[] fields
* *
* @mixin \Illuminate\Database\Eloquent\Builder * @mixin \Illuminate\Database\Eloquent\Builder
* @mixin \Illuminate\Notifications\Notifiable * @mixin \Illuminate\Notifications\Notifiable
@ -212,6 +213,16 @@ class User extends Authenticatable
return $this->hasMany(UserAward::class, 'user_id'); return $this->hasMany(UserAward::class, 'user_id');
} }
/**
* The bid rows
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function bids()
{
return $this->hasMany(Bid::class, 'user_id');
}
public function home_airport() public function home_airport()
{ {
return $this->belongsTo(Airport::class, 'home_airport_id'); return $this->belongsTo(Airport::class, 'home_airport_id');
@ -227,22 +238,9 @@ class User extends Authenticatable
return $this->belongsTo(Pirep::class, 'last_pirep_id'); return $this->belongsTo(Pirep::class, 'last_pirep_id');
} }
/** public function fields()
* These are the flights they've bid on
*/
// public function flights()
// {
// return $this->belongsToMany(Flight::class, 'bids');
// }
/**
* The bid rows
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function bids()
{ {
return $this->hasMany(Bid::class, 'user_id'); return $this->hasMany(UserFieldValue::class, 'user_id');
} }
public function pireps() public function pireps()

49
app/Models/UserField.php Normal file
View File

@ -0,0 +1,49 @@
<?php
namespace App\Models;
use App\Contracts\Model;
/**
* @property string name
* @property string slug
* @property string value Only set if "squashed"
* @property bool show_on_registration
* @property bool required
* @property bool private
*/
class UserField extends Model
{
public $table = 'user_fields';
protected $fillable = [
'name',
'description',
'show_on_registration', // Show on the registration form?
'required', // Required to be filled out in registration?
'private', // Whether this is shown on the user's public profile
'active',
];
protected $casts = [
'show_on_registration' => 'boolean',
'required' => 'boolean',
'private' => 'boolean',
'active' => 'boolean',
];
public static $rules = [
'name' => 'required',
'description' => 'nullable',
];
/**
* Get the slug so we can use it in forms
*
* @return string
*/
public function getSlugAttribute(): string
{
return str_slug($this->name, '_');
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace App\Models;
use App\Contracts\Model;
/**
* @property string name
* @property string value
* @property UserField field
* @property User user
*/
class UserFieldValue extends Model
{
public $table = 'user_field_values';
protected $fillable = [
'user_field_id',
'user_id',
'value',
];
public static $rules = [];
/**
* Foreign Keys
*/
public function field()
{
return $this->belongsTo(UserField::class, 'user_field_id');
}
public function user()
{
return $this->belongsTo(User::class, 'user_id');
}
}

View File

@ -17,7 +17,7 @@ class BaseNotification extends Notification implements ShouldQueue
{ {
// Look in the notifications.channels config and see where this particular // Look in the notifications.channels config and see where this particular
// notification can go. Map it to $channels // notification can go. Map it to $channels
$klass = get_class($this); $klass = static::class;
$notif_config = config('notifications.channels', []); $notif_config = config('notifications.channels', []);
if (!array_key_exists($klass, $notif_config)) { if (!array_key_exists($klass, $notif_config)) {
Log::error('Notification type '.$klass.' missing from notifications config, defaulting to mail'); Log::error('Notification type '.$klass.' missing from notifications config, defaulting to mail');

View File

@ -262,10 +262,10 @@ class RouteServiceProvider extends ServiceProvider
Route::resource('flightfields', 'FlightFieldController') Route::resource('flightfields', 'FlightFieldController')
->middleware('ability:admin,flights'); ->middleware('ability:admin,flights');
// pirep related routes Route::resource('userfields', 'UserFieldController')->middleware('ability:admin,users');
Route::get('pireps/fares', 'PirepController@fares')
->middleware('ability:admin,pireps');
// pirep related routes
Route::get('pireps/fares', 'PirepController@fares')->middleware('ability:admin,pireps');
Route::get('pireps/pending', 'PirepController@pending') Route::get('pireps/pending', 'PirepController@pending')
->middleware('ability:admin,pireps'); ->middleware('ability:admin,pireps');

View File

@ -0,0 +1,32 @@
<?php
namespace App\Repositories;
use App\Contracts\Repository;
use App\Models\UserField;
use App\Models\UserFieldValue;
class UserFieldRepository extends Repository
{
protected $fieldSearchable = [
'name' => 'like',
];
/**
* @return string
*/
public function model(): string
{
return UserField::class;
}
/**
* Return whether or not this field is in use by a value
*
* @param $id
*/
public function isInUse($id): bool
{
return UserFieldValue::where(['user_field_id' => $id])->exists();
}
}

View File

@ -5,12 +5,11 @@ namespace App\Repositories;
use App\Contracts\Repository; use App\Contracts\Repository;
use App\Models\Enums\UserState; use App\Models\Enums\UserState;
use App\Models\User; use App\Models\User;
use App\Models\UserField;
use App\Repositories\Criteria\WhereCriteria; use App\Repositories\Criteria\WhereCriteria;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Collection;
/**
* Class UserRepository
*/
class UserRepository extends Repository class UserRepository extends Repository
{ {
protected $fieldSearchable = [ protected $fieldSearchable = [
@ -29,6 +28,26 @@ class UserRepository extends Repository
return User::class; return User::class;
} }
/**
* Get all of the fields which has the mapped values
*
* @param \App\Models\User $user
*
* @return \App\Models\UserField[]|\Illuminate\Database\Eloquent\Collection|\Illuminate\Support\Collection
*/
public function getUserFields(User $user): Collection
{
return (UserField::all())->map(function ($field, $_) use ($user) {
foreach ($user->fields as $userFieldValue) {
if ($userFieldValue->field->slug === $field->slug) {
$field->value = $userFieldValue->value;
}
}
return $field;
});
}
/** /**
* Number of PIREPs that are pending * Number of PIREPs that are pending
* *

View File

@ -28,6 +28,7 @@ services:
mysql: mysql:
image: mysql:5.7.26 image: mysql:5.7.26
command: "--innodb_use_native_aio=0"
environment: environment:
MYSQL_DATABASE: phpvms MYSQL_DATABASE: phpvms
MYSQL_USER: phpvms MYSQL_USER: phpvms

View File

@ -90,7 +90,7 @@ abstract class BaseImporter
$idx = $start + 1; $idx = $start + 1;
$manifest[] = [ $manifest[] = [
'importer' => get_class($this), 'importer' => static::class,
'start' => $start, 'start' => $start,
'end' => $end, 'end' => $end,
'message' => 'Importing '.$this->table.' ('.$idx.' - '.$end.' of '.$total_rows.')', 'message' => 'Importing '.$this->table.' ('.$idx.' - '.$end.' of '.$total_rows.')',

View File

@ -33,7 +33,7 @@ class ClearDatabase extends BaseImporter
{ {
return [ return [
[ [
'importer' => get_class($this), 'importer' => static::class,
'start' => 0, 'start' => 0,
'end' => 1, 'end' => 1,
'message' => 'Clearing database', 'message' => 'Clearing database',

View File

@ -16,7 +16,7 @@ class FinalizeImporter extends BaseImporter
{ {
return [ return [
[ [
'importer' => get_class($this), 'importer' => static::class,
'start' => 0, 'start' => 0,
'end' => 1, 'end' => 1,
'message' => 'Finalizing import', 'message' => 'Finalizing import',

View File

@ -2,8 +2,7 @@
<div class="header"> <div class="header">
@component('admin.components.info') @component('admin.components.info')
Flights fields that can be filled out. You can still add other custom fields These are fields that can be filled out by users
directly in the flight, but this provides a template for all flights.
@endcomponent @endcomponent
</div> </div>
@ -17,8 +16,8 @@
<tr> <tr>
<td>{{ $field->name }}</td> <td>{{ $field->name }}</td>
<td class="text-right"> <td class="text-right">
{{ Form::open(['route' => ['admin.flightfields.destroy', $field->id], 'method' => 'delete']) }} {{ Form::open(['route' => ['admin.userfields.destroy', $field->id], 'method' => 'delete']) }}
<a href="{{ route('admin.flightfields.edit', [$field->id]) }}" <a href="{{ route('admin.userfields.edit', [$field->id]) }}"
class='btn btn-sm btn-success btn-icon'> class='btn btn-sm btn-success btn-icon'>
<i class="fas fa-pencil-alt"></i></a> <i class="fas fa-pencil-alt"></i></a>

View File

@ -0,0 +1,11 @@
@extends('admin.app')
@section('title', 'Adding Field')
@section('content')
<div class="card border-blue-bottom">
<div class="content">
{{ Form::open(['route' => 'admin.userfields.store']) }}
@include('admin.userfields.fields')
{{ Form::close() }}
</div>
</div>
@endsection

View File

@ -0,0 +1,11 @@
@extends('admin.app')
@section('title', 'Editing ' . $field->name)
@section('content')
<div class="card border-blue-bottom">
<div class="content">
{{ Form::model($field, ['route' => ['admin.userfields.update', $field->id], 'method' => 'patch']) }}
@include('admin.userfields.fields')
{{ Form::close() }}
</div>
</div>
@endsection

View File

@ -0,0 +1,64 @@
<div class="row">
<div class="form-group col-sm-6">
{{ Form::label('name', 'Name:') }}&nbsp;&nbsp;<span class="required">*</span>
{{ Form::text('name', null, ['class' => 'form-control']) }}
<p class="text-danger">{{ $errors->first('name') }}</p>
</div>
<div class="form-group col-sm-6">
{{ Form::label('description', 'Description:') }}
{{ Form::text('description', null, ['class' => 'form-control']) }}
<p class="text-danger">{{ $errors->first('description') }}</p>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="checkbox">
<label class="checkbox-inline">
{{ Form::label('required', 'Required:') }}
<input name="required" type="hidden" value="0" />
{{ Form::checkbox('required') }}
</label>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="checkbox">
<label class="checkbox-inline">
{{ Form::label('show_on_registration', 'Show On Registration:') }}
<input name="show_on_registration" type="hidden" value="0" />
{{ Form::checkbox('show_on_registration') }}
</label>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="checkbox">
<label class="checkbox-inline">
{{ Form::label('private', 'Private (only visible to admins):') }}
<input name="private" type="hidden" value="0" />
{{ Form::checkbox('private') }}
</label>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="checkbox">
<label class="checkbox-inline">
{{ Form::label('active', 'Active:') }}
<input name="active" type="hidden" value="0" />
{{ Form::checkbox('active') }}
</label>
</div>
</div>
</div>
<div class="row">
<!-- Submit Field -->
<div class="form-group col-sm-12">
{{ Form::button('Save', ['type' => 'submit', 'class' => 'btn btn-success']) }}
<a href="{{ route('admin.userfields.index') }}" class="btn btn-default">Cancel</a>
</div>
</div>

View File

@ -0,0 +1,13 @@
@extends('admin.app')
@section('title', 'User Fields')
@section('actions')
<li><a href="{{ route('admin.userfields.create') }}"><i class="ti-plus"></i>Add Field</a></li>
@endsection
@section('content')
<div class="card border-blue-bottom">
<div class="content">
@include('admin.userfields.table')
</div>
</div>
@endsection

View File

@ -0,0 +1,33 @@
<div class="content table-responsive table-full-width">
<div class="header">
@component('admin.components.info')
These are custom fields that can be filled out by a user
@endcomponent
</div>
<table class="table table-hover table-responsive" id="pirepFields-table">
<thead>
<th>Name</th>
<th></th>
</thead>
<tbody>
@foreach($fields as $field)
<tr>
<td>{{ $field->name }}</td>
<td class="text-right">
{{ Form::open(['route' => ['admin.userfields.destroy', $field->id], 'method' => 'delete']) }}
<a href="{{ route('admin.userfields.edit', [$field->id]) }}"
class='btn btn-sm btn-success btn-icon'>
<i class="fas fa-pencil-alt"></i></a>
{{ Form::button('<i class="fa fa-times"></i>',
['type' => 'submit', 'class' => 'btn btn-sm btn-danger btn-icon',
'onclick' => "return confirm('Are you sure?')"]) }}
{{ Form::close() }}
</td>
</tr>
@endforeach
</tbody>
</table>
</div>

View File

@ -94,10 +94,11 @@
<div class="row"> <div class="row">
<div class="form-group col-sm-12"> <div class="form-group col-sm-12">
<table class="table table-hover"> <table class="table table-hover">
{{--<tr> <tr>
<td>API Key</td> <td colspan="2">
<td>{{ $user->api_key }}</td> <h5>User Details</h5>
</tr>--}} </td>
</tr>
<tr> <tr>
<td>Total Flights</td> <td>Total Flights</td>
<td>{{ $user->flights }}</td> <td>{{ $user->flights }}</td>
@ -126,6 +127,22 @@
<td>@lang('profile.opt-in')</td> <td>@lang('profile.opt-in')</td>
<td>{{ $user->opt_in ? __('common.yes') : __('common.no') }}</td> <td>{{ $user->opt_in ? __('common.yes') : __('common.no') }}</td>
</tr> </tr>
@if($user->fields)
<tr>
<td colspan="2">
<h5>Custom Fields</h5>
</td>
</tr>
{{-- Custom Fields --}}
@foreach($user->fields as $field)
<tr>
<td>{{ $field->field->name }}</td>
<td>{{ $field->value }}</td>
</tr>
@endforeach
@endif
</table> </table>
</div> </div>
</div> </div>

View File

@ -2,6 +2,10 @@
@section('title', 'Users') @section('title', 'Users')
@section('actions') @section('actions')
<li><a href="{{ route('admin.userfields.index') }}">
<i class="ti-user"></i>Profile Fields</a>
</li>
<li><a href="{{ route('admin.users.index') }}?search=state:0"> <li><a href="{{ route('admin.users.index') }}?search=state:0">
<i class="ti-user"></i>@lang(UserState::label(UserState::PENDING))</a> <i class="ti-user"></i>@lang(UserState::label(UserState::PENDING))</a>
</li> </li>

View File

@ -85,6 +85,18 @@
<p class="text-danger">{{ $errors->first('password_confirmation') }}</p> <p class="text-danger">{{ $errors->first('password_confirmation') }}</p>
@endif @endif
@if($userFields)
@foreach($userFields as $field)
<label for="field_{{ $field->slug }}" class="control-label">{{ $field->name }}</label>
<div class="input-group form-group-no-border {{ $errors->has('field_'.$field->slug) ? 'has-danger' : '' }}">
{{ Form::text('field_'.$field->slug, null, ['class' => 'form-control']) }}
</div>
@if ($errors->has('field_'.$field->slug))
<p class="text-danger">{{ $errors->first('field_'.$field->slug) }}</p>
@endif
@endforeach
@endif
@if(config('captcha.enabled')) @if(config('captcha.enabled'))
<label for="g-recaptcha-response" class="control-label">@lang('auth.fillcaptcha')</label> <label for="g-recaptcha-response" class="control-label">@lang('auth.fillcaptcha')</label>
<div <div

View File

@ -16,6 +16,6 @@
{{ $field->value }} {{ $field->value }}
@endif @endif
</div> </div>
<p class="text-danger">{{ $errors->first($field->slug) }}</p> <p class="text-danger">{{ $errors->first('field_'.$field->slug) }}</p>
</td> </td>
</tr> </tr>

View File

@ -0,0 +1,16 @@
<tr>
<td>
{{ $field->field->name }}
@if($field->field->required === true)
<span class="text-danger">*</span>
@endif
</td>
<td>
<div class="input-group input-group-sm form-group">
{{ Form::text($field->field->slug, $field->value, [
'class' => 'form-control',
]) }}
</div>
<p class="text-danger">{{ $errors->first('field_'.$field->slug) }}</p>
</td>
</tr>

View File

@ -108,6 +108,27 @@
@endif @endif
</td> </td>
</tr> </tr>
{{-- Custom fields --}}
@foreach($userFields as $field)
<tr>
<td>
{{ $field->name }}
@if($field->required === true)
<span class="text-danger">*</span>
@endif
</td>
<td>
<div class="input-group input-group-sm form-group">
{{ Form::text('field_'.$field->slug, $field->value, [
'class' => 'form-control',
]) }}
</div>
<p class="text-danger">{{ $errors->first('field_'.$field->slug) }}</p>
</td>
</tr>
@endforeach
<tr> <tr>
<td>@lang('profile.opt-in')</td> <td>@lang('profile.opt-in')</td>
<td> <td>

View File

@ -128,4 +128,20 @@
</div> </div>
</div> </div>
@endif @endif
<div class="clearfix" style="height: 50px;"></div>
<div class="row">
<div class="col-sm-12">
<table class="table table-full-width">
@foreach($userFields as $field)
@if($field->public === true)
<tr>
<td>{{ $field->name }}</td>
<td>{{ $field->value }}</td>
</tr>
@endif
@endforeach
</table>
</div>
</div>
@endsection @endsection