Skip to content

Commit fb7c15a

Browse files
authored
Add bread-crubs navigation on albums (#4478)
1 parent 155bc3b commit fb7c15a

35 files changed

Lines changed: 415 additions & 8 deletions

app/Actions/Album/Breadcrumb.php

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
3+
/**
4+
* SPDX-License-Identifier: MIT
5+
* Copyright (c) 2017-2018 Tobias Reich
6+
* Copyright (c) 2018-2026 LycheeOrg.
7+
*/
8+
9+
namespace App\Actions\Album;
10+
11+
use App\Http\Resources\Models\BreadcrumbItemResource;
12+
use App\Models\Album;
13+
use App\Policies\AlbumPolicy;
14+
use App\Policies\AlbumQueryPolicy;
15+
use Illuminate\Database\Query\Builder as BaseBuilder;
16+
use Illuminate\Support\Facades\Auth;
17+
use Illuminate\Support\Facades\DB;
18+
19+
class Breadcrumb
20+
{
21+
public function __construct(
22+
protected readonly AlbumQueryPolicy $album_query_policy,
23+
) {
24+
}
25+
26+
/**
27+
* @return BreadcrumbItemResource[]
28+
*/
29+
public function do(Album $album): array
30+
{
31+
// Disable if v8 is disabled, as the breadcrumb feature is only available in v8.
32+
if (config('features.v8') === false) {
33+
return [];
34+
}
35+
36+
$ancestors = DB::table('albums')
37+
->join('base_albums', 'base_albums.id', '=', 'albums.id')
38+
->where('albums._lft', '<', $album->_lft)
39+
->where('albums._rgt', '>', $album->_rgt)
40+
->select(['albums.id', 'base_albums.title', 'base_albums.slug'])
41+
->orderByDesc('albums._lft')
42+
->get();
43+
44+
if ($ancestors->isEmpty()) {
45+
return [];
46+
}
47+
48+
$accessible_ids = $this->getAccessibleAncestorIds($ancestors->pluck('id')->all());
49+
50+
$result = [];
51+
foreach ($ancestors as $ancestor) {
52+
if (!in_array($ancestor->id, $accessible_ids, true)) {
53+
$result[] = new BreadcrumbItemResource(null, '...', null);
54+
break;
55+
}
56+
$result[] = new BreadcrumbItemResource($ancestor->id, $ancestor->title, $ancestor->slug);
57+
}
58+
59+
return array_reverse($result);
60+
}
61+
62+
/**
63+
* @param string[] $ancestor_ids
64+
*
65+
* @return string[]
66+
*/
67+
private function getAccessibleAncestorIds(array $ancestor_ids): array
68+
{
69+
$user = Auth::user();
70+
71+
if ($user?->may_administrate === true) {
72+
return $ancestor_ids;
73+
}
74+
75+
$unlocked_album_ids = AlbumPolicy::getUnlockedAlbumIDs();
76+
77+
$query = DB::table('base_albums')
78+
->whereIn('base_albums.id', $ancestor_ids)
79+
->select('base_albums.id');
80+
81+
$this->album_query_policy->joinSubComputedAccessPermissions(
82+
$query, 'base_albums.id', 'left', '', false, $user
83+
);
84+
85+
$query->where(fn (BaseBuilder $q) => $this->album_query_policy->appendAccessibilityConditions($q, $user, $unlocked_album_ids));
86+
87+
return $query->pluck('id')->all();
88+
}
89+
}

app/Http/Controllers/Admin/SettingsController.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class SettingsController extends Controller
3434
'site_logo',
3535
'landing_logo',
3636
'landing_header_logo',
37+
'breadcrumb_enabled',
3738
];
3839

