Merge branch 'develop' into github-action-deploy

This commit is contained in:
Jonny Barnes 2021-10-24 18:14:43 +01:00
commit 005d3cdf90
87 changed files with 8359 additions and 11741 deletions

View file

@ -7,7 +7,7 @@ APP_LOG_LEVEL=warning
DB_CONNECTION=pgsql DB_CONNECTION=pgsql
DB_HOST=127.0.0.1 DB_HOST=127.0.0.1
DB_PORT=5432 DB_PORT=5432
DB_DATABASE=jbuktest DB_DATABASE=jbukdev_testing
DB_USERNAME=postgres DB_USERNAME=postgres
DB_PASSWORD=postgres DB_PASSWORD=postgres

View file

@ -1,17 +0,0 @@
APP_ENV=testing
APP_DEBUG=true
APP_KEY=base64:6DJhvZLVjE6dD4Cqrteh+6Z5vZlG+v/soCKcDHLOAH0=
APP_URL=http://jonnybarnes.localhost
APP_LONGURL=jonnybarnes.localhost
APP_SHORTURL=jmb.localhost
DB_CONNECTION=travis
CACHE_DRIVER=array
SESSION_DRIVER=array
QUEUE_DRIVER=sync
SCOUT_DRIVER=pgsql
DISPLAY_NAME='Travis Test'
USER_NAME=travis

View file

@ -11,11 +11,11 @@ jobs:
services: services:
postgres: postgres:
image: postgres:13.1 image: postgres:13.4
env: env:
POSTGRES_USER: postgres POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres POSTGRES_PASSWORD: postgres
POSTGRES_DB: jbuktest POSTGRES_DB: jbukdev_testing
ports: ports:
- 5432:5432 - 5432:5432
@ -36,15 +36,13 @@ jobs:
- name: Copy .env - name: Copy .env
run: php -r "file_exists('.env') || copy('.env.github', '.env');" run: php -r "file_exists('.env') || copy('.env.github', '.env');"
- name: Install dependencies - name: Install dependencies
run: composer install -q --no-ansi --no-interaction --no-progress run: composer install --quiet --no-ansi --no-interaction --no-progress
- name: Generate key - name: Generate key
run: php artisan key:generate run: php artisan key:generate
- name: Setup directory permissions - name: Setup directory permissions
run: chmod -R 777 storage bootstrap/cache run: chmod -R 777 storage bootstrap/cache
- name: Setup test database - name: Setup test database
run: | run: php artisan migrate
php artisan migrate
php artisan db:seed
- name: Execute tests (Unit and Feature tests) via PHPUnit - name: Execute tests (Unit and Feature tests) via PHPUnit
run: vendor/bin/phpunit run: vendor/bin/phpunit
- name: Run phpcs - name: Run phpcs

View file

