Upgrade to Laravel 13

This commit is contained in:
Jonny Barnes 2026-04-07 09:01:19 +01:00
commit 9f012b01e4
Signed by: jonny
SSH key fingerprint: SHA256:CTuSlns5U7qlD9jqHvtnVmfYV3Zwl2Z7WnJ4/dqOaL8
117 changed files with 1878 additions and 2215 deletions

View file

@ -5,6 +5,8 @@ declare(strict_types=1);
namespace App\Models;
use Cviebrock\EloquentSluggable\Sluggable;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
@ -18,23 +20,14 @@ use League\CommonMark\MarkdownConverter;
use Spatie\CommonMarkHighlighter\FencedCodeRenderer;
use Spatie\CommonMarkHighlighter\IndentedCodeRenderer;
#[Table('articles')]
#[Fillable(['url', 'title', 'main', 'published'])]
class Article extends Model
{
use HasFactory;
use Sluggable;
use SoftDeletes;
/** @var string */
protected $table = 'articles';
/** @var array<int, string> */
protected $fillable = [
'url',
'title',
'main',
'published',
];
/** @var array<string, string> */
protected $casts = [
'created_at' => 'datetime',
@ -100,7 +93,7 @@ class Article extends Model
protected function link(): Attribute
{
return Attribute::get(
get: fn () => '/blog/' . $this->updated_at->year . '/' . $this->updated_at->format('m') . '/' . $this->titleurl,
get: fn () => '/blog/'.$this->updated_at->year.'/'.$this->updated_at->format('m').'/'.$this->titleurl,
);
}
@ -112,15 +105,15 @@ class Article extends Model
if ($year === null) {
return $query;
}
$start = $year . '-01-01 00:00:00';
$end = ($year + 1) . '-01-01 00:00:00';
$start = $year.'-01-01 00:00:00';
$end = ($year + 1).'-01-01 00:00:00';
if (($month !== null) && ($month !== 12)) {
$start = $year . '-' . $month . '-01 00:00:00';
$end = $year . '-' . ($month + 1) . '-01 00:00:00';
$start = $year.'-'.$month.'-01 00:00:00';
$end = $year.'-'.($month + 1).'-01 00:00:00';
}
if ($month === 12) {
$start = $year . '-12-01 00:00:00';
$end = ($year + 1) . '-01-01 00:00:00';
$start = $year.'-12-01 00:00:00';
$end = ($year + 1).'-01-01 00:00:00';
}
return $query->where([

View file

@ -4,18 +4,17 @@ declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
#[Fillable(['url', 'name', 'content'])]
class Bookmark extends Model
{
use HasFactory;
/** @var array<int, string> */
protected $fillable = ['url', 'name', 'content'];
/** @var array<string, string> */
protected $casts = [
'syndicates' => 'array',
@ -26,10 +25,10 @@ class Bookmark extends Model
return $this->belongsToMany('App\Models\Tag');
}
protected function local_uri(): Attribute
protected function localUri(): Attribute
{
return Attribute::get(
get: fn () => config('app.url') . '/bookmarks/' . $this->id,
get: fn () => config('app.url').'/bookmarks/'.$this->id,
);
}
}

View file

@ -4,28 +4,26 @@ declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Table;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
#[Table('contacts')]
#[Fillable(['nick', 'name', 'homepage', 'twitter', 'facebook'])]
class Contact extends Model
{
use HasFactory;
/** @var string */
protected $table = 'contacts';
/** @var array<int, string> */
protected $fillable = ['nick', 'name', 'homepage', 'twitter', 'facebook'];
protected function photo(): Attribute
{
$photo = '/assets/profile-images/default-image';
if (array_key_exists('homepage', $this->attributes) && ! empty($this->attributes['homepage'])) {
$host = parse_url($this->attributes['homepage'], PHP_URL_HOST);
if (file_exists(public_path() . '/assets/profile-images/' . $host . '/image')) {
$photo = '/assets/profile-images/' . $host . '/image';
if (file_exists(public_path().'/assets/profile-images/'.$host.'/image')) {
$photo = '/assets/profile-images/'.$host.'/image';
}
}

View file

@ -5,20 +5,19 @@ declare(strict_types=1);
namespace App\Models;
use App\Traits\FilterHtml;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Mf2;
#[Fillable(['url'])]
class Like extends Model
{
use FilterHtml;
use HasFactory;
/** @var array<int, string> */
protected $fillable = ['url'];
protected function url(): Attribute
{
return Attribute::set(

View file

@ -4,22 +4,20 @@ declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Table;
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\Str;
#[Table('media_endpoint')]
#[Fillable(['token', 'path', 'type', 'image_widths'])]
class Media extends Model
{
use HasFactory;
/** @var string */
protected $table = 'media_endpoint';
/** @var array<int, string> */
protected $fillable = ['token', 'path', 'type', 'image_widths'];
public function notes(): BelongsToMany
{
return $this->belongsToMany(Note::class)
@ -35,7 +33,7 @@ class Media extends Model
return $attributes['path'];
}
return config('app.url') . '/storage/' . $attributes['path'];
return config('app.url').'/storage/'.$attributes['path'];
}
);
}
@ -80,7 +78,7 @@ class Media extends Model
$basename = $this->getBasename($path);
$extension = $this->getExtension($path);
return config('app.url') . '/storage/' . $basename . '-' . $size . '.' . $extension;
return config('app.url').'/storage/'.$basename.'-'.$size.'.'.$extension;
}
private function getBasename(string $path): string
@ -91,7 +89,7 @@ class Media extends Model
array_pop($filenameParts);
return ltrim(array_reduce($filenameParts, static function ($carry, $item) {
return $carry . '.' . $item;
return $carry.'.'.$item;
}, ''), '.');
}

View file

@ -4,20 +4,18 @@ declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Table;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
#[Table('clients')]
#[Fillable(['client_url', 'client_name'])]
class MicropubClient extends Model
{
use HasFactory;
/** @var string */
protected $table = 'clients';
/** @var array<int, string> */
protected $fillable = ['client_url', 'client_name'];
public function notes(): HasMany
{
return $this->hasMany('App\Models\Note', 'client_id', 'client_url');

View file

@ -6,9 +6,12 @@ namespace App\Models;
use App\CommonMark\Generators\MentionGenerator;
use App\CommonMark\Renderers\MentionRenderer;
use Codebird\Codebird;
use Exception;
use App\Observers\NoteObserver;
use GuzzleHttp\Client;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Hidden;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Attributes\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
@ -31,6 +34,10 @@ use Normalizer;
use Spatie\CommonMarkHighlighter\FencedCodeRenderer;
use Spatie\CommonMarkHighlighter\IndentedCodeRenderer;
#[Table('notes')]
#[Fillable(['note', 'in_reply_to', 'client_id'])]
#[Hidden(['searchable'])]
#[ObservedBy(NoteObserver::class)]
class Note extends Model
{
use HasFactory;
@ -61,16 +68,6 @@ class Note extends Model
/** @var string */
protected $table = 'notes';
/** @var array<int, string> */
protected $fillable = [
'note',
'in_reply_to',
'client_id',
];
/** @var array<int, string> */
protected $hidden = ['searchable'];
public function tags(): BelongsToMany
{
return $this->belongsToMany(Tag::class);
@ -126,7 +123,7 @@ class Note extends Model
public function getNoteAttribute(?string $value): ?string
{
if ($value === null && $this->place !== null) {
$value = '📍: <a href="' . $this->place->uri . '">' . $this->place->name . '</a>';
$value = '📍: <a href="'.$this->place->uri.'">'.$this->place->name.'</a>';
}
// if $value is still null, just return null
@ -150,13 +147,13 @@ class Note extends Model
foreach ($this->media as $media) {
if ($media->type === 'image') {
$note .= PHP_EOL . '<img src="' . $media->url . '" alt="">';
$note .= PHP_EOL.'<img src="'.$media->url.'" alt="">';
}
if ($media->type === 'audio') {
$note .= PHP_EOL . '<audio src="' . $media->url . '">';
$note .= PHP_EOL.'<audio src="'.$media->url.'">';
}
if ($media->type === 'video') {
$note .= PHP_EOL . '<video src="' . $media->url . '">';
$note .= PHP_EOL.'<video src="'.$media->url.'">';
}
}
@ -176,7 +173,7 @@ class Note extends Model
public function getUriAttribute(): string
{
return config('app.url') . '/notes/' . $this->nb60id;
return config('app.url').'/notes/'.$this->nb60id;
}
public function getIso8601Attribute(): string
@ -241,43 +238,6 @@ class Note extends Model
return null;
}
/**
* Get the OEmbed html for a tweet the note is a reply to.
*/
public function getTwitterAttribute(): ?object
{
if (
$this->in_reply_to === null ||
! $this->isTwitterLink($this->in_reply_to)
) {
return null;
}
$tweetId = basename($this->in_reply_to);
if (Cache::has($tweetId)) {
return Cache::get($tweetId);
}
try {
$codebird = resolve(Codebird::class);
$oEmbed = $codebird->statuses_oembed([
'url' => $this->in_reply_to,
'dnt' => true,
'align' => 'center',
'maxwidth' => 512,
]);
if ($oEmbed->httpstatus >= 400) {
throw new Exception;
}
} catch (Exception $e) {
return null;
}
Cache::put($tweetId, $oEmbed, ($oEmbed->cache_age));
return $oEmbed;
}
/**
* Scope a query to select a note via a NewBase60 id.
*/
@ -313,14 +273,14 @@ class Note extends Model
self::USERNAMES_REGEX,
function ($matches) {
if (is_null($this->contacts[$matches[1]])) {
return '<a href="https://twitter.com/' . $matches[1] . '">' . $matches[0] . '</a>';
return '<a href="https://twitter.com/'.$matches[1].'">'.$matches[0].'</a>';
}
$contact = $this->contacts[$matches[1]]; // easier to read the following code
$host = parse_url($contact->homepage, PHP_URL_HOST);
$contact->photo = '/assets/profile-images/default-image';
if (file_exists(public_path() . '/assets/profile-images/' . $host . '/image')) {
$contact->photo = '/assets/profile-images/' . $host . '/image';
if (file_exists(public_path().'/assets/profile-images/'.$host.'/image')) {
$contact->photo = '/assets/profile-images/'.$host.'/image';
}
return trim(view('templates.mini-hcard', ['contact' => $contact])->render());
@ -371,8 +331,8 @@ class Note extends Model
'/#([^\s[:punct:]]+)/',
function ($matches) {
return '<a rel="tag" class="p-category" href="/notes/tagged/'
. Tag::normalize($matches[1]) . '">#'
. $matches[1] . '</a>';
.Tag::normalize($matches[1]).'">#'
.$matches[1].'</a>';
},
$note
);
@ -404,7 +364,7 @@ class Note extends Model
public function reverseGeoCode(float $latitude, float $longitude): string
{
$latLng = $latitude . ',' . $longitude;
$latLng = $latitude.','.$longitude;
return Cache::get($latLng, function () use ($latLng, $latitude, $longitude) {
$guzzle = resolve(Client::class);
@ -422,46 +382,41 @@ class Note extends Model
if (isset($json->address->suburb)) {
$locality = $json->address->suburb;
if (isset($json->address->city)) {
$locality .= ', ' . $json->address->city;
$locality .= ', '.$json->address->city;
}
$address = '<span class="p-locality">'
. $locality
. '</span>, <span class="p-country-name">'
. $json->address->country
. '</span>';
.$locality
.'</span>, <span class="p-country-name">'
.$json->address->country
.'</span>';
Cache::forever($latLng, $address);
return $address;
}
if (isset($json->address->city)) {
$address = '<span class="p-locality">'
. $json->address->city
. '</span>, <span class="p-country-name">'
. $json->address->country
. '</span>';
.$json->address->city
.'</span>, <span class="p-country-name">'
.$json->address->country
.'</span>';
Cache::forever($latLng, $address);
return $address;
}
if (isset($json->address->county)) {
$address = '<span class="p-region">'
. $json->address->county
. '</span>, <span class="p-country-name">'
. $json->address->country
. '</span>';
.$json->address->county
.'</span>, <span class="p-country-name">'
.$json->address->country
.'</span>';
Cache::forever($latLng, $address);
return $address;
}
$address = '<span class="p-country-name">' . $json->address->country . '</span>';
$address = '<span class="p-country-name">'.$json->address->country.'</span>';
Cache::forever($latLng, $address);
return $address;
});
}
private function isTwitterLink(string $inReplyTo): bool
{
return str_starts_with($inReplyTo, 'https://twitter.com/');
}
}

View file

@ -4,20 +4,16 @@ declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
#[Fillable(['passkey_id', 'passkey'])]
class Passkey extends Model
{
use HasFactory;
/** @inerhitDoc */
protected $fillable = [
'passkey_id',
'passkey',
];
public function user(): BelongsTo
{
return $this->belongsTo(User::class);

View file

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Models;
use Cviebrock\EloquentSluggable\Sluggable;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
@ -12,6 +13,7 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Str;
#[Fillable(['name', 'slug'])]
class Place extends Model
{
use HasFactory;
@ -22,9 +24,6 @@ class Place extends Model
return 'slug';
}
/** @var array<int, string> */
protected $fillable = ['name', 'slug'];
/** @var array<string, string> */
protected $casts = [
'latitude' => 'float',
@ -77,7 +76,7 @@ class Place extends Model
protected function uri(): Attribute
{
return Attribute::get(
get: static fn ($value, $attributes) => config('app.url') . '/places/' . $attributes['slug'],
get: static fn ($value, $attributes) => config('app.url').'/places/'.$attributes['slug'],
);
}

View file

@ -4,40 +4,20 @@ declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Attributes\Appends;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Visible;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
#[Fillable(['uid', 'name', 'service_name', 'service_url', 'service_photo', 'user_name', 'user_url', 'user_photo'])]
#[Visible(['uid', 'name', 'service', 'user'])]
#[Appends(['service', 'user'])]
class SyndicationTarget extends Model
{
use HasFactory;
/** @var array<int, string> */
protected $fillable = [
'uid',
'name',
'service_name',
'service_url',
'service_photo',
'user_name',
'user_url',
'user_photo',
];
/** @var array<int, string> */
protected $visible = [
'uid',
'name',
'service',
'user',
];
/** @var array<int, string> */
protected $appends = [
'service',
'user',
];
protected function service(): Attribute
{
return Attribute::get(

View file

@ -4,19 +4,18 @@ declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Attributes\Guarded;
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\Str;
#[Guarded(['id'])]
class Tag extends Model
{
use HasFactory;
/** @var array<int, string> */
protected $guarded = ['id'];
public function notes(): BelongsToMany
{
return $this->belongsToMany(Note::class);

View file

@ -4,27 +4,34 @@ declare(strict_types=1);
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Database\Factories\UserFactory;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Hidden;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
#[Fillable(['name', 'password'])]
#[Hidden(['password', 'remember_token'])]
class User extends Authenticatable
{
use HasFactory;
use Notifiable;
/** @use HasFactory<UserFactory> */
use HasFactory, Notifiable;
/** @var array<int, string> */
protected $fillable = [
'name', 'password',
];
/** @var array<int, string> */
protected $hidden = [
'current_password',
'password',
'remember_token',
];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}
public function passkey(): HasMany
{

View file

@ -5,27 +5,23 @@ declare(strict_types=1);
namespace App\Models;
use App\Traits\FilterHtml;
use Codebird\Codebird;
use Exception;
use Illuminate\Database\Eloquent\Attributes\Guarded;
use Illuminate\Database\Eloquent\Attributes\Table;
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;
#[Table('webmentions')]
#[Guarded(['id'])]
class WebMention extends Model
{
use FilterHtml;
use HasFactory;
/** @var string */
protected $table = 'webmentions';
/** @var array<int, string> */
protected $guarded = ['id'];
public function commentable(): MorphTo
{
return $this->morphTo();
@ -127,22 +123,9 @@ class WebMention extends Model
return str_replace('http://', 'https://', $url);
}
if ($host === 'twitter.com') {
if (Cache::has($url)) {
return Cache::get($url);
}
$username = ltrim(parse_url($url, PHP_URL_PATH), '/');
$codebird = resolve(Codebird::class);
$info = $codebird->users_show(['screen_name' => $username]);
$profile_image = $info->profile_image_url_https;
Cache::put($url, $profile_image, 10080); // 1 week
return $profile_image;
}
$filesystem = new Filesystem;
if ($filesystem->exists(public_path() . '/assets/profile-images/' . $host . '/image')) {
return '/assets/profile-images/' . $host . '/image';
if ($filesystem->exists(public_path().'/assets/profile-images/'.$host.'/image')) {
return '/assets/profile-images/'.$host.'/image';
}
return $url;