Passed
Push — 0.5 ( 53ba8a...29d487 )
by Philippe
58:35 queued 55:54
created

Page   A

Complexity

Total Complexity 42

Size/Duplication

Total Lines 259
Duplicated Lines 0 %

Test Coverage

Coverage 75.53%

Importance

Changes 5
Bugs 2 Features 0
Metric Value
wmc 42
eloc 106
dl 0
loc 259
ccs 71
cts 94
cp 0.7553
rs 9.0399
c 5
b 2
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A mediaUrls() 0 9 2
A flatReference() 0 3 1
A findPublished() 0 3 1
A clearCachedUrls() 0 3 1
A managedModelKey() 0 7 2
A resolveUrl() 0 5 1
A __construct() 0 9 2
A mediaUrl() 0 3 1
A scopeSortedByCreated() 0 3 1
A url() 0 17 4
A getAttribute() 0 9 2
A flatReferenceGroup() 0 10 2
A modules() 0 3 1
A flatReferenceLabel() 0 9 4
A baseUrlSegment() 0 24 5
A changeStateOf() 0 10 2
A statusAsPlainLabel() 0 15 4
A statusAsLabel() 0 15 4
A menuLabel() 0 3 1
A stateOf() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Page often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Page, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Thinktomorrow\Chief\Pages;
6
7
use Illuminate\Support\Str;
8
use Illuminate\Support\Collection;
9
use Illuminate\Database\Eloquent\Model;
10
use Thinktomorrow\Chief\Fragments\HasFragments;
11
use Thinktomorrow\Chief\States\PageState;
12
use Thinktomorrow\Chief\Management\ManagedModel;
13
use Thinktomorrow\Chief\Urls\MemoizedUrlRecord;
14
use Thinktomorrow\Chief\Urls\ProvidesUrl\ProvidesUrl;
15
use Thinktomorrow\Chief\States\State\StatefulContract;
16
use Thinktomorrow\Chief\Urls\ProvidesUrl\ResolvingRoute;
17
use Thinktomorrow\Chief\Concerns\Viewable\Viewable;
18
use Thinktomorrow\Chief\Concerns\Viewable\ViewableContract;
19
use Thinktomorrow\Chief\Modules\Module;
20
use Thinktomorrow\Chief\Concerns\Featurable;
21
use Thinktomorrow\Chief\Menu\ActsAsMenuItem;
22
use Illuminate\Database\Eloquent\SoftDeletes;
23
use Thinktomorrow\Chief\Relations\ActsAsChild;
24
use Thinktomorrow\Chief\Snippets\WithSnippets;
25
use Thinktomorrow\Chief\Relations\ActsAsParent;
26
use Thinktomorrow\Chief\Relations\ActingAsChild;
27
use Thinktomorrow\AssetLibrary\AssetTrait;
28
use Thinktomorrow\Chief\Relations\ActingAsParent;
29
use Thinktomorrow\Chief\Concerns\Morphable\Morphable;
30
use Thinktomorrow\Chief\FlatReferences\FlatReference;
31
use Thinktomorrow\Chief\States\Archivable\Archivable;
32
use Astrotomic\Translatable\Translatable as BaseTranslatable;
33
use Thinktomorrow\Chief\States\Publishable\Publishable;
34
use Thinktomorrow\Chief\Concerns\Translatable\Translatable;
35
use Thinktomorrow\AssetLibrary\HasAsset;
36
use Thinktomorrow\Chief\Concerns\Morphable\MorphableContract;
37
use Thinktomorrow\Chief\Concerns\Translatable\TranslatableContract;
38
use Thinktomorrow\Chief\Urls\UrlRecordNotFound;
39
40
class Page extends Model implements ManagedModel, TranslatableContract, HasAsset, ActsAsParent, ActsAsChild, ActsAsMenuItem, MorphableContract, ViewableContract, ProvidesUrl, StatefulContract
41
{
42
    use BaseTranslatable {
0 ignored issues
show
Bug introduced by
The trait Astrotomic\Translatable\Translatable requires the property $each which is not provided by Thinktomorrow\Chief\Pages\Page.
Loading history...
43
        getAttribute as getTranslatableAttribute;
44
    }
45
46
    use Morphable;
0 ignored issues
show
Bug introduced by
The trait Thinktomorrow\Chief\Concerns\Morphable\Morphable requires the property $morph_key which is not provided by Thinktomorrow\Chief\Pages\Page.
Loading history...
47
    use AssetTrait;
0 ignored issues
show
introduced by
The trait Thinktomorrow\AssetLibrary\AssetTrait requires some properties which are not provided by Thinktomorrow\Chief\Pages\Page: $assetRelation, $pivot, $fallbackPath, $assetFallbackLocale, $each, $locale, $mediaConversionRegistrations, $useAssetFallbackLocale, $fallbackUrl, $media, $collection_name
Loading history...
48
    use Translatable;
49
    use SoftDeletes;
50
    use Publishable;
51
    use Featurable;
52
    use Archivable;
53
    use ActingAsParent;
54
    use ActingAsChild;
55
    use WithSnippets;
56
    use ResolvingRoute;
57
    use Viewable;
0 ignored issues
show
introduced by
The trait Thinktomorrow\Chief\Concerns\Viewable\Viewable requires some properties which are not provided by Thinktomorrow\Chief\Pages\Page: $content, $viewKey
Loading history...
58
    use HasFragments;
59
60
    // Explicitly mention the translation model so on inheritance the child class uses the proper default translation model
61
    protected $translationModel = PageTranslation::class;
62
    protected $translationForeignKey = 'page_id';
63
    protected $translatedAttributes = [
64
        'title',
65
        'content',
66
        'short',
67
        'seo_title',
68
        'seo_description',
69
        'seo_keywords',
70
        'seo_image',
71
    ];
72
73
    public $table = "pages";
74
    protected $guarded = [];
75
    protected $with = ['translations'];
76
77
    protected $baseViewPath;
78
    protected static $baseUrlSegment = '/';
79
80
    protected static $cachedUrls = [];
81
82 580
    public static function clearCachedUrls()
83
    {
84 580
        static::$cachedUrls = null;
85 580
    }
86
87 264
    final public function __construct(array $attributes = [])
88
    {
89 264
        $this->constructWithSnippets();
90
91 264
        if (!isset($this->baseViewPath)) {
92 264
            $this->baseViewPath = config('thinktomorrow.chief.base-view-paths.pages', 'pages');
93
        }
94
95 264
        parent::__construct($attributes);
96 264
    }
97
98 580
    public static function managedModelKey(): string
99
    {
100 580
        if (isset(static::$managedModelKey)) {
101 580
            return static::$managedModelKey;
0 ignored issues
show
Bug introduced by
The property managedModelKey does not seem to exist on Thinktomorrow\Chief\Pages\Page. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
102
        }
103
104
        throw new \Exception('Missing required static property \'managedModelKey\' on ' . static::class . '.');
105
    }
106
107
    /**
108
     * Parse and render any found snippets in custom
109
     * or translatable attribute values.
110
     *
111
     * @param string $value
112
     * @return mixed|null|string|string[]
113
     */
114 230
    public function getAttribute($value)
115
    {
116 230
        $value = $this->getTranslatableAttribute($value);
117
118 230
        if ($this->shouldParseWithSnippets($value)) {
119 1
            $value = $this->parseWithSnippets($value);
120
        }
121
122 230
        return $value;
123
    }
124
125
    /**
126
     * Page specific modules. We exclude text modules since they are modules in pure
127
     * technical terms and not so much as behavioural elements for the admin.
128
     *
129
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
130
     */
131 2
    public function modules()
132
    {
133 2
        return $this->morphMany(Module::class, 'owner')->where('morph_key', '<>', 'text');
134
    }
135
136 25
    public function flatReference(): FlatReference
137
    {
138 25
        return new FlatReference(static::class, $this->id);
139
    }
140
141 110
    public function flatReferenceLabel(): string
142
    {
143 110
        if ($this->exists) {
144 110
            $status = !$this->isPublished() ? ' [' . $this->statusAsPlainLabel() . ']' : null;
145
146 110
            return $this->title ? $this->title . $status : '';
0 ignored issues
show
Bug introduced by
The property title does not seem to exist on Thinktomorrow\Chief\Pages\Page. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
147
        }
148
149
        return '';
150
    }
151
152 5
    public function flatReferenceGroup(): string
153
    {
154 5
        $classKey = get_class($this);
155 5
        if (property_exists($this, 'labelSingular')) {
156 4
            $labelSingular = $this->labelSingular;
0 ignored issues
show
Bug introduced by
The property labelSingular does not seem to exist on Thinktomorrow\Chief\Pages\Page. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
157
        } else {
158 1
            $labelSingular = Str::singular($classKey);
159
        }
160
161 5
        return $labelSingular;
162
    }
163
164
    public function mediaUrls($type = null, $size = 'full'): Collection
165
    {
166
        $assets = $this->assets($type, app()->getLocale())->map->url($size);
0 ignored issues
show
introduced by
The method getLocale() does not exist on Illuminate\Container\Container. Are you sure you never get this type here, but always one of the subclasses? ( Ignorable by Annotation )

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

166
        $assets = $this->assets($type, app()->/** @scrutinizer ignore-call */ getLocale())->map->url($size);
Loading history...
167
168
        if ($assets->first() == null) {
169
            $assets = $this->assets($type)->map->url($size);
170
        }
171
172
        return $assets;
173
    }
174
175
    public function mediaUrl($type = null, $size = 'full'): ?string
176
    {
177
        return $this->mediaUrls($type, $size)->first();
178
    }
179
180
    public static function findPublished($id)
181
    {
182
        return static::published()->find($id);
183
    }
184
185 1
    public function scopeSortedByCreated($query)
186
    {
187 1
        $query->orderBy('created_at', 'DESC');
188 1
    }
189
190
    /** @inheritdoc */
191 30
    public function url(string $locale = null): string
192
    {
193 30
        if (!$locale) {
194 19
            $locale = app()->getLocale();
195
        }
196
        try {
197 30
            $memoizedKey = $this->getMorphClass() . '-' . $this->id . '-' . $locale;
198
199 30
            if (isset(static::$cachedUrls[$memoizedKey])) {
200 6
                return static::$cachedUrls[$memoizedKey];
201
            }
202
203 30
            $slug = MemoizedUrlRecord::findByModel($this, $locale)->slug;
204
205 18
            return static::$cachedUrls[$memoizedKey] = $this->resolveUrl($locale, [$slug]);
206 14
        } catch (UrlRecordNotFound $e) {
207 14
            return '';
208
        }
209
    }
210
211 98
    public function resolveUrl(string $locale = null, $parameters = null): string
212
    {
213 98
        $routeName = config('thinktomorrow.chief.route.name');
214
215 98
        return $this->resolveRoute($routeName, $parameters, $locale);
0 ignored issues
show
Bug introduced by
It seems like $locale can also be of type string; however, parameter $locale of Thinktomorrow\Chief\Pages\Page::resolveRoute() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

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

215
        return $this->resolveRoute($routeName, $parameters, /** @scrutinizer ignore-type */ $locale);
Loading history...
216
    }
217
218
    /** @inheritdoc */
219 4
    public function baseUrlSegment(string $locale = null): string
220
    {
221 4
        if (!isset(static::$baseUrlSegment)) {
222 1
            return '/';
223
        }
224
225 3
        if (!is_array(static::$baseUrlSegment)) {
0 ignored issues
show
introduced by
The condition is_array(static::baseUrlSegment) is always false.
Loading history...
226
            return static::$baseUrlSegment;
227
        }
228
229
        // When an array, we try to locate the expected segment by locale
230 90
        $key = $locale ?? app()->getlocale();
231
232 90
        if (isset(static::$baseUrlSegment[$key])) {
233
            return static::$baseUrlSegment[$key];
234
        }
235
236 90
        $fallback_locale = config('app.fallback_locale');
237 81
        if (isset(static::$baseUrlSegment[$fallback_locale])) {
238
            return static::$baseUrlSegment[$fallback_locale];
239
        }
240
241 9
        // Fall back to first entry in case no match is found
242
        return reset(static::$baseUrlSegment);
243 9
    }
244 9
245
    public function menuLabel(): string
246
    {
247 1
        return $this->title ?? '';
0 ignored issues
show
Bug introduced by
The property title does not seem to exist on Thinktomorrow\Chief\Pages\Page. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
248 1
    }
249 1
250
    public function statusAsLabel()
251
    {
252
        if ($this->isPublished()) {
253
            return '<a href="' . $this->url() . '" target="_blank"><em>online</em></a>';
254
        }
255
256 6
        if ($this->isDraft()) {
257
            return '<a href="' . $this->url() . '" target="_blank" class="text-error"><em>offline</em></a>';
258 6
        }
259
260
        if ($this->isArchived()) {
261
            return '<span><em>gearchiveerd</em></span>';
262
        }
263
264
        return '-';
265
    }
266
267
    public function statusAsPlainLabel()
268
    {
269
        if ($this->isPublished()) {
270
            return 'online';
271
        }
272
273
        if ($this->isDraft()) {
274
            return 'offline';
275
        }
276
277
        if ($this->isArchived()) {
278 108
            return 'gearchiveerd';
279
        }
280 108
281
        return '-';
282
    }
283
284 108
    public function stateOf($key): string
285 101
    {
286
        return $this->$key ?? PageState::DRAFT;
287
    }
288 8
289 8
    public function changeStateOf($key, $state)
290
    {
291
        // Ignore change to current state - it should not trigger events either
292
        if ($state === $this->stateOf($key)) {
293
            return;
294
        }
295 130
296
        PageState::assertNewState($this, $key, $state);
297 130
298
        $this->$key = $state;
299
    }
300
}
301