Passed
Push — dependabot/npm_and_yarn/@vue/t... ( 1a0b23...9776fc )
by
unknown
84:45 queued 64:29
created

SaveUrlSlugs   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 165
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 21
eloc 56
c 0
b 0
f 0
dl 0
loc 165
rs 10

10 Methods

Rating   Name   Duplication   Size   Complexity  
A saveRecord() 0 31 5
A prependBaseUrlSegment() 0 8 2
A handle() 0 11 3
A deleteIdenticalRedirects() 0 10 3
A createRecord() 0 7 1
A strict() 0 5 1
A cleanupExistingRecords() 0 13 1
A deleteRecord() 0 3 1
A __construct() 0 3 1
A deleteIdenticalRecords() 0 16 3
1
<?php
2
3
namespace Thinktomorrow\Chief\Urls\Application;
4
5
use Thinktomorrow\Chief\Urls\ProvidesUrl\ProvidesUrl;
6
use Thinktomorrow\Chief\Urls\UrlRecord;
7
8
class SaveUrlSlugs
9
{
10
    /** @var bool */
11
    private $strict = true;
12
13
    /** @var ProvidesUrl */
14
    private $model;
15
16
    private $existingRecords;
17
18
    public function __construct(ProvidesUrl $model)
19
    {
20
        $this->model = $model;
21
    }
22
23
    /**
24
     * Saving urls slugs in strict mode prevents identical urls to be automatically removed.
25
     * When set to false, this would remove the identical url records.
26
     *
27
     * @param bool $strict
28
     * @return $this
29
     */
30
    public function strict(bool $strict = true)
31
    {
32
        $this->strict = $strict;
33
34
        return $this;
35
    }
36
37
    public function handle(array $slugs): void
38
    {
39
        $this->existingRecords = UrlRecord::getByModel($this->model);
40
41
        foreach ($slugs as $locale => $slug) {
42
            if (!$slug) {
43
                $this->deleteRecord($locale);
44
                continue;
45
            }
46
47
            $this->saveRecord($locale, $this->prependBaseUrlSegment($slug, $locale));
48
        }
49
    }
50
51
    private function deleteRecord(string $locale)
52
    {
53
        return $this->saveRecord($locale, null);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->saveRecord($locale, null) targeting Thinktomorrow\Chief\Urls...eUrlSlugs::saveRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
54
    }
55
56
    private function saveRecord(string $locale, ?string $slug)
57
    {
58
        // Existing ones for this locale?
59
        $nonRedirectsWithSameLocale = $this->existingRecords->filter(function ($record) use ($locale) {
60
            return (
61
                $record->locale == $locale &&
62
                !$record->isRedirect()
63
            );
64
        });
65
66
        // If slug entry is left empty, all existing records will be deleted
67
        if (!$slug) {
68
            $nonRedirectsWithSameLocale->each(function ($existingRecord) {
69
                $existingRecord->delete();
70
            });
71
72
            return;
73
        }
74
75
        $this->cleanupExistingRecords($locale, $slug);
76
77
        // If slug entry is left empty, all existing records will be deleted
78
        if ($nonRedirectsWithSameLocale->isEmpty()) {
79
            $this->createRecord($locale, $slug);
80
            return;
81
        }
82
83
        // Only replace the existing records that differ from the current passed slugs
84
        $nonRedirectsWithSameLocale->each(function ($existingRecord) use ($slug) {
85
            if ($existingRecord->slug != $slug) {
86
                $existingRecord->replaceAndRedirect(['slug' => $slug]);
87
            }
88
        });
89
    }
90
91
    private function createRecord($locale, $slug)
92
    {
93
        UrlRecord::create([
94
            'locale'              => $locale,
95
            'slug'                => $slug,
96
            'model_type'          => $this->model->getMorphClass(),
0 ignored issues
show
Bug introduced by
The method getMorphClass() does not exist on Thinktomorrow\Chief\Urls\ProvidesUrl\ProvidesUrl. Since it exists in all sub-types, consider adding an abstract or default implementation to Thinktomorrow\Chief\Urls\ProvidesUrl\ProvidesUrl. ( Ignorable by Annotation )

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

96
            'model_type'          => $this->model->/** @scrutinizer ignore-call */ getMorphClass(),
Loading history...
97
            'model_id'            => $this->model->id,
0 ignored issues
show
Bug introduced by
Accessing id on the interface Thinktomorrow\Chief\Urls\ProvidesUrl\ProvidesUrl suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
98
        ]);
99
    }
100
101
    /**
102
     * @param string $locale
103
     * @param string|null $slug
104
     */
105
    private function cleanupExistingRecords(string $locale, string $slug): void
106
    {
107
        // In the case where we have any redirects that match the given slug, we need to
108
        // remove the redirect record in favour of the newly added one.
109
        $this->deleteIdenticalRedirects($this->existingRecords, $locale, $slug);
110
111
        $sameExistingRecords = UrlRecord::where('slug', $slug)->where('locale', $locale)->get();
112
113
        // Also delete any redirects that match this locale and slug but are related to another model
114
        $this->deleteIdenticalRedirects($sameExistingRecords, $locale, $slug);
115
116
        // Also delete any urls that match this locale and slug but are related to another model
117
        $this->deleteIdenticalRecords($sameExistingRecords);
118
    }
119
120
    /**
121
     * Remove any redirects owned by this model that equal the new slug.
122
     *
123
     * @param $existingRecords
124
     * @param $locale
125
     * @param $slug
126
     */
127
    private function deleteIdenticalRedirects($existingRecords, $locale, $slug): void
128
    {
129
        $existingRecords->filter(function ($record) use ($locale) {
130
            return (
131
                $record->locale == $locale &&
132
                $record->isRedirect()
133
            );
134
        })->each(function ($existingRecord) use ($slug) {
135
            if ($existingRecord->slug == $slug) {
136
                $existingRecord->delete();
137
            }
138
        });
139
    }
140
141
    private function deleteIdenticalRecords($existingRecords): void
142
    {
143
        if ($this->strict) {
144
            return;
145
        }
146
147
        // The old homepage url should be removed since this is no longer in effect.
148
        // In case of any redirect to this old homepage, the last used redirect is now back in effect.
149
        $existingRecords->reject(function ($existingRecord) {
150
            return (
151
                $existingRecord->model_type == $this->model->getMorphClass() &&
152
                $existingRecord->model_id == $this->model->id);
0 ignored issues
show
Bug introduced by
Accessing id on the interface Thinktomorrow\Chief\Urls\ProvidesUrl\ProvidesUrl suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
153
        })->each(function ($existingRecord) {
154
155
            // TODO: if there is a redirect to this page, we'll take this one as the new url
156
            $existingRecord->delete();
157
        });
158
    }
159
160
    /**
161
     * @param string $slug
162
     * @param $locale
163
     * @return string
164
     */
165
    private function prependBaseUrlSegment(string $slug, $locale): string
166
    {
167
        $slugWithBaseSegment = $this->model->baseUrlSegment($locale) . '/' . $slug;
168
        $slugWithBaseSegment = trim($slugWithBaseSegment, '/');
169
170
        // If slug with base segment is empty string, it means that the passed slug was probably a "/" character.
171
        // so we'll want to return it in case the base segment is not added.
172
        return $slugWithBaseSegment ?: '/';
173
    }
174
}
175