diff --git a/app/Console/Commands/CopyMediaToLocal.php b/app/Console/Commands/CopyMediaToLocal.php new file mode 100644 index 00000000..2e8d2bce --- /dev/null +++ b/app/Console/Commands/CopyMediaToLocal.php @@ -0,0 +1,69 @@ +path; + + $this->info('Processing: ' . $filename); + + // If the file is already saved locally skip to next one + if (Storage::disk('local')->exists('public/' . $filename)) { + $this->info('File already exists locally, skipping'); + + continue; + } + + // Copy the file from S3 to the local filesystem + if (! Storage::disk('s3')->exists($filename)) { + $this->error('File does not exist on S3'); + + continue; + } + $contents = Storage::disk('s3')->get($filename); + Storage::disk('local')->put('public/' . $filename, $contents); + + // Copy -medium and -small versions if they exist + $filenameParts = explode('.', $filename); + $extension = array_pop($filenameParts); + $basename = trim(implode('.', $filenameParts), '.'); + $mediumFilename = $basename . '-medium.' . $extension; + $smallFilename = $basename . '-small.' . $extension; + if (Storage::disk('s3')->exists($mediumFilename)) { + Storage::disk('local')->put('public/' . $mediumFilename, Storage::disk('s3')->get($mediumFilename)); + } + if (Storage::disk('s3')->exists($smallFilename)) { + Storage::disk('local')->put('public/' . $smallFilename, Storage::disk('s3')->get($smallFilename)); + } + } + } +} diff --git a/app/Http/Controllers/Admin/ContactsController.php b/app/Http/Controllers/Admin/ContactsController.php index 836c99cc..f29397e3 100644 --- a/app/Http/Controllers/Admin/ContactsController.php +++ b/app/Http/Controllers/Admin/ContactsController.php @@ -40,7 +40,7 @@ class ContactsController extends Controller */ public function store(): RedirectResponse { - $contact = new Contact(); + $contact = new Contact; $contact->name = request()->input('name'); $contact->nick = request()->input('nick'); $contact->homepage = request()->input('homepage'); @@ -79,7 +79,7 @@ class ContactsController extends Controller if (request()->hasFile('avatar') && (request()->input('homepage') != '')) { $dir = parse_url(request()->input('homepage'), PHP_URL_HOST); $destination = public_path() . '/assets/profile-images/' . $dir; - $filesystem = new Filesystem(); + $filesystem = new Filesystem; if ($filesystem->isDirectory($destination) === false) { $filesystem->makeDirectory($destination); } @@ -139,7 +139,7 @@ class ContactsController extends Controller } if ($avatar !== null) { $directory = public_path() . '/assets/profile-images/' . parse_url($contact->homepage, PHP_URL_HOST); - $filesystem = new Filesystem(); + $filesystem = new Filesystem; if ($filesystem->isDirectory($directory) === false) { $filesystem->makeDirectory($directory); } diff --git a/app/Http/Controllers/Admin/PasskeysController.php b/app/Http/Controllers/Admin/PasskeysController.php index 49ca481b..8325a59e 100644 --- a/app/Http/Controllers/Admin/PasskeysController.php +++ b/app/Http/Controllers/Admin/PasskeysController.php @@ -116,8 +116,8 @@ class PasskeysController extends Controller throw new WebAuthnException('No public key credential request options found'); } - $attestationStatementSupportManager = new AttestationStatementSupportManager(); - $attestationStatementSupportManager->add(new NoneAttestationStatementSupport()); + $attestationStatementSupportManager = new AttestationStatementSupportManager; + $attestationStatementSupportManager->add(new NoneAttestationStatementSupport); $webauthnSerializer = (new WebauthnSerializerFactory( $attestationStatementSupportManager @@ -133,12 +133,12 @@ class PasskeysController extends Controller throw new WebAuthnException('Invalid response type'); } - $algorithmManager = new Manager(); - $algorithmManager->add(new Ed25519()); - $algorithmManager->add(new ES256()); - $algorithmManager->add(new RS256()); + $algorithmManager = new Manager; + $algorithmManager->add(new Ed25519); + $algorithmManager->add(new ES256); + $algorithmManager->add(new RS256); - $ceremonyStepManagerFactory = new CeremonyStepManagerFactory(); + $ceremonyStepManagerFactory = new CeremonyStepManagerFactory; $ceremonyStepManagerFactory->setAlgorithmManager($algorithmManager); $ceremonyStepManagerFactory->setAttestationStatementSupportManager( $attestationStatementSupportManager @@ -206,8 +206,8 @@ class PasskeysController extends Controller ], 400); } - $attestationStatementSupportManager = new AttestationStatementSupportManager(); - $attestationStatementSupportManager->add(new NoneAttestationStatementSupport()); + $attestationStatementSupportManager = new AttestationStatementSupportManager; + $attestationStatementSupportManager->add(new NoneAttestationStatementSupport); $webauthnSerializer = (new WebauthnSerializerFactory( $attestationStatementSupportManager @@ -240,15 +240,15 @@ class PasskeysController extends Controller 'json' ); - $algorithmManager = new Manager(); - $algorithmManager->add(new Ed25519()); - $algorithmManager->add(new ES256()); - $algorithmManager->add(new RS256()); + $algorithmManager = new Manager; + $algorithmManager->add(new Ed25519); + $algorithmManager->add(new ES256); + $algorithmManager->add(new RS256); - $attestationStatementSupportManager = new AttestationStatementSupportManager(); - $attestationStatementSupportManager->add(new NoneAttestationStatementSupport()); + $attestationStatementSupportManager = new AttestationStatementSupportManager; + $attestationStatementSupportManager->add(new NoneAttestationStatementSupport); - $ceremonyStepManagerFactory = new CeremonyStepManagerFactory(); + $ceremonyStepManagerFactory = new CeremonyStepManagerFactory; $ceremonyStepManagerFactory->setAlgorithmManager($algorithmManager); $ceremonyStepManagerFactory->setAttestationStatementSupportManager( $attestationStatementSupportManager diff --git a/app/Http/Controllers/ContactsController.php b/app/Http/Controllers/ContactsController.php index 503a75ff..75b103a8 100644 --- a/app/Http/Controllers/ContactsController.php +++ b/app/Http/Controllers/ContactsController.php @@ -18,7 +18,7 @@ class ContactsController extends Controller */ public function index(): View { - $filesystem = new Filesystem(); + $filesystem = new Filesystem; $contacts = Contact::all(); foreach ($contacts as $contact) { $contact->homepageHost = parse_url($contact->homepage, PHP_URL_HOST); @@ -40,7 +40,7 @@ class ContactsController extends Controller $contact->homepageHost = parse_url($contact->homepage, PHP_URL_HOST); $file = public_path() . '/assets/profile-images/' . $contact->homepageHost . '/image'; - $filesystem = new Filesystem(); + $filesystem = new Filesystem; $image = ($filesystem->exists($file)) ? '/assets/profile-images/' . $contact->homepageHost . '/image' : diff --git a/app/Http/Controllers/MicropubController.php b/app/Http/Controllers/MicropubController.php index 8a395ee0..ac25a815 100644 --- a/app/Http/Controllers/MicropubController.php +++ b/app/Http/Controllers/MicropubController.php @@ -53,13 +53,13 @@ class MicropubController extends Controller try { $tokenData = $this->tokenService->validateToken($request->input('access_token')); } catch (RequiredConstraintsViolated|InvalidTokenStructure|CannotDecodeContent) { - $micropubResponses = new MicropubResponses(); + $micropubResponses = new MicropubResponses; return $micropubResponses->invalidTokenResponse(); } if ($tokenData->claims()->has('scope') === false) { - $micropubResponses = new MicropubResponses(); + $micropubResponses = new MicropubResponses; return $micropubResponses->tokenHasNoScopeResponse(); } @@ -73,7 +73,7 @@ class MicropubController extends Controller } if (! in_array('create', $scopes)) { - $micropubResponses = new MicropubResponses(); + $micropubResponses = new MicropubResponses; return $micropubResponses->insufficientScopeResponse(); } @@ -91,7 +91,7 @@ class MicropubController extends Controller $scopes = explode(' ', $scopes); } if (! in_array('create', $scopes)) { - $micropubResponses = new MicropubResponses(); + $micropubResponses = new MicropubResponses; return $micropubResponses->insufficientScopeResponse(); } @@ -109,7 +109,7 @@ class MicropubController extends Controller $scopes = explode(' ', $scopes); } if (! in_array('update', $scopes)) { - $micropubResponses = new MicropubResponses(); + $micropubResponses = new MicropubResponses; return $micropubResponses->insufficientScopeResponse(); } @@ -136,7 +136,7 @@ class MicropubController extends Controller try { $tokenData = $this->tokenService->validateToken($request->input('access_token')); } catch (RequiredConstraintsViolated|InvalidTokenStructure) { - return (new MicropubResponses())->invalidTokenResponse(); + return (new MicropubResponses)->invalidTokenResponse(); } if ($request->input('q') === 'syndicate-to') { diff --git a/app/Http/Controllers/MicropubMediaController.php b/app/Http/Controllers/MicropubMediaController.php index e07e979f..e3ed8b61 100644 --- a/app/Http/Controllers/MicropubMediaController.php +++ b/app/Http/Controllers/MicropubMediaController.php @@ -39,13 +39,13 @@ class MicropubMediaController extends Controller try { $tokenData = $this->tokenService->validateToken($request->input('access_token')); } catch (RequiredConstraintsViolated|InvalidTokenStructure) { - $micropubResponses = new MicropubResponses(); + $micropubResponses = new MicropubResponses; return $micropubResponses->invalidTokenResponse(); } if ($tokenData->claims()->has('scope') === false) { - $micropubResponses = new MicropubResponses(); + $micropubResponses = new MicropubResponses; return $micropubResponses->tokenHasNoScopeResponse(); } @@ -55,7 +55,7 @@ class MicropubMediaController extends Controller $scopes = explode(' ', $scopes); } if (! in_array('create', $scopes)) { - $micropubResponses = new MicropubResponses(); + $micropubResponses = new MicropubResponses; return $micropubResponses->insufficientScopeResponse(); } @@ -111,13 +111,13 @@ class MicropubMediaController extends Controller try { $tokenData = $this->tokenService->validateToken($request->input('access_token')); } catch (RequiredConstraintsViolated|InvalidTokenStructure) { - $micropubResponses = new MicropubResponses(); + $micropubResponses = new MicropubResponses; return $micropubResponses->invalidTokenResponse(); } if ($tokenData->claims()->has('scope') === false) { - $micropubResponses = new MicropubResponses(); + $micropubResponses = new MicropubResponses; return $micropubResponses->tokenHasNoScopeResponse(); } @@ -127,7 +127,7 @@ class MicropubMediaController extends Controller $scopes = explode(' ', $scopes); } if (! in_array('create', $scopes)) { - $micropubResponses = new MicropubResponses(); + $micropubResponses = new MicropubResponses; return $micropubResponses->insufficientScopeResponse(); } @@ -140,7 +140,10 @@ class MicropubMediaController extends Controller ], 400); } - if ($request->file('file')->isValid() === false) { + /** @var UploadedFile $file */ + $file = $request->file('file'); + + if ($file->isValid() === false) { return response()->json([ 'response' => 'error', 'error' => 'invalid_request', @@ -148,7 +151,7 @@ class MicropubMediaController extends Controller ], 400); } - $filename = $this->saveFile($request->file('file')); + $filename = Storage::disk('local')->putFile('media', $file); /** @var ImageManager $manager */ $manager = resolve(ImageManager::class); @@ -162,18 +165,11 @@ class MicropubMediaController extends Controller $media = Media::create([ 'token' => $request->bearerToken(), - 'path' => 'media/' . $filename, + 'path' => $filename, 'type' => $this->getFileTypeFromMimeType($request->file('file')->getMimeType()), 'image_widths' => $width, ]); - // put the file on S3 initially, the ProcessMedia job may edit this - Storage::disk('s3')->putFileAs( - 'media', - new File(storage_path('app') . '/' . $filename), - $filename - ); - ProcessMedia::dispatch($filename); return response()->json([ @@ -237,7 +233,7 @@ class MicropubMediaController extends Controller * * @throws Exception */ - private function saveFile(UploadedFile $file): string + private function saveFileToLocal(UploadedFile $file): string { $filename = Uuid::uuid4()->toString() . '.' . $file->extension(); Storage::disk('local')->putFileAs('', $file, $filename); diff --git a/app/Http/Controllers/NotesController.php b/app/Http/Controllers/NotesController.php index bef422cb..5c25771f 100644 --- a/app/Http/Controllers/NotesController.php +++ b/app/Http/Controllers/NotesController.php @@ -67,7 +67,7 @@ class NotesController extends Controller */ public function redirect(int $decId): RedirectResponse { - return redirect(config('app.url') . '/notes/' . (new Numbers())->numto60($decId)); + return redirect(config('app.url') . '/notes/' . (new Numbers)->numto60($decId)); } /** diff --git a/app/Jobs/DownloadWebMention.php b/app/Jobs/DownloadWebMention.php index a32b25a9..d8f97328 100644 --- a/app/Jobs/DownloadWebMention.php +++ b/app/Jobs/DownloadWebMention.php @@ -38,7 +38,7 @@ class DownloadWebMention implements ShouldQueue //4XX and 5XX responses should get Guzzle to throw an exception, //Laravel should catch and retry these automatically. if ($response->getStatusCode() === 200) { - $filesystem = new FileSystem(); + $filesystem = new FileSystem; $filename = storage_path('HTML') . '/' . $this->createFilenameFromURL($this->source); //backup file first $filenameBackup = $filename . '.' . date('Y-m-d') . '.backup'; diff --git a/app/Jobs/ProcessMedia.php b/app/Jobs/ProcessMedia.php index 6b6a1dcf..e57acf3e 100644 --- a/app/Jobs/ProcessMedia.php +++ b/app/Jobs/ProcessMedia.php @@ -32,35 +32,35 @@ class ProcessMedia implements ShouldQueue */ public function handle(ImageManager $manager): void { - //open file + // Open file try { - $image = $manager->read(storage_path('app') . '/' . $this->filename); + $image = $manager->read(storage_path('app/media/') . $this->filename); } catch (DecoderException) { // not an image; delete file and end job - unlink(storage_path('app') . '/' . $this->filename); + unlink(storage_path('app/media/') . $this->filename); return; } - //create smaller versions if necessary + + // Save the file publicly + Storage::disk('local')->copy('media/' . $this->filename, 'public/media/' . $this->filename); + + // Create smaller versions if necessary if ($image->width() > 1000) { $filenameParts = explode('.', $this->filename); $extension = array_pop($filenameParts); // the following achieves this data flow // foo.bar.png => ['foo', 'bar', 'png'] => ['foo', 'bar'] => foo.bar - $basename = ltrim(array_reduce($filenameParts, function ($carry, $item) { - return $carry . '.' . $item; - }, ''), '.'); - $medium = $image->resize(1000, null, function ($constraint) { - $constraint->aspectRatio(); - }); - Storage::disk('s3')->put('media/' . $basename . '-medium.' . $extension, (string) $medium->encode()); - $small = $image->resize(500, null, function ($constraint) { - $constraint->aspectRatio(); - }); - Storage::disk('s3')->put('media/' . $basename . '-small.' . $extension, (string) $small->encode()); + $basename = trim(implode('.', $filenameParts), '.'); + + $medium = $image->resize(width: 1000); + Storage::disk('local')->put('public/media/' . $basename . '-medium.' . $extension, (string) $medium->encode()); + + $small = $image->resize(width: 500); + Storage::disk('local')->put('public/media/' . $basename . '-small.' . $extension, (string) $small->encode()); } - // now we can delete the locally saved image - unlink(storage_path('app') . '/' . $this->filename); + // Now we can delete the locally saved image + unlink(storage_path('app/media/') . $this->filename); } } diff --git a/app/Jobs/ProcessWebMention.php b/app/Jobs/ProcessWebMention.php index a5afd300..24c7f477 100644 --- a/app/Jobs/ProcessWebMention.php +++ b/app/Jobs/ProcessWebMention.php @@ -44,7 +44,7 @@ class ProcessWebMention implements ShouldQueue try { $response = $guzzle->request('GET', $this->source); } catch (RequestException $e) { - throw new RemoteContentNotFoundException(); + throw new RemoteContentNotFoundException; } $this->saveRemoteContent((string) $response->getBody(), $this->source); $microformats = Mf2\parse((string) $response->getBody(), $this->source); @@ -85,7 +85,7 @@ class ProcessWebMention implements ShouldQueue }// foreach // no webmention in the db so create new one - $webmention = new WebMention(); + $webmention = new WebMention; $type = $parser->getMentionType($microformats); // throw error here? dispatch(new SaveProfileImage($microformats)); $webmention->source = $this->source; diff --git a/app/Jobs/SendWebMentions.php b/app/Jobs/SendWebMentions.php index 89babc89..4252f8e0 100644 --- a/app/Jobs/SendWebMentions.php +++ b/app/Jobs/SendWebMentions.php @@ -108,7 +108,7 @@ class SendWebMentions implements ShouldQueue } $urls = []; - $dom = new \DOMDocument(); + $dom = new \DOMDocument; $dom->loadHTML($html); $anchors = $dom->getElementsByTagName('a'); foreach ($anchors as $anchor) { diff --git a/app/Models/Article.php b/app/Models/Article.php index 42895f8d..bfbd5d51 100644 --- a/app/Models/Article.php +++ b/app/Models/Article.php @@ -58,10 +58,10 @@ class Article extends Model { return Attribute::get( get: function () { - $environment = new Environment(); - $environment->addExtension(new CommonMarkCoreExtension()); - $environment->addRenderer(FencedCode::class, new FencedCodeRenderer()); - $environment->addRenderer(IndentedCode::class, new IndentedCodeRenderer()); + $environment = new Environment; + $environment->addExtension(new CommonMarkCoreExtension); + $environment->addRenderer(FencedCode::class, new FencedCodeRenderer); + $environment->addRenderer(IndentedCode::class, new IndentedCodeRenderer); $markdownConverter = new MarkdownConverter($environment); return $markdownConverter->convert($this->main)->getContent(); diff --git a/app/Models/Media.php b/app/Models/Media.php index c4dd6d5c..3d923bed 100644 --- a/app/Models/Media.php +++ b/app/Models/Media.php @@ -33,7 +33,7 @@ class Media extends Model return $attributes['path']; } - return config('filesystems.disks.s3.url') . '/' . $attributes['path']; + return config('app.url') . '/storage/' . $attributes['path']; } ); } @@ -78,7 +78,7 @@ class Media extends Model $basename = $this->getBasename($path); $extension = $this->getExtension($path); - return config('filesystems.disks.s3.url') . '/' . $basename . '-' . $size . '.' . $extension; + return config('app.url') . '/storage/' . $basename . '-' . $size . '.' . $extension; } private function getBasename(string $path): string diff --git a/app/Models/Note.php b/app/Models/Note.php index f854b598..9c58bb3e 100644 --- a/app/Models/Note.php +++ b/app/Models/Note.php @@ -271,7 +271,7 @@ class Note extends Model ]); if ($oEmbed->httpstatus >= 400) { - throw new Exception(); + throw new Exception; } } catch (Exception $e) { return null; @@ -388,18 +388,18 @@ class Note extends Model 'mentions_handle' => [ 'prefix' => '@', 'pattern' => '([\w@.])+(\b)', - 'generator' => new MentionGenerator(), + 'generator' => new MentionGenerator, ], ], ]; $environment = new Environment($config); - $environment->addExtension(new CommonMarkCoreExtension()); - $environment->addExtension(new AutolinkExtension()); - $environment->addExtension(new MentionExtension()); - $environment->addRenderer(Mention::class, new MentionRenderer()); - $environment->addRenderer(FencedCode::class, new FencedCodeRenderer()); - $environment->addRenderer(IndentedCode::class, new IndentedCodeRenderer()); + $environment->addExtension(new CommonMarkCoreExtension); + $environment->addExtension(new AutolinkExtension); + $environment->addExtension(new MentionExtension); + $environment->addRenderer(Mention::class, new MentionRenderer); + $environment->addRenderer(FencedCode::class, new FencedCodeRenderer); + $environment->addRenderer(IndentedCode::class, new IndentedCodeRenderer); $markdownConverter = new MarkdownConverter($environment); return $markdownConverter->convert($note)->getContent(); diff --git a/app/Models/WebMention.php b/app/Models/WebMention.php index cf418b90..a9930851 100644 --- a/app/Models/WebMention.php +++ b/app/Models/WebMention.php @@ -42,7 +42,7 @@ class WebMention extends Model return null; } - $authorship = new Authorship(); + $authorship = new Authorship; $hCard = $authorship->findAuthor(json_decode($attributes['mf2'], true)); if ($hCard === false) { @@ -140,7 +140,7 @@ class WebMention extends Model return $profile_image; } - $filesystem = new Filesystem(); + $filesystem = new Filesystem; if ($filesystem->exists(public_path() . '/assets/profile-images/' . $host . '/image')) { return '/assets/profile-images/' . $host . '/image'; } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index d073e1d1..4c9573b2 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -88,9 +88,9 @@ class AppServiceProvider extends ServiceProvider $this->app->bind('Lcobucci\JWT\Configuration', function () { $key = InMemory::plainText(config('app.key')); - $config = Configuration::forSymmetricSigner(new Sha256(), $key); + $config = Configuration::forSymmetricSigner(new Sha256, $key); - $config->setValidationConstraints(new SignedWith(new Sha256(), $key)); + $config->setValidationConstraints(new SignedWith(new Sha256, $key)); return $config; }); @@ -98,7 +98,7 @@ class AppServiceProvider extends ServiceProvider // Configure HtmlSanitizer $this->app->bind(HtmlSanitizer::class, function () { return new HtmlSanitizer( - (new HtmlSanitizerConfig()) + (new HtmlSanitizerConfig) ->allowSafeElements() ->forceAttribute('a', 'rel', 'noopener nofollow') ); diff --git a/app/Services/BookmarkService.php b/app/Services/BookmarkService.php index 792cb81c..1a17dd08 100644 --- a/app/Services/BookmarkService.php +++ b/app/Services/BookmarkService.php @@ -62,7 +62,7 @@ class BookmarkService extends Service $response = $client->request('GET', 'https://web.archive.org/save/' . $url); } catch (ClientException $e) { //throw an exception to be caught - throw new InternetArchiveException(); + throw new InternetArchiveException; } if ($response->hasHeader('Content-Location')) { if (Str::startsWith(Arr::get($response->getHeader('Content-Location'), 0), '/web')) { @@ -71,6 +71,6 @@ class BookmarkService extends Service } //throw an exception to be caught - throw new InternetArchiveException(); + throw new InternetArchiveException; } } diff --git a/app/Services/Micropub/UpdateService.php b/app/Services/Micropub/UpdateService.php index ac9d360a..144984c2 100644 --- a/app/Services/Micropub/UpdateService.php +++ b/app/Services/Micropub/UpdateService.php @@ -83,7 +83,7 @@ class UpdateService if ($property === 'photo') { foreach ($value as $photoURL) { if (Str::startsWith($photoURL, 'https://')) { - $media = new Media(); + $media = new Media; $media->path = $photoURL; $media->type = 'image'; $media->save(); diff --git a/app/Services/PlaceService.php b/app/Services/PlaceService.php index d3756253..a63caa98 100644 --- a/app/Services/PlaceService.php +++ b/app/Services/PlaceService.php @@ -25,7 +25,7 @@ class PlaceService $data['latitude'] = $matches[0][0]; $data['longitude'] = $matches[0][1]; } - $place = new Place(); + $place = new Place; $place->name = $data['name']; $place->description = $data['description']; $place->latitude = $data['latitude']; @@ -53,7 +53,7 @@ class PlaceService if (Arr::has($checkin, 'properties.latitude') === false) { throw new \InvalidArgumentException('Missing required longitude/latitude'); } - $place = new Place(); + $place = new Place; $place->name = Arr::get($checkin, 'properties.name.0'); $place->external_urls = Arr::get($checkin, 'properties.url.0'); $place->latitude = Arr::get($checkin, 'properties.latitude.0'); diff --git a/app/Services/TokenService.php b/app/Services/TokenService.php index fddccff0..d0240f6a 100644 --- a/app/Services/TokenService.php +++ b/app/Services/TokenService.php @@ -19,7 +19,7 @@ class TokenService $config = resolve(Configuration::class); $token = $config->builder() - ->issuedAt(new DateTimeImmutable()) + ->issuedAt(new DateTimeImmutable) ->withClaim('client_id', $data['client_id']) ->withClaim('me', $data['me']) ->withClaim('scope', $data['scope']) diff --git a/database/seeders/ContactsTableSeeder.php b/database/seeders/ContactsTableSeeder.php index b3097ce3..577fe153 100644 --- a/database/seeders/ContactsTableSeeder.php +++ b/database/seeders/ContactsTableSeeder.php @@ -27,7 +27,7 @@ class ContactsTableSeeder extends Seeder 'homepage' => 'https://aaronparecki.com', 'facebook' => '123456', ]); - $fs = new FileSystem(); + $fs = new FileSystem; if (! $fs->exists(public_path('assets/profile-images/aaronparecki.com'))) { $fs->makeDirectory(public_path('assets/profile-images/aaronparecki.com')); } diff --git a/database/seeders/LikesTableSeeder.php b/database/seeders/LikesTableSeeder.php index 5a1f365c..0436ad64 100644 --- a/database/seeders/LikesTableSeeder.php +++ b/database/seeders/LikesTableSeeder.php @@ -20,7 +20,7 @@ class LikesTableSeeder extends Seeder Like::factory(10)->create(); $now = Carbon::now()->subDays(rand(3, 6)); - $faker = new Generator(); + $faker = new Generator; $faker->addProvider(new \Faker\Provider\en_US\Person($faker)); $faker->addProvider(new \Faker\Provider\Lorem($faker)); $faker->addProvider(new \Faker\Provider\Internet($faker)); diff --git a/database/seeders/NotesTableSeeder.php b/database/seeders/NotesTableSeeder.php index 298205e3..b73c2e34 100644 --- a/database/seeders/NotesTableSeeder.php +++ b/database/seeders/NotesTableSeeder.php @@ -154,7 +154,7 @@ class NotesTableSeeder extends Seeder ->update(['updated_at' => $now->toDateTimeString()]); $now = Carbon::now()->subHours(5); - $noteJustCheckin = new Note(); + $noteJustCheckin = new Note; $noteJustCheckin->setCreatedAt($now); $place = Place::find(1); $noteJustCheckin->place()->associate($place); @@ -164,12 +164,12 @@ class NotesTableSeeder extends Seeder ->update(['updated_at' => $now->toDateTimeString()]); $now = Carbon::now()->subHours(4); - $media = new Media(); + $media = new Media; $media->path = 'media/f1bc8faa-1a8f-45b8-a9b1-57282fa73f87.jpg'; $media->type = 'image'; $media->image_widths = '3648'; $media->save(); - $noteWithOnlyImage = new Note(); + $noteWithOnlyImage = new Note; $noteWithOnlyImage->setCreatedAt($now); $noteWithOnlyImage->setUpdatedAt($now); $noteWithOnlyImage->save(); diff --git a/database/seeders/PlacesTableSeeder.php b/database/seeders/PlacesTableSeeder.php index f9f0884f..90a63ff8 100644 --- a/database/seeders/PlacesTableSeeder.php +++ b/database/seeders/PlacesTableSeeder.php @@ -14,7 +14,7 @@ class PlacesTableSeeder extends Seeder */ public function run(): void { - $place = new Place(); + $place = new Place; $place->name = 'The Bridgewater Pub'; $place->description = 'A lovely local pub with a decent selection of cask ales'; $place->latitude = 53.4983; diff --git a/storage/app/.gitignore b/storage/app/.gitignore index 8f4803c0..fedb287f 100644 --- a/storage/app/.gitignore +++ b/storage/app/.gitignore @@ -1,3 +1,4 @@ * +!private/ !public/ !.gitignore diff --git a/storage/app/private/.gitignore b/storage/app/private/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/storage/app/private/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/tests/DuskTestCase.php b/tests/DuskTestCase.php index 3f9e26fc..0f891ea1 100644 --- a/tests/DuskTestCase.php +++ b/tests/DuskTestCase.php @@ -32,7 +32,7 @@ abstract class DuskTestCase extends BaseTestCase { $desiredCapabilities = DesiredCapabilities::chrome(); - $options = new ChromeOptions(); + $options = new ChromeOptions; $options->addArguments([ 'headless', 'disable-gpu', diff --git a/tests/Feature/LikesTest.php b/tests/Feature/LikesTest.php index a4899e21..45c24e63 100644 --- a/tests/Feature/LikesTest.php +++ b/tests/Feature/LikesTest.php @@ -78,7 +78,7 @@ class LikesTest extends TestCase /** @test */ public function likeWithSimpleAuthor(): void { - $like = new Like(); + $like = new Like; $like->url = 'http://example.org/note/id'; $like->save(); $id = $like->id; @@ -107,7 +107,7 @@ class LikesTest extends TestCase $this->app->bind(Client::class, function () use ($client) { return $client; }); - $authorship = new Authorship(); + $authorship = new Authorship; $job->handle($client, $authorship); @@ -117,7 +117,7 @@ class LikesTest extends TestCase /** @test */ public function likeWithHCard(): void { - $like = new Like(); + $like = new Like; $like->url = 'http://example.org/note/id'; $like->save(); $id = $like->id; @@ -150,7 +150,7 @@ class LikesTest extends TestCase $this->app->bind(Client::class, function () use ($client) { return $client; }); - $authorship = new Authorship(); + $authorship = new Authorship; $job->handle($client, $authorship); @@ -160,7 +160,7 @@ class LikesTest extends TestCase /** @test */ public function likeWithoutMicroformats(): void { - $like = new Like(); + $like = new Like; $like->url = 'http://example.org/note/id'; $like->save(); $id = $like->id; @@ -186,7 +186,7 @@ class LikesTest extends TestCase $this->app->bind(Client::class, function () use ($client) { return $client; }); - $authorship = new Authorship(); + $authorship = new Authorship; $job->handle($client, $authorship); @@ -196,7 +196,7 @@ class LikesTest extends TestCase /** @test */ public function likeThatIsATweet(): void { - $like = new Like(); + $like = new Like; $like->url = 'https://twitter.com/jonnybarnes/status/1050823255123251200'; $like->save(); $id = $like->id; @@ -226,7 +226,7 @@ class LikesTest extends TestCase ->willReturn($info); $this->app->instance(Codebird::class, $codebirdMock); - $authorship = new Authorship(); + $authorship = new Authorship; $job->handle($client, $authorship); @@ -236,7 +236,7 @@ class LikesTest extends TestCase /** @test */ public function noErrorForFailureToPosseWithBridgy(): void { - $like = new Like(); + $like = new Like; $like->url = 'https://twitter.com/jonnybarnes/status/1050823255123251200'; $like->save(); $id = $like->id; @@ -264,7 +264,7 @@ class LikesTest extends TestCase ->willReturn($info); $this->app->instance(Codebird::class, $codebirdMock); - $authorship = new Authorship(); + $authorship = new Authorship; $job->handle($client, $authorship); diff --git a/tests/Feature/MicropubControllerTest.php b/tests/Feature/MicropubControllerTest.php index 412401d1..06935d55 100644 --- a/tests/Feature/MicropubControllerTest.php +++ b/tests/Feature/MicropubControllerTest.php @@ -291,7 +291,7 @@ class MicropubControllerTest extends TestCase */ public function micropubClientApiRequestCreatesNewNoteWithExistingPlaceInLocationData(): void { - $place = new Place(); + $place = new Place; $place->name = 'Test Place'; $place->latitude = 1.23; $place->longitude = 4.56; diff --git a/tests/Feature/MicropubMediaTest.php b/tests/Feature/MicropubMediaTest.php index be05cb4e..7abafd9b 100644 --- a/tests/Feature/MicropubMediaTest.php +++ b/tests/Feature/MicropubMediaTest.php @@ -10,6 +10,7 @@ use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Http\UploadedFile; use Illuminate\Support\Facades\Queue; use Illuminate\Support\Facades\Storage; +use Illuminate\Support\Str; use Tests\TestCase; use Tests\TestToken; @@ -22,9 +23,7 @@ class MicropubMediaTest extends TestCase public function emptyResponseForLastUploadWhenNoneFound(): void { // Make sure there’s no media - Media::all()->each(function ($media) { - $media->delete(); - }); + $this->assertCount(0, Media::all()); $response = $this->get( '/api/media?q=last', @@ -82,10 +81,8 @@ class MicropubMediaTest extends TestCase public function clientCanListLastUpload(): void { Queue::fake(); - Storage::fake('s3'); $file = __DIR__ . '/../aaron.png'; $token = $this->getToken(); - config(['filesystems.disks.s3.url' => 'https://s3.example.com']); $response = $this->post( '/api/media', @@ -95,12 +92,8 @@ class MicropubMediaTest extends TestCase ['HTTP_Authorization' => 'Bearer ' . $token] ); - $location = $response->headers->get('Location'); - - $this->assertStringStartsWith('https://s3.example.com/', $location); - $path = parse_url($response->headers->get('Location'), PHP_URL_PATH); - $filename = substr($path, 7); + $filename = Str::chopStart($path, '/media/'); $lastUploadResponse = $this->get( '/api/media?q=last', @@ -109,14 +102,14 @@ class MicropubMediaTest extends TestCase $lastUploadResponse->assertJson(['url' => $response->headers->get('Location')]); // now remove file - unlink(storage_path('app/') . $filename); + unlink(storage_path('app/media/') . $filename); + $this->removeDirIfEmpty(storage_path('app/media')); } /** @test */ public function clientCanSourceUploads(): void { Queue::fake(); - Storage::fake('s3'); $file = __DIR__ . '/../aaron.png'; $token = $this->getToken(); @@ -129,7 +122,7 @@ class MicropubMediaTest extends TestCase ); $path = parse_url($response->headers->get('Location'), PHP_URL_PATH); - $filename = substr($path, 7); + $filename = Str::chopStart($path, '/media/'); $sourceUploadResponse = $this->get( '/api/media?q=source', @@ -141,14 +134,14 @@ class MicropubMediaTest extends TestCase ]]]); // now remove file - unlink(storage_path('app/') . $filename); + unlink(storage_path('app/media/') . $filename); + $this->removeDirIfEmpty(storage_path('app/media')); } /** @test */ public function clientCanSourceUploadsWithLimit(): void { Queue::fake(); - Storage::fake('s3'); $file = __DIR__ . '/../aaron.png'; $token = $this->getToken(); @@ -161,7 +154,7 @@ class MicropubMediaTest extends TestCase ); $path = parse_url($response->headers->get('Location'), PHP_URL_PATH); - $filename = substr($path, 7); + $filename = Str::chopStart($path, '/media/'); $sourceUploadResponse = $this->get( '/api/media?q=source&limit=1', @@ -175,7 +168,8 @@ class MicropubMediaTest extends TestCase $this->assertCount(1, json_decode($sourceUploadResponse->getContent(), true)['items']); // now remove file - unlink(storage_path('app/') . $filename); + unlink(storage_path('app/media/') . $filename); + $this->removeDirIfEmpty(storage_path('app/media')); } /** @test */ @@ -256,7 +250,6 @@ class MicropubMediaTest extends TestCase public function mediaEndpointUploadFile(): void { Queue::fake(); - Storage::fake('s3'); $file = __DIR__ . '/../aaron.png'; $response = $this->post( @@ -268,18 +261,18 @@ class MicropubMediaTest extends TestCase ); $path = parse_url($response->headers->get('Location'), PHP_URL_PATH); - $filename = substr($path, 7); + $filename = Str::chopStart($path, '/media/'); Queue::assertPushed(ProcessMedia::class); - Storage::disk('local')->assertExists($filename); + Storage::disk('local')->assertExists($path); // now remove file - unlink(storage_path('app/') . $filename); + unlink(storage_path('app/media/') . $filename); + $this->removeDirIfEmpty(storage_path('app/media')); } /** @test */ public function mediaEndpointUploadAudioFile(): void { Queue::fake(); - Storage::fake('s3'); $file = __DIR__ . '/../audio.mp3'; $response = $this->post( @@ -291,18 +284,18 @@ class MicropubMediaTest extends TestCase ); $path = parse_url($response->headers->get('Location'), PHP_URL_PATH); - $filename = substr($path, 7); + $filename = Str::chopStart($path, '/media/'); Queue::assertPushed(ProcessMedia::class); - Storage::disk('local')->assertExists($filename); + Storage::disk('local')->assertExists($path); // now remove file - unlink(storage_path('app/') . $filename); + unlink(storage_path('app/media/') . $filename); + $this->removeDirIfEmpty(storage_path('app/media')); } /** @test */ public function mediaEndpointUploadVideoFile(): void { Queue::fake(); - Storage::fake('s3'); $file = __DIR__ . '/../video.ogv'; $response = $this->post( @@ -314,18 +307,18 @@ class MicropubMediaTest extends TestCase ); $path = parse_url($response->headers->get('Location'), PHP_URL_PATH); - $filename = substr($path, 7); + $filename = Str::chopStart($path, '/media/'); Queue::assertPushed(ProcessMedia::class); - Storage::disk('local')->assertExists($filename); + Storage::disk('local')->assertExists($path); // now remove file - unlink(storage_path('app/') . $filename); + unlink(storage_path('app/media/') . $filename); + $this->removeDirIfEmpty(storage_path('app/media')); } /** @test */ public function mediaEndpointUploadDocumentFile(): void { Queue::fake(); - Storage::fake('s3'); $response = $this->post( '/api/media', @@ -336,11 +329,12 @@ class MicropubMediaTest extends TestCase ); $path = parse_url($response->headers->get('Location'), PHP_URL_PATH); - $filename = substr($path, 7); + $filename = Str::chopStart($path, '/media/'); Queue::assertPushed(ProcessMedia::class); - Storage::disk('local')->assertExists($filename); + Storage::disk('local')->assertExists($path); // now remove file - unlink(storage_path('app/') . $filename); + unlink(storage_path('app/media/') . $filename); + $this->removeDirIfEmpty(storage_path('app/media')); } /** @test */ diff --git a/tests/Feature/ParseCachedWebMentionsTest.php b/tests/Feature/ParseCachedWebMentionsTest.php index 6d54e37c..ca829491 100644 --- a/tests/Feature/ParseCachedWebMentionsTest.php +++ b/tests/Feature/ParseCachedWebMentionsTest.php @@ -60,7 +60,7 @@ class ParseCachedWebMentionsTest extends TestCase protected function tearDown(): void { - $fs = new FileSystem(); + $fs = new FileSystem; if ($fs->exists(storage_path() . '/HTML/https')) { $fs->deleteDirectory(storage_path() . '/HTML/https'); } diff --git a/tests/Feature/TokenServiceTest.php b/tests/Feature/TokenServiceTest.php index d5e3136d..f9b3773c 100644 --- a/tests/Feature/TokenServiceTest.php +++ b/tests/Feature/TokenServiceTest.php @@ -21,7 +21,7 @@ class TokenServiceTest extends TestCase */ public function tokenserviceCreatesAndValidatesTokens(): void { - $tokenService = new TokenService(); + $tokenService = new TokenService; $data = [ 'me' => 'https://example.org', 'client_id' => 'https://quill.p3k.io', @@ -55,7 +55,7 @@ class TokenServiceTest extends TestCase $config = resolve(Configuration::class); $token = $config->builder() - ->issuedAt(new DateTimeImmutable()) + ->issuedAt(new DateTimeImmutable) ->withClaim('client_id', $data['client_id']) ->withClaim('me', $data['me']) ->withClaim('scope', $data['scope']) @@ -63,7 +63,7 @@ class TokenServiceTest extends TestCase ->getToken($config->signer(), InMemory::plainText(random_bytes(32))) ->toString(); - $service = new TokenService(); + $service = new TokenService; $service->validateToken($token); } } diff --git a/tests/TestCase.php b/tests/TestCase.php index 2932d4a6..cade29d9 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -7,4 +7,13 @@ use Illuminate\Foundation\Testing\TestCase as BaseTestCase; abstract class TestCase extends BaseTestCase { use CreatesApplication; + + public function removeDirIfEmpty(string $dir): void + { + // scandir() will always return `.` and `..` so even an “empty” + // directory will have a count of 2 + if (is_dir($dir) && count(scandir($dir)) === 2) { + rmdir($dir); + } + } } diff --git a/tests/TestToken.php b/tests/TestToken.php index 397967dc..287e2757 100644 --- a/tests/TestToken.php +++ b/tests/TestToken.php @@ -12,7 +12,7 @@ trait TestToken $config = $this->app->make(Configuration::class); return $config->builder() - ->issuedAt(new DateTimeImmutable()) + ->issuedAt(new DateTimeImmutable) ->withClaim('client_id', 'https://quill.p3k.io') ->withClaim('me', 'http://jonnybarnes.localhost') ->withClaim('scope', ['create', 'update']) @@ -25,7 +25,7 @@ trait TestToken $config = $this->app->make(Configuration::class); return $config->builder() - ->issuedAt(new DateTimeImmutable()) + ->issuedAt(new DateTimeImmutable) ->withClaim('client_id', 'https://quill.p3k.io') ->withClaim('me', 'https://jonnybarnes.localhost') ->withClaim('scope', 'view') @@ -38,7 +38,7 @@ trait TestToken $config = $this->app->make(Configuration::class); return $config->builder() - ->issuedAt(new DateTimeImmutable()) + ->issuedAt(new DateTimeImmutable) ->withClaim('client_id', 'https://quill.p3k.io') ->withClaim('me', 'https://jonnybarnes.localhost') ->getToken($config->signer(), $config->signingKey()) diff --git a/tests/Unit/ArticlesTest.php b/tests/Unit/ArticlesTest.php index 2327eac2..e214d9d1 100644 --- a/tests/Unit/ArticlesTest.php +++ b/tests/Unit/ArticlesTest.php @@ -16,7 +16,7 @@ class ArticlesTest extends TestCase /** @test */ public function titleSlugIsGeneratedAutomatically(): void { - $article = new Article(); + $article = new Article; $article->title = 'My Title'; $article->main = 'Content'; $article->save(); @@ -27,7 +27,7 @@ class ArticlesTest extends TestCase /** @test */ public function markdownContentIsConverted(): void { - $article = new Article(); + $article = new Article; $article->main = 'Some *markdown*'; $this->assertEquals('
Some markdown
' . PHP_EOL, $article->html); diff --git a/tests/Unit/BookmarksTest.php b/tests/Unit/BookmarksTest.php index 7916b7a7..3a4aabd7 100644 --- a/tests/Unit/BookmarksTest.php +++ b/tests/Unit/BookmarksTest.php @@ -34,7 +34,7 @@ class BookmarksTest extends TestCase $handler = HandlerStack::create($mock); $client = new Client(['handler' => $handler]); $this->app->instance(Client::class, $client); - $url = (new BookmarkService())->getArchiveLink('https://example.org'); + $url = (new BookmarkService)->getArchiveLink('https://example.org'); $this->assertEquals('/web/1234/example.org', $url); } @@ -49,7 +49,7 @@ class BookmarksTest extends TestCase $handler = HandlerStack::create($mock); $client = new Client(['handler' => $handler]); $this->app->instance(Client::class, $client); - (new BookmarkService())->getArchiveLink('https://example.org'); + (new BookmarkService)->getArchiveLink('https://example.org'); } /** @test */ @@ -63,6 +63,6 @@ class BookmarksTest extends TestCase $handler = HandlerStack::create($mock); $client = new Client(['handler' => $handler]); $this->app->instance(Client::class, $client); - (new BookmarkService())->getArchiveLink('https://example.org'); + (new BookmarkService)->getArchiveLink('https://example.org'); } } diff --git a/tests/Unit/Jobs/DownloadWebMentionJobTest.php b/tests/Unit/Jobs/DownloadWebMentionJobTest.php index 94b23dce..03e15a82 100644 --- a/tests/Unit/Jobs/DownloadWebMentionJobTest.php +++ b/tests/Unit/Jobs/DownloadWebMentionJobTest.php @@ -16,7 +16,7 @@ class DownloadWebMentionJobTest extends TestCase { protected function tearDown(): void { - $fs = new FileSystem(); + $fs = new FileSystem; if ($fs->exists(storage_path() . '/HTML/https')) { $fs->deleteDirectory(storage_path() . '/HTML/https'); } diff --git a/tests/Unit/Jobs/ProcessBookmarkJobTest.php b/tests/Unit/Jobs/ProcessBookmarkJobTest.php index b0584adc..4078712e 100644 --- a/tests/Unit/Jobs/ProcessBookmarkJobTest.php +++ b/tests/Unit/Jobs/ProcessBookmarkJobTest.php @@ -46,7 +46,7 @@ class ProcessBookmarkJobTest extends TestCase $bookmark = Bookmark::factory()->create(); $service = $this->createMock(BookmarkService::class); $service->method('getArchiveLink') - ->will($this->throwException(new InternetArchiveException())); + ->will($this->throwException(new InternetArchiveException)); $this->app->instance(BookmarkService::class, $service); $job = new ProcessBookmark($bookmark); diff --git a/tests/Unit/Jobs/ProcessMediaJobTest.php b/tests/Unit/Jobs/ProcessMediaJobTest.php index 64d7396f..ae870d18 100644 --- a/tests/Unit/Jobs/ProcessMediaJobTest.php +++ b/tests/Unit/Jobs/ProcessMediaJobTest.php @@ -14,38 +14,48 @@ class ProcessMediaJobTest extends TestCase /** @test */ public function nonMediaFilesAreNotSaved(): void { - Storage::fake('s3'); $manager = app()->make(ImageManager::class); - Storage::disk('local')->put('file.txt', 'This is not an image'); + Storage::disk('local')->put('media/file.txt', 'This is not an image'); $job = new ProcessMedia('file.txt'); $job->handle($manager); - $this->assertFalse(file_exists(storage_path('app') . '/file.txt')); + $this->assertFileDoesNotExist(storage_path('app/media/') . 'file.txt'); } /** @test */ public function smallImagesAreNotResized(): void { - Storage::fake('s3'); $manager = app()->make(ImageManager::class); - Storage::disk('local')->put('aaron.png', file_get_contents(__DIR__ . '/../../aaron.png')); + Storage::disk('local')->put('media/aaron.png', file_get_contents(__DIR__ . '/../../aaron.png')); $job = new ProcessMedia('aaron.png'); $job->handle($manager); - $this->assertFalse(file_exists(storage_path('app') . '/aaron.png')); + $this->assertFileDoesNotExist(storage_path('app/media/') . 'aaron.png'); + + // Tidy up files created by the job + Storage::disk('local')->delete('public/media/aaron.png'); + Storage::disk('local')->delete('public/media'); } /** @test */ public function largeImagesHaveSmallerImagesCreated(): void { $manager = app()->make(ImageManager::class); - Storage::disk('local')->put('test-image.jpg', file_get_contents(__DIR__.'/../../test-image.jpg')); - Storage::fake('s3'); + Storage::disk('local')->put('media/test-image.jpg', file_get_contents(__DIR__.'/../../test-image.jpg')); $job = new ProcessMedia('test-image.jpg'); $job->handle($manager); - Storage::disk('s3')->assertExists('media/test-image-small.jpg'); - Storage::disk('s3')->assertExists('media/test-image-medium.jpg'); - $this->assertFalse(file_exists(storage_path('app') . '/test-image.jpg')); + Storage::disk('local')->assertExists('public/media/test-image.jpg'); + Storage::disk('local')->assertExists('public/media/test-image-small.jpg'); + Storage::disk('local')->assertExists('public/media/test-image-medium.jpg'); + + $this->assertFileDoesNotExist(storage_path('app/media/') . 'test-image.jpg'); + + // Tidy up files created by the job + Storage::disk('local')->delete('public/media/test-image.jpg'); + Storage::disk('local')->delete('public/media/test-image-small.jpg'); + Storage::disk('local')->delete('public/media/test-image-medium.jpg'); + $this->removeDirIfEmpty(storage_path('app/public/media')); + $this->removeDirIfEmpty(storage_path('app/media')); } } diff --git a/tests/Unit/Jobs/ProcessWebMentionJobTest.php b/tests/Unit/Jobs/ProcessWebMentionJobTest.php index b792b8d2..e9b24bde 100644 --- a/tests/Unit/Jobs/ProcessWebMentionJobTest.php +++ b/tests/Unit/Jobs/ProcessWebMentionJobTest.php @@ -25,7 +25,7 @@ class ProcessWebMentionJobTest extends TestCase protected function tearDown(): void { - $fs = new FileSystem(); + $fs = new FileSystem; if ($fs->exists(storage_path() . '/HTML/https')) { $fs->deleteDirectory(storage_path() . '/HTML/https'); } @@ -37,7 +37,7 @@ class ProcessWebMentionJobTest extends TestCase { $this->expectException(RemoteContentNotFoundException::class); - $parser = new Parser(); + $parser = new Parser; $mock = new MockHandler([ new Response(404), ]); @@ -56,7 +56,7 @@ class ProcessWebMentionJobTest extends TestCase { Queue::fake(); - $parser = new Parser(); + $parser = new Parser; $html = <<<'HTML'Hello