3940
/**

app/Http/Controllers/Gallery/AlbumHeadController.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
namespace App\Http\Controllers\Gallery;
1010

11+
use App\Actions\Album\Breadcrumb;
1112
use App\Events\Metrics\AlbumVisit;
1213
use App\Exceptions\Internal\LycheeLogicException;
1314
use App\Http\Controllers\MetricsController;
@@ -33,6 +34,11 @@ class AlbumHeadController extends Controller
3334
{
3435
use HasVisitorIdTrait;
3536

37+
public function __construct(
38+
private readonly Breadcrumb $breadcrumb,
39+
) {
40+
}
41+
3642
/**
3743
* Provided an albumID, returns the album metadata without children/photos collections.
3844
*
@@ -48,7 +54,7 @@ public function get(GetAlbumHeadRequest $request): HeadAbstractAlbumResource
4854
$album_resource = match (true) {
4955
$request->album() instanceof BaseSmartAlbum => new HeadSmartAlbumResource($request->album()),
5056
$request->album() instanceof TagAlbum => new HeadTagAlbumResource($request->album()),
51-
$request->album() instanceof Album => new HeadAlbumResource($request->album()),
57+
$request->album() instanceof Album => new HeadAlbumResource($request->album(), $config->is_breadcrumb_enabled ? $this->breadcrumb->do($request->album()) : []),
5258
// @codeCoverageIgnoreStart
5359
default => throw new LycheeLogicException('This should not happen'),
5460
// @codeCoverageIgnoreEnd

app/Http/Resources/GalleryConfigs/AlbumConfig.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class AlbumConfig extends Data
3636
public bool $is_mod_frame_enabled;
3737
public bool $is_search_accessible;
3838
public bool $is_nsfw_warning_visible;
39+
public bool $is_breadcrumb_enabled;
3940
public AspectRatioCSSType $album_thumb_css_aspect_ratio;
4041
public PhotoLayoutType $photo_layout;
4142
public bool $is_album_timeline_enabled = false;
@@ -49,6 +50,7 @@ public function __construct(AbstractAlbum $album)
4950

5051
$this->is_base_album = $album instanceof BaseAlbum;
5152
$this->is_model_album = $album instanceof Album;
53+
$this->is_breadcrumb_enabled = $this->is_model_album && $config_manager->getValueAsBool('breadcrumb_enabled');
5254
$this->is_password_protected = !$is_accessible && $public_perm?->password !== null;
5355
$this->is_nsfw_warning_visible =
5456
$album instanceof BaseAlbum &&
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
/**
4+
* SPDX-License-Identifier: MIT
5+
* Copyright (c) 2017-2018 Tobias Reich
6+
* Copyright (c) 2018-2026 LycheeOrg.
7+
*/
8+
9+
namespace App\Http\Resources\Models;
10+
11+
use Spatie\LaravelData\Data;
12+
use Spatie\TypeScriptTransformer\Attributes\TypeScript;
13+
14+
#[TypeScript()]
15+
class BreadcrumbItemResource extends Data
16+
{
17+
public function __construct(
18+
public ?string $id,
19+
public string $title,
20+
public ?string $slug,
21+
) {
22+
}
23+
}

app/Http/Resources/Models/HeadAlbumResource.php

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Illuminate\Support\Facades\Auth;
1919
use Illuminate\Support\Facades\Gate;
2020
use Spatie\LaravelData\Data;
21+
use Spatie\TypeScriptTransformer\Attributes\LiteralTypeScriptType;
2122
use Spatie\TypeScriptTransformer\Attributes\TypeScript;
2223

2324
#[TypeScript()]
@@ -55,7 +56,14 @@ class HeadAlbumResource extends Data
5556

5657
public ?AlbumStatisticsResource $statistics = null;
5758

58-
public function __construct(Album $album)
59+
/** @var BreadcrumbItemResource[] */
60+
#[LiteralTypeScriptType('App.Http.Resources.Models.BreadcrumbItemResource[]')]
61+
public array $breadcrumb;
62+
63+
/**
64+
* @param BreadcrumbItemResource[] $breadcrumb
65+
*/
66+
public function __construct(Album $album, array $breadcrumb = [])
5967
{
6068
$this->id = $album->id;
6169
$this->title = $album->title;
@@ -85,6 +93,7 @@ public function __construct(Album $album)
8593
$url = $this->getHeaderUrl($album);
8694
$this->preFormattedData = new PreFormattedAlbumData($album, $url);
8795
$this->is_pinned = $album->is_pinned;
96+
$this->breadcrumb = $breadcrumb;
8897

8998
if ($this->rights->can_edit) {
9099
$this->editable = EditableBaseAlbumResource::fromModel($album);
@@ -95,8 +104,11 @@ public function __construct(Album $album)
95104
}
96105
}
97106

