1
0
Fork 0
mirror of https://github.com/archtechx/laravel-seo.git synced 2025-12-12 09:54:03 +00:00

Add Previewify image provider (#20)

* Add previewify image provider

* feat: support for setting image with the @seo blade tag (resolves #22)
This commit is contained in:
Tobias Petry 2022-06-11 20:17:48 +02:00 committed by GitHub
parent 9ce6843879
commit cf8ec8d3ae
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 162 additions and 7 deletions

View file

@ -7,7 +7,7 @@ By default, it uses `<title>` and OpenGraph tags. It also ships with a Twitter e
**Features**: **Features**:
- Setting SEO tags from PHP - Setting SEO tags from PHP
- Setting SEO tags from Blade - Setting SEO tags from Blade
- Integration with [Flipp](https://useflipp.com), to automatically generate cover images - Integration with [Flipp](https://useflipp.com) and [Previewify](https://previewify.app), to automatically generate cover images
- Custom extension support - Custom extension support
- Expressive & simple API - Expressive & simple API
- Customizable views - Customizable views
@ -216,6 +216,43 @@ The `flipp()` method also returns a signed URL to the image, which lets you use
<img alt="@seo('title')" src="@seo('flipp', 'blog')"> <img alt="@seo('title')" src="@seo('flipp', 'blog')">
``` ```
### Previewify 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).
2. Go to `config/services.php` and add:
```php
'previewify' => [
'key' => env('PREVIEWIFY_KEY'),
],
```
Then, register your templates, for example in `AppServiceProvider`:
```php
seo()->previewify('blog', 24);
seo()->previewify('page', 83);
```
After that, you can use the templates by calling `seo()->previewify()` like this:
```php
seo()->previewify('blog', ['title' => 'Foo', 'content' => 'bar'])`
```
The call will set the generated image as the OpenGraph and Twitter card images. The generated URLs are signed.
If no data array is provided, the method will use the `title` and `description` from the current SEO config:
```php
seo()->title($post->title);
seo()->description($post->excerpt);
seo()->previewify('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.
```php
<img alt="@seo('title')" src="@seo('previewify', 'blog')">
```
## Examples ## Examples
### Service Provider ### Service Provider

View file

@ -14,5 +14,6 @@ parameters:
ignoreErrors: ignoreErrors:
# Waiting for https://github.com/phpstan/phpstan/issues/5706 # Waiting for https://github.com/phpstan/phpstan/issues/5706
- '#^Cannot call method (flipp|get|set)\(\) on ArchTech\\SEO\\SEOManager\|array\|string\|null\.$#' - '#^Cannot call method (flipp|previewify|get|set)\(\) on ArchTech\\SEO\\SEOManager\|array\|string\|null\.$#'
- '#^Method ArchTech\\SEO\\SEOManager::flipp\(\) should return static\(ArchTech\\SEO\\SEOManager\)\|string but returns array\|string\|null\.$#' - '#^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\.$#'

View file

@ -181,6 +181,32 @@ class SEOManager
return $this->set('image', "https://s.useflipp.com/{$template}.png?s={$signature}&v={$query}"); 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
{
if (is_string($data) || is_int($data)) {
$this->meta("previewify.templates.$alias", (string) $data);
return $this;
}
if ($data === null) {
$data = [
'title' => $this->raw('title'),
'description' => $this->raw('description'),
];
}
$query = base64_encode(json_encode($data, JSON_THROW_ON_ERROR));
/** @var string $template */
$template = $this->meta("previewify.templates.$alias");
$signature = hash_hmac('sha256', $query, config('services.previewify.key'));
return $this->set('image', "https://previewify.app/generate/templates/{$template}/signed?signature={$signature}&fields={$query}");
}
/** Enable favicon extension. */ /** Enable favicon extension. */
public function favicon(): static public function favicon(): static
{ {

View file

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace ArchTech\SEO; namespace ArchTech\SEO;
use ArchTech\SEO\Commands\GenerateFaviconsCommand; use ArchTech\SEO\Commands\GenerateFaviconsCommand;
use Illuminate\Support\Arr;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use ImLiam\BladeHelper\BladeHelperServiceProvider; use ImLiam\BladeHelper\BladeHelperServiceProvider;
use ImLiam\BladeHelper\Facades\BladeHelper; use ImLiam\BladeHelper\Facades\BladeHelper;
@ -32,11 +33,11 @@ class SEOServiceProvider extends ServiceProvider
], 'seo-views'); ], 'seo-views');
BladeHelper::directive('seo', function (...$args) { BladeHelper::directive('seo', function (...$args) {
// Flipp supports more arguments // Flipp and Previewify support more arguments
if ($args[0] === 'flipp') { if (in_array($args[0], ['flipp', 'previewify'], true)) {
array_shift($args); $method = array_shift($args);
return seo()->flipp(...$args); return seo()->{$method}(...$args);
} }
// Two arguments indicate that we're setting a value, e.g. `@seo('title', 'foo') // Two arguments indicate that we're setting a value, e.g. `@seo('title', 'foo')
@ -46,7 +47,13 @@ class SEOServiceProvider extends ServiceProvider
// An array means we don't return anything, e.g. `@seo(['title' => 'foo']) // An array means we don't return anything, e.g. `@seo(['title' => 'foo'])
if (is_array($args[0])) { if (is_array($args[0])) {
seo($args[0]); foreach ($args[0] as $type => $value) {
if (in_array($type, ['flipp', 'previewify'], true)) {
seo()->{$type}(...Arr::wrap($value));
} else {
seo()->set($type, $value);
}
}
return null; return null;
} }

View file

@ -68,3 +68,10 @@ test('flipp uses the raw title and description', function () {
->toContain('s.useflipp.com/abcdefg') ->toContain('s.useflipp.com/abcdefg')
->toContain(base64_encode(json_encode(['title' => 'foo', 'description' => 'bar']))); ->toContain(base64_encode(json_encode(['title' => 'foo', 'description' => 'bar'])));
}); });
test('the @seo helper can be used for setting a flipp image', function () {
seo()->flipp('blog', 'abcdefg');
blade("@seo(['flipp' => ['blog', ['title' => 'abc', 'excerpt' => 'def']]])");
expect(seo('image'))->toContain('s.useflipp.com/abcdefg');
});

View file

@ -0,0 +1,77 @@
<?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', 'excerpt' => 'def']))
->toContain('previewify.app/generate/templates/1')
->toContain(base64_encode(json_encode(['title' => 'abc', '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(['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(['title' => 'foo', '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(['title' => 'foo', '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');
});