diff --git a/README.md b/README.md index 71a7b1a..de200c8 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ By default, it uses `` and OpenGraph tags. It also ships with a Twitter e **Features**: - Setting SEO tags from PHP - Setting SEO tags from Blade -- Integration with [Flipp](https://useflipp.com) and [Previewify](https://previewify.app), to automatically generate cover images +- Integration with [Flipp](https://useflipp.com) and [PreviewLinks](https://previewlinks.io), to automatically generate cover images - Custom extension support - Expressive & simple API - Customizable views @@ -228,26 +228,26 @@ The `flipp()` method also returns a signed URL to the image, which lets you use <img alt="@seo('title')" src="@seo('flipp', 'blog')"> ``` -### Previewify integration +### PreviewLinks integration -First, you need to add your Previewify API keys: -1. Add your API key to the `PREVIEWIFY_KEY` environment variable. You can get the key [here](https://previewify.app/app/account). +First, you need to add your PreviewLinks API keys: +1. Add your API key to the `PREVIEWLINKS_API_TOKEN` environment variable. You can get the key [here](https://previewlinks.io/app/account). 2. Go to `config/services.php` and add: ```php - 'previewify' => [ - 'key' => env('PREVIEWIFY_KEY'), + 'previewlinks' => [ + 'key' => env('PREVIEWLINKS_API_TOKEN'), ], ``` Then, register your templates, for example in `AppServiceProvider`: ```php -seo()->previewify('blog', 24); -seo()->previewify('page', 83); +seo()->previewlink('blog', 24); +seo()->previewlink('page', 83); ``` -After that, you can use the templates by calling `seo()->previewify()` like this: +After that, you can use the templates by calling `seo()->previewlink()` like this: ```php -seo()->previewify('blog', ['title' => 'Foo', 'content' => 'bar'])` +seo()->previewlink('blog', ['title' => 'Foo', 'content' => 'bar'])` ``` The call will set the generated image as the OpenGraph and Twitter card images. The generated URLs are signed. @@ -257,16 +257,16 @@ If no data array is provided, the method will use the `title` and `description` ```php seo()->title($post->title); seo()->description($post->excerpt); -seo()->previewify('blog'); +seo()->previewlink('blog'); ``` -The `previewify()` method also returns a signed URL to the image, which lets you use it in other places, such as blog cover images. +The `previewlink()` method also returns a signed URL to the image, which lets you use it in other places, such as blog cover images. ```php -<img alt="@seo('title')" src="@seo('previewify', 'blog')"> +<img alt="@seo('title')" src="@seo('previewlink', 'blog')"> ``` > **Note** -> The `previewify:` prefix will be automatically prepended to all provided data keys. +> The `previewlink:` prefix will be automatically prepended to all provided data keys. ## Examples diff --git a/phpstan.neon b/phpstan.neon index 9cc794e..c9ed62a 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -14,6 +14,6 @@ parameters: ignoreErrors: - '#^Method ArchTech\\SEO\\SEOManager::flipp\(\) should return static\(ArchTech\\SEO\\SEOManager\)\|string but returns array\|string\|null\.$#' - - '#^Method ArchTech\\SEO\\SEOManager::previewify\(\) should return static\(ArchTech\\SEO\\SEOManager\)\|string but returns array\|string\|null\.$#' + - '#^Method ArchTech\\SEO\\SEOManager::previewlink\(\) should return static\(ArchTech\\SEO\\SEOManager\)\|string but returns array\|string\|null\.$#' - '#^Method ArchTech\\SEO\\SEOManager::render\(\) has parameter \$args with no type specified\.$#' - '#^Parameter \#1 \$value of function e expects#' diff --git a/src/SEOManager.php b/src/SEOManager.php index 2cfd4c4..3b11ebd 100644 --- a/src/SEOManager.php +++ b/src/SEOManager.php @@ -184,23 +184,23 @@ public function flipp(string $alias, string|array $data = null): string|static return $this->set('image', "https://s.useflipp.com/{$template}.png?s={$signature}&v={$query}"); } - /** Configure or use Previewify. */ - public function previewify(string $alias, int|string|array $data = null): string|static + /** Configure or use PreviewLinks. */ + public function previewlink(string $alias, int|string|array $data = null): string|static { if (is_string($data) || is_int($data)) { - $this->meta("previewify.templates.$alias", (string) $data); + $this->meta("previewlink.templates.$alias", (string) $data); return $this; } if ($data === null) { $data = [ - 'previewify:title' => $this->raw('title'), - 'previewify:description' => $this->raw('description'), + 'previewlinks:title' => $this->raw('title'), + 'previewlinks:description' => $this->raw('description'), ]; } else { $data = array_combine( - array_map(fn ($key) => str_starts_with($key, 'previewify:') ? $key : "previewify:{$key}", array_keys($data)), + array_map(fn ($key) => str_starts_with($key, 'previewlinks:') ? $key : "previewlinks:{$key}", array_keys($data)), $data, ); } @@ -208,11 +208,11 @@ public function previewify(string $alias, int|string|array $data = null): string $query = base64_encode(json_encode($data, JSON_THROW_ON_ERROR)); /** @var string $template */ - $template = $this->meta("previewify.templates.$alias"); + $template = $this->meta("previewlink.templates.$alias"); - $signature = hash_hmac('sha256', $query, config('services.previewify.key')); + $signature = hash_hmac('sha256', $query, config('services.previewlinks.key')); - return $this->set('image', "https://previewify.app/generate/templates/{$template}/signed?signature={$signature}&fields={$query}"); + return $this->set('image', "https://previewlinks.io/generate/templates/{$template}/signed?fields={$query}&signature={$signature}"); } /** Enable favicon extension. */ @@ -336,11 +336,11 @@ public function __call(string $name, array $arguments): string|array|null|static */ public function render(...$args): array|string|null { - // Flipp and Previewify support more arguments - if (in_array($args[0], ['flipp', 'previewify'], true)) { + // Flipp and PreviewLinks support more arguments + if (in_array($args[0], ['flipp', 'previewlink'], true)) { $method = array_shift($args); - // The `flipp` and `previewify` methods return image URLs + // The `flipp` and `previewlink` methods return image URLs // so we don't sanitize the returned value with e() here return $this->{$method}(...$args); } @@ -353,7 +353,7 @@ public function render(...$args): array|string|null // An array means we don't return anything, e.g. `@seo(['title' => 'foo']) if (is_array($args[0])) { foreach ($args[0] as $type => $value) { - if (in_array($type, ['flipp', 'previewify'], true)) { + if (in_array($type, ['flipp', 'previewlink'], true)) { $this->{$type}(...Arr::wrap($value)); } else { $this->set($type, $value); diff --git a/tests/Pest/PreviewLinksTest.php b/tests/Pest/PreviewLinksTest.php new file mode 100644 index 0000000..4a54dc0 --- /dev/null +++ b/tests/Pest/PreviewLinksTest.php @@ -0,0 +1,77 @@ +<?php + +beforeEach(fn () => config(['services.previewlinks.key' => 'abc'])); + +test('previewlink templates can be set', function () { + seo()->previewlink('blog', 1); + + expect(seo()->meta('previewlink.templates')) + ->toHaveCount(1) + ->toHaveKey('blog', '1'); +}); + +test('previewlink makes a request to the template not the alias', function () { + seo()->previewlink('blog', 1); + expect(seo()->previewlink('blog')) + ->toContain('previewlinks.io/generate/templates/1'); +}); + +test('previewlink templates can be given data', function () { + seo()->previewlink('blog', 1); + expect(seo()->previewlink('blog', ['title' => 'abc', 'previewlinks:excerpt' => 'def'])) + ->toContain('previewlinks.io/generate/templates/1') + ->toContain(base64_encode(json_encode(['previewlinks:title' => 'abc', 'previewlinks:excerpt' => 'def']))); +}); + +test('the previewlink method returns a link to a signed url', function () { + seo()->previewlink('blog', 1); + + expect(seo()->previewlink('blog', ['title' => 'abc'])) + ->toContain('?signature=' . hash_hmac('sha256', base64_encode(json_encode(['previewlinks:title' => 'abc'])), config('services.previewlinks.key'))); +}); + +test("previewlink templates use default data when they're not passed any data explicitly", function () { + seo()->previewlink('blog', 1); + + seo()->title('foo')->description('bar'); + + expect(seo()->previewlink('blog')) + ->toContain('previewlinks.io/generate/templates/1') + ->toContain(base64_encode(json_encode(['previewlinks:title' => 'foo', 'previewlinks:description' => 'bar']))); +}); + +test('previewlink images are used as the cover images', function () { + seo()->previewlink('blog', 1); + + seo()->title('foo')->description('bar'); + + expect(seo()->previewlink('blog')) + ->toBe(seo('image')); +}); + +test('the blade directive can be used with previewlinks', function () { + seo()->previewlink('blog', 1); + + seo()->title('foo')->description('bar'); + + expect(blade("@seo('previewlink', 'blog')"))->toBe(seo()->previewlink('blog')); + expect(blade("@seo('previewlink', 'blog', ['title' => 'abc'])"))->toBe(seo()->previewlink('blog', ['title' => 'abc'])); +}); + +test('previewlink uses the raw title and description', function () { + seo()->previewlink('blog', 1); + + seo()->title(modify: fn (string $title) => $title . ' - modified'); + seo()->title('foo')->description('bar'); + + expect(seo()->previewlink('blog')) + ->toContain('previewlinks.io/generate/templates/1') + ->toContain(base64_encode(json_encode(['previewlinks:title' => 'foo', 'previewlinks:description' => 'bar']))); +}); + +test('the @seo helper can be used for setting a previewlinks image', function () { + seo()->previewlink('blog', 1); + blade("@seo(['previewlink' => ['blog', ['title' => 'abc', 'excerpt' => 'def']]])"); + + expect(seo('image'))->toContain('previewlinks.io/generate/templates/1'); +}); diff --git a/tests/Pest/PreviewifyTest.php b/tests/Pest/PreviewifyTest.php deleted file mode 100644 index 9ac59c8..0000000 --- a/tests/Pest/PreviewifyTest.php +++ /dev/null @@ -1,77 +0,0 @@ -<?php - -beforeEach(fn () => config(['services.previewify.key' => 'abc'])); - -test('previewify templates can be set', function () { - seo()->previewify('blog', 1); - - expect(seo()->meta('previewify.templates')) - ->toHaveCount(1) - ->toHaveKey('blog', '1'); -}); - -test('previewify makes a request to the template not the alias', function () { - seo()->previewify('blog', 1); - expect(seo()->previewify('blog')) - ->toContain('previewify.app/generate/templates/1'); -}); - -test('previewify templates can be given data', function () { - seo()->previewify('blog', 1); - expect(seo()->previewify('blog', ['title' => 'abc', 'previewify:excerpt' => 'def'])) - ->toContain('previewify.app/generate/templates/1') - ->toContain(base64_encode(json_encode(['previewify:title' => 'abc', 'previewify:excerpt' => 'def']))); -}); - -test('the previewify method returns a link to a signed url', function () { - seo()->previewify('blog', 1); - - expect(seo()->previewify('blog', ['title' => 'abc'])) - ->toContain('?signature=' . hash_hmac('sha256', base64_encode(json_encode(['previewify:title' => 'abc'])), config('services.previewify.key'))); -}); - -test("previewify templates use default data when they're not passed any data explicitly", function () { - seo()->previewify('blog', 1); - - seo()->title('foo')->description('bar'); - - expect(seo()->previewify('blog')) - ->toContain('previewify.app/generate/templates/1') - ->toContain(base64_encode(json_encode(['previewify:title' => 'foo', 'previewify:description' => 'bar']))); -}); - -test('previewify images are used as the cover images', function () { - seo()->previewify('blog', 1); - - seo()->title('foo')->description('bar'); - - expect(seo()->previewify('blog')) - ->toBe(seo('image')); -}); - -test('the blade directive can be used with previewify', function () { - seo()->previewify('blog', 1); - - seo()->title('foo')->description('bar'); - - expect(blade("@seo('previewify', 'blog')"))->toBe(seo()->previewify('blog')); - expect(blade("@seo('previewify', 'blog', ['title' => 'abc'])"))->toBe(seo()->previewify('blog', ['title' => 'abc'])); -}); - -test('previewify uses the raw title and description', function () { - seo()->previewify('blog', 1); - - seo()->title(modify: fn (string $title) => $title . ' - modified'); - seo()->title('foo')->description('bar'); - - expect(seo()->previewify('blog')) - ->toContain('previewify.app/generate/templates/1') - ->toContain(base64_encode(json_encode(['previewify:title' => 'foo', 'previewify:description' => 'bar']))); -}); - -test('the @seo helper can be used for setting a previewify image', function () { - seo()->previewify('blog', 1); - blade("@seo(['previewify' => ['blog', ['title' => 'abc', 'excerpt' => 'def']]])"); - - expect(seo('image'))->toContain('previewify.app/generate/templates/1'); -});