Merge pull request #244 from jonnybarnes/develop
MTM: Various dependency updates and new deployment code
This commit is contained in:
commit
3f891b580c
94 changed files with 14734 additions and 10416 deletions
|
|
@ -16,3 +16,6 @@ indent_size = 4
|
||||||
[Makefile]
|
[Makefile]
|
||||||
indent_style = tab
|
indent_style = tab
|
||||||
tab_width = 4
|
tab_width = 4
|
||||||
|
|
||||||
|
[yml]
|
||||||
|
indent_size = 2
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
17
.env.travis
17
.env.travis
|
|
@ -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
|
|
||||||
142
.github/workflows/deploy.yml
vendored
Normal file
142
.github/workflows/deploy.yml
vendored
Normal file
|
|
@ -0,0 +1,142 @@
|
||||||
|
name: Deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
name: Deploy
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: Hetzner
|
||||||
|
env:
|
||||||
|
repository: 'jonnybarnes/jonnybarnes.uk'
|
||||||
|
newReleaseName: '${{ github.run_id }}'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: 🌍 Set Environment Variables
|
||||||
|
run: |
|
||||||
|
echo "releasesDir=${{ secrets.DEPLOYMENT_BASE_DIR }}/releases" >> $GITHUB_ENV
|
||||||
|
echo "persistentDir=${{ secrets.DEPLOYMENT_BASE_DIR }}/persistent" >> $GITHUB_ENV
|
||||||
|
echo "currentDir=${{ secrets.DEPLOYMENT_BASE_DIR }}/current" >> $GITHUB_ENV
|
||||||
|
- name: 🌎 Set Environment Variables Part 2
|
||||||
|
run: |
|
||||||
|
echo "newReleaseDir=${{ env.releasesDir }}/${{ env.newReleaseName }}" >> $GITHUB_ENV
|
||||||
|
- name: 🔄 Clone Repository
|
||||||
|
uses: appleboy/ssh-action@master
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.DEPLOYMENT_HOST }}
|
||||||
|
port: ${{ secrets.DEPLOYMENT_PORT }}
|
||||||
|
username: ${{ secrets.DEPLOYMENT_USER }}
|
||||||
|
key: ${{ secrets.DEPLOYMENT_KEY }}
|
||||||
|
script: |
|
||||||
|
[ -d ${{ env.releasesDir }} ] || mkdir ${{ env.releasesDir }}
|
||||||
|
[ -d ${{ env.persistentDir }} ] || mkdir ${{ env.persistentDir }}
|
||||||
|
[ -d ${{ env.persistentDir }}/storage ] || mkdir ${{ env.persistentDir }}/storage
|
||||||
|
|
||||||
|
cd ${{ env.releasesDir }}
|
||||||
|
|
||||||
|
# Create new release directory
|
||||||
|
mkdir ${{ env.newReleaseDir }}
|
||||||
|
|
||||||
|
# Clone app
|
||||||
|
git clone --depth 1 --branch main https://github.com/${{ env.repository }} ${{ env.newReleaseName }}
|
||||||
|
|
||||||
|
# Mark release
|
||||||
|
cd ${{ env.newReleaseDir }}
|
||||||
|
echo "${{ env.newReleaseName }}" > public/release-name.txt
|
||||||
|
|
||||||
|
# Fix cache directory permissions
|
||||||
|
sudo chown -R ${{ secrets.HTTP_USER }}:${{ secrets.HTTP_USER }} bootstrap/cache
|
||||||
|
|
||||||
|
- name: 🎵 Run Composer
|
||||||
|
uses: appleboy/ssh-action@master
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.DEPLOYMENT_HOST }}
|
||||||
|
port: ${{ secrets.DEPLOYMENT_PORT }}
|
||||||
|
username: ${{ secrets.DEPLOYMENT_USER }}
|
||||||
|
key: ${{ secrets.DEPLOYMENT_KEY }}
|
||||||
|
script: |
|
||||||
|
cd ${{ env.newReleaseDir }}
|
||||||
|
composer install --prefer-dist --no-scripts --no-dev --no-progress --optimize-autoloader --quiet --no-interaction
|
||||||
|
|
||||||
|
- name: 🔗 Update Symlinks
|
||||||
|
uses: appleboy/ssh-action@master
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.DEPLOYMENT_HOST }}
|
||||||
|
port: ${{ secrets.DEPLOYMENT_PORT }}
|
||||||
|
username: ${{ secrets.DEPLOYMENT_USER }}
|
||||||
|
key: ${{ secrets.DEPLOYMENT_KEY }}
|
||||||
|
script: |
|
||||||
|
# Import the environment config
|
||||||
|
cd ${{ env.newReleaseDir }};
|
||||||
|
ln -nfs ${{ secrets.DEPLOYMENT_BASE_DIR }}/.env .env;
|
||||||
|
|
||||||
|
# Remove the storage directory and replace with persistent data
|
||||||
|
rm -rf ${{ env.newReleaseDir }}/storage;
|
||||||
|
cd ${{ env.newReleaseDir }};
|
||||||
|
ln -nfs ${{ secrets.DEPLOYMENT_BASE_DIR }}/persistent/storage storage;
|
||||||
|
|
||||||
|
# Remove the public/profile-images directory and replace with persistent data
|
||||||
|
rm -rf ${{ env.newReleaseDir }}/public/assets/profile-images;
|
||||||
|
cd ${{ env.newReleaseDir }};
|
||||||
|
ln -nfs ${{ secrets.DEPLOYMENT_BASE_DIR }}/persistent/profile-images public/assets/profile-images;
|
||||||
|
|
||||||
|
# Add the persistent files data
|
||||||
|
cd ${{ env.newReleaseDir }};
|
||||||
|
ln -nfs ${{ secrets.DEPLOYMENT_BASE_DIR }}/persistent/files public/files;
|
||||||
|
|
||||||
|
# Add the persistent fonts data
|
||||||
|
cd ${{ env.newReleaseDir }};
|
||||||
|
ln -nfs ${{ secrets.DEPLOYMENT_BASE_DIR }}/persistent/fonts public/fonts;
|
||||||
|
|
||||||
|
- name: ✨ Optimize Installation
|
||||||
|
uses: appleboy/ssh-action@master
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.DEPLOYMENT_HOST }}
|
||||||
|
port: ${{ secrets.DEPLOYMENT_PORT }}
|
||||||
|
username: ${{ secrets.DEPLOYMENT_USER }}
|
||||||
|
key: ${{ secrets.DEPLOYMENT_KEY }}
|
||||||
|
script: |
|
||||||
|
cd ${{ env.newReleaseDir }};
|
||||||
|
sudo runuser -u ${{ secrets.HTTP_USER }} -- php artisan clear-compiled;
|
||||||
|
|
||||||
|
- name: 🙈 Migrate database
|
||||||
|
uses: appleboy/ssh-action@master
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.DEPLOYMENT_HOST }}
|
||||||
|
port: ${{ secrets.DEPLOYMENT_PORT }}
|
||||||
|
username: ${{ secrets.DEPLOYMENT_USER }}
|
||||||
|
key: ${{ secrets.DEPLOYMENT_KEY }}
|
||||||
|
script: |
|
||||||
|
cd ${{ env.newReleaseDir }}
|
||||||
|
sudo runuser -u ${{ secrets.HTTP_USER }} -- php artisan migrate --force
|
||||||
|
|
||||||
|
- name: 🙏 Bless release
|
||||||
|
uses: appleboy/ssh-action@master
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.DEPLOYMENT_HOST }}
|
||||||
|
port: ${{ secrets.DEPLOYMENT_PORT }}
|
||||||
|
username: ${{ secrets.DEPLOYMENT_USER }}
|
||||||
|
key: ${{ secrets.DEPLOYMENT_KEY }}
|
||||||
|
script: |
|
||||||
|
ln -nfs ${{ env.newReleaseDir }} ${{ env.currentDir }};
|
||||||
|
cd ${{ env.newReleaseDir }}
|
||||||
|
sudo runuser -u ${{ secrets.HTTP_USER }} -- php artisan horizon:terminate
|
||||||
|
sudo runuser -u ${{ secrets.HTTP_USER }} -- php artisan config:cache
|
||||||
|
sudo runuser -u ${{ secrets.HTTP_USER }} -- php artisan event:cache
|
||||||
|
sudo runuser -u ${{ secrets.HTTP_USER }} -- php artisan route:cache
|
||||||
|
sudo runuser -u ${{ secrets.HTTP_USER }} -- php artisan view:cache
|
||||||
|
|
||||||
|
sudo systemctl restart php-fpm.service
|
||||||
|
sudo systemctl restart jbuk-horizon.service
|
||||||
|
|
||||||
|
- name: 🚾 Clean up old releases
|
||||||
|
uses: appleboy/ssh-action@master
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.DEPLOYMENT_HOST }}
|
||||||
|
port: ${{ secrets.DEPLOYMENT_PORT }}
|
||||||
|
username: ${{ secrets.DEPLOYMENT_USER }}
|
||||||
|
key: ${{ secrets.DEPLOYMENT_KEY }}
|
||||||
|
script: |
|
||||||
|
fd '.+' ${{ env.releasesDir }} -d 1 | head -n -3 | xargs -d "\n" -I'{}' sudo chown -R ${{ secrets.DEPLOYMENT_USER }}:${{ secrets.DEPLOYMENT_USER }} {}/bootstrap/cache;
|
||||||
|
fd '.+' ${{ env.releasesDir }} -d 1 | head -n -3 | xargs -d "\n" -I'{}' rm -rf {};
|
||||||
12
.github/workflows/run-tests.yml
vendored
12
.github/workflows/run-tests.yml
vendored
|
|
@ -11,11 +11,11 @@ jobs:
|
||||||
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:12
|
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,18 +36,14 @@ 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
|
||||||
run: phpcs
|
run: phpcs
|
||||||
- name: Check for security vulnerabilities
|
|
||||||
run: php vendor/bin/security-checker security:check
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"extends": ["stylelint-config-standard", "stylelint-a11y/recommended"],
|
"extends": ["stylelint-config-standard"],
|
||||||
"rules": {
|
"rules": {
|
||||||
"indentation": 4
|
"indentation": 4
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
use SensioLabs\Security\SecurityChecker;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @codeCoverageIgnore
|
|
||||||
*/
|
|
||||||
class SecurityCheck extends Command
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The name and signature of the console command.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $signature = 'security:check';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The console command description.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $description = 'Run the SensioLab’s Security Check tool';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Security Checker intergation service.
|
|
||||||
*
|
|
||||||
* @var SecurityChecker
|
|
||||||
*/
|
|
||||||
protected $securityChecker;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new command instance.
|
|
||||||
*
|
|
||||||
* @param SecurityChecker $SecurityChecker
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function __construct(SecurityChecker $securityChecker)
|
|
||||||
{
|
|
||||||
parent::__construct();
|
|
||||||
|
|
||||||
$this->securityChecker = $securityChecker;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the console command.
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function handle(): int
|
|
||||||
{
|
|
||||||
$alerts = $this->securityChecker->check(base_path() . '/composer.lock');
|
|
||||||
if (count($alerts) === 0) {
|
|
||||||
$this->info('No security vulnerabilities found.');
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
$this->error('vulnerabilities found');
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -13,7 +13,6 @@ class Kernel extends ConsoleKernel
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $commands = [
|
protected $commands = [
|
||||||
Commands\SecurityCheck::class,
|
|
||||||
Commands\ParseCachedWebMentions::class,
|
Commands\ParseCachedWebMentions::class,
|
||||||
Commands\ReDownloadWebMentions::class,
|
Commands\ReDownloadWebMentions::class,
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ use App\Models\Place;
|
||||||
use App\Services\Micropub\{HCardService, HEntryService, UpdateService};
|
use App\Services\Micropub\{HCardService, HEntryService, UpdateService};
|
||||||
use App\Services\TokenService;
|
use App\Services\TokenService;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Lcobucci\JWT\Encoding\CannotDecodeContent;
|
||||||
use Lcobucci\JWT\Token\InvalidTokenStructure;
|
use Lcobucci\JWT\Token\InvalidTokenStructure;
|
||||||
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
|
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
|
||||||
use Monolog\Handler\StreamHandler;
|
use Monolog\Handler\StreamHandler;
|
||||||
|
|
@ -43,7 +44,7 @@ class MicropubController extends Controller
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$tokenData = $this->tokenService->validateToken(request()->input('access_token'));
|
$tokenData = $this->tokenService->validateToken(request()->input('access_token'));
|
||||||
} catch (RequiredConstraintsViolated | InvalidTokenStructure $exception) {
|
} catch (RequiredConstraintsViolated | InvalidTokenStructure | CannotDecodeContent $exception) {
|
||||||
$micropubResponses = new MicropubResponses();
|
$micropubResponses = new MicropubResponses();
|
||||||
|
|
||||||
return $micropubResponses->invalidTokenResponse();
|
return $micropubResponses->invalidTokenResponse();
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}, ''), '.');
|
}, ''), '.');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,12 +108,12 @@ 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.
|
||||||
*
|
*
|
||||||
* @param array $attributes
|
* @param array $attributes
|
||||||
*/
|
*/
|
||||||
public function __construct(array $attributes = [])
|
public function __construct(array $attributes = [])
|
||||||
{
|
{
|
||||||
|
|
@ -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/');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue