28 KiB
Profile Incomplete Configuration Reference
This document provides comprehensive documentation on the profile_incomplete configuration system, including how it's evaluated, enforced, and where it's used throughout the application.
Table of Contents
- Overview
- Configuration Structure
- Evaluation Logic
- Enforcement Locations
- Related Profile States
- Implementation Status
- Testing
- Configuration Examples
Overview
The profile_incomplete configuration system provides a mechanism to identify and optionally hide profiles that lack sufficient information. This helps ensure that only profiles with adequate content appear in search results and other discovery features.
Purpose
- Encourage users to complete their profiles with meaningful content
- Filter incomplete profiles from search results and messaging
- Maintain a quality threshold for publicly visible profiles
- Allow administrators to still view and manage incomplete profiles
Configuration Structure
Location
Configuration is defined in:
config/timebank-default.php.example(lines 200-208)config/timebank_cc.php.example(lines 186-194)- Active config loaded via
TIMEBANK_CONFIGenvironment variable
Settings
'profile_incomplete' => [
// Visibility settings
'messenger_hidden' => true, // Not searchable in chat messenger
'profile_search_hidden' => true, // Hidden from main search bar
'profile_hidden' => true, // Profile page access control
'profile_labeled' => true, // Show "incomplete" label on profile
'show_warning_modal' => true, // Show warning modal when viewing own incomplete profile
// Completion criteria - fields
'check_fields' => [
'about', // Extended description field
'about_short', // Short description field
'motivation', // Motivation/why joining field
'cyclos_skills', // Skills/services offered field
],
'check_fields_min_total_length' => 100, // Min total characters across all fields
// Completion criteria - relations
'check_relations' => [
'tags', // Skill tags
'languages', // Spoken languages
'locations', // Geographic locations
],
],
Completion Criteria
A profile is considered COMPLETE when ALL THREE of these conditions are met:
- ✓ At least one field from
check_fieldshas data - ✓ Total character length across all
check_fields≥check_fields_min_total_length(default: 100) - ✓ At least one relation from
check_relationsexists (tags, languages, OR locations)
If any condition fails, the profile is marked as INCOMPLETE.
Evaluation Logic
Primary Method: hasIncompleteProfile()
Location: app/Traits/ProfileTrait.php:264-310
Usage:
$user = User::find($id);
$isIncomplete = $user->hasIncompleteProfile($user);
Algorithm:
public function hasIncompleteProfile($model)
{
$config = timebank_config('profile_incomplete');
if (!$config) {
return false; // No config = assume complete
}
// 1. Check fields
$hasFieldData = false;
$totalFieldLength = 0;
foreach ($config['check_fields'] as $field) {
if (!empty($model->{$field})) {
$hasFieldData = true;
$totalFieldLength += strlen(trim($model->{$field}));
}
}
// 2. Check minimum length requirement
$meetsMinLength = $totalFieldLength >= $config['check_fields_min_total_length'];
// 3. Check relations
$hasRelationData = false;
foreach ($config['check_relations'] as $relation) {
if (!$model->relationLoaded($relation)) {
$model->load($relation);
}
if (!$model->{$relation}->isEmpty()) {
$hasRelationData = true;
break;
}
}
// Complete when ALL three conditions are met
$isComplete = $hasFieldData && $meetsMinLength && $hasRelationData;
// Return true if incomplete, false if complete
return !$isComplete;
}
Performance Notes:
- Lazy-loads relations only if not already loaded
- Exits early on first found relation (OR logic)
- No database queries if relations pre-loaded
Enforcement Locations
1. Main Search Bar (ACTIVE)
Location: app/Http/Livewire/MainSearchBar.php:676-684
When: Processing profile search results
Logic:
if (
timebank_config('profile_incomplete.profile_search_hidden')
&& method_exists($model, 'hasIncompleteProfile')
&& $model->hasIncompleteProfile($model)
) {
return []; // Exclude from search results
}
Notes:
- Only enforced for non-Admin/non-Bank users
- Organizations temporarily exempted (line 681-682) for debugging
- Filters out incomplete profiles before they reach the user
Affected Models:
App\Models\UserApp\Models\Organization(currently exempted)App\Models\Bank
2. Browse by Tag Categories (ACTIVE)
Location: app/Http/Livewire/MainBrowseTagCategories.php:523-564
Status: ✓ FULLY IMPLEMENTED
When: Filtering profiles during category-based search results
Implementation:
private function filterProfile($model, bool $canManageProfiles)
{
if ($canManageProfiles) {
return $model;
}
// Checks profile_inactive
// Checks profile_email_unverified
// Checks deleted_at
// Check incomplete profiles
if (
timebank_config('profile_incomplete.profile_search_hidden')
&& method_exists($model, 'hasIncompleteProfile')
&& $model->hasIncompleteProfile($model)
) {
return null;
}
return $model;
}
Notes:
- Only enforced for non-Admin/non-Bank users
- Consistent with MainSearchBar implementation
- Filters out incomplete profiles before they reach the user
Affected Models:
App\Models\UserApp\Models\OrganizationApp\Models\Bank
3. Profile Page Access (ACTIVE)
Configuration: profile_incomplete.profile_hidden and profile_incomplete.profile_labeled
Behavior:
- When
profile_hiddenistrue, non-Admin/non-Bank users cannot view the profile page - Returns "profile not found" view for unauthorized access
- Admins and Banks can always view incomplete profiles
Current Status: ✓ FULLY IMPLEMENTED
Location: app/Http/Controllers/ProfileController.php:354-407
The getActiveStates() method checks all profile states:
profile_inactive.profile_hidden✓profile_email_unverified.profile_hidden✓profile_incomplete.profile_hidden✓
Implementation:
private function getActiveStates($profile)
{
$inactive = false;
$hidden = false;
$inactiveLabel = false;
$inactiveSince = '';
$emailUnverifiedLabel = false;
$incompleteLabel = false;
$removedSince = '';
// ... existing inactive checks ...
// ... existing email_unverified checks ...
// Check incomplete profiles
if (method_exists($profile, 'hasIncompleteProfile') && $profile->hasIncompleteProfile($profile)) {
if (timebank_config('profile_incomplete.profile_hidden')) {
$hidden = true;
}
if (timebank_config('profile_incomplete.profile_labeled')) {
$incompleteLabel = true;
}
}
// ... existing deleted_at checks ...
return compact('inactive', 'hidden', 'inactiveLabel', 'inactiveSince',
'emailUnverifiedLabel', 'incompleteLabel', 'removedSince');
}
How it Works:
- Called in
showUser(),showOrganization(), andshowBank()methods - Sets
$hidden = truewhen profile is incomplete and config enabled - Admin override applied in calling methods (lines 88-95, 155-162, 224-231)
- When
$hidden && !$canManageProfiles, returnsprofile.not_foundview
Affected Models:
App\Models\UserApp\Models\OrganizationApp\Models\Bank
4. Profile Labels (FULLY IMPLEMENTED)
Configuration: profile_incomplete.profile_labeled
Expected Behavior:
- When
true, show an "Incomplete Profile" badge/label on the profile page - Only visible to Admins/Banks who can view incomplete profiles
Current Status: ✓ FULLY IMPLEMENTED
Backend Implementation: ✓ COMPLETED
ProfileController.php:418-425sets$incompleteLabelvariable- Value passed to views via
$statesarray (lines 117, 194, 272) - Available in views as
$incompleteLabel
Frontend Implementation: ✓ COMPLETED
resources/views/profile/show.blade.php:18passes variable to Livewire componentresources/views/livewire/profile/show.blade.php:35-49displays label in top section (near name)resources/views/livewire/profile/show.blade.php:263-267displays label in activity info section- Label uses
text-red-700styling to match inactive/removed profile labels and email unverified labels
Configuration:
- Enabled in
config/timebank_cc.php:204(profile_labeled => true) - Enabled in
config/timebank-default.php:204(profile_labeled => true)
5. Incomplete Profile Warning Modal (FULLY IMPLEMENTED)
Configuration: profile_incomplete.show_warning_modal
Expected Behavior:
- When
true, show a modal dialog when user views their own incomplete profile - Modal explains profile is hidden and provides guidance on completing it
- Only shown when viewing own profile (not when admins/banks view other incomplete profiles)
- Can be dismissed by clicking close button or clicking outside modal
Current Status: ✓ FULLY IMPLEMENTED
Backend Implementation: ✓ COMPLETED
ProfileController.php:115, 214, 315sets$showIncompleteWarningvariable based on config- Checks
profile_incomplete.show_warning_modalAND profile incompleteness - Only triggered when viewing own profile
- Implemented in
showUser(),showOrganization(), andshowBank()methods - Also implemented in
edit()method (line 360) for edit pages
Frontend Implementation: ✓ COMPLETED
resources/views/profile/show.blade.php:24-92contains modal markupresources/views/profile-user/edit.blade.php:18-86contains modal for edit pages- Uses Alpine.js for show/hide functionality
- Includes backdrop with click-outside-to-close
- Displays SidePost content (type:
SiteContents\ProfileIncomplete) - Fallback title and description if SidePost not configured
Modal Features:
- Warning icon with attention-grabbing styling
- Dismissible with ESC key or close button
- Prevents body scrolling when open
- Smooth transitions (Alpine.js x-transition)
- Theme-aware styling (uses theme color classes)
Configuration:
- Enabled in
config/timebank_cc.php:208(show_warning_modal => true) - Works independently from
profile_hiddensetting - Can show modal even if profile is accessible to others
6. Chat Messenger (NOT IMPLEMENTED)
Configuration: profile_incomplete.messenger_hidden
Expected Behavior:
- Incomplete profiles should not appear in messenger user search
- Cannot start new conversations with incomplete profiles
- Existing conversations remain accessible
Current Status: ❌ NOT IMPLEMENTED
Package: namu/wirechat (WireChat)
Location to Implement:
- Search participants functionality in WireChat
- May require custom override or event listener
Implementation Approach:
// In a custom service provider or WireChat override
Event::listen(\Namu\WireChat\Events\SearchingParticipants::class, function ($event) {
if (!timebank_config('profile_incomplete.messenger_hidden')) {
return;
}
$event->query->where(function ($q) {
// Filter out incomplete profiles
$q->whereHas('user', function ($userQuery) {
// Add logic to check profile completeness
});
});
});
Related Profile States
The application has three similar profile state systems:
1. Profile Inactive (profile_inactive)
Configuration:
'profile_inactive' => [
're-activate_at_login' => true,
'messenger_hidden' => true,
'profile_search_hidden' => true,
'profile_hidden' => true,
'profile_labeled' => true,
],
Evaluation:
- Checked via
$profile->isActive()method - Based on
inactive_atfield (null or future = active, past = inactive)
Enforcement: ✓ FULLY IMPLEMENTED
- Main search bar: Filtered ✓ (
MainSearchBar.php:654-659) - Browse tags: Filtered ✓ (
MainBrowseTagCategories.php:530-536) - Profile page: Hidden/Labeled ✓ (
ProfileController.php:363-374) - Messenger: Hidden ✓ (assumed via WireChat)
2. Profile Email Unverified (profile_email_unverified)
Configuration:
'profile_email_unverified' => [
'messenger_hidden' => true,
'profile_search_hidden' => true,
'profile_hidden' => false,
'profile_labeled' => false,
],
Evaluation:
- Checked via
$profile->isEmailVerified()method - Based on
email_verified_atfield (null or future = unverified)
Enforcement: ✓ FULLY IMPLEMENTED
- Main search bar: Filtered ✓ (
MainSearchBar.php:661-666) - Browse tags: Filtered ✓ (
MainBrowseTagCategories.php:538-544) - Profile page: Hidden/Labeled ✓ (
ProfileController.php:376-384) - Messenger: Hidden ✓ (assumed via WireChat)
3. Profile Incomplete (profile_incomplete)
Configuration: (as documented above)
Evaluation:
- Checked via
$profile->hasIncompleteProfile($profile)method - Based on field content and relations
Enforcement: ⚠️ PARTIALLY IMPLEMENTED
- Main search bar: Filtered ✓ (
MainSearchBar.php:676-684) - Browse tags: Filtered ✓ (
MainBrowseTagCategories.php:554-561) - Profile page: Hidden ✓ (
ProfileController.php:387-394) - Profile labels: NOT IMPLEMENTED ❌ (prepared but requires view updates)
- Messenger: NOT IMPLEMENTED ❌
Implementation Status
Summary Table
| Feature | Config Key | Main Search | Browse Tags | Profile Page | Labels | Warning Modal | Messenger |
|---|---|---|---|---|---|---|---|
| Inactive | profile_inactive |
✓ | ✓ | ✓ | ✓ | N/A | ✓ |
| Email Unverified | profile_email_unverified |
✓ | ✓ | ✓ | ✓ | N/A | ✓ |
| Incomplete | profile_incomplete |
✓ | ✓ | ✓ | ✓ | ✓ | ❌ |
Priority Tasks
HIGH: Implement incomplete profile filtering in✓ COMPLETEDMainBrowseTagCategoriesHIGH: Implement profile page hiding/access control✓ COMPLETEDMEDIUM: Implement incomplete profile labels on profile pages✓ COMPLETEDMEDIUM: Implement incomplete profile warning modal✓ COMPLETED- MEDIUM: Implement messenger filtering
LOW: Remove temporary Organization exemption in✓ COMPLETEDMainSearchBar
Testing
Manual Testing Checklist
Setup:
- Create test user with minimal profile data
- Verify profile is incomplete:
$user->hasIncompleteProfile($user)returnstrue
Test Cases:
Test 1: Field Content Requirements
$user = User::factory()->create([
'about' => '',
'about_short' => '',
'motivation' => '',
'cyclos_skills' => '',
]);
// Add tags and locations
$user->tags()->attach([1, 2]);
$user->locations()->attach(1);
// Should be incomplete (no field data)
assertTrue($user->hasIncompleteProfile($user));
// Add 50 characters
$user->about = str_repeat('a', 50);
$user->save();
// Still incomplete (< 100 chars)
assertTrue($user->hasIncompleteProfile($user));
// Add 50 more characters
$user->about_short = str_repeat('b', 50);
$user->save();
// Now complete (100+ chars, has relations)
assertFalse($user->hasIncompleteProfile($user));
Test 2: Relation Requirements
$user = User::factory()->create([
'about' => str_repeat('test ', 25), // 125 characters
]);
// No relations yet
// Should be incomplete (no relations)
assertTrue($user->hasIncompleteProfile($user));
// Add a tag
$user->tags()->attach(1);
// Now complete
assertFalse($user->hasIncompleteProfile($user));
Test 3: Search Filtering
// Create incomplete user
$incompleteUser = User::factory()->create(['about' => '']);
// Search as regular user
actingAs($regularUser);
$results = app(MainSearchBar::class)->search($incompleteUser->name);
// Should NOT find incomplete user
assertEmpty($results);
// Search as admin
actingAs($admin);
$results = app(MainSearchBar::class)->search($incompleteUser->name);
// SHOULD find incomplete user (admins can see all)
assertNotEmpty($results);
Test 4: Profile Page Access
$incompleteUser = User::factory()->create(['about' => '']);
// Test with profile_hidden = true
config(['timebank-cc.profile_incomplete.profile_hidden' => true]);
// Access as regular user
actingAs($regularUser);
$response = $this->get(route('profile.show', ['type' => 'user', 'id' => $incompleteUser->id]));
$response->assertViewIs('profile.not_found'); // Should be hidden
// Access as admin
actingAs($admin);
$response = $this->get(route('profile.show', ['type' => 'user', 'id' => $incompleteUser->id]));
$response->assertViewIs('profile.show'); // Should be visible
Automated Test Suite
Create: tests/Feature/ProfileIncompleteTest.php
<?php
namespace Tests\Feature;
use App\Models\User;
use App\Models\Tag;
use App\Models\Locations\City;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class ProfileIncompleteTest extends TestCase
{
use RefreshDatabase;
/** @test */
public function profile_with_no_content_is_incomplete()
{
$user = User::factory()->create([
'about' => '',
'about_short' => '',
'motivation' => '',
'cyclos_skills' => '',
]);
$this->assertTrue($user->hasIncompleteProfile($user));
}
/** @test */
public function profile_with_content_but_no_relations_is_incomplete()
{
$user = User::factory()->create([
'about' => str_repeat('test ', 25), // 125 chars
]);
$this->assertTrue($user->hasIncompleteProfile($user));
}
/** @test */
public function profile_with_short_content_and_relations_is_incomplete()
{
$user = User::factory()->create([
'about' => 'Short text', // < 100 chars
]);
$user->tags()->attach(Tag::factory()->create()->tag_id);
$this->assertTrue($user->hasIncompleteProfile($user));
}
/** @test */
public function profile_with_sufficient_content_and_relations_is_complete()
{
$user = User::factory()->create([
'about' => str_repeat('test ', 25), // 125 chars
]);
$user->tags()->attach(Tag::factory()->create()->tag_id);
$this->assertFalse($user->hasIncompleteProfile($user));
}
/** @test */
public function incomplete_profiles_hidden_from_main_search()
{
$this->markTestIncomplete('Implement when search filtering is active');
}
/** @test */
public function admins_can_see_incomplete_profiles_in_search()
{
$this->markTestIncomplete('Implement when search filtering is active');
}
}
Configuration Examples
Example 1: Strict Requirements (Default)
Requires meaningful content across multiple fields and at least one relation.
'profile_incomplete' => [
'messenger_hidden' => true,
'profile_search_hidden' => true,
'profile_hidden' => false,
'profile_labeled' => false,
'check_fields' => ['about', 'about_short', 'motivation', 'cyclos_skills'],
'check_fields_min_total_length' => 100,
'check_relations' => ['tags', 'languages', 'locations'],
],
Use Case: Quality-focused communities that want only well-documented profiles visible.
Example 2: Minimal Requirements
Only requires basic information.
'profile_incomplete' => [
'messenger_hidden' => false,
'profile_search_hidden' => true,
'profile_hidden' => false,
'profile_labeled' => true,
'check_fields' => ['about_short'],
'check_fields_min_total_length' => 20,
'check_relations' => ['locations'],
],
Use Case: Welcoming communities that want to encourage participation but still require location.
Example 3: Skills-Focused Platform
Emphasizes skills/services offered.
'profile_incomplete' => [
'messenger_hidden' => true,
'profile_search_hidden' => true,
'profile_hidden' => false,
'profile_labeled' => true,
'check_fields' => ['cyclos_skills', 'motivation'],
'check_fields_min_total_length' => 50,
'check_relations' => ['tags', 'locations'],
],
Use Case: Service exchange platforms where skills are the primary discovery method.
Example 4: Disabled
Turns off incomplete profile filtering entirely.
'profile_incomplete' => [
'messenger_hidden' => false,
'profile_search_hidden' => false,
'profile_hidden' => false,
'profile_labeled' => false,
'check_fields' => [],
'check_fields_min_total_length' => 0,
'check_relations' => [],
],
Use Case: Testing environments or very small communities where all profiles should be visible.
Best Practices
1. Gradual Enforcement
Start with lenient settings and tighten over time as users complete profiles:
// Week 1-2: Just label incomplete profiles
'profile_labeled' => true,
'profile_search_hidden' => false,
// Week 3-4: Hide from search but allow messenger
'profile_labeled' => true,
'profile_search_hidden' => true,
'messenger_hidden' => false,
// Week 5+: Full enforcement
'profile_labeled' => true,
'profile_search_hidden' => true,
'messenger_hidden' => true,
2. Clear User Communication
When enforcing incomplete profile restrictions:
- Show clear messages on why their profile is hidden
- Provide checklist of what's needed to complete profile
- Calculate and display completion percentage
- Send reminder emails to users with incomplete profiles
3. Admin Override
Always allow Admins and Banks to:
- View all profiles regardless of completion status
- Search for incomplete profiles
- Message incomplete profiles
This is already implemented in the permission checks.
4. Performance Optimization
When checking many profiles:
- Eager load relations to avoid N+1 queries
- Cache profile completion status
- Consider adding a
profile_completed_attimestamp field
// Efficient bulk checking
User::with(['tags', 'languages', 'locations'])
->get()
->filter(fn($user) => !$user->hasIncompleteProfile($user));
Troubleshooting
Issue: Profile shows as incomplete but appears complete
Diagnosis:
$user = User::with(['tags', 'languages', 'locations'])->find($id);
dd([
'has_field_data' => !empty($user->about) || !empty($user->about_short),
'field_lengths' => [
'about' => strlen($user->about ?? ''),
'about_short' => strlen($user->about_short ?? ''),
'motivation' => strlen($user->motivation ?? ''),
'cyclos_skills' => strlen($user->cyclos_skills ?? ''),
],
'total_length' => strlen(trim($user->about ?? '')) + strlen(trim($user->about_short ?? '')),
'has_tags' => !$user->tags->isEmpty(),
'has_languages' => !$user->languages->isEmpty(),
'has_locations' => !$user->locations->isEmpty(),
]);
Common Causes:
- Whitespace-only content in fields (use
trim()) - Relations not eager-loaded (causing empty collection check to fail)
- Total character count just under 100 threshold
Issue: Admin sees incomplete profiles in search
This is correct behavior. Admins should see all profiles.
Verify with:
$canManageProfiles = $this->getCanManageProfiles();
// Returns true for Admin/Bank profiles
Migration Guide
Adding Completion Tracking
To track when profiles become complete:
Migration:
Schema::table('users', function (Blueprint $table) {
$table->timestamp('profile_completed_at')->nullable()->after('email_verified_at');
});
Model Method:
public function markAsComplete()
{
if (!$this->hasIncompleteProfile($this)) {
$this->profile_completed_at = now();
$this->save();
}
}
Usage:
// After profile edit
$user->markAsComplete();
See Also
- SEARCH_REFERENCE.md - Main search implementation details
- STYLE_GUIDE.md - UI patterns for profile badges/labels
config/timebank-default.php.example- Full configuration referenceapp/Traits/ProfileTrait.php- Core profile methodsapp/Traits/ActiveStatesTrait.php- Active/inactive state management
Changelog
2025-11-03 - Bank Profile Updates
- ✓ Added
cyclos_skillscolumn to banks table via migration - ✓ Updated
showBank()method to includecyclos_skillsin select statement - Banks now have skills field matching Users and Organizations
2025-11-03 - Search Improvements
- ✓ Removed temporary Organization exemption from incomplete profile filtering in
MainSearchBar - ✓ All profile types (Users, Organizations, Banks) now properly filtered when incomplete
- ✓ Fixed search dropdown showing empty border when no suggestions available
- Search behavior now consistent across all profile types
2025-11-03 - Warning Modal Implementation
- ✓ Added
show_warning_modalconfiguration setting toprofile_incomplete - ✓ Implemented warning modal display when viewing own incomplete profile
- ✓ Modal appears on both profile view and edit pages
- ✓ Uses Alpine.js for smooth transitions and dismissal
- ✓ Integrates with SidePost content system for customizable messaging
- ✓ Modal checks
show_warning_modalconfig (independent fromprofile_hidden)
2025-11-03 - Profile Visibility Refinements
- ✓ Updated incomplete label visibility logic for admins/banks vs regular users
- ✓ Regular users respect
profile_labeledconfig setting - ✓ Admins/Banks always see incomplete labels regardless of config
- ✓ Fixed profile access control to properly hide incomplete/inactive profiles
- ✓ Changed incomplete label styling to
text-red-700(matching inactive/removed)
2025-01-31 - Bank Profile Access Fix
- ✓ Added
canViewIncompleteProfiles()method toProfilePermissionTrait - ✓ Banks can now view incomplete profiles (checks profile type, not permissions)
- ✓ Maintains existing permission-based
getCanManageProfiles()method - ✓ Updated all three profile show methods to use new check
- No database migration needed - profile type check only
2025-01-31 - Profile Labels Implementation
- ✓ Enabled
profile_incomplete.profile_labeledin both config files - ✓ Enabled
profile_email_unverified.profile_labeledin both config files - Frontend labels already implemented in Livewire component (lines 35-49, 263-267)
- Incomplete profiles now show red (
text-red-700) warning label to Admins/Banks - Email unverified profiles now show red (
text-red-700) warning label to Admins/Banks - Labels appear in both top section (near name) and activity info section
2025-01-31 - Browse Categories & Profile Page Implementation
- ✓ Implemented incomplete profile filtering in
MainBrowseTagCategories - ✓ Implemented profile page access control in
ProfileController - ✓ Added
incompleteLabelvariable to profile state system - Backend now fully ready for incomplete profile enforcement
2025-01-31 - Initial Documentation
- Documented current implementation status
- Identified missing enforcement locations
- Created testing guidelines
- Provided configuration examples
Last Updated: 2025-11-03 Maintainer: Development Team Status: Mostly Implemented (Search ✓, Browse ✓, Profile Page Hidden ✓, Labels ✓, Warning Modal ✓ - Only Messenger Pending)