diff --git a/app/Http/Controllers/MicropubMediaController.php b/app/Http/Controllers/MicropubMediaController.php index b360228c..9cf0d55a 100644 --- a/app/Http/Controllers/MicropubMediaController.php +++ b/app/Http/Controllers/MicropubMediaController.php @@ -75,7 +75,7 @@ class MicropubMediaController extends Controller return [ 'url' => $mediaItem->url, 'published' => $mediaItem->created_at->toW3cString(), - 'mime_type' => $mediaItem->getMimeType(), + 'mime_type' => $mediaItem->mimetype, ]; }); diff --git a/app/Models/Article.php b/app/Models/Article.php index 0aac8d7f..11256946 100644 --- a/app/Models/Article.php +++ b/app/Models/Article.php @@ -6,6 +6,7 @@ namespace App\Models; use Cviebrock\EloquentSluggable\Sluggable; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; @@ -58,70 +59,54 @@ class Article extends Model */ protected $guarded = ['id']; - /** - * Process the article for display. - * - * @return string - */ - public function getHtmlAttribute(): string + protected function html(): Attribute { - $environment = new Environment(); - $environment->addExtension(new CommonMarkCoreExtension()); - $environment->addRenderer(FencedCode::class, new FencedCodeRenderer()); - $environment->addRenderer(IndentedCode::class, new IndentedCodeRenderer()); - $markdownConverter = new MarkdownConverter($environment); + return Attribute::get( + get: function () { + $environment = new Environment(); + $environment->addExtension(new CommonMarkCoreExtension()); + $environment->addRenderer(FencedCode::class, new FencedCodeRenderer()); + $environment->addRenderer(IndentedCode::class, new IndentedCodeRenderer()); + $markdownConverter = new MarkdownConverter($environment); - return $markdownConverter->convert($this->main)->getContent(); + return $markdownConverter->convert($this->main)->getContent(); + }, + ); } - /** - * Convert updated_at to W3C time format. - * - * @return string - */ - public function getW3cTimeAttribute(): string + protected function w3cTime(): Attribute { - return $this->updated_at->toW3CString(); + return Attribute::get( + get: fn () => $this->updated_at->toW3CString(), + ); } - /** - * Convert updated_at to a tooltip appropriate format. - * - * @return string - */ - public function getTooltipTimeAttribute(): string + protected function tooltipTime(): Attribute { - return $this->updated_at->toRFC850String(); + return Attribute::get( + get: fn () => $this->updated_at->toRFC850String(), + ); } - /** - * Convert updated_at to a human readable format. - * - * @return string - */ - public function getHumanTimeAttribute(): string + protected function humanTime(): Attribute { - return $this->updated_at->diffForHumans(); + return Attribute::get( + get: fn () => $this->updated_at->diffForHumans(), + ); } - /** - * Get the pubdate value for RSS feeds. - * - * @return string - */ - public function getPubdateAttribute(): string + protected function pubdate(): Attribute { - return $this->updated_at->toRSSString(); + return Attribute::get( + get: fn () => $this->updated_at->toRSSString(), + ); } - /** - * A link to the article, i.e. `/blog/1999/12/25/merry-christmas`. - * - * @return string - */ - public function getLinkAttribute(): string + protected function link(): Attribute { - return '/blog/' . $this->updated_at->year . '/' . $this->updated_at->format('m') . '/' . $this->titleurl; + return Attribute::get( + get: fn () => '/blog/' . $this->updated_at->year . '/' . $this->updated_at->format('m') . '/' . $this->titleurl, + ); } /** @@ -134,7 +119,7 @@ class Article extends Model */ public function scopeDate(Builder $query, int $year = null, int $month = null): Builder { - if ($year == null) { + if ($year === null) { return $query; } $start = $year . '-01-01 00:00:00'; diff --git a/app/Models/Bookmark.php b/app/Models/Bookmark.php index b5a88e58..097b461e 100644 --- a/app/Models/Bookmark.php +++ b/app/Models/Bookmark.php @@ -4,43 +4,11 @@ declare(strict_types=1); namespace App\Models; -use Illuminate\Database\Eloquent\Builder; -use Illuminate\Database\Eloquent\Collection; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; -use Illuminate\Support\Carbon; -/** - * App\Models\Bookmark. - * - * @property int $id - * @property string $url - * @property string|null $name - * @property string|null $content - * @property string|null $screenshot - * @property string|null $archive - * @property array|null $syndicates - * @property Carbon|null $created_at - * @property Carbon|null $updated_at - * @property-read string $longurl - * @property-read Collection|Tag[] $tags - * @property-read int|null $tags_count - * - * @method static Builder|Bookmark newModelQuery() - * @method static Builder|Bookmark newQuery() - * @method static Builder|Bookmark query() - * @method static Builder|Bookmark whereArchive($value) - * @method static Builder|Bookmark whereContent($value) - * @method static Builder|Bookmark whereCreatedAt($value) - * @method static Builder|Bookmark whereId($value) - * @method static Builder|Bookmark whereName($value) - * @method static Builder|Bookmark whereScreenshot($value) - * @method static Builder|Bookmark whereSyndicates($value) - * @method static Builder|Bookmark whereUpdatedAt($value) - * @method static Builder|Bookmark whereUrl($value) - * @mixin Eloquent - */ class Bookmark extends Model { use HasFactory; @@ -71,13 +39,10 @@ class Bookmark extends Model return $this->belongsToMany('App\Models\Tag'); } - /** - * The full url of a bookmark. - * - * @return string - */ - public function getLongurlAttribute(): string + protected function longurl(): Attribute { - return config('app.url') . '/bookmarks/' . $this->id; + return Attribute::get( + get: fn () => config('app.url') . '/bookmarks/' . $this->id, + ); } } diff --git a/app/Models/Like.php b/app/Models/Like.php index c7a17448..074fb9bd 100644 --- a/app/Models/Like.php +++ b/app/Models/Like.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace App\Models; use App\Traits\FilterHtml; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Arr; @@ -17,46 +18,38 @@ class Like extends Model protected $fillable = ['url']; - /** - * Normalize the URL of a Like. - * - * @param string $value The provided URL - */ - public function setUrlAttribute(string $value) + protected function url(): Attribute { - $this->attributes['url'] = normalize_url($value); + return Attribute::set( + set: fn ($value) => normalize_url($value), + ); } - /** - * Normalize the URL of the author of the like. - * - * @param string|null $value The author’s url - */ - public function setAuthorUrlAttribute(?string $value) + protected function authorUrl(): Attribute { - $this->attributes['author_url'] = normalize_url($value); + return Attribute::set( + set: fn ($value) => normalize_url($value), + ); } - /** - * If the content contains HTML, filter it. - * - * @param string|null $value The content of the like - * @return string|null - */ - public function getContentAttribute(?string $value): ?string + protected function content(): Attribute { - if ($value === null) { - return null; - } + return Attribute::get( + get: function ($value, $attributes) { + if ($value === null) { + return null; + } - $mf2 = Mf2\parse($value, $this->url); + $mf2 = Mf2\parse($value, $attributes['url']); - if (Arr::get($mf2, 'items.0.properties.content.0.html')) { - return $this->filterHtml( - $mf2['items'][0]['properties']['content'][0]['html'] - ); - } + if (Arr::get($mf2, 'items.0.properties.content.0.html')) { + return $this->filterHtml( + $mf2['items'][0]['properties']['content'][0]['html'] + ); + } - return $value; + return $value; + } + ); } } diff --git a/app/Models/Media.php b/app/Models/Media.php index 85ec0290..83f13e84 100644 --- a/app/Models/Media.php +++ b/app/Models/Media.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\Models; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -37,53 +38,63 @@ class Media extends Model return $this->belongsTo(Note::class); } - /** - * Get the URL for an S3 media file. - * - * @return string - */ - public function getUrlAttribute(): string + protected function url(): Attribute { - if (Str::startsWith($this->path, 'https://')) { - return $this->path; - } + return Attribute::get( + get: function ($value, $attributes) { + if (Str::startsWith($attributes['path'], 'https://')) { + return $attributes['path']; + } - return config('filesystems.disks.s3.url') . '/' . $this->path; + return config('filesystems.disks.s3.url') . '/' . $attributes['path']; + } + ); } - /** - * Get the URL for the medium size of an S3 image file. - * - * @return string - */ - public function getMediumurlAttribute(): string + protected function mediumurl(): Attribute { - $basename = $this->getBasename($this->path); - $extension = $this->getExtension($this->path); - - return config('filesystems.disks.s3.url') . '/' . $basename . '-medium.' . $extension; + return Attribute::get( + get: fn ($value, $attributes) => $this->getSizeUrl($attributes['path'], 'medium'), + ); } - /** - * Get the URL for the small size of an S3 image file. - * - * @return string - */ - public function getSmallurlAttribute(): string + protected function smallmurl(): Attribute { - $basename = $this->getBasename($this->path); - $extension = $this->getExtension($this->path); - - return config('filesystems.disks.s3.url') . '/' . $basename . '-small.' . $extension; + return Attribute::get( + get: fn ($value, $attributes) => $this->getSizeUrl($attributes['path'], 'small'), + ); } - /** - * Give the real part of a filename, i.e. strip the file extension. - * - * @param string $path - * @return string - */ - public function getBasename(string $path): string + protected function mimetype(): Attribute + { + return Attribute::get( + get: function ($value, $attributes) { + $extension = $this->getExtension($attributes['path']); + + return match ($extension) { + 'gif' => 'image/gif', + 'jpeg', 'jpg' => 'image/jpeg', + 'png' => 'image/png', + 'svg' => 'image/svg+xml', + 'tiff' => 'image/tiff', + 'webp' => 'image/webp', + 'mp4' => 'video/mp4', + 'mkv' => 'video/mkv', + default => 'application/octet-stream', + }; + }, + ); + } + + private function getSizeUrl(string $path, string $size): string + { + $basename = $this->getBasename($path); + $extension = $this->getExtension($path); + + return config('filesystems.disks.s3.url') . '/' . $basename . '-' . $size . '.' . $extension; + } + + private function getBasename(string $path): string { // the following achieves this data flow // foo.bar.png => ['foo', 'bar', 'png'] => ['foo', 'bar'] => foo.bar @@ -95,40 +106,10 @@ class Media extends Model }, ''), '.'); } - /** - * Get the extension from a given filename. - * - * @param string $path - * @return string - */ - public function getExtension(string $path): string + private function getExtension(string $path): string { $parts = explode('.', $path); return array_pop($parts); } - - /** - * Get the mime type of the media file. - * - * For now we will just use the extension, but this could be improved. - * - * @return string - */ - public function getMimeType(): string - { - $extension = $this->getExtension($this->path); - - return match ($extension) { - 'gif' => 'image/gif', - 'jpeg', 'jpg' => 'image/jpeg', - 'png' => 'image/png', - 'svg' => 'image/svg+xml', - 'tiff' => 'image/tiff', - 'webp' => 'image/webp', - 'mp4' => 'video/mp4', - 'mkv' => 'video/mkv', - default => 'application/octet-stream', - }; - } } diff --git a/app/Models/Place.php b/app/Models/Place.php index 593d3167..b1cc5cd4 100644 --- a/app/Models/Place.php +++ b/app/Models/Place.php @@ -6,6 +6,7 @@ namespace App\Models; use Cviebrock\EloquentSluggable\Sluggable; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -105,53 +106,46 @@ class Place extends Model ])); } - /** - * The Long URL for a place. - * - * @return string - */ - public function getLongurlAttribute(): string + protected function longurl(): Attribute { - return config('app.url') . '/places/' . $this->slug; + return Attribute::get( + get: fn ($value, $attributes) => config('app.url') . '/places/' . $attributes['slug'], + ); } - /** - * The Short URL for a place. - * - * @return string - */ - public function getShorturlAttribute(): string + protected function shorturl(): Attribute { - return config('app.shorturl') . '/places/' . $this->slug; + return Attribute::get( + get: fn ($value, $attributes) => config('app.shorturl') . '/places/' . $attributes['slug'], + ); } - /** - * This method is an alternative for `longurl`. - * - * @return string - */ - public function getUriAttribute(): string + protected function uri(): Attribute { - return $this->longurl; + return Attribute::get( + get: fn () => $this->longurl, + ); } - /** - * Dealing with a jsonb column, so we check input first. - * - * @param string|null $url - */ - public function setExternalUrlsAttribute(?string $url) + protected function externalUrls(): Attribute { - if ($url === null) { - return; - } - $type = $this->getType($url); - $already = []; - if (array_key_exists('external_urls', $this->attributes)) { - $already = json_decode($this->attributes['external_urls'], true); - } - $already[$type] = $url; - $this->attributes['external_urls'] = json_encode($already); + return Attribute::set( + set: function ($value, $attributes) { + if ($value === null) { + return $attributes['external_urls'] ?? null; + } + + $type = $this->getType($value); + $already = []; + + if (array_key_exists('external_urls', $attributes)) { + $already = json_decode($attributes['external_urls'], true); + } + $already[$type] = $value; + + return json_encode($already); + } + ); } /** diff --git a/app/Models/Tag.php b/app/Models/Tag.php index a5a48536..0e4c2f93 100644 --- a/app/Models/Tag.php +++ b/app/Models/Tag.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\Models; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; @@ -40,14 +41,11 @@ class Tag extends Model return $this->belongsToMany('App\Models\Bookmark'); } - /** - * When creating a Tag model instance, invoke the nomralize method on the tag. - * - * @param string $value - */ - public function setTagAttribute(string $value) + protected function tag(): Attribute { - $this->attributes['tag'] = $this->normalize($value); + return Attribute::set( + set: fn ($value) => self::normalize($value), + ); } /** diff --git a/app/Models/WebMention.php b/app/Models/WebMention.php index 06d21b59..37c4427f 100644 --- a/app/Models/WebMention.php +++ b/app/Models/WebMention.php @@ -6,13 +6,14 @@ namespace App\Models; use App\Traits\FilterHtml; use Codebird\Codebird; +use Exception; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\MorphTo; use Illuminate\Filesystem\Filesystem; use Illuminate\Support\Facades\Cache; use Jonnybarnes\WebmentionsParser\Authorship; -use Jonnybarnes\WebmentionsParser\Exceptions\AuthorshipParserException; class WebMention extends Model { @@ -43,72 +44,79 @@ class WebMention extends Model return $this->morphTo(); } - /** - * Get the author of the webmention. - * - * @return array - * - * @throws AuthorshipParserException - */ - public function getAuthorAttribute(): array + protected function author(): Attribute { - $authorship = new Authorship(); - $hCard = $authorship->findAuthor(json_decode($this->mf2, true)); + return Attribute::get( + get: function ($value, $attributes) { + if ( + ! array_key_exists('mf2', $attributes) || + $attributes['mf2'] === null + ) { + return null; + } - if ($hCard === false) { - return []; - } + $authorship = new Authorship(); + $hCard = $authorship->findAuthor(json_decode($attributes['mf2'], true)); - if ( - array_key_exists('properties', $hCard) && - array_key_exists('photo', $hCard['properties']) - ) { - $hCard['properties']['photo'][0] = $this->createPhotoLink($hCard['properties']['photo'][0]); - } + if ($hCard === false) { + return null; + } - return $hCard; - } + if ( + array_key_exists('properties', $hCard) && + array_key_exists('photo', $hCard['properties']) + ) { + $hCard['properties']['photo'][0] = $this->createPhotoLink($hCard['properties']['photo'][0]); + } - /** - * Get the published value for the webmention. - * - * @return string|null - */ - public function getPublishedAttribute(): ?string - { - $mf2 = $this->mf2 ?? ''; - $microformats = json_decode($mf2, true); - if (isset($microformats['items'][0]['properties']['published'][0])) { - try { - $published = carbon()->parse( - $microformats['items'][0]['properties']['published'][0] - )->toDayDateTimeString(); - } catch (\Exception $exception) { - $published = $this->updated_at->toDayDateTimeString(); + return $hCard; } - } else { - $published = $this->updated_at->toDayDateTimeString(); - } - - return $published; + ); } - /** - * Get the filtered HTML of a reply. - * - * @return string|null - */ - public function getReplyAttribute(): ?string + protected function published(): Attribute { - if ($this->mf2 === null) { - return null; - } - $microformats = json_decode($this->mf2, true); - if (isset($microformats['items'][0]['properties']['content'][0]['html'])) { - return $this->filterHtml($microformats['items'][0]['properties']['content'][0]['html']); - } + return Attribute::get( + get: function ($value, $attributes) { + $mf2 = $attributes['mf2'] ?? ''; + $microformats = json_decode($mf2, true); + if (isset($microformats['items'][0]['properties']['published'][0])) { + try { + $published = carbon()->parse( + $microformats['items'][0]['properties']['published'][0] + )->toDayDateTimeString(); + } catch (Exception) { + $published = $this->updated_at->toDayDateTimeString(); + } + } else { + $published = $this->updated_at->toDayDateTimeString(); + } - return null; + return $published; + } + ); + } + + protected function reply(): Attribute + { + return Attribute::get( + get: function ($value, $attributes) { + if ( + ! array_key_exists('mf2', $attributes) || + $attributes['mf2'] === null + ) { + return null; + } + + $microformats = json_decode($attributes['mf2'], true); + + if (isset($microformats['items'][0]['properties']['content'][0]['html'])) { + return $this->filterHtml($microformats['items'][0]['properties']['content'][0]['html']); + } + + return null; + } + ); } /** @@ -121,11 +129,13 @@ class WebMention extends Model { $url = normalize_url($url); $host = parse_url($url, PHP_URL_HOST); - if ($host == 'pbs.twimg.com') { + + if ($host === 'pbs.twimg.com') { //make sure we use HTTPS, we know twitter supports it return str_replace('http://', 'https://', $url); } - if ($host == 'twitter.com') { + + if ($host === 'twitter.com') { if (Cache::has($url)) { return Cache::get($url); } @@ -137,6 +147,7 @@ class WebMention extends Model return $profile_image; } + $filesystem = new Filesystem(); if ($filesystem->exists(public_path() . '/assets/profile-images/' . $host . '/image')) { return '/assets/profile-images/' . $host . '/image'; diff --git a/tests/Unit/PlacesTest.php b/tests/Unit/PlacesTest.php index 6b5eddef..192458dc 100644 --- a/tests/Unit/PlacesTest.php +++ b/tests/Unit/PlacesTest.php @@ -83,7 +83,6 @@ class PlacesTest extends TestCase 'url' => ['https://www.openstreetmap.org/way/1234'], ], ]); - $this->assertInstanceOf('App\Models\Place', $ret); $this->assertCount(11, Place::all()); }