diff --git a/app/Http/Controllers/CommunityController.php b/app/Http/Controllers/CommunityController.php
index 0da7f9d95..70a4c3f61 100644
--- a/app/Http/Controllers/CommunityController.php
+++ b/app/Http/Controllers/CommunityController.php
@@ -24,7 +24,11 @@ public function index(UserFilters $filters)
->where('avatar_path', '<>', 'images/default-avatar.png')
->paginate(10);
- $teachers = User::role('leading teacher')->where('approved', 1)->with('city')->get();
+ $teachers = User::role('leading teacher')
+ ->where('approved', 1)
+ ->filter($filters)
+ ->with(['city', 'expertises'])
+ ->get();
$countries = Country::withCoordinators();
@@ -33,6 +37,7 @@ public function index(UserFilters $filters)
'countries' => $countries,
'teachers' => $teachers,
'country_iso' => request()->get('country_iso'),
+ 'default_avatar' => asset('images/default.png'),
]);
}
}
diff --git a/app/User.php b/app/User.php
index de4d0ad4d..8292554a4 100644
--- a/app/User.php
+++ b/app/User.php
@@ -341,6 +341,34 @@ public function getAvatarPathAttribute($avatar)
return Storage::disk('s3')->url($avatar);
}
+ /**
+ * Public avatar URL for community pages. Falls back to a local placeholder
+ * when the stored path is empty or a known default (S3 defaults are often inaccessible).
+ */
+ public function communityAvatarUrl(?string $fallback = null): string
+ {
+ $fallback = $fallback ?: asset('images/default.png');
+ $raw = $this->attributes['avatar_path'] ?? null;
+
+ if ($raw === null || trim((string) $raw) === '') {
+ return $fallback;
+ }
+
+ $normalized = strtolower(trim((string) $raw));
+ $defaults = [
+ 'avatars/default_avatar.png',
+ 'avatars/default.png',
+ 'images/default-avatar.png',
+ 'images/default.png',
+ ];
+
+ if (in_array($normalized, $defaults, true)) {
+ return $fallback;
+ }
+
+ return $this->avatar_path;
+ }
+
/**
* Get the path to the user's avatar.
*
diff --git a/resources/views/community.blade.php b/resources/views/community.blade.php
index 60de1020a..24a238168 100755
--- a/resources/views/community.blade.php
+++ b/resources/views/community.blade.php
@@ -246,9 +246,10 @@ class="duration-500 transform -translate-x-6 min-w-4 group-hover:translate-x-0"
@@ -571,6 +572,7 @@ class="text-[#1C4DA1] text-2xl md:text-4xl leading-[44px] font-medium font-['Mon
var markers = {};
var selectedMarker = null;
var allTeachers = [];
+ var defaultAvatar = @json($default_avatar ?? asset('images/default.png'));
// Function to populate teacher information in the right sidebar
function populateTeacherInfo(teachers, city = null) {
@@ -642,9 +644,10 @@ function populateTeacherInfo(teachers, city = null) {
- ${ teacher.avatar_path
- ? `

`
- : '' }
+

`;
});
@@ -709,21 +712,21 @@ function populateTeacherInfo(teachers, city = null) {
mymap.setView([centerInfo.latitude, centerInfo.longitude], centerInfo.zoom);
});
- // Populate the global teachers array from PHP data
+ // Populate the global teachers array from PHP data (already filtered by country)
@foreach ($teachers->groupBy('city_id') as $cityId => $teachersInCity)
@foreach ($teachersInCity as $teacher)
allTeachers.push({
- firstname: "{{ $teacher->firstname }}",
- lastname: "{{ $teacher->lastname }}",
- email: "{{ $teacher->email }}",
- country_iso: "{{ $teacher->country_iso }}",
- twitter: "{{ $teacher->twitter }}",
- website: "{{ $teacher->website }}",
+ firstname: @json($teacher->firstname),
+ lastname: @json($teacher->lastname),
+ email: @json($teacher->email),
+ country_iso: @json($teacher->country_iso),
+ twitter: @json($teacher->twitter),
+ website: @json($teacher->website),
bio: @json($teacher->bio),
- avatar_path: "{{ $teacher->avatar_path }}",
- city: "{{ $teacher->city->city ?? 'N/A' }}",
- latitude: "{{ $teacher->city->latitude ?? '' }}",
- longitude: "{{ $teacher->city->longitude ?? '' }}",
+ avatar_path: @json($teacher->communityAvatarUrl($default_avatar)),
+ city: @json($teacher->city->city ?? 'N/A'),
+ latitude: @json($teacher->city->latitude ?? ''),
+ longitude: @json($teacher->city->longitude ?? ''),
expertises: @json($teacher->expertises->pluck('name')->toArray())
});
@endforeach
@@ -758,15 +761,15 @@ function populateTeacherInfo(teachers, city = null) {
var teacherList = [
@foreach ($teachersInCity as $teacher)
{
- firstname: "{{ $teacher->firstname }}",
- lastname: "{{ $teacher->lastname }}",
- email: "{{ $teacher->email }}",
- country_iso: "{{ $teacher->country_iso }}",
- twitter: "{{ $teacher->twitter }}",
- website: "{{ $teacher->website }}",
+ firstname: @json($teacher->firstname),
+ lastname: @json($teacher->lastname),
+ email: @json($teacher->email),
+ country_iso: @json($teacher->country_iso),
+ twitter: @json($teacher->twitter),
+ website: @json($teacher->website),
bio: @json($teacher->bio),
- avatar_path: "{{ $teacher->avatar_path }}",
- city: "{{ $teacher->city->city ?? 'N/A' }}",
+ avatar_path: @json($teacher->communityAvatarUrl($default_avatar)),
+ city: @json($teacher->city->city ?? 'N/A'),
expertises: @json($teacher->expertises->pluck('name')->toArray())
},
@endforeach
diff --git a/tests/Feature/CommunityAmbassadorFilteringTest.php b/tests/Feature/CommunityAmbassadorFilteringTest.php
index cb6b4971c..5e5cc1def 100644
--- a/tests/Feature/CommunityAmbassadorFilteringTest.php
+++ b/tests/Feature/CommunityAmbassadorFilteringTest.php
@@ -2,6 +2,9 @@
namespace Tests\Feature;
+use App\City;
+use App\Country;
+use App\User;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
@@ -15,9 +18,9 @@ public function community_view_does_not_include_ambassador_without_bio_or_avatar
{
$this->seed('RolesAndPermissionsSeeder');
$this->seed('LeadingTeacherRoleSeeder');
- $fr = \App\Country::factory()->create(['iso' => 'FR']);
+ $fr = Country::factory()->create(['iso' => 'FR']);
- $bad = \App\User::factory()->create([
+ $bad = User::factory()->create([
'country_iso' => $fr->iso,
'bio' => null,
'avatar_path' => null,
@@ -28,8 +31,52 @@ public function community_view_does_not_include_ambassador_without_bio_or_avatar
$res->assertViewHas('ambassadors', function ($paginator) use ($bad) {
$collection = $paginator->getCollection();
- return !$collection->contains('id', $bad->id);
+
+ return ! $collection->contains('id', $bad->id);
});
}
-}
+ #[Test]
+ public function community_leading_teachers_are_filtered_by_selected_country(): void
+ {
+ $this->seed('RolesAndPermissionsSeeder');
+ $this->seed('LeadingTeacherRoleSeeder');
+
+ $lt = Country::factory()->create(['iso' => 'LT']);
+ $fr = Country::factory()->create(['iso' => 'FR']);
+ $vilnius = City::factory()->create([
+ 'country_iso' => 'LT',
+ 'city' => 'Vilnius',
+ 'latitude' => 54.6833,
+ 'longitude' => 25.2833,
+ ]);
+
+ $lithuanian = User::factory()->create([
+ 'country_iso' => $lt->iso,
+ 'city_id' => $vilnius->id,
+ 'approved' => 1,
+ 'firstname' => 'Dovile',
+ 'lastname' => 'Testiene',
+ 'avatar_path' => null,
+ ])->assignRole('leading teacher');
+
+ $french = User::factory()->create([
+ 'country_iso' => $fr->iso,
+ 'approved' => 1,
+ 'firstname' => 'Marie',
+ 'lastname' => 'Dupont',
+ ])->assignRole('leading teacher');
+
+ $res = $this->get('/community?country_iso=LT');
+ $res->assertOk();
+
+ $res->assertViewHas('teachers', function ($teachers) use ($lithuanian, $french) {
+ return $teachers->contains('id', $lithuanian->id)
+ && ! $teachers->contains('id', $french->id);
+ });
+
+ $res->assertSee('Dovile', false);
+ $res->assertDontSee('Marie', false);
+ $res->assertSee(asset('images/default.png'), false);
+ }
+}
diff --git a/tests/Unit/UserCommunityAvatarUrlTest.php b/tests/Unit/UserCommunityAvatarUrlTest.php
new file mode 100644
index 000000000..2f063dbed
--- /dev/null
+++ b/tests/Unit/UserCommunityAvatarUrlTest.php
@@ -0,0 +1,46 @@
+setRawAttributes(['avatar_path' => null]);
+
+ $this->assertSame(
+ asset('images/default.png'),
+ $user->communityAvatarUrl()
+ );
+ }
+
+ #[Test]
+ public function default_s3_avatar_uses_local_placeholder(): void
+ {
+ $user = new User;
+ $user->setRawAttributes(['avatar_path' => 'avatars/default_avatar.png']);
+
+ $this->assertSame(
+ asset('images/default.png'),
+ $user->communityAvatarUrl()
+ );
+ }
+
+ #[Test]
+ public function real_avatar_keeps_storage_url(): void
+ {
+ $user = new User;
+ $user->setRawAttributes(['avatar_path' => 'avatars/200/photo.jpg']);
+
+ $this->assertStringContainsString(
+ 'avatars/200/photo.jpg',
+ $user->communityAvatarUrl()
+ );
+ }
+}