@ -24,16 +24,6 @@ class ParseCachedWebMentions extends Command
*/ */
protected $description = 'Re-parse the webmention’s cached HTML'; protected $description = 'Re-parse the webmention’s cached HTML';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/** /**
* Execute the console command. * Execute the console command.
* *
@ -41,15 +31,15 @@ class ParseCachedWebMentions extends Command
*/ */
public function handle(FileSystem $filesystem) public function handle(FileSystem $filesystem)
{ {
$HTMLfiles = $filesystem->allFiles(storage_path() . '/HTML'); $htmlFiles = $filesystem->allFiles(storage_path() . '/HTML');
foreach ($HTMLfiles as $file) { foreach ($htmlFiles as $file) {
if ($file->getExtension() != 'backup') { //we don’t want to parse.backup files if ($file->getExtension() !== 'backup') { //we don’t want to parse `.backup` files
$filepath = $file->getPathname(); $filepath = $file->getPathname();
$this->info('Loading HTML from: ' . $filepath); $this->info('Loading HTML from: ' . $filepath);
$html = $filesystem->get($filepath); $html = $filesystem->get($filepath);
$url = $this->URLFromFilename($filepath); $url = $this->urlFromFilename($filepath);
$microformats = \Mf2\parse($html, $url);
$webmention = WebMention::where('source', $url)->firstOrFail(); $webmention = WebMention::where('source', $url)->firstOrFail();
$microformats = \Mf2\parse($html, $url);
$webmention->mf2 = json_encode($microformats); $webmention->mf2 = json_encode($microformats);
$webmention->save(); $webmention->save();
$this->info('Saved the microformats to the database.'); $this->info('Saved the microformats to the database.');
@ -63,12 +53,12 @@ class ParseCachedWebMentions extends Command
* @param string * @param string
* @return string * @return string
*/ */
private function URLFromFilename(string $filepath): string private function urlFromFilename(string $filepath): string
{ {
$dir = mb_substr($filepath, mb_strlen(storage_path() . '/HTML/')); $dir = mb_substr($filepath, mb_strlen(storage_path() . '/HTML/'));
$url = str_replace(['http/', 'https/'], ['http://', 'https://'], $dir); $url = str_replace(['http/', 'https/'], ['http://', 'https://'], $dir);
if (mb_substr($url, -10) == 'index.html') { if (mb_substr($url, -10) === 'index.html') {
$url = mb_substr($url, 0, mb_strlen($url) - 10); $url = mb_substr($url, 0, -10);
} }
return $url; return $url;

View file

@ -7,16 +7,10 @@ namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Article; use App\Models\Article;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View; use Illuminate\View\View;
class ArticlesController extends Controller class ArticlesController extends Controller
{ {
/**
* List the articles that can be edited.
*
* @return \Illuminate\View\View
*/
public function index(): View public function index(): View
{ {
$posts = Article::select('id', 'title', 'published')->orderBy('id', 'desc')->get(); $posts = Article::select('id', 'title', 'published')->orderBy('id', 'desc')->get();
@ -24,11 +18,6 @@ class ArticlesController extends Controller
return view('admin.articles.index', ['posts' => $posts]); return view('admin.articles.index', ['posts' => $posts]);
} }
/**
* Show the new article form.
*
* @return \Illuminate\View\View
*/
public function create(): View public function create(): View
{ {
$message = session('message'); $message = session('message');
@ -36,11 +25,6 @@ class ArticlesController extends Controller
return view('admin.articles.create', ['message' => $message]); return view('admin.articles.create', ['message' => $message]);
} }
/**
* Process an incoming request for a new article and save it.
*
* @return \Illuminate\Http\RedirectResponse
*/
public function store(): RedirectResponse public function store(): RedirectResponse
{ {
//if a `.md` is attached use that for the main content. //if a `.md` is attached use that for the main content.
@ -49,42 +33,21 @@ class ArticlesController extends Controller
$content = $file->fread($file->getSize()); $content = $file->fread($file->getSize());
} }
$main = $content ?? request()->input('main'); $main = $content ?? request()->input('main');
$article = Article::create( Article::create([
[
'url' => request()->input('url'), 'url' => request()->input('url'),
'title' => request()->input('title'), 'title' => request()->input('title'),
'main' => $main, 'main' => $main,
'published' => request()->input('published') ?? 0, 'published' => request()->input('published') ?? 0,
] ]);
);
return redirect('/admin/blog'); return redirect('/admin/blog');
} }
/** public function edit(Article $article): View
* Show the edit form for an existing article.
*
* @param int $articleId
* @return \Illuminate\View\View
*/
public function edit(int $articleId): View
{ {
$post = Article::select( return view('admin.articles.edit', ['article' => $article]);
'title',
'main',
'url',
'published'
)->where('id', $articleId)->get();
return view('admin.articles.edit', ['id' => $articleId, 'post' => $post]);
} }
/**
* Process an incoming request to edit an article.
*
* @param int $articleId
* @return \Illuminate\Http\RedirectResponse
*/
public function update(int $articleId): RedirectResponse public function update(int $articleId): RedirectResponse
{ {
$article = Article::find($articleId); $article = Article::find($articleId);
@ -97,12 +60,6 @@ class ArticlesController extends Controller
return redirect('/admin/blog'); return redirect('/admin/blog');
} }
/**
* Process a request to delete an aricle.
*
* @param int $articleId
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(int $articleId): RedirectResponse public function destroy(int $articleId): RedirectResponse
{ {
Article::where('id', $articleId)->delete(); Article::where('id', $articleId)->delete();

View file

@ -59,18 +59,14 @@ class ArticlesController extends Controller
* We only have the ID, work out post title, year and month * We only have the ID, work out post title, year and month
* and redirect to it. * and redirect to it.
* *
* @param int $idFromUrl * @param string $idFromUrl
* @return RedirectResponse * @return RedirectResponse
*/ */
public function onlyIdInUrl(int $idFromUrl): RedirectResponse public function onlyIdInUrl(string $idFromUrl): RedirectResponse
{ {
$realId = resolve(Numbers::class)->b60tonum((string) $idFromUrl); $realId = resolve(Numbers::class)->b60tonum($idFromUrl);
try {
$article = Article::findOrFail($realId); $article = Article::findOrFail($realId);
} catch (ModelNotFoundException $exception) {
abort(404);
}
return redirect($article->link); return redirect($article->link);
} }

View file

@ -18,19 +18,15 @@ class CSPHeader
{ {
// headers have to be single-line strings, // headers have to be single-line strings,
// so we concat multiple lines // so we concat multiple lines
// phpcs:disable // phpcs:disable Generic.Files.LineLength.TooLong
return $next($request) return $next($request)
->header( ->header(
'Content-Security-Policy', 'Content-Security-Policy',
"default-src 'self'; " . "default-src 'self'; " .
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://api.mapbox.com https://api.tiles.mapbox.com blob:; " . "style-src 'self' cloud.typography.com jonnybarnes.uk; " .
"style-src 'self' 'unsafe-inline' https://api.mapbox.com https://api.tiles.mapbox.com cloud.typography.com jonnybarnes.uk; " . "img-src 'self' data: blob: https://pbs.twimg.com https://jbuk-media.s3-eu-west-1.amazonaws.com https://jbuk-media-dev.s3-eu-west-1.amazonaws.com https://secure.gravatar.com https://graph.facebook.com *.fbcdn.net https://*.cdninstagram.com https://*.4sqi.net https://upload.wikimedia.org; " .
"img-src 'self' data: blob: https://pbs.twimg.com https://api.mapbox.com https://*.tiles.mapbox.com https://jbuk-media.s3-eu-west-1.amazonaws.com https://jbuk-media-dev.s3-eu-west-1.amazonaws.com https://secure.gravatar.com https://graph.facebook.com *.fbcdn.net https://*.cdninstagram.com https://*.4sqi.net https://upload.wikimedia.org; " .
"font-src 'self' data:; " . "font-src 'self' data:; " .
"connect-src 'self' https://api.mapbox.com https://*.tiles.mapbox.com https://events.mapbox.com data: blob:; " .
"worker-src 'self' blob:; " .
"frame-src 'self' https://www.youtube.com blob:; " . "frame-src 'self' https://www.youtube.com blob:; " .
'child-src blob:; ' .
'upgrade-insecure-requests; ' . 'upgrade-insecure-requests; ' .
'block-all-mixed-content; ' . 'block-all-mixed-content; ' .
'report-to csp-endpoint; ' . 'report-to csp-endpoint; ' .
@ -43,6 +39,6 @@ class CSPHeader
"'max-age': 10886400" . "'max-age': 10886400" .
'}' '}'
); );
// phpcs:enable // phpcs:enable Generic.Files.LineLength.TooLong
} }
} }

View file

@ -41,11 +41,13 @@ class SaveProfileImage implements ShouldQueue
{ {
try { try {
$author = $authorship->findAuthor($this->microformats); $author = $authorship->findAuthor($this->microformats);
} catch (AuthorshipParserException $e) { } catch (AuthorshipParserException) {
return; return null;
} }
$photo = Arr::get($author, 'properties.photo.0'); $photo = Arr::get($author, 'properties.photo.0');
$home = Arr::get($author, 'properties.url.0'); $home = Arr::get($author, 'properties.url.0');
//dont save pbs.twimg.com links //dont save pbs.twimg.com links
if ( if (
$photo $photo
@ -53,16 +55,18 @@ class SaveProfileImage implements ShouldQueue
&& parse_url($photo, PHP_URL_HOST) != 'twitter.com' && parse_url($photo, PHP_URL_HOST) != 'twitter.com'
) { ) {
$client = resolve(Client::class); $client = resolve(Client::class);
try { try {
$response = $client->get($photo); $response = $client->get($photo);
$image = $response->getBody(); $image = $response->getBody();
} catch (RequestException $e) { } catch (RequestException) {
// we are opening and reading the default image so that // we are opening and reading the default image so that
$default = public_path() . '/assets/profile-images/default-image'; $default = public_path() . '/assets/profile-images/default-image';
$handle = fopen($default, 'rb'); $handle = fopen($default, 'rb');
$image = fread($handle, filesize($default)); $image = fread($handle, filesize($default));
fclose($handle); fclose($handle);
} }
$path = public_path() . '/assets/profile-images/' . parse_url($home, PHP_URL_HOST) . '/image'; $path = public_path() . '/assets/profile-images/' . parse_url($home, PHP_URL_HOST) . '/image';
$parts = explode('/', $path); $parts = explode('/', $path);
$name = array_pop($parts); $name = array_pop($parts);

View file

@ -6,7 +6,10 @@ namespace App\Jobs;
use App\Models\Note; use App\Models\Note;
use GuzzleHttp\Client; use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Header;
use GuzzleHttp\Psr7\Uri; use GuzzleHttp\Psr7\Uri;
use GuzzleHttp\Psr7\UriResolver;
use GuzzleHttp\Psr7\Utils;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
@ -65,14 +68,14 @@ class SendWebMentions implements ShouldQueue
* @param string $url * @param string $url
* @return string|null * @return string|null
*/ */
public function discoverWebmentionEndpoint(string $url) public function discoverWebmentionEndpoint(string $url): ?string
{ {
//let’s not send webmentions to myself //let’s not send webmentions to myself
if (parse_url($url, PHP_URL_HOST) == config('app.longurl')) { if (parse_url($url, PHP_URL_HOST) === config('app.longurl')) {
return; return null;
} }
if (Str::startsWith($url, '/notes/tagged/')) { if (Str::startsWith($url, '/notes/tagged/')) {
return; return null;
} }
$endpoint = null; $endpoint = null;
@ -80,7 +83,7 @@ class SendWebMentions implements ShouldQueue
$guzzle = resolve(Client::class); $guzzle = resolve(Client::class);
$response = $guzzle->get($url); $response = $guzzle->get($url);
//check HTTP Headers for webmention endpoint //check HTTP Headers for webmention endpoint
$links = \GuzzleHttp\Psr7\parse_header($response->getHeader('Link')); $links = Header::parse($response->getHeader('Link'));
foreach ($links as $link) { foreach ($links as $link) {
if (mb_stristr($link['rel'], 'webmention')) { if (mb_stristr($link['rel'], 'webmention')) {
return $this->resolveUri(trim($link[0], '<>'), $url); return $this->resolveUri(trim($link[0], '<>'), $url);
@ -110,7 +113,7 @@ class SendWebMentions implements ShouldQueue
*/ */
public function getLinks(?string $html): array public function getLinks(?string $html): array
{ {
if ($html == '' || is_null($html)) { if ($html === '' || is_null($html)) {
return []; return [];
} }
@ -136,13 +139,13 @@ class SendWebMentions implements ShouldQueue
*/ */
public function resolveUri(string $url, string $base): string public function resolveUri(string $url, string $base): string
{ {
$endpoint = \GuzzleHttp\Psr7\uri_for($url); $endpoint = Utils::uriFor($url);
if ($endpoint->getScheme() != '') { if ($endpoint->getScheme() !== '') {
return (string) $endpoint; return (string) $endpoint;
} }
return (string) Uri::resolve( return (string) UriResolver::resolve(
\GuzzleHttp\Psr7\uri_for($base), Utils::uriFor($base),
$endpoint $endpoint
); );
} }

View file

@ -7,6 +7,7 @@ namespace App\Models;
use Cviebrock\EloquentSluggable\Sluggable; use Cviebrock\EloquentSluggable\Sluggable;
use Eloquent; use Eloquent;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
@ -58,6 +59,7 @@ use Spatie\CommonMarkHighlighter\IndentedCodeRenderer;
*/ */
class Article extends Model class Article extends Model
{ {
use HasFactory;
use Sluggable; use Sluggable;
use SoftDeletes; use SoftDeletes;

View file

@ -6,6 +6,7 @@ namespace App\Models;
use Eloquent; use Eloquent;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
@ -35,6 +36,8 @@ use Illuminate\Support\Carbon;
*/ */
class Contact extends Model class Contact extends Model
{ {
use HasFactory;
/** /**
* The database table used by the model. * The database table used by the model.
* *

View file

@ -6,6 +6,7 @@ namespace App\Models;
use Eloquent; use Eloquent;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
@ -41,6 +42,8 @@ use Illuminate\Support\Str;
*/ */
class Media extends Model class Media extends Model
{ {
use HasFactory;
/** /**
* The table associated with the model. * The table associated with the model.
* *
@ -62,7 +65,7 @@ class Media extends Model
*/ */
public function note(): BelongsTo public function note(): BelongsTo
{ {
return $this->belongsTo('App\Models\Note'); return $this->belongsTo(Note::class);
} }
/** /**
@ -118,7 +121,7 @@ class Media extends Model
$filenameParts = explode('.', $path); $filenameParts = explode('.', $path);
array_pop($filenameParts); array_pop($filenameParts);
return ltrim(array_reduce($filenameParts, function ($carry, $item) { return ltrim(array_reduce($filenameParts, static function ($carry, $item) {
return $carry . '.' . $item; return $carry . '.' . $item;
}, ''), '.'); }, ''), '.');
} }

View file

@ -5,14 +5,15 @@ declare(strict_types=1);
namespace App\Models; namespace App\Models;
use App\Exceptions\TwitterContentException; use App\Exceptions\TwitterContentException;
use Barryvdh\LaravelIdeHelper\Eloquent;
use Codebird\Codebird; use Codebird\Codebird;
use Eloquent;
use Exception; use Exception;
use GuzzleHttp\Client; use GuzzleHttp\Client;
use Illuminate\Database\Eloquent\Relations\{BelongsTo, BelongsToMany, HasMany, MorphMany}; use Illuminate\Database\Eloquent\Relations\{BelongsTo, BelongsToMany, HasMany, MorphMany};
use Illuminate\Database\Eloquent\{Builder, Collection, Factories\HasFactory, Model, SoftDeletes}; use Illuminate\Database\Eloquent\{Builder, Collection, Factories\HasFactory, Model, SoftDeletes};
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use JetBrains\PhpStorm\ArrayShape;
use Jonnybarnes\IndieWeb\Numbers; use Jonnybarnes\IndieWeb\Numbers;
use Laravel\Scout\Searchable; use Laravel\Scout\Searchable;
use League\CommonMark\Block\Element\{FencedCode, IndentedCode}; use League\CommonMark\Block\Element\{FencedCode, IndentedCode};
@ -20,6 +21,11 @@ use League\CommonMark\Extension\Autolink\AutolinkExtension;
use League\CommonMark\{CommonMarkConverter, Environment}; use League\CommonMark\{CommonMarkConverter, Environment};
use Normalizer; use Normalizer;
use Spatie\CommonMarkHighlighter\{FencedCodeRenderer, IndentedCodeRenderer}; use Spatie\CommonMarkHighlighter\{FencedCodeRenderer, IndentedCodeRenderer};
use App\Models\Tag;
use App\Models\MicropubClient;
use App\Models\WebMention;
use App\Models\Place;
use App\Models\Media;
/** /**
* App\Models\Note. * App\Models\Note.
@ -102,7 +108,7 @@ class Note extends Model
/** /**
* This variable is used to keep track of contacts in a note. * This variable is used to keep track of contacts in a note.
*/ */
protected $contacts; protected ?array $contacts;
/** /**
* Set our contacts variable to null. * Set our contacts variable to null.
@ -145,9 +151,9 @@ class Note extends Model
* *
* @return BelongsToMany * @return BelongsToMany
*/ */
public function tags() public function tags(): BelongsToMany
{ {
return $this->belongsToMany('App\Models\Tag'); return $this->belongsToMany(Tag::class);
} }
/** /**
@ -155,9 +161,9 @@ class Note extends Model
* *
* @return BelongsTo * @return BelongsTo
*/ */
public function client() public function client(): BelongsTo
{ {
return $this->belongsTo('App\Models\MicropubClient', 'client_id', 'client_url'); return $this->belongsTo(MicropubClient::class, 'client_id', 'client_url');
} }
/** /**
@ -165,9 +171,9 @@ class Note extends Model
* *
* @return MorphMany * @return MorphMany
*/ */
public function webmentions() public function webmentions(): MorphMany
{ {
return $this->morphMany('App\Models\WebMention', 'commentable'); return $this->morphMany(WebMention::class, 'commentable');
} }
/** /**
@ -175,9 +181,9 @@ class Note extends Model
* *
* @return BelongsTo * @return BelongsTo
*/ */
public function place() public function place(): BelongsTo
{ {
return $this->belongsTo('App\Models\Place'); return $this->belongsTo(Place::class);
} }
/** /**
@ -185,9 +191,9 @@ class Note extends Model
* *
* @return HasMany * @return HasMany
*/ */
public function media() public function media(): HasMany
{ {
return $this->hasMany('App\Models\Media'); return $this->hasMany(Media::class);
} }
/** /**
@ -195,6 +201,7 @@ class Note extends Model
* *
* @return array * @return array
*/ */
#[ArrayShape(['note' => "null|string"])]
public function toSearchableArray(): array public function toSearchableArray(): array
{ {
return [ return [
@ -207,7 +214,7 @@ class Note extends Model
* *
* @param string|null $value * @param string|null $value
*/ */
public function setNoteAttribute(?string $value) public function setNoteAttribute(?string $value): void
{ {
if ($value !== null) { if ($value !== null) {
$normalized = normalizer_normalize($value, Normalizer::FORM_C); $normalized = normalizer_normalize($value, Normalizer::FORM_C);
@ -253,13 +260,13 @@ class Note extends Model
$note = $this->note; $note = $this->note;
foreach ($this->media as $media) { foreach ($this->media as $media) {
if ($media->type == 'image') { if ($media->type === 'image') {
$note .= '<img src="' . $media->url . '" alt="">'; $note .= '<img src="' . $media->url . '" alt="">';
} }
if ($media->type == 'audio') { if ($media->type === 'audio') {
$note .= '<audio src="' . $media->url . '">'; $note .= '<audio src="' . $media->url . '">';
} }
if ($media->type == 'video') { if ($media->type === 'video') {
$note .= '<video src="' . $media->url . '">'; $note .= '<video src="' . $media->url . '">';
} }
} }
@ -398,7 +405,10 @@ class Note extends Model
*/ */
public function getTwitterAttribute(): ?object public function getTwitterAttribute(): ?object
{ {
if ($this->in_reply_to == null || mb_substr($this->in_reply_to, 0, 20, 'UTF-8') !== 'https://twitter.com/') { if (
$this->in_reply_to === null ||
!$this->isTwitterLink($this->in_reply_to)
) {
return null; return null;
} }
@ -569,7 +579,7 @@ class Note extends Model
public function autoLinkHashtag(string $note): string public function autoLinkHashtag(string $note): string
{ {
return preg_replace_callback( return preg_replace_callback(
'/#([^\s]*)\b/', '/#([^\s[:punct:]]+)/',
function ($matches) { function ($matches) {
return '<a rel="tag" class="p-category" href="/notes/tagged/' return '<a rel="tag" class="p-category" href="/notes/tagged/'
. Tag::normalize($matches[1]) . '">#' . Tag::normalize($matches[1]) . '">#'
@ -660,4 +670,9 @@ class Note extends Model
return $address; return $address;
}); });
} }
private function isTwitterLink(string $inReplyTo): bool
{
return str_starts_with($inReplyTo, 'https://twitter.com/');
}
} }

View file

@ -11,6 +11,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Str;
/** /**
* App\Models\Tag. * App\Models\Tag.
@ -50,7 +51,7 @@ class Tag extends Model
*/ */
public function notes() public function notes()
{ {
return $this->belongsToMany('App\Models\Note'); return $this->belongsToMany(Note::class);
} }
/** /**
@ -82,13 +83,6 @@ class Tag extends Model
*/ */
public static function normalize(string $tag): string public static function normalize(string $tag): string
{ {
return mb_strtolower( return Str::slug($tag);
preg_replace(
'/&([a-z]{1,2})(acute|cedil|circ|grave|lig|orn|ring|slash|th|tilde|uml|caron);/i',
'$1',
htmlentities($tag)
),
'UTF-8'
);
} }
} }

View file

@ -8,6 +8,7 @@ use App\Traits\FilterHtml;
use Codebird\Codebird; use Codebird\Codebird;
use Eloquent; use Eloquent;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo; use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Filesystem\Filesystem; use Illuminate\Filesystem\Filesystem;
@ -55,6 +56,7 @@ use Jonnybarnes\WebmentionsParser\Exceptions\AuthorshipParserException;
class WebMention extends Model class WebMention extends Model
{ {
use FilterHtml; use FilterHtml;
use HasFactory;
/** /**
* The database table used by the model. * The database table used by the model.

View file

@ -9,7 +9,7 @@
], ],
"license": "CC0-1.0", "license": "CC0-1.0",
"require": { "require": {
"php": "^7.4|^8.0", "php": "^8.0",
"ext-intl": "*", "ext-intl": "*",
"ext-json": "*", "ext-json": "*",
"ext-dom": "*", "ext-dom": "*",
@ -31,7 +31,7 @@
"league/commonmark": "^1.0", "league/commonmark": "^1.0",
"league/flysystem-aws-s3-v3": "^1.0", "league/flysystem-aws-s3-v3": "^1.0",
"mf2/mf2": "~0.3", "mf2/mf2": "~0.3",
"pmatseykanets/laravel-scout-postgres": "dev-php8", "pmatseykanets/laravel-scout-postgres": "^7.3",
"predis/predis": "~1.0", "predis/predis": "~1.0",
"spatie/browsershot": "~3.0", "spatie/browsershot": "~3.0",
"spatie/commonmark-highlighter": "^2.0", "spatie/commonmark-highlighter": "^2.0",
@ -48,6 +48,7 @@
"nunomaduro/collision": "^5.0", "nunomaduro/collision": "^5.0",
"phpunit/php-code-coverage": "^9.2", "phpunit/php-code-coverage": "^9.2",
"phpunit/phpunit": "^9.0", "phpunit/phpunit": "^9.0",
"spatie/laravel-ray": "^1.12",
"vimeo/psalm": "^4.0" "vimeo/psalm": "^4.0"
}, },
"config": { "config": {
@ -91,11 +92,5 @@
"test": [ "test": [
"vendor/bin/phpunit --stop-on-failure" "vendor/bin/phpunit --stop-on-failure"
] ]
},
"repositories": [
{
"type": "vcs",
"url": "https://github.com/jonnybarnes/laravel-scout-postgres"
} }
]
} }

3062
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,34 @@
<?php
namespace Database\Factories;
use App\Models\Article;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Carbon;
class ArticleFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = Article::class;
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
'titleurl' => $this->faker->slug(3),
'title' => $this->faker->words(3, true),
'main' => $this->faker->paragraphs(4, true),
'published' => 1,
'created_at' => Carbon::now()->toDateTimeString(),
'updated_at' => Carbon::now()->toDateTimeString(),
];
}
}

View file

@ -0,0 +1,35 @@
<?php
namespace Database\Factories;
use App\Models\Contact;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Carbon;
class ContactFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = Contact::class;
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
'nick' => mb_strtolower($this->faker->firstName),
'name' => $this->faker->name(),
'homepage' => $this->faker->url,
'twitter' => mb_strtolower($this->faker->firstName),
'facebook' => $this->faker->randomNumber(5),
'created_at' => Carbon::now()->toDateTimeString(),
'updated_at' => Carbon::now()->toDateTimeString(),
];
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace Database\Factories;
use App\Models\Media;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Carbon;
class MediaFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = Media::class;
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
'path' => 'media/' . $this->faker->uuid . '.jpg',
'type' => 'image',
'created_at' => Carbon::now()->toDateTimeString(),
'updated_at' => Carbon::now()->toDateTimeString(),
];
}
}

Some files were not shown because too many files have changed in this diff Show more