98-
public static function fromModel(Album $album): HeadAlbumResource
107+
/**
108+
* @param BreadcrumbItemResource[] $breadcrumb
109+
*/
110+
public static function fromModel(Album $album, array $breadcrumb = []): HeadAlbumResource
99111
{
100-
return new self($album);
112+
return new self($album, $breadcrumb);
101113
}
102114
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
/**
4+
* SPDX-License-Identifier: MIT
5+
* Copyright (c) 2017-2018 Tobias Reich
6+
* Copyright (c) 2018-2026 LycheeOrg.
7+
*/
8+
9+
use App\Models\Extensions\BaseConfigMigration;
10+
11+
return new class() extends BaseConfigMigration {
12+
public const CAT = 'Gallery';
13+
14+
public function getConfigs(): array
15+
{
16+
return [
17+
[
18+
'key' => 'breadcrumb_enabled',
19+
'value' => '1',
20+
'cat' => self::CAT,
21+
'type_range' => self::BOOL,
22+
'is_secret' => false,
23+
'description' => 'Enable breadcrumb navigation in the album header',
24+
'details' => 'Display the album ancestry as breadcrumbs in the header bar instead of the back button and title.',
25+
'level' => 0,
26+
'order' => 85,
27+
'is_expert' => false,
28+
],
29+
];
30+
}
31+
};

lang/ar/all_settings.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@
368368
'album_header_landing_title_enabled' => 'Display landing title on album header',
369369
'sm_card_album_source' => 'Album photo source for social media cards',
370370
'sm_card_image_url' => 'Fallback image URL or photo ID for social media cards',
371+
'breadcrumb_enabled' => 'Enable breadcrumb navigation in the album header',
371372
],
372373
'details' => [
373374
'version' => '',
@@ -730,6 +731,7 @@
730731
'album_header_landing_title_enabled' => 'Display the landing title at the bottom of the Album header. You can configure the landing title in the Landing page module.',
731732
'sm_card_album_source' => 'Select whether the header or cover photo of an album is used as the Open Graph image when sharing links on social media.',
732733
'sm_card_image_url' => 'URL or photo ID used as the Open Graph image when no album-specific image is available. If empty, the landing page background is used.',
734+
'breadcrumb_enabled' => 'Display the album ancestry as breadcrumbs in the header bar instead of the back button and title.',
733735
],
734736

735737
'category_name' => [

lang/bg/all_settings.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,7 @@
366366
'album_header_landing_title_enabled' => 'Display landing title on album header',
367367
'sm_card_album_source' => 'Album photo source for social media cards',
368368
'sm_card_image_url' => 'Fallback image URL or photo ID for social media cards',
369+
'breadcrumb_enabled' => 'Enable breadcrumb navigation in the album header',
369370
],
370371
'details' => [
371372
'version' => '',
@@ -728,6 +729,7 @@
728729
'album_header_landing_title_enabled' => 'Display the landing title at the bottom of the Album header. You can configure the landing title in the Landing page module.',
729730
'sm_card_album_source' => 'Select whether the header or cover photo of an album is used as the Open Graph image when sharing links on social media.',
730731
'sm_card_image_url' => 'URL or photo ID used as the Open Graph image when no album-specific image is available. If empty, the landing page background is used.',
732+
'breadcrumb_enabled' => 'Display the album ancestry as breadcrumbs in the header bar instead of the back button and title.',
731733
],
732734
'category_name' => [
733735
'config' => 'Основни',

lang/cz/all_settings.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@
368368
'album_header_landing_title_enabled' => 'Display landing title on album header',
369369
'sm_card_album_source' => 'Album photo source for social media cards',
370370
'sm_card_image_url' => 'Fallback image URL or photo ID for social media cards',
371+
'breadcrumb_enabled' => 'Enable breadcrumb navigation in the album header',
371372
],
372373
'details' => [
373374
'version' => '',
@@ -730,6 +731,7 @@
730731
'album_header_landing_title_enabled' => 'Display the landing title at the bottom of the Album header. You can configure the landing title in the Landing page module.',
731732
'sm_card_album_source' => 'Select whether the header or cover photo of an album is used as the Open Graph image when sharing links on social media.',
732733
'sm_card_image_url' => 'URL or photo ID used as the Open Graph image when no album-specific image is available. If empty, the landing page background is used.',
734+
'breadcrumb_enabled' => 'Display the album ancestry as breadcrumbs in the header bar instead of the back button and title.',
733735
],
734736

735737
'category_name' => [

0 commit comments

Comments
 (0)