Passed
Push — master ( 2a3d19...5e9e4e )
by Caen
05:36 queued 13s
created

makeRelatedPublications()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 43
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 19
nc 6
nop 1
dl 0
loc 43
rs 9.0111
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Hyde\Publications\Views\Components;
6
7
use Hyde\Hyde;
8
use Hyde\Publications\Concerns\PublicationFieldTypes;
9
use Hyde\Publications\Models\PublicationFieldDefinition;
10
use Hyde\Publications\Pages\PublicationPage;
11
use Hyde\Publications\Publications;
12
use Illuminate\Contracts\View\View;
13
use Illuminate\Support\Collection;
14
use Illuminate\View\Component;
15
16
use function collect;
17
use function count;
18
use function view;
19
20
class RelatedPublicationsComponent extends Component
21
{
22
    /** @var Collection<string, PublicationPage> */
23
    public Collection $relatedPublications;
24
25
    public string $title = 'Related Publications';
26
27
    public function __construct(string $title = 'Related Publications', int $limit = 5)
28
    {
29
        $this->relatedPublications = collect($this->makeRelatedPublications($limit));
0 ignored issues
show
Bug introduced by
$this->makeRelatedPublications($limit) of type array is incompatible with the type Illuminate\Contracts\Support\Arrayable expected by parameter $value of collect(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

29
        $this->relatedPublications = collect(/** @scrutinizer ignore-type */ $this->makeRelatedPublications($limit));
Loading history...
30
        $this->title = $title;
31
    }
32
33
    /** @interitDoc */
34
    public function render(): View
35
    {
36
        return view('hyde-publications::components.related-publications');
37
    }
38
39
    protected function makeRelatedPublications(int $limit = 5): array
40
    {
41
        // Get current publicationType from the current page
42
        $currentHydePage = Hyde::currentRoute()->getPage();
43
44
        // If not a publication page, exit early
45
        if (! $currentHydePage instanceof PublicationPage) {
46
            return [];
47
        }
48
49
        $publicationType = $currentHydePage->getType();
50
51
        // Get the tag fields for the current publicationType or exit early if there aren't any
52
        $publicationTypeTagFields = $publicationType->getFields()->filter(function (PublicationFieldDefinition $field): bool {
53
            return $field->type === PublicationFieldTypes::Tag;
54
        });
55
        if ($publicationTypeTagFields->isEmpty()) {
56
            return [];
57
        }
58
59
        // Get a list of all pages for this page's publicationType: 1 means we only have current page & no related pages exist
60
        $publicationPages = Publications::getPublicationsForType($publicationType)->keyBy('identifier');
61
        if ($publicationPages->count() <= 1) {
62
            return [];
63
        }
64
65
        // Get all tags for the current page
66
        $currentPageTags = $this->getTagsForPage($publicationPages->get($currentHydePage->getIdentifier()), $publicationTypeTagFields);
67
        if ($currentPageTags->isEmpty()) {
68
            return [];
69
        }
70
71
        // Forget the current page pages since we don't want to show it as a related page against itself
72
        $publicationPages->forget($currentHydePage->getIdentifier());
73
74
        // Get all related pages
75
        $allRelatedPages = $this->getAllRelatedPages($publicationPages, $publicationTypeTagFields, $currentPageTags);
76
        if ($allRelatedPages->isEmpty()) {
77
            return [];
78
        }
79
80
        // Sort them by relevance (count of shared tags & newest dates)
81
        return $this->sortRelatedPagesByRelevance($allRelatedPages, $limit)->all();
82
    }
83
84
    protected function getTagsForPage(PublicationPage $publicationPage, Collection $tagFields): Collection
85
    {
86
        $thisPageTags = collect();
87
88
        // There could be multiple tag fields, but most publication types will only have one
89
        foreach ($tagFields as $tagField) {
90
            $thisPageTags = $thisPageTags->merge($publicationPage->matter->get($tagField->name, []));
91
        }
92
93
        return $thisPageTags;
94
    }
95
96
    protected function getAllRelatedPages(Collection $publicationPages, Collection $tagFields, Collection $currPageTags): Collection
97
    {
98
        $allRelatedPages = collect();
99
100
        foreach ($publicationPages as $publicationPage) {
101
            $publicationPageTags = $this->getTagsForPage($publicationPage, $tagFields);
102
            $matchedTagCount = $publicationPageTags->intersect($currPageTags)->count();
103
104
            // We have shared/matching tags, add this page info to $allRelatedPages
105
            if ($matchedTagCount) {
106
                $allRelatedPages->add(
107
                    collect([
0 ignored issues
show
Bug introduced by
array('count' => $matche...e' => $publicationPage) of type array<string,integer|mixed> is incompatible with the type Illuminate\Contracts\Support\Arrayable expected by parameter $value of collect(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

107
                    collect(/** @scrutinizer ignore-type */ [
Loading history...
108
                        'count'      => $matchedTagCount,
109
                        'identifier' => $publicationPage->identifier,
110
                        'page'       => $publicationPage,
111
                    ])
112
                );
113
            }
114
        }
115
116
        return $allRelatedPages;
117
    }
118
119
    protected function sortRelatedPagesByRelevance(Collection $allRelatedPages, int $max): Collection
120
    {
121
        $relatedPages = collect();
122
123
        // Group related pages by the number of shared tags and then sort by keys (number of shared tags) descending
124
        $allRelatedPagesGrouped = $allRelatedPages->groupBy('count')->sortKeysDesc(SORT_NUMERIC);
125
126
        // Iterate over groups
127
        foreach ($allRelatedPagesGrouped as $relatedPagesGroup) {
128
            // Sort group by recency, with the latest pages first
129
            $sortedPageGroup = $relatedPagesGroup->sortByDesc('page.matter.__createdAt');
130
131
            // Now add to $relatedPages, and stop when hitting $max
132
            foreach ($sortedPageGroup as $page) {
133
                $relatedPages->put($page['identifier'], $page['page']);
134
                if (count($relatedPages) >= $max) {
135
                    break 2;
136
                }
137
            }
138
        }
139
140
        return $relatedPages;
141
    }
142
}
143