@@ -5,26 +5,25 @@ declare(strict_types=1);
namespace App\Services ;
use App\Models\Artwork ;
use App\Models\Collection as CollectionModel ;
use App\Models\Leaderboard ;
use App\Models\Tag ;
use App\Services\HomepageAnnouncementService ;
use App\Services\ArtworkSearchService ;
use App\Models\User ;
use App\Services\EarlyGrowth\EarlyGrowth ;
use App\Services\EarlyGrowth\GridFiller ;
use App\Services\Maturity\ArtworkMaturityService ;
use App\Services\Recommendations\RecommendationFeedResolver ;
use App\Services\UserPreferenceService ;
use App\Services\Worlds\WorldService ;
use App\Support\ArtworkFeaturedImagePath ;
use App\Support\AvatarUrl ;
use App\Models\Collection as CollectionModel ;
use cPad\Plugins\News\Models\NewsArticle ;
use Illuminate\Contracts\Cache\Repository as CacheRepository ;
use Illuminate\Database\QueryException ;
use Illuminate\Support\Collection ;
use Illuminate\Support\Str ;
use Illuminate\Support\Facades\Cache ;
use Illuminate\Support\Facades\DB ;
use Illuminate\Support\Facades\Log ;
use Illuminate\Database\QueryException ;
use cPad\Plugins\News\Models\NewsArticle ;
use App\Services\Maturity\ArtworkMaturityService ;
use Illuminate\Support\Str ;
/**
* HomepageService
@@ -36,7 +35,9 @@ use App\Services\Maturity\ArtworkMaturityService;
final class HomepageService
{
private const CACHE_TTL = 300 ; // 5 minutes
private const DEFAULT_ARTWORK_RAIL_LIMIT = 10 ;
private const ARTWORK_SERIALIZATION_RELATIONS = [
'user:id,name,username' ,
'user.profile:user_id,avatar_hash' ,
@@ -46,12 +47,13 @@ final class HomepageService
];
public function __construct (
private readonly ArtworkService $artworks ,
private readonly ArtworkSearchService $search ,
private readonly ArtworkMaturityService $maturity ,
private readonly UserPreferenceService $pref s,
private readonly ArtworkService $artworks ,
private readonly ArtworkSearchService $search ,
private readonly ArtworkMaturityService $maturity ,
private readonly ArtworkFeaturedImagePath $featuredImage s,
private readonly UserPreferenceService $prefs ,
private readonly RecommendationFeedResolver $feedResolver ,
private readonly GridFiller $gridFiller ,
private readonly GridFiller $gridFiller ,
private readonly CollectionDiscoveryService $collectionDiscovery ,
private readonly CollectionService $collectionService ,
private readonly CollectionSurfaceService $collectionSurfaces ,
@@ -118,7 +120,7 @@ final class HomepageService
{
$configuredStore = ( string ) config ( 'homepage.cache_store' , 'homepage' );
if ( is_array ( config ( 'cache.stores.' . $configuredStore ))) {
if ( is_array ( config ( 'cache.stores.' . $configuredStore ))) {
return $configuredStore ;
}
@@ -128,22 +130,22 @@ final class HomepageService
private function buildGuestPayload () : array
{
return [
'hero' => $this -> getHeroArtwork (),
'hero' => $this -> getHeroArtwork (),
'announcement' => $this -> homepageAnnouncements -> toHomepagePayload ( $this -> homepageAnnouncements -> getActiveForHomepage ()),
'community_favorites' => $this -> getCommunityFavorites (),
'hall_of_fame' => $this -> getHallOfFame (),
'rising' => $this -> getRising (),
'rising' => $this -> getRising (),
'trending' => $this -> getTrending (),
'fresh' => $this -> getFreshUploads (),
'fresh' => $this -> getFreshUploads (),
'collections_featured' => $this -> getFeaturedCollections (),
'collections_trending' => $this -> getTrendingCollections (),
'collections_editorial' => $this -> getEditorialCollections (),
'collections_community' => $this -> getCommunityCollections (),
'world_spotlight' => $this -> worlds -> homepageSpotlight (),
'groups' => $this -> getHomepageGroups (),
'tags' => $this -> getPopularTags (),
'groups' => $this -> getHomepageGroups (),
'tags' => $this -> getPopularTags (),
'creators' => $this -> getCreatorSpotlight (),
'news' => $this -> getNews (),
'news' => $this -> getNews (),
];
}
@@ -167,44 +169,44 @@ final class HomepageService
*
* Sections :
* 1. user_data – welcome row counts ( messages , notifications , new followers )
* 2. from_following – artworks from creators you follow
* 3. for_you – personalized recommendation preview
* 4. trending – same trending feed as guests
* 5. by_categories – fresh uploads in user ' s favourite categories
* 6. suggested_creators – creators the user might want to follow
* 7. tags / creators / news – shared with guest homepage
* 2. from_following – artworks from creators you follow
* 3. for_you – personalized recommendation preview
* 4. trending – same trending feed as guests
* 5. by_categories – fresh uploads in user ' s favourite categories
* 6. suggested_creators – creators the user might want to follow
* 7. tags / creators / news – shared with guest homepage
*/
public function allForUser ( \App\Models\ User $user ) : array
public function allForUser ( User $user ) : array
{
$prefs = $this -> prefs -> build ( $user );
return [
'is_logged_in' => true ,
'user_data' => $this -> getUserData ( $user ),
'hero' => $this -> getHeroArtwork (),
'announcement' => $this -> homepageAnnouncements -> toHomepagePayload ( $this -> homepageAnnouncements -> getActiveForHomepage ()),
'is_logged_in' => true ,
'user_data' => $this -> getUserData ( $user ),
'hero' => $this -> getHeroArtwork (),
'announcement' => $this -> homepageAnnouncements -> toHomepagePayload ( $this -> homepageAnnouncements -> getActiveForHomepage ()),
'community_favorites' => $this -> getCommunityFavorites (),
'hall_of_fame' => $this -> getHallOfFame (),
'for_you' => $this -> getForYouPreview ( $user ),
'from_following' => $this -> getFollowingFeed ( $user , $prefs ),
'rising' => $this -> getRising (),
'trending' => $this -> getTrending (),
'fresh' => $this -> getFreshUploads (),
'hall_of_fame' => $this -> getHallOfFame (),
'for_you' => $this -> getForYouPreview ( $user ),
'from_following' => $this -> getFollowingFeed ( $user , $prefs ),
'rising' => $this -> getRising (),
'trending' => $this -> getTrending (),
'fresh' => $this -> getFreshUploads (),
'collections_featured' => $this -> getFeaturedCollections (),
'collections_recent' => $this -> getRecentCollections (),
'collections_recent' => $this -> getRecentCollections (),
'collections_trending' => $this -> getTrendingCollections (),
'collections_editorial' => $this -> getEditorialCollections (),
'collections_community' => $this -> getCommunityCollections (),
'world_spotlight' => $this -> worlds -> homepageSpotlight ( $user ),
'groups' => $this -> getHomepageGroups ( $user ),
'by_tags' => $this -> getByTags ( $prefs [ 'top_tags' ] ? ? []),
'by_categories' => $this -> getByCategories ( $prefs [ 'top_categories' ] ? ? []),
'suggested_creators' => $this -> getSuggestedCreators ( $user , $prefs ),
'tags' => $this -> getPopularTags (),
'creators' => $this -> getCreatorSpotlight (),
'news' => $this -> getNews (),
'preferences' => [
'top_tags' => $prefs [ 'top_tags' ] ? ? [],
'groups' => $this -> getHomepageGroups ( $user ),
'by_tags' => $this -> getByTags ( $prefs [ 'top_tags' ] ? ? []),
'by_categories' => $this -> getByCategories ( $prefs [ 'top_categories' ] ? ? []),
'suggested_creators' => $this -> getSuggestedCreators ( $user , $prefs ),
'tags' => $this -> getPopularTags (),
'creators' => $this -> getCreatorSpotlight (),
'news' => $this -> getNews (),
'preferences' => [
'top_tags' => $prefs [ 'top_tags' ] ? ? [],
'top_categories' => $prefs [ 'top_categories' ] ? ? [],
],
];
@@ -213,7 +215,7 @@ final class HomepageService
/**
* " For You " homepage preview backed by the personalized feed engine .
*/
public function getForYouPreview ( \App\Models\ User $user , int $limit = 12 ) : array
public function getForYouPreview ( User $user , int $limit = 12 ) : array
{
try {
$feed = $this -> feedResolver -> getFeed (( int ) $user -> id , max ( $limit * 3 , $limit ));
@@ -246,7 +248,7 @@ final class HomepageService
'category_slug' => ( string ) ( $item [ 'category_slug' ] ? ? '' ),
'content_type_name' => ( string ) ( $item [ 'content_type_name' ] ? ? '' ),
'content_type_slug' => ( string ) ( $item [ 'content_type_slug' ] ? ? '' ),
'url' => ( string ) ( $item [ 'url' ] ? ? ( '/art/' . (( int ) ( $item [ 'id' ] ? ? 0 )) . '/' . ( $item [ 'slug' ] ? ? '' ))),
'url' => ( string ) ( $item [ 'url' ] ? ? ( '/art/' . (( int ) ( $item [ 'id' ] ? ? 0 )) . '/' . ( $item [ 'slug' ] ? ? '' ))),
'width' => isset ( $item [ 'width' ]) ? ( int ) $item [ 'width' ] : null ,
'height' => isset ( $item [ 'height' ]) ? ( int ) $item [ 'height' ] : null ,
'published_at' => $item [ 'published_at' ] ? ? null ,
@@ -268,6 +270,7 @@ final class HomepageService
}) -> values () -> all ();
} catch ( \Throwable $e ) {
Log :: warning ( 'HomepageService::getForYouPreview failed' , [ 'error' => $e -> getMessage ()]);
return [];
}
}
@@ -336,7 +339,7 @@ final class HomepageService
);
}
public function getHomepageGroups ( ? \App\Models\ User $viewer = null ) : array
public function getHomepageGroups ( ? User $viewer = null ) : array
{
if ( ! $viewer ) {
return Cache :: remember ( 'homepage.groups' , self :: CACHE_TTL , fn () : array => $this -> buildHomepageGroups ());
@@ -345,7 +348,7 @@ final class HomepageService
return $this -> buildHomepageGroups ( $viewer );
}
private function buildHomepageGroups ( ? \App\Models\ User $viewer = null ) : array
private function buildHomepageGroups ( ? User $viewer = null ) : array
{
$featured = $this -> groupDiscovery -> surfaceCards ( $viewer , 'featured' , 4 );
$spotlight = $featured [ 0 ] ? ? null ;
@@ -369,7 +372,7 @@ final class HomepageService
*/
public function getHeroArtwork () : ? array
{
return Cache :: remember ( 'homepage.hero.' . $this -> viewerCacheSegment (), self :: CACHE_TTL , function () : ? array {
return Cache :: remember ( 'homepage.hero.' . $this -> viewerCacheSegment (), self :: CACHE_TTL , function () : ? array {
$artwork = $this -> artworks -> getFeaturedArtworkWinner ();
if ( ! $artwork instanceof Artwork ) {
@@ -387,7 +390,14 @@ final class HomepageService
$artwork -> loadMissing ( self :: ARTWORK_SERIALIZATION_RELATIONS );
}
return $artwork ? $this -> serializeArtwork ( $artwork , 'lg' ) : null ;
if ( ! $artwork instanceof Artwork ) {
return null ;
}
$payload = $this -> serializeArtwork ( $artwork , 'lg' );
$payload [ 'featured_image' ] = $this -> serializeFeaturedHeroImage ( $artwork );
return $payload ;
});
}
@@ -468,8 +478,8 @@ final class HomepageService
return Cache :: remember ( " homepage.rising. { $limit } . { $this -> viewerCacheSegment () } " , 120 , function () use ( $limit , $cutoff ) : array {
try {
$results = $this -> search -> searchWithThumbnailPreference ([
'filter' => 'is_public = true AND is_approved = true AND created_at >= "' . $cutoff . '"' ,
'sort' => [ 'heat_score:desc' , 'engagement_velocity:desc' , 'created_at:desc' ],
'filter' => 'is_public = true AND is_approved = true AND created_at >= "' . $cutoff . '"' ,
'sort' => [ 'heat_score:desc' , 'engagement_velocity:desc' , 'created_at:desc' ],
], $limit , true , 1 );
$items = $this -> prepareArtworksForSerialization ( $this -> searchResultCollection ( $results ));
@@ -558,8 +568,8 @@ final class HomepageService
return Cache :: remember ( " homepage.trending. { $limit } . { $this -> viewerCacheSegment () } " , self :: CACHE_TTL , function () use ( $limit , $cutoff ) : array {
try {
$results = $this -> search -> searchWithThumbnailPreference ([
'filter' => 'is_public = true AND is_approved = true AND created_at >= "' . $cutoff . '"' ,
'sort' => [ 'ranking_score:desc' , 'engagement_velocity:desc' , 'views:desc' ],
'filter' => 'is_public = true AND is_approved = true AND created_at >= "' . $cutoff . '"' ,
'sort' => [ 'ranking_score:desc' , 'engagement_velocity:desc' , 'views:desc' ],
], $limit , true , 1 );
$items = $this -> prepareArtworksForSerialization ( $this -> searchResultCollection ( $results ));
@@ -614,7 +624,7 @@ final class HomepageService
public function getFreshUploads ( int $limit = 10 ) : array
{
// Include EGS mode in cache key so toggling EGS updates the section within TTL
$egsKey = EarlyGrowth :: gridFillerEnabled () ? 'egs-' . EarlyGrowth :: mode () : 'std' ;
$egsKey = EarlyGrowth :: gridFillerEnabled () ? 'egs-' . EarlyGrowth :: mode () : 'std' ;
$cacheKey = " homepage.fresh. { $limit } . { $egsKey } . { $this -> viewerCacheSegment () } " ;
return Cache :: remember ( $cacheKey , self :: CACHE_TTL , function () use ( $limit ) : array {
@@ -646,9 +656,9 @@ final class HomepageService
-> limit ( $limit )
-> get ([ 'id' , 'name' , 'slug' , 'usage_count' ])
-> map ( fn ( $t ) => [
'id' => $t -> id ,
'name' => $t -> name ,
'slug' => $t -> slug ,
'id' => $t -> id ,
'name' => $t -> name ,
'slug' => $t -> slug ,
'count' => ( int ) $t -> usage_count ,
])
-> values ()
@@ -724,18 +734,18 @@ final class HomepageService
return $rows -> map ( function ( $u ) use ( $thumbsByUser ) {
$artworkForBg = $thumbsByUser -> get ( $u -> id );
$bgThumb = $artworkForBg ? $artworkForBg -> thumbUrl ( 'md' ) : null ;
$bgThumb = $artworkForBg ? $artworkForBg -> thumbUrl ( 'md' ) : null ;
return [
'id' => $u -> id ,
'name' => $u -> name ,
'uploads' => ( int ) $u -> upload_count ,
'id' => $u -> id ,
'name' => $u -> name ,
'uploads' => ( int ) $u -> upload_count ,
'weekly_uploads' => ( int ) $u -> weekly_uploads ,
'views' => ( int ) $u -> total_views ,
'awards' => ( int ) $u -> total_awards ,
'url' => $u -> username ? '/@' . $u -> username : '/profile/' . $u -> id ,
'avatar' => AvatarUrl :: forUser (( int ) $u -> id , $u -> avatar_hash ? : null , 128 ),
'bg_thumb' => $bgThumb ,
'views' => ( int ) $u -> total_views ,
'awards' => ( int ) $u -> total_awards ,
'url' => $u -> username ? '/@' . $u -> username : '/profile/' . $u -> id ,
'avatar' => AvatarUrl :: forUser (( int ) $u -> id , $u -> avatar_hash ? : null , 128 ),
'bg_thumb' => $bgThumb ,
];
}) -> values () -> all ();
} catch ( QueryException $e ) {
@@ -778,7 +788,7 @@ final class HomepageService
-> select ( 't.id' , 't.title' , 't.created_at' , 't.slug as thread_slug' )
-> where ( function ( $q ) {
$q -> where ( 't.category_id' , 2876 )
-> orWhereIn ( 'c.slug' , [ 'news' , 'forum-news' ]);
-> orWhereIn ( 'c.slug' , [ 'news' , 'forum-news' ]);
})
-> whereNull ( 't.deleted_at' )
-> orderByDesc ( 't.created_at' )
@@ -789,7 +799,7 @@ final class HomepageService
'id' => $row -> id ,
'title' => $row -> title ,
'date' => $row -> created_at ,
'url' => '/forum/thread/' . $row -> id . '-' . ( $row -> thread_slug ? ? 'post' ),
'url' => '/forum/thread/' . $row -> id . '-' . ( $row -> thread_slug ? ? 'post' ),
]) -> values () -> all ();
} catch ( QueryException $e ) {
Log :: warning ( 'HomepageService::getNews DB error' , [
@@ -809,7 +819,7 @@ final class HomepageService
* Welcome - row counts : unread messages , unread notifications , new followers .
* Returns quickly from DB using simple COUNTs ; never throws .
*/
public function getUserData ( \App\Models\ User $user ) : array
public function getUserData ( User $user ) : array
{
try {
$unreadMessages = DB :: table ( 'conversations as c' )
@@ -834,13 +844,13 @@ final class HomepageService
}
return [
'id' => $user -> id ,
'name' => $user -> name ,
'username' => $user -> username ,
'avatar' => AvatarUrl :: forUser (( int ) $user -> id , $user -> profile ? -> avatar_hash ? ? null , 64 ),
'messages_unread' => ( int ) $unreadMessages ,
'id' => $user -> id ,
'name' => $user -> name ,
'username' => $user -> username ,
'avatar' => AvatarUrl :: forUser (( int ) $user -> id , $user -> profile ? -> avatar_hash ? ? null , 64 ),
'messages_unread' => ( int ) $unreadMessages ,
'notifications_unread' => ( int ) $unreadNotifications ,
'followers_count' => ( int ) ( $user -> statistics ? -> followers_count ? ? 0 ),
'followers_count' => ( int ) ( $user -> statistics ? -> followers_count ? ? 0 ),
];
}
@@ -848,7 +858,7 @@ final class HomepageService
* Suggested creators : active public uploaders NOT already followed by the user ,
* ranked by follower count . Optionally filtered to the user ' s top categories .
*/
public function getSuggestedCreators ( \App\Models\ User $user , array $prefs , int $limit = 8 ) : array
public function getSuggestedCreators ( User $user , array $prefs , int $limit = 8 ) : array
{
return Cache :: remember (
" homepage.suggested. { $user -> id } " ,
@@ -878,16 +888,17 @@ final class HomepageService
$rows = $query -> get ();
return $rows -> map ( fn ( $u ) => [
'id' => $u -> id ,
'name' => $u -> name ,
'username' => $u -> username ,
'url' => $u -> username ? '/@' . $u -> username : '/profile/' . $u -> id ,
'avatar' => AvatarUrl :: forUser (( int ) $u -> id , $u -> avatar_hash ? : null , 64 ),
'id' => $u -> id ,
'name' => $u -> name ,
'username' => $u -> username ,
'url' => $u -> username ? '/@' . $u -> username : '/profile/' . $u -> id ,
'avatar' => AvatarUrl :: forUser (( int ) $u -> id , $u -> avatar_hash ? : null , 64 ),
'followers_count' => ( int ) $u -> followers_count ,
'artworks_count' => ( int ) $u -> artworks_count ,
'artworks_count' => ( int ) $u -> artworks_count ,
]) -> values () -> all ();
} catch ( \Throwable $e ) {
Log :: warning ( 'HomepageService::getSuggestedCreators failed' , [ 'error' => $e -> getMessage ()]);
return [];
}
}
@@ -897,7 +908,7 @@ final class HomepageService
/**
* Latest artworks from creators the user follows ( max 12 ) .
*/
public function getFollowingFeed ( \App\Models\ User $user , array $prefs ) : array
public function getFollowingFeed ( User $user , array $prefs ) : array
{
$followingIds = $prefs [ 'followed_creators' ] ? ? [];
@@ -950,6 +961,7 @@ final class HomepageService
-> all ();
} catch ( \Throwable $e ) {
Log :: warning ( 'HomepageService::getByTags failed' , [ 'error' => $e -> getMessage ()]);
return [];
}
}
@@ -980,6 +992,7 @@ final class HomepageService
-> all ();
} catch ( \Throwable $e ) {
Log :: warning ( 'HomepageService::getByCategories failed' , [ 'error' => $e -> getMessage ()]);
return [];
}
}
@@ -1010,7 +1023,7 @@ final class HomepageService
/**
* Ensure serialized artwork payloads do not trigger lazy - loading per item .
*
* @ param Collection < int , Artwork > $artworks
* @ param Collection < int , Artwork > $artworks
* @ return Collection < int , Artwork >
*/
private function prepareArtworksForSerialization ( Collection $artworks ) : Collection
@@ -1029,7 +1042,7 @@ final class HomepageService
/**
* Backfill sparse homepage rails with recent archive artworks while preserving lead ordering .
*
* @ param Collection < int , Artwork > $artworks
* @ param Collection < int , Artwork > $artworks
* @ return Collection < int , Artwork >
*/
private function fillArtworkRailFromArchive ( Collection $artworks , int $limit , ? callable $fallbackConstraint = null ) : Collection
@@ -1069,7 +1082,7 @@ final class HomepageService
}
/**
* @ param Collection < int , array < string , mixed >> $items
* @ param Collection < int , array < string , mixed >> $items
* @ return Collection < int , array < string , mixed >>
*/
private function filterMissingThumbnailPayloadItems ( Collection $items ) : Collection
@@ -1142,50 +1155,50 @@ final class HomepageService
$thumbMd = $artwork -> thumbUrl ( 'md' );
$thumbLg = $artwork -> thumbUrl ( 'lg' );
$thumbXl = $artwork -> thumbUrl ( 'xl' );
$thumb = $preferSize === 'lg' ? ( $thumbLg ? ? $thumbMd ) : ( $thumbMd ? ? $thumbLg );
$thumb = $preferSize === 'lg' ? ( $thumbLg ? ? $thumbMd ) : ( $thumbMd ? ? $thumbLg );
$primaryCategory = $artwork -> categories -> sortBy ( 'sort_order' ) -> first ();
$thumbSrcset = collect ([
$thumbSm ? $thumbSm . ' 320w' : null ,
$thumbMd ? $thumbMd . ' 640w' : null ,
$thumbLg ? $thumbLg . ' 1280w' : null ,
$thumbXl ? $thumbXl . ' 1920w' : null ,
$thumbSm ? $thumbSm . ' 320w' : null ,
$thumbMd ? $thumbMd . ' 640w' : null ,
$thumbLg ? $thumbLg . ' 1280w' : null ,
$thumbXl ? $thumbXl . ' 1920w' : null ,
]) -> filter () -> implode ( ', ' );
$publisher = $this -> mapArtworkPublisherPayload ( $artwork );
$isGroupPublisher = ( $publisher [ 'type' ] ? ? null ) === 'group' ;
$authorId = $artwork -> user_id ;
$authorName = $isGroupPublisher ? (( string ) ( $publisher [ 'name' ] ? ? 'Skinbase Group' )) : ( $artwork -> user ? -> name ? ? 'Artist' );
$authorId = $artwork -> user_id ;
$authorName = $isGroupPublisher ? (( string ) ( $publisher [ 'name' ] ? ? 'Skinbase Group' )) : ( $artwork -> user ? -> name ? ? 'Artist' );
$authorUsername = $isGroupPublisher ? '' : ( $artwork -> user ? -> username ? ? '' );
$avatarHash = $artwork -> user ? -> profile ? -> avatar_hash ? ? null ;
$authorAvatar = $isGroupPublisher
$avatarHash = $artwork -> user ? -> profile ? -> avatar_hash ? ? null ;
$authorAvatar = $isGroupPublisher
? ( $publisher [ 'avatar_url' ] ? ? null )
: AvatarUrl :: forUser (( int ) $authorId , $avatarHash , 64 );
return $this -> maturity -> decoratePayload ([
'id' => $artwork -> id ,
'title' => $artwork -> title ? ? 'Untitled' ,
'slug' => $artwork -> slug ,
'author' => $authorName ,
'author_id' => $authorId ,
'id' => $artwork -> id ,
'title' => $artwork -> title ? ? 'Untitled' ,
'slug' => $artwork -> slug ,
'author' => $authorName ,
'author_id' => $authorId ,
'author_username' => $authorUsername ,
'author_avatar' => $authorAvatar ,
'author_avatar' => $authorAvatar ,
'published_as_type' => $artwork -> publishedAsType (),
'publisher' => $publisher ,
'thumb' => $thumb ,
'thumb_sm' => $thumbSm ,
'thumb_md' => $thumbMd ,
'thumb_lg' => $thumbLg ,
'thumb_xl' => $thumbXl ,
'thumb_srcset' => $thumbSrcset !== '' ? $thumbSrcset : null ,
'category_name' => $primaryCategory -> name ? ? '' ,
'category_slug' => $primaryCategory -> slug ? ? '' ,
'publisher' => $publisher ,
'thumb' => $thumb ,
'thumb_sm' => $thumbSm ,
'thumb_md' => $thumbMd ,
'thumb_lg' => $thumbLg ,
'thumb_xl' => $thumbXl ,
'thumb_srcset' => $thumbSrcset !== '' ? $thumbSrcset : null ,
'category_name' => $primaryCategory -> name ? ? '' ,
'category_slug' => $primaryCategory -> slug ? ? '' ,
'content_type_name' => $primaryCategory ? -> contentType ? -> name ? ? '' ,
'content_type_slug' => $primaryCategory ? -> contentType ? -> slug ? ? '' ,
'url' => '/art/' . $artwork -> id . '/' . ( $artwork -> slug ? ? '' ),
'width' => $artwork -> width ,
'height' => $artwork -> height ,
'published_at' => $artwork -> published_at ? -> toIso8601String (),
'url' => '/art/' . $artwork -> id . '/' . ( $artwork -> slug ? ? '' ),
'width' => $artwork -> width ,
'height' => $artwork -> height ,
'published_at' => $artwork -> published_at ? -> toIso8601String (),
'medals' => [
'gold' => ( int ) ( $awardStat ? -> gold_count ? ? 0 ),
'silver' => ( int ) ( $awardStat ? -> silver_count ? ? 0 ),
@@ -1197,6 +1210,80 @@ final class HomepageService
], $artwork , request () -> user ());
}
/**
* @ return array < string , mixed >
*/
private function serializeFeaturedHeroImage ( Artwork $artwork ) : array
{
$variants = $this -> featuredImages -> variants ();
$variantUrls = [];
foreach ( array_keys ( $variants ) as $variant ) {
$variantUrls [ $variant ] = $artwork -> hasFeaturedThumbnail ( $variant )
? $this -> featuredImages -> url ( $artwork , $variant )
: null ;
}
$preloadSrcset = collect ( $variants )
-> map ( function ( array $config , string $variant ) use ( $variantUrls ) : ? string {
$url = $variantUrls [ $variant ] ? ? null ;
return $url ? $url . ' ' . ( int ) $config [ 'width' ] . 'w' : null ;
})
-> filter ()
-> implode ( ', ' );
$xsSources = collect ([ 'xs' , 'mobile_sm' ])
-> map ( function ( string $variant ) use ( $variantUrls , $variants ) : ? string {
$url = $variantUrls [ $variant ] ? ? null ;
return $url ? $url . ' ' . ( int ) ( $variants [ $variant ][ 'width' ] ? ? 0 ) . 'w' : null ;
})
-> filter ()
-> implode ( ', ' );
$mobileSources = collect ([ 'mobile_sm' , 'mobile' ])
-> map ( function ( string $variant ) use ( $variantUrls , $variants ) : ? string {
$url = $variantUrls [ $variant ] ? ? null ;
return $url ? $url . ' ' . ( int ) ( $variants [ $variant ][ 'width' ] ? ? 0 ) . 'w' : null ;
})
-> filter ()
-> implode ( ', ' );
$desktopSources = collect ([ 'desktop' , 'desktop_xl' ])
-> map ( function ( string $variant ) use ( $variantUrls , $variants ) : ? string {
$url = $variantUrls [ $variant ] ? ? null ;
return $url ? $url . ' ' . ( int ) ( $variants [ $variant ][ 'width' ] ? ? 0 ) . 'w' : null ;
})
-> filter ()
-> implode ( ', ' );
$pictureSources = array_values ( array_filter ([
$xsSources !== '' ? [ 'media' => '(max-width: 479px)' , 'srcset' => $xsSources , 'sizes' => '100vw' ] : null ,
$mobileSources !== '' ? [ 'media' => '(max-width: 767px)' , 'srcset' => $mobileSources , 'sizes' => '100vw' ] : null ,
! empty ( $variantUrls [ 'tablet' ]) ? [ 'media' => '(max-width: 1279px)' , 'srcset' => $variantUrls [ 'tablet' ] . ' ' . ( int ) ( $variants [ 'tablet' ][ 'width' ] ? ? 0 ) . 'w' , 'sizes' => '100vw' ] : null ,
$desktopSources !== '' ? [ 'media' => '(min-width: 1280px)' , 'srcset' => $desktopSources , 'sizes' => '100vw' ] : null ,
]));
return [
'alt' => $artwork -> featuredImageAltText (),
'variants' => $variantUrls ,
'sources' => $pictureSources ,
'img_src' => $artwork -> featuredThumbnailUrl ( 'desktop' ),
'img_srcset' => $preloadSrcset !== '' ? $preloadSrcset : ( $artwork -> thumb_srcset ? ? null ),
'img_sizes' => '100vw' ,
'preload_url' => $variantUrls [ 'desktop_xl' ]
? ? $variantUrls [ 'desktop' ]
? ? $artwork -> thumbUrl ( 'xl' )
? ? $artwork -> thumbUrl ( 'lg' )
? ? 'https://files.skinbase.org/default/missing_xl.webp' ,
'preload_srcset' => $preloadSrcset !== '' ? $preloadSrcset : ( $artwork -> thumb_srcset ? ? null ),
'preload_sizes' => '100vw' ,
];
}
/**
* @ return array < string , mixed >| null
*/
@@ -1233,8 +1320,8 @@ final class HomepageService
$payload [ 'metric_badge' ] = [
'label' => $surface === 'community_favorites'
? '30d medals: ' . $score
: 'All-time medals: ' . $score ,
? '30d medals: ' . $score
: 'All-time medals: ' . $score ,
'className' => $surface === 'community_favorites'
? 'bg-amber-500/14 text-amber-100 ring-amber-300/30'
: 'bg-cyan-500/14 text-cyan-100 ring-cyan-300/30' ,
@@ -1245,6 +1332,6 @@ final class HomepageService
private function viewerCacheSegment () : string
{
return 'visibility-' . $this -> maturity -> viewerPreferences ( request () -> user ())[ 'visibility' ];
return 'visibility-' . $this -> maturity -> viewerPreferences ( request () -> user ())[ 'visibility' ];
}
}