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_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=jbuktest
DB_DATABASE=jbukdev_testing
DB_USERNAME=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:
postgres:
image: postgres:13.1
image: postgres:13.4
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: jbuktest
POSTGRES_DB: jbukdev_testing
ports:
- 5432:5432
@ -36,15 +36,13 @@ jobs:
- name: Copy .env
run: php -r "file_exists('.env') || copy('.env.github', '.env');"
- 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
run: php artisan key:generate
- name: Setup directory permissions
run: chmod -R 777 storage bootstrap/cache
- name: Setup test database
run: |
php artisan migrate
php artisan db:seed
run: php artisan migrate
- name: Execute tests (Unit and Feature tests) via PHPUnit
run: vendor/bin/phpunit
- name: Run phpcs

View file

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

View file

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

View file

@ -18,19 +18,15 @@ class CSPHeader
{
// headers have to be single-line strings,
// so we concat multiple lines
// phpcs:disable
// phpcs:disable Generic.Files.LineLength.TooLong
return $next($request)
->header(
'Content-Security-Policy',
"default-src 'self'; " .
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://api.mapbox.com https://api.tiles.mapbox.com blob:; " .
"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://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; " .
"style-src 'self' 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; " .
"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:; " .
'child-src blob:; ' .
'upgrade-insecure-requests; ' .
'block-all-mixed-content; ' .
'report-to csp-endpoint; ' .
@ -43,6 +39,6 @@ class CSPHeader
"'max-age': 10886400" .
'}'
);
// phpcs:enable
// phpcs:enable Generic.Files.LineLength.TooLong
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -6,6 +6,7 @@ namespace App\Models;
use Eloquent;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Carbon;
@ -41,6 +42,8 @@ use Illuminate\Support\Str;
*/
class Media extends Model
{
use HasFactory;
/**
* The table associated with the model.
*
@ -62,7 +65,7 @@ class Media extends Model
*/
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);
array_pop($filenameParts);
return ltrim(array_reduce($filenameParts, function ($carry, $item) {
return ltrim(array_reduce($filenameParts, static function ($carry, $item) {
return $carry . '.' . $item;
}, ''), '.');
}

View file

@ -5,14 +5,15 @@ declare(strict_types=1);
namespace App\Models;
use App\Exceptions\TwitterContentException;
use Barryvdh\LaravelIdeHelper\Eloquent;
use Codebird\Codebird;
use Eloquent;
use Exception;
use GuzzleHttp\Client;
use Illuminate\Database\Eloquent\Relations\{BelongsTo, BelongsToMany, HasMany, MorphMany};
use Illuminate\Database\Eloquent\{Builder, Collection, Factories\HasFactory, Model, SoftDeletes};
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Cache;
use JetBrains\PhpStorm\ArrayShape;
use Jonnybarnes\IndieWeb\Numbers;
use Laravel\Scout\Searchable;
use League\CommonMark\Block\Element\{FencedCode, IndentedCode};
@ -20,6 +21,11 @@ use League\CommonMark\Extension\Autolink\AutolinkExtension;
use League\CommonMark\{CommonMarkConverter, Environment};
use Normalizer;
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.
@ -102,12 +108,12 @@ class Note extends Model
/**
* This variable is used to keep track of contacts in a note.
*/
protected $contacts;
protected ?array $contacts;
/**
* Set our contacts variable to null.
*
* @param array $attributes
* @param array $attributes
*/
public function __construct(array $attributes = [])
{
@ -145,9 +151,9 @@ class Note extends Model
*
* @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
*/
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
*/
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
*/
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
*/
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
*/
#[ArrayShape(['note' => "null|string"])]
public function toSearchableArray(): array
{
return [
@ -207,7 +214,7 @@ class Note extends Model
*
* @param string|null $value
*/
public function setNoteAttribute(?string $value)
public function setNoteAttribute(?string $value): void
{
if ($value !== null) {
$normalized = normalizer_normalize($value, Normalizer::FORM_C);
@ -253,13 +260,13 @@ class Note extends Model
$note = $this->note;
foreach ($this->media as $media) {
if ($media->type == 'image') {
if ($media->type === 'image') {
$note .= '<img src="' . $media->url . '" alt="">';
}
if ($media->type == 'audio') {
if ($media->type === 'audio') {
$note .= '<audio src="' . $media->url . '">';
}
if ($media->type == 'video') {
if ($media->type === 'video') {
$note .= '<video src="' . $media->url . '">';
}
}
@ -398,7 +405,10 @@ class Note extends Model
*/
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;
}
@ -569,7 +579,7 @@ class Note extends Model
public function autoLinkHashtag(string $note): string
{
return preg_replace_callback(
'/#([^\s]*)\b/',
'/#([^\s[:punct:]]+)/',
function ($matches) {
return '<a rel="tag" class="p-category" href="/notes/tagged/'
. Tag::normalize($matches[1]) . '">#'
@ -660,4 +670,9 @@ class Note extends Model
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\Relations\BelongsToMany;
use Illuminate\Support\Carbon;
use Illuminate\Support\Str;
/**
* App\Models\Tag.
@ -50,7 +51,7 @@ class Tag extends Model
*/
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
{
return mb_strtolower(
preg_replace(
'/&([a-z]{1,2})(acute|cedil|circ|grave|lig|orn|ring|slash|th|tilde|uml|caron);/i',
'$1',
htmlentities($tag)
),
'UTF-8'
);
return Str::slug($tag);
}
}

View file

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

View file

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

3064
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