Initial commit
This commit is contained in:
711
app/Models/User.php
Normal file
711
app/Models/User.php
Normal file
@@ -0,0 +1,711 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Helpers\StringHelper;
|
||||
use App\Models\Account;
|
||||
use App\Models\Admin;
|
||||
use App\Models\Bank;
|
||||
use App\Models\Language;
|
||||
use App\Models\Locations\Location;
|
||||
use App\Models\Organization;
|
||||
use App\Models\Post;
|
||||
use App\Notifications\VerifyProfileEmail;
|
||||
use App\Traits\ActiveStatesTrait;
|
||||
use App\Traits\HasPresence;
|
||||
use App\Traits\LocationTrait;
|
||||
use App\Traits\ProfileTrait;
|
||||
use App\Traits\TaggableWithLocale;
|
||||
use Cog\Contracts\Love\Reactable\Models\Reactable as ReactableInterface;
|
||||
use Cog\Contracts\Love\Reacterable\Models\Reacterable as ReacterableInterface;
|
||||
use Cog\Laravel\Love\Reactable\Models\Traits\Reactable;
|
||||
use Cog\Laravel\Love\Reacterable\Models\Traits\Reacterable;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Laravel\Fortify\TwoFactorAuthenticatable;
|
||||
use Laravel\Jetstream\HasProfilePhoto;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
use Laravel\Scout\Searchable;
|
||||
use Namu\WireChat\Traits\Chatable;
|
||||
use Spatie\Activitylog\LogOptions;
|
||||
use Spatie\Activitylog\Traits\LogsActivity;
|
||||
use Spatie\Permission\Traits\HasRoles;
|
||||
|
||||
class User extends Authenticatable implements MustVerifyEmail, ReacterableInterface, ReactableInterface
|
||||
{
|
||||
use HasApiTokens;
|
||||
use HasFactory;
|
||||
use Notifiable;
|
||||
use TwoFactorAuthenticatable;
|
||||
use HasProfilePhoto;
|
||||
use HasApiTokens; // Laravel Sanctum API support
|
||||
use HasRoles; // Spatie Permissions
|
||||
use LogsActivity; // Spatie Activity Log
|
||||
use TaggableWithLocale;
|
||||
use Reacterable; // cybercog/laravel-love
|
||||
use Reactable; // cybercog/laravel-love
|
||||
use Searchable; // laravel/scout with ElasticSearch
|
||||
use LocationTrait;
|
||||
use Chatable;
|
||||
use ActiveStatesTrait;
|
||||
use HasPresence;
|
||||
use ProfileTrait;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'full_name',
|
||||
'email',
|
||||
'profile_photo_path',
|
||||
'about',
|
||||
'about_short',
|
||||
'motivation',
|
||||
'website',
|
||||
'phone',
|
||||
'phone_public',
|
||||
'password',
|
||||
'limit_min',
|
||||
'limit_max',
|
||||
'lang_preference',
|
||||
'last_login_at',
|
||||
'last_login_ip',
|
||||
'principles_terms_accepted'
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
* BEWARE: API'S CAN POTENTIALLY EXPOSE ALL VISIBLE FIELDS
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $hidden = [
|
||||
'email',
|
||||
'email_verified_at',
|
||||
'full_name',
|
||||
'password',
|
||||
'remember_token',
|
||||
'phone',
|
||||
'cyclos_id',
|
||||
'cyclos_salt',
|
||||
'cyclos_skills',
|
||||
'two_factor_confirmed_at',
|
||||
'two_factor_recovery_codes',
|
||||
'two_factor_secret',
|
||||
'limit_min',
|
||||
'limit_max',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'last_login_at',
|
||||
'last_login_ip',
|
||||
'principles_terms_accepted',
|
||||
'inactive_at',
|
||||
'deleted_at',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'phone_public' => 'boolean',
|
||||
'date_of_birth' => 'date',
|
||||
'email_verified_at' => 'datetime',
|
||||
'principles_terms_accepted' => 'array',
|
||||
'inactive_at' => 'datetime',
|
||||
'deleted_at' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* The name of the guard used by this model.
|
||||
* This is used to determine the default guard for authentication.
|
||||
*/
|
||||
protected string $guard_name = 'web';
|
||||
protected function getDefaultGuardName(): string
|
||||
{
|
||||
return $this->guard_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the index name for the model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function searchableAs()
|
||||
{
|
||||
return 'users_index';
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert this model to a searchable array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toSearchableArray()
|
||||
{
|
||||
// Prepare eager loaded relationships
|
||||
$this->load(
|
||||
'languages',
|
||||
'locations.district.translations',
|
||||
'locations.city.translations',
|
||||
'locations.division.translations',
|
||||
'locations.country.translations',
|
||||
'tags.contexts.tags',
|
||||
'tags.contexts.tags.locale',
|
||||
'tags.contexts.category.ancestorsAndSelf',
|
||||
);
|
||||
|
||||
return [
|
||||
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
|
||||
//TODO: Update to multilang database structure in future, this ElasticSearch index is already prepared for it
|
||||
'about_nl' => $this->about,
|
||||
'about_en' => $this->about,
|
||||
'about_fr' => $this->about,
|
||||
'about_de' => $this->about,
|
||||
'about_es' => $this->about,
|
||||
'about_short_nl' => $this->about_short,
|
||||
'about_short_en' => $this->about_short,
|
||||
'about_short_fr' => $this->about_short,
|
||||
'about_short_de' => $this->about_short,
|
||||
'about_short_es' => $this->about_short,
|
||||
'motivation_nl' => $this->motivation,
|
||||
'motivation_en' => $this->motivation,
|
||||
'motivation_fr' => $this->motivation,
|
||||
'motivation_de' => $this->motivation,
|
||||
'motivation_es' => $this->motivation,
|
||||
'cyclos_skills' => $this->cyclos_skills, // Legacy column, will not be used in the future
|
||||
'website' => $this->website,
|
||||
|
||||
|
||||
'last_login_at' => $this->last_login_at,
|
||||
'lang_preference' => $this->lang_preference,
|
||||
|
||||
'languages' => $this->languages->map(function ($language) { // map() as languages is a collection
|
||||
return [
|
||||
'id' => $language->id,
|
||||
'name' => $language->name,
|
||||
'lang_code' => $language->lang_code,
|
||||
];
|
||||
}),
|
||||
|
||||
'locations' => $this->locations->map(function ($location) { // map() as locations is a collection
|
||||
return [
|
||||
'id' => $location->id,
|
||||
'district' => $location->district ? $location->district->translations->map(function ($translation) { // map() as translations is a collection
|
||||
return $translation->name;
|
||||
})->toArray() : [],
|
||||
'city' => $location->city ? $location->city->translations->map(function ($translation) { // map() as translations is a collection
|
||||
return $translation->name;
|
||||
})->toArray() : [],
|
||||
'division' => $location->division ? $location->division->translations->map(function ($translation) { // map() as translations is a collection
|
||||
return $translation->name;
|
||||
})->toArray() : [],
|
||||
'country' => $location->country ? $location->country->translations->map(function ($translation) { // map() as translations is a collection
|
||||
return $translation->name;
|
||||
})->toArray() : [],
|
||||
];
|
||||
}),
|
||||
|
||||
'tags' => $this->tags->map(function ($tag) {
|
||||
return [
|
||||
'contexts' => $tag->contexts
|
||||
->map(function ($context) {
|
||||
return [
|
||||
'tags' => $context->tags->map(function ($tag) {
|
||||
// Include the locale in the field name for tags
|
||||
return [
|
||||
'name_' . $tag->locale->locale => StringHelper::DutchTitleCase($tag->normalized),
|
||||
];
|
||||
}),
|
||||
'categories' => Category::with(['translations' => function ($query) { $query->select('category_id', 'locale', 'name');}])
|
||||
->find([ $context->category->ancestorsAndSelf()->get()->flatMap(function ($related) {
|
||||
$categoryPath = explode('.', $related->path);
|
||||
return $categoryPath;
|
||||
})
|
||||
->unique()->values()->toArray()
|
||||
])->map(function ($category) {
|
||||
// Include the locale in the field name for categories
|
||||
return $category->translations->mapWithKeys(function ($translation) {
|
||||
return ['name_' . $translation->locale => StringHelper::DutchTitleCase($translation->name)];
|
||||
});
|
||||
}),
|
||||
];
|
||||
}),
|
||||
];
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the user's profile.
|
||||
* One-to-one
|
||||
*/
|
||||
public function profile()
|
||||
{
|
||||
return $this->hasOne(Profile::class);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the user's organization(s).
|
||||
* Many-to-many.
|
||||
*/
|
||||
public function organizations()
|
||||
{
|
||||
return $this->belongsToMany(Organization::class);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the user's bank(s) that it can manage.
|
||||
* Many-to-many.
|
||||
*/
|
||||
public function banksManaged()
|
||||
{
|
||||
return $this->belongsToMany(Bank::class, 'bank_user', 'user_id', 'bank_id');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine if the user has any non-personal profiles.
|
||||
*
|
||||
* Checks whether the user is associated with any organizations, manages any banks,
|
||||
* or holds any administrative assignments. Returns true if at least one of these
|
||||
* related profiles exists; otherwise returns false.
|
||||
*
|
||||
* @return bool True if the user has organization, bank-managed, or admin profiles; false otherwise.
|
||||
*/
|
||||
public function hasOtherProfiles()
|
||||
{
|
||||
return $this->organizations()->exists()
|
||||
|| $this->banksManaged()->exists()
|
||||
|| $this->admins()->exists();
|
||||
}
|
||||
|
||||
|
||||
public function banksClient()
|
||||
{
|
||||
return $this->morphToMany(Bank::class, 'client', 'bank_clients')
|
||||
->wherePivot('relationship_type', 'local');
|
||||
}
|
||||
|
||||
|
||||
public function attachBankClient($bank, $relationshipType = null)
|
||||
{
|
||||
// Set default relationship type if not provided
|
||||
$relationshipType = $relationshipType ?? 'local';
|
||||
|
||||
// Accept either ID or Bank model
|
||||
$bankId = $bank instanceof Bank ? $bank->id : $bank;
|
||||
|
||||
// Verify existence if numeric ID was provided
|
||||
if (is_numeric($bankId)) {
|
||||
if (!Bank::where('id', $bankId)->exists()) {
|
||||
throw new \Exception("Bank with ID {$bankId} does not exist");
|
||||
}
|
||||
}
|
||||
|
||||
$this->banksClient()->sync([$bankId => [
|
||||
'relationship_type' => $relationshipType,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now()
|
||||
]]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function detachBankClient($bank, $relationshipType = null)
|
||||
{
|
||||
// Set default relationship type if not provided
|
||||
$relationshipType = $relationshipType ?? 'local';
|
||||
|
||||
// Accept either ID or Bank model
|
||||
$bankId = $bank instanceof Bank ? $bank->id : $bank;
|
||||
|
||||
// Verify existence if numeric ID was provided
|
||||
if (is_numeric($bankId)) {
|
||||
if (!Bank::where('id', $bankId)->exists()) {
|
||||
throw new \Exception("Bank with ID {$bankId} does not exist");
|
||||
}
|
||||
}
|
||||
|
||||
// Detach with additional pivot conditions
|
||||
$this->banksClient()->wherePivot('relationship_type', $relationshipType)
|
||||
->detach($bankId);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get the user's admin(s) that it can manage.
|
||||
* Many-to-many.
|
||||
*/
|
||||
public function admins()
|
||||
{
|
||||
return $this->belongsToMany(Admin::class);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get all related accounts of the user.
|
||||
* One-to-many polymorphic.
|
||||
*/
|
||||
public function accounts()
|
||||
{
|
||||
return $this->morphMany(Account::class, 'accountable');
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Check if the user has any associated accounts.
|
||||
*
|
||||
* @return bool Returns true if the user has accounts, false otherwise.
|
||||
*/
|
||||
public function hasAccounts()
|
||||
{
|
||||
$accountsExists = DB::table('accounts')
|
||||
->where('accountable_id', $this->id)
|
||||
->where('accountable_type', 'App\Models\User')
|
||||
->exists();
|
||||
return $accountsExists;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get all related languages of the user.
|
||||
* Many-to-many polymorphic.
|
||||
*/
|
||||
public function languages()
|
||||
{
|
||||
return $this->morphToMany(Language::class, 'languagable')->withPivot('competence');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all related socials of the user.
|
||||
* Many-to-many polymorphic.
|
||||
*/
|
||||
public function socials()
|
||||
{
|
||||
return $this->morphToMany(Social::class, 'sociable')->withPivot('id', 'user_on_social', 'server_of_social');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get all related the locations of the user.
|
||||
* One-to-many polymorph.
|
||||
*/
|
||||
public function locations()
|
||||
{
|
||||
return $this->morphMany(Location::class, 'locatable');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get all of the user's message settings.
|
||||
*/
|
||||
public function message_settings()
|
||||
{
|
||||
return $this->morphOne(MessageSetting::class, 'message_settingable');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Configure the activity log options for the User model.
|
||||
*
|
||||
* This method sets up the logging to:
|
||||
* - Only log changes to the 'name', 'password', and 'last_login_ip' attributes.
|
||||
* - Log only when these attributes have been modified (dirty).
|
||||
* - Prevent submission of empty logs.
|
||||
* - Use 'user' as the log name.
|
||||
*
|
||||
* @return \Spatie\Activitylog\LogOptions
|
||||
*/
|
||||
public function getActivitylogOptions(): LogOptions
|
||||
{
|
||||
return LogOptions::defaults()
|
||||
->logOnly([
|
||||
'name',
|
||||
'last_login_ip',
|
||||
'inactive_at',
|
||||
'deleted_at',
|
||||
])
|
||||
->logOnlyDirty() // Only log attributes that have been changed
|
||||
->dontSubmitEmptyLogs()
|
||||
->useLogName('User');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get all of the User's posts.
|
||||
*/
|
||||
public function posts()
|
||||
{
|
||||
return $this->morphMany(Post::class, 'postable');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get all post translations updated by the user.
|
||||
*/
|
||||
public function post_translations_updated()
|
||||
{
|
||||
return $this->hasMany(PostTranslation::class, 'updated_by_user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the User's categories.
|
||||
*/
|
||||
public function categories()
|
||||
{
|
||||
return $this->morphMany(Category::class, 'categoryable');
|
||||
}
|
||||
|
||||
|
||||
public function sendEmailVerificationNotification()
|
||||
{
|
||||
\Mail::to($this->email)->send(new \App\Mail\VerifyProfileEmailMailable($this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the password reset notification.
|
||||
*
|
||||
* @param string $token
|
||||
* @return void
|
||||
*/
|
||||
public function sendPasswordResetNotification($token)
|
||||
{
|
||||
$locale = $this->lang_preference ?? config('app.fallback_locale');
|
||||
$this->notify(new \App\Notifications\UserPasswordResetNotification($token, $locale));
|
||||
}
|
||||
|
||||
/**
|
||||
* Wirechat: Returns the URL for the user's cover image (avatar).
|
||||
* Adjust the 'avatar_url' field to your database setup.
|
||||
*/
|
||||
public function getCoverUrlAttribute(): ?string
|
||||
{
|
||||
return Storage::url($this->profile_photo_path) ?? null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Wirechat:Returns the URL for the user's profile page.
|
||||
* Adjust the 'profile' route as needed for your setup.
|
||||
*/
|
||||
public function getProfileUrlAttribute(): ?string
|
||||
{
|
||||
return route('profile.show_by_type_and_id', ['type' => __('user'), 'id' => $this->id]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Wirechat: Returns the display name for the user.
|
||||
* Modify this to use your preferred name field.
|
||||
*/
|
||||
public function getDisplayNameAttribute(): ?string
|
||||
{
|
||||
if ($this->full_name !== $this->name) {
|
||||
return $this->full_name . ' (' . $this->name . ')';
|
||||
} elseif ($this->name) {
|
||||
return $this->name;
|
||||
} else {
|
||||
return __('User');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Search for users when creating a new chat or adding members to a group.
|
||||
* Customize the search logic to limit results, such as restricting to friends or eligible users only.
|
||||
*/
|
||||
public function searchChatables(string $query): ?\Illuminate\Support\Collection
|
||||
{
|
||||
$searchableFields = ['name', 'full_name'];
|
||||
|
||||
// Search across all profile types
|
||||
$users = User::where(function ($queryBuilder) use ($searchableFields, $query) {
|
||||
foreach ($searchableFields as $field) {
|
||||
$queryBuilder->orWhere($field, 'LIKE', '%'.$query.'%');
|
||||
}
|
||||
})->limit(6)->get();
|
||||
|
||||
$organizations = \App\Models\Organization::where(function ($queryBuilder) use ($searchableFields, $query) {
|
||||
foreach ($searchableFields as $field) {
|
||||
$queryBuilder->orWhere($field, 'LIKE', '%'.$query.'%');
|
||||
}
|
||||
})->limit(6)->get();
|
||||
|
||||
$banks = \App\Models\Bank::where(function ($queryBuilder) use ($searchableFields, $query) {
|
||||
foreach ($searchableFields as $field) {
|
||||
$queryBuilder->orWhere($field, 'LIKE', '%'.$query.'%');
|
||||
}
|
||||
})->limit(6)->get();
|
||||
|
||||
$admins = \App\Models\Admin::where(function ($queryBuilder) use ($searchableFields, $query) {
|
||||
foreach ($searchableFields as $field) {
|
||||
$queryBuilder->orWhere($field, 'LIKE', '%'.$query.'%');
|
||||
}
|
||||
})->limit(6)->get();
|
||||
|
||||
// Combine all results into a base Collection to avoid serialization issues
|
||||
$results = collect()
|
||||
->merge($users->all())
|
||||
->merge($organizations->all())
|
||||
->merge($banks->all())
|
||||
->merge($admins->all());
|
||||
|
||||
// Filter out profiles based on configuration
|
||||
return $results->filter(function ($profile) {
|
||||
// Check inactive profiles
|
||||
if (timebank_config('profile_inactive.messenger_hidden') && !$profile->isActive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check email unverified profiles
|
||||
if (timebank_config('profile_email_unverified.messenger_hidden') && !$profile->isEmailVerified()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check incomplete profiles
|
||||
if (
|
||||
timebank_config('profile_incomplete.messenger_hidden')
|
||||
&& method_exists($profile, 'hasIncompleteProfile')
|
||||
&& $profile->hasIncompleteProfile($profile)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})->take(6)->values();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Wirechat: Determine if the user can create new groups.
|
||||
*/
|
||||
public function canCreateGroups(): bool
|
||||
{
|
||||
return timebank_config('profiles.user.messenger_can_create_groups');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user has any transactions with another model (User, Organization, Bank).
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @return bool
|
||||
*/
|
||||
public function hasTransactionsWith($model): bool
|
||||
{
|
||||
$modelClass = get_class($model);
|
||||
$modelId = $model->id;
|
||||
|
||||
// Check if this user is involved in any transaction where the other model is either the sender or receiver
|
||||
return Transaction::where(function ($query) use ($modelClass, $modelId) {
|
||||
$query->whereHas('accountFrom.accountable', function ($q) use ($modelClass, $modelId) {
|
||||
$q->where('accountable_type', $modelClass)
|
||||
->where('accountable_id', $modelId);
|
||||
})
|
||||
->orWhereHas('accountTo.accountable', function ($q) use ($modelClass, $modelId) {
|
||||
$q->where('accountable_type', $modelClass)
|
||||
->where('accountable_id', $modelId);
|
||||
});
|
||||
})
|
||||
->where(function ($query) {
|
||||
$query->whereHas('accountFrom.accountable', function ($q) {
|
||||
$q->where('accountable_type', User::class)
|
||||
->where('accountable_id', $this->id);
|
||||
})
|
||||
->orWhereHas('accountTo.accountable', function ($q) {
|
||||
$q->where('accountable_type', User::class)
|
||||
->where('accountable_id', $this->id);
|
||||
});
|
||||
})
|
||||
->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message settings for this user
|
||||
*/
|
||||
public function messageSettings()
|
||||
{
|
||||
return $this->morphMany(MessageSetting::class, 'message_settingable');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user has accepted the principles.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasAcceptedPrinciples(): bool
|
||||
{
|
||||
return !is_null($this->principles_terms_accepted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user needs to re-accept principles due to a newer version.
|
||||
* Compares the stored version with the current active principles post.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function needsToReacceptPrinciples(): bool
|
||||
{
|
||||
// First, check if there's a current principles post available
|
||||
// This must come BEFORE checking user acceptance status to avoid redirect loops
|
||||
$currentPost = Post::with(['translations' => function ($query) {
|
||||
$locale = app()->getLocale();
|
||||
$query->where('locale', 'like', $locale . '%')
|
||||
->whereDate('from', '<=', now())
|
||||
->where(function ($query) {
|
||||
$query->whereDate('till', '>', now())->orWhereNull('till');
|
||||
})
|
||||
->orderBy('updated_at', 'desc')
|
||||
->limit(1);
|
||||
}])
|
||||
->whereHas('category', function ($query) {
|
||||
$query->where('type', 'SiteContents\Static\Principles');
|
||||
})
|
||||
->first();
|
||||
|
||||
// Failsafe: If no current principles post exists, no need to accept/re-accept
|
||||
if (!$currentPost || !$currentPost->translations->first()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If user hasn't accepted at all, they need to accept
|
||||
if (!$this->hasAcceptedPrinciples()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$currentTranslation = $currentPost->translations->first();
|
||||
$acceptedData = $this->principles_terms_accepted;
|
||||
|
||||
// Compare the updated_at timestamp
|
||||
if (isset($acceptedData['updated_at'])) {
|
||||
$acceptedUpdatedAt = \Carbon\Carbon::parse($acceptedData['updated_at']);
|
||||
$currentUpdatedAt = \Carbon\Carbon::parse($currentTranslation->updated_at);
|
||||
|
||||
return $currentUpdatedAt->isAfter($acceptedUpdatedAt);
|
||||
}
|
||||
|
||||
return true; // If no timestamp stored, require re-acceptance
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user