diff --git a/.gitignore b/.gitignore index a147aee2..a1213f63 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ /public/build /public/hot /public/storage +/public/files /storage/*.key /storage/pail /vendor diff --git a/app/Http/Controllers/BrowseGalleryController.php b/app/Http/Controllers/BrowseGalleryController.php new file mode 100644 index 00000000..78509a23 --- /dev/null +++ b/app/Http/Controllers/BrowseGalleryController.php @@ -0,0 +1,181 @@ +query('sort', 'latest'); + $perPage = $this->resolvePerPage($request); + + $artworks = $this->artworks->browsePublicArtworks($perPage, $sort); + + $mainCategories = $this->mainCategories(); + + return view('gallery.index', [ + 'gallery_type' => 'browse', + 'mainCategories' => $mainCategories, + 'subcategories' => $mainCategories, + 'contentType' => null, + 'category' => null, + 'artworks' => $artworks, + 'hero_title' => 'Browse Artworks', + 'hero_description' => 'List of all uploaded artworks across Skins, Wallpapers, Photography, and Other.', + 'breadcrumbs' => collect(), + 'page_title' => 'Browse Uploaded Artworks - Photography, Wallpapers and Skins at SkinBase', + 'page_meta_description' => "Browse Uploaded Photography, Wallpapers and Skins to one of the world's oldest online social community for artists and art enthusiasts.", + 'page_meta_keywords' => 'photography, wallpapers, skins, stock, browse, social, community, artist, picture, photo', + 'page_canonical' => url('/browse'), + ]); + } + + public function content(Request $request, string $contentTypeSlug, ?string $path = null) + { + $contentSlug = strtolower($contentTypeSlug); + if (! in_array($contentSlug, self::CONTENT_TYPE_SLUGS, true)) { + abort(404); + } + + $contentType = ContentType::where('slug', $contentSlug)->first(); + if (! $contentType) { + abort(404); + } + + $sort = (string) $request->query('sort', 'latest'); + $perPage = $this->resolvePerPage($request); + + $mainCategories = $this->mainCategories(); + $rootCategories = $contentType->rootCategories()->orderBy('sort_order')->orderBy('name')->get(); + + $normalizedPath = trim((string) $path, '/'); + if ($normalizedPath === '') { + $artworks = $this->artworks->getArtworksByContentType($contentSlug, $perPage, $sort); + + return view('gallery.index', [ + 'gallery_type' => 'content-type', + 'mainCategories' => $mainCategories, + 'subcategories' => $rootCategories, + 'contentType' => $contentType, + 'category' => null, + 'artworks' => $artworks, + 'hero_title' => $contentType->name, + 'hero_description' => $contentType->description ?? ($contentType->name . ' artworks on Skinbase.'), + 'breadcrumbs' => collect([(object) ['name' => $contentType->name, 'url' => '/' . $contentSlug]]), + 'page_title' => $contentType->name, + 'page_meta_description' => $contentType->description ?? ($contentType->name . ' artworks on Skinbase'), + 'page_meta_keywords' => strtolower($contentType->slug) . ', skinbase, artworks, wallpapers, skins, photography', + 'page_canonical' => url('/' . $contentSlug), + ]); + } + + $segments = array_values(array_filter(explode('/', $normalizedPath))); + $category = Category::findByPath($contentSlug, $segments); + if (! $category) { + abort(404); + } + + $artworks = $this->artworks->getArtworksByCategoryPath(array_merge([$contentSlug], $segments), $perPage, $sort); + + $subcategories = $category->children()->orderBy('sort_order')->orderBy('name')->get(); + if ($subcategories->isEmpty()) { + $subcategories = $rootCategories; + } + + $breadcrumbs = collect($category->breadcrumbs) + ->map(function (Category $crumb) { + return (object) [ + 'name' => $crumb->name, + 'url' => $crumb->url, + ]; + }); + + return view('gallery.index', [ + 'gallery_type' => 'category', + 'mainCategories' => $mainCategories, + 'subcategories' => $subcategories, + 'contentType' => $contentType, + 'category' => $category, + 'artworks' => $artworks, + 'hero_title' => $category->name, + 'hero_description' => $category->description ?? ($contentType->name . ' artworks on Skinbase.'), + 'breadcrumbs' => $breadcrumbs, + 'page_title' => $category->name, + 'page_meta_description' => $category->description ?? ($contentType->name . ' artworks on Skinbase'), + 'page_meta_keywords' => strtolower($contentType->slug) . ', skinbase, artworks, wallpapers, skins, photography', + 'page_canonical' => url('/' . $contentSlug . '/' . strtolower($category->full_slug_path)), + ]); + } + + public function showArtwork(Request $request, string $contentTypeSlug, string $categoryPath, string $artwork) + { + return app(ArtworkController::class)->show( + $request, + strtolower($contentTypeSlug), + trim($categoryPath, '/'), + $artwork + ); + } + + public function legacyCategory(Request $request, ?string $group = null, ?string $slug = null, ?string $id = null) + { + if ($id !== null && ctype_digit((string) $id)) { + $category = Category::with('contentType')->find((int) $id); + if (! $category || ! $category->contentType) { + abort(404); + } + + return redirect($category->url, 301); + } + + $contentSlug = strtolower((string) $group); + if (! in_array($contentSlug, self::CONTENT_TYPE_SLUGS, true)) { + abort(404); + } + + $target = '/' . $contentSlug; + $normalizedSlug = trim((string) $slug, '/'); + if ($normalizedSlug !== '') { + $target .= '/' . strtolower($normalizedSlug); + } + + if ($request->query()) { + $target .= '?' . http_build_query($request->query()); + } + + return redirect($target, 301); + } + + private function resolvePerPage(Request $request): int + { + $value = (int) $request->query('per_page', 40); + + return max(12, min($value, 80)); + } + + private function mainCategories(): Collection + { + return ContentType::orderBy('id') + ->get(['name', 'slug']) + ->map(function (ContentType $type) { + return (object) [ + 'id' => $type->id, + 'name' => $type->name, + 'slug' => $type->slug, + 'url' => '/' . strtolower($type->slug), + ]; + }); + } +} diff --git a/resources/views/legacy/browse.blade.php b/resources/views/gallery/index.blade.php similarity index 67% rename from resources/views/legacy/browse.blade.php rename to resources/views/gallery/index.blade.php index d1cd50a5..396d623b 100644 --- a/resources/views/legacy/browse.blade.php +++ b/resources/views/gallery/index.blade.php @@ -24,20 +24,31 @@
Main Categories:
Browse Subcategories:
@@ -48,21 +59,34 @@
-
Browse
+
+ @if(($gallery_type ?? null) === 'browse') + Browse + @elseif(isset($contentType) && $contentType) + {{ $contentType->name }} + @if(($gallery_type ?? null) === 'category') + @foreach($breadcrumbs as $crumb) + + {{ $crumb->name }} + @endforeach + @endif + @endif +
-

Browse Artworks

+

{{ $hero_title ?? 'Browse Artworks' }}

-
All categories
-

List of all uploaded artworks across Skins, Wallpapers, Photography, and Other.

+
{{ $hero_title ?? 'Browse Artworks' }}
+

{!! $hero_description ?? '' !!}

+
-
+
@forelse ($artworks as $art) @include('legacy._artwork_card', ['art' => $art]) @@ -77,7 +101,9 @@
- {{ $artworks->withQueryString()->links() }} + @if ($artworks instanceof \Illuminate\Contracts\Pagination\Paginator) + {{ method_exists($artworks, 'withQueryString') ? $artworks->withQueryString()->links() : $artworks->links() }} + @endif
@@ -90,9 +116,6 @@ @push('styles') @endpush diff --git a/resources/views/legacy/_artwork_card.blade.php b/resources/views/legacy/_artwork_card.blade.php index d0c37cf5..1d7d1fa7 100644 --- a/resources/views/legacy/_artwork_card.blade.php +++ b/resources/views/legacy/_artwork_card.blade.php @@ -17,16 +17,48 @@ $category = trim((string) ($art->category_name ?? $art->category ?? 'General')); $license = trim((string) ($art->license ?? 'Standard')); $resolution = trim((string) ($art->resolution ?? ((isset($art->width, $art->height) && $art->width && $art->height) ? ($art->width . '×' . $art->height) : ''))); - $likes = (int) ($art->likes ?? $art->favourites ?? 0); - $downloads = (int) ($art->downloads ?? $art->downloaded ?? 0); + // Safe integer extractor: handle numeric, arrays, Collections, or relations + $safeInt = function ($v, $fallback = 0) { + if (is_numeric($v)) return (int) $v; + if (is_array($v)) return count($v); + if (is_object($v)) { + // Eloquent Collections implement Countable and have count() + if (method_exists($v, 'count')) return (int) $v->count(); + if ($v instanceof Countable) return (int) count($v); + } + return (int) $fallback; + }; + + $likes = $safeInt($art->likes ?? $art->favourites ?? 0); + $downloads = $safeInt($art->downloads ?? $art->downloaded ?? 0); $img_src = (string) ($art->thumb ?? $art->thumbnail_url ?? '/images/placeholder.jpg'); $img_srcset = (string) ($art->thumb_srcset ?? $art->thumbnail_srcset ?? $img_src); $img_avif_srcset = (string) ($art->thumb_avif_srcset ?? $img_srcset); $img_webp_srcset = (string) ($art->thumb_webp_srcset ?? $img_srcset); - $img_width = max(1, (int) ($art->width ?? 800)); - $img_height = max(1, (int) ($art->height ?? 600)); + // Width/height may be stored directly or as a related object; handle safely + $resolveDimension = function ($val, $fallback) { + if (is_numeric($val)) return (int) $val; + if (is_array($val)) { + $v = reset($val); + return is_numeric($v) ? (int) $v : (int) $fallback; + } + if (is_object($val)) { + if (method_exists($val, 'first')) { + $f = $val->first(); + if (is_object($f) && isset($f->width)) return (int) ($f->width ?: $fallback); + if (is_object($f) && isset($f->height)) return (int) ($f->height ?: $fallback); + } + // Try numeric cast otherwise + if (isset($val->width)) return (int) $val->width; + if (isset($val->height)) return (int) $val->height; + } + return (int) $fallback; + }; + + $img_width = max(1, $resolveDimension($art->width ?? null, 800)); + $img_height = max(1, $resolveDimension($art->height ?? null, 600)); $contentUrl = $img_src; $cardUrl = (string) ($art->url ?? '#'); diff --git a/resources/views/legacy/category-slug.blade.php b/resources/views/legacy/category-slug.blade.php deleted file mode 100644 index ff9f492d..00000000 --- a/resources/views/legacy/category-slug.blade.php +++ /dev/null @@ -1,148 +0,0 @@ -@extends('layouts.nova') - -@php - use App\Banner; -@endphp - -@section('content') -
- @php Banner::ShowResponsiveAd(); @endphp - -
-
-
- - - - - -
-
-
- -
-
- {{ $contentType->name }} - @foreach ($category->breadcrumbs as $crumb) - {{ $crumb->name }} - @endforeach -
- - @php - // Use the current (last) breadcrumb as the header so - // the page shows the leaf category name (e.g. "Winamp"). - $breadcrumbs = is_array($category->breadcrumbs) ? $category->breadcrumbs : [$category]; - $headerCategory = end($breadcrumbs) ?: $category; - @endphp - -

{{ $headerCategory->name }}

- -
-
-
{{ $headerCategory->name }}
-

{!! $headerCategory->description ?? ($contentType->name . ' artworks on Skinbase.') !!}

-
-
- {{-- soft fade at bottom of hero to blend into main background --}} - -
-
- - -
-
- @forelse ($artworks as $art) - @include('legacy._artwork_card', ['art' => $art]) - @empty -
-
No Artworks Yet
-
-

Once uploads arrive they will appear here. Check back soon.

-
-
- @endforelse -
- -
- @if ($artworks instanceof \Illuminate\Contracts\Pagination\Paginator) - {{ $artworks->links() }} - @endif -
- -
-
-
-
-
-
-@endsection - -@push('styles') - -@endpush - -@push('scripts') - -@endpush diff --git a/resources/views/legacy/content-type.blade.php b/resources/views/legacy/content-type.blade.php deleted file mode 100644 index c1fc897a..00000000 --- a/resources/views/legacy/content-type.blade.php +++ /dev/null @@ -1,158 +0,0 @@ -@extends('layouts.nova') - -@php - use App\Banner; -@endphp - -@section('content') -
- @php Banner::ShowResponsiveAd(); @endphp - -
-
-
- - - - - -
-
-
- -
- - -

{{ $contentType->name }}

- -
-
-
{{ $contentType->name }}
-

{!! $page_meta_description ?? ($contentType->name . ' artworks on Skinbase.') !!}

-
-
- {{-- soft fade at bottom of hero to blend into main background --}} - -
-
- - -
-
- @forelse ($artworks as $art) - @include('legacy._artwork_card', ['art' => $art]) - @empty -
-
No Artworks Yet
-
-

Once uploads arrive they will appear here. Check back soon.

-
-
- @endforelse -
- -
- {{ $artworks->withQueryString()->links('pagination::tailwind') }} -
- -
-
-
-
-
-
-@endsection - -@push('styles') - -@endpush - -@push('scripts') - -@endpush diff --git a/resources/views/legacy/photography.blade.php b/resources/views/legacy/photography.blade.php deleted file mode 100644 index 6296018a..00000000 --- a/resources/views/legacy/photography.blade.php +++ /dev/null @@ -1,153 +0,0 @@ -@extends('layouts.nova') - -@section('content') -
- @php \App\Banner::ShowResponsiveAd(); @endphp - - - -
- -
- -
-
- - - - - - - -
- - -
- {{ method_exists($artworks, 'withQueryString') ? $artworks->withQueryString()->links() : '' }} -
-
-
-
- -
-@endsection - -@push('styles') - -@endpush - -@push('scripts') - -@endpush diff --git a/routes/legacy.php b/routes/legacy.php index 6c9d9309..15384dd4 100644 --- a/routes/legacy.php +++ b/routes/legacy.php @@ -7,7 +7,6 @@ use App\Http\Controllers\Legacy\AvatarController; use App\Http\Controllers\Legacy\ForumController; use App\Http\Controllers\Legacy\NewsController; use App\Http\Controllers\Legacy\CategoryController; -use App\Http\Controllers\Legacy\BrowseController; use App\Http\Controllers\Legacy\FeaturedArtworksController; use App\Http\Controllers\Legacy\DailyUploadsController; use App\Http\Controllers\Legacy\ChatController; @@ -27,7 +26,7 @@ use App\Http\Controllers\BrowseCategoriesController; use App\Http\Controllers\GalleryController; use App\Http\Controllers\Legacy\ReceivedCommentsController; use App\Http\Controllers\Legacy\UserController as LegacyUserController; -use App\Http\Controllers\Legacy\PhotographyController; +use App\Http\Controllers\BrowseGalleryController; // Legacy site routes Route::get('/', [HomeController::class, 'index'])->name('legacy.home'); @@ -44,9 +43,9 @@ Route::get('/forum/{topic_id}/{slug?}', [ForumController::class, 'topic'])->wher Route::get('/news/{id}/{slug?}', [NewsController::class, 'show'])->where('id', '\\d+')->name('legacy.news.show'); Route::get('/categories', [CategoryController::class, 'index'])->name('legacy.categories'); -Route::get('/category/{group}/{slug?}/{id?}', [CategoryController::class, 'show'])->name('legacy.category'); +Route::get('/category/{group}/{slug?}/{id?}', [BrowseGalleryController::class, 'legacyCategory'])->name('legacy.category'); -Route::get('/browse', [BrowseController::class, 'index'])->name('legacy.browse'); +Route::get('/browse', [BrowseGalleryController::class, 'browse'])->name('legacy.browse'); Route::get('/featured', [FeaturedArtworksController::class, 'index'])->name('legacy.featured'); Route::get('/featured-artworks', [FeaturedArtworksController::class, 'index'])->name('legacy.featured_artworks'); Route::get('/daily-uploads', [DailyUploadsController::class, 'index'])->name('legacy.daily_uploads'); diff --git a/routes/web.php b/routes/web.php index 013a97c3..925767c0 100644 --- a/routes/web.php +++ b/routes/web.php @@ -115,12 +115,12 @@ Route::bind('artwork', function ($value) { // to serve photography's root page. This catch-all route delegates to a controller that // will forward to the appropriate existing controller (artwork or category handlers). // Provide a named route alias for legacy artwork URL generation used in tests. -Route::get('/{contentTypeSlug}/{categoryPath}/{artwork}', [\App\Http\Controllers\ContentRouterController::class, 'handle']) +Route::get('/{contentTypeSlug}/{categoryPath}/{artwork}', [\App\Http\Controllers\BrowseGalleryController::class, 'showArtwork']) ->where('contentTypeSlug', 'photography|wallpapers|skins|other') ->where('categoryPath', '[^/]+(?:/[^/]+)*') ->name('artworks.show'); -Route::get('/{contentTypeSlug}/{path?}', [\App\Http\Controllers\ContentRouterController::class, 'handle']) +Route::get('/{contentTypeSlug}/{path?}', [\App\Http\Controllers\BrowseGalleryController::class, 'content']) ->where('contentTypeSlug', 'photography|wallpapers|skins|other') ->where('path', '.*') ->name('content.route');