Skip to content

Commit cf8ec8d

Browse files
authored
Add Previewify image provider (#20)
* Add previewify image provider * feat: support for setting image with the @seo blade tag (resolves #22)
1 parent 9ce6843 commit cf8ec8d

File tree

6 files changed

+162
-7
lines changed

6 files changed

+162
-7
lines changed

README.md

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ By default, it uses `<title>` and OpenGraph tags. It also ships with a Twitter e
77
**Features**:
88
- Setting SEO tags from PHP
99
- Setting SEO tags from Blade
10-
- Integration with [Flipp](https://useflipp.com), to automatically generate cover images
10+
- Integration with [Flipp](https://useflipp.com) and [Previewify](https://previewify.app), to automatically generate cover images
1111
- Custom extension support
1212
- Expressive & simple API
1313
- Customizable views
@@ -216,6 +216,43 @@ The `flipp()` method also returns a signed URL to the image, which lets you use
216216
<img alt="@seo('title')" src="@seo('flipp', 'blog')">
217217
```
218218

219+
### Previewify integration
220+
221+
First, you need to add your Previewify API keys:
222+
1. Add your API key to the `PREVIEWIFY_KEY` environment variable. You can get the key [here](https://previewify.app/app/account).
223+
2. Go to `config/services.php` and add:
224+
```php
225+
'previewify' => [
226+
'key' => env('PREVIEWIFY_KEY'),
227+
],
228+
```
229+
230+
Then, register your templates, for example in `AppServiceProvider`:
231+
```php
232+
seo()->previewify('blog', 24);
233+
seo()->previewify('page', 83);
234+
```
235+
236+
After that, you can use the templates by calling `seo()->previewify()` like this:
237+
```php
238+
seo()->previewify('blog', ['title' => 'Foo', 'content' => 'bar'])`
239+
```
240+
241+
The call will set the generated image as the OpenGraph and Twitter card images. The generated URLs are signed.
242+
243+
If no data array is provided, the method will use the `title` and `description` from the current SEO config:
244+
245+
```php
246+
seo()->title($post->title);
247+
seo()->description($post->excerpt);
248+
seo()->previewify('blog');
249+
```
250+
251+
The `previewify()` method also returns a signed URL to the image, which lets you use it in other places, such as blog cover images.
252+
```php
253+
<img alt="@seo('title')" src="@seo('previewify', 'blog')">
254+
```
255+
219256
## Examples
220257

221258
### Service Provider

phpstan.neon

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ parameters:
1414

1515
ignoreErrors:
1616
# Waiting for https://github.com/phpstan/phpstan/issues/5706
17-
- '#^Cannot call method (flipp|get|set)\(\) on ArchTech\\SEO\\SEOManager\|array\|string\|null\.$#'
17+
- '#^Cannot call method (flipp|previewify|get|set)\(\) on ArchTech\\SEO\\SEOManager\|array\|string\|null\.$#'
1818
- '#^Method ArchTech\\SEO\\SEOManager::flipp\(\) should return static\(ArchTech\\SEO\\SEOManager\)\|string but returns array\|string\|null\.$#'
19+
- '#^Method ArchTech\\SEO\\SEOManager::previewify\(\) should return static\(ArchTech\\SEO\\SEOManager\)\|string but returns array\|string\|null\.$#'

src/SEOManager.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,32 @@ public function flipp(string $alias, string|array $data = null): string|static
181181
return $this->set('image', "https://s.useflipp.com/{$template}.png?s={$signature}&v={$query}");
182182
}
183183

184+
/** Configure or use Previewify. */
185+
public function previewify(string $alias, int|string|array $data = null): string|static
186+
{
187+
if (is_string($data) || is_int($data)) {
188+
$this->meta("previewify.templates.$alias", (string) $data);
189+
190+
return $this;
191+
}
192+
193+
if ($data === null) {
194+
$data = [
195+
'title' => $this->raw('title'),
196+
'description' => $this->raw('description'),
197+
];
198+
}
199+
200+
$query = base64_encode(json_encode($data, JSON_THROW_ON_ERROR));
201+
202+
/** @var string $template */
203+
$template = $this->meta("previewify.templates.$alias");
204+
205+
$signature = hash_hmac('sha256', $query, config('services.previewify.key'));
206+
207+
return $this->set('image', "https://previewify.app/generate/templates/{$template}/signed?signature={$signature}&fields={$query}");
208+
}
209+
184210
/** Enable favicon extension. */
185211
public function favicon(): static
186212
{

src/SEOServiceProvider.php

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace ArchTech\SEO;
66

77
use ArchTech\SEO\Commands\GenerateFaviconsCommand;
8+
use Illuminate\Support\Arr;
89
use Illuminate\Support\ServiceProvider;
910
use ImLiam\BladeHelper\BladeHelperServiceProvider;
1011
use ImLiam\BladeHelper\Facades\BladeHelper;
@@ -32,11 +33,11 @@ public function boot(): void
3233
], 'seo-views');
3334

3435
BladeHelper::directive('seo', function (...$args) {
35-
// Flipp supports more arguments
36-
if ($args[0] === 'flipp') {
37-
array_shift($args);
36+
// Flipp and Previewify support more arguments
37+
if (in_array($args[0], ['flipp', 'previewify'], true)) {
38+
$method = array_shift($args);
3839

39-
return seo()->flipp(...$args);
40+
return seo()->{$method}(...$args);
4041
}
4142

4243
// Two arguments indicate that we're setting a value, e.g. `@seo('title', 'foo')
@@ -46,7 +47,13 @@ public function boot(): void
4647

4748
// An array means we don't return anything, e.g. `@seo(['title' => 'foo'])
4849
if (is_array($args[0])) {
49-
seo($args[0]);
50+
foreach ($args[0] as $type => $value) {
51+
if (in_array($type, ['flipp', 'previewify'], true)) {
52+
seo()->{$type}(...Arr::wrap($value));
53+
} else {
54+
seo()->set($type, $value);
55+
}
56+
}
5057

5158
return null;
5259
}

tests/Pest/FlippTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,10 @@
6868
->toContain('s.useflipp.com/abcdefg')
6969
->toContain(base64_encode(json_encode(['title' => 'foo', 'description' => 'bar'])));
7070
});
71+
72+
test('the @seo helper can be used for setting a flipp image', function () {
73+
seo()->flipp('blog', 'abcdefg');
74+
blade("@seo(['flipp' => ['blog', ['title' => 'abc', 'excerpt' => 'def']]])");
75+
76+
expect(seo('image'))->toContain('s.useflipp.com/abcdefg');
77+
});

tests/Pest/PreviewifyTest.php

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
3+
beforeEach(fn () => config(['services.previewify.key' => 'abc']));
4+
5+
test('previewify templates can be set', function () {
6+
seo()->previewify('blog', 1);
7+
8+
expect(seo()->meta('previewify.templates'))
9+
->toHaveCount(1)
10+
->toHaveKey('blog', '1');
11+
});
12+
13+
test('previewify makes a request to the template not the alias', function () {
14+
seo()->previewify('blog', 1);
15+
expect(seo()->previewify('blog'))
16+
->toContain('previewify.app/generate/templates/1');
17+
});
18+
19+
test('previewify templates can be given data', function () {
20+
seo()->previewify('blog', 1);
21+
expect(seo()->previewify('blog', ['title' => 'abc', 'excerpt' => 'def']))
22+
->toContain('previewify.app/generate/templates/1')
23+
->toContain(base64_encode(json_encode(['title' => 'abc', 'excerpt' => 'def'])));
24+
});
25+
26+
test('the previewify method returns a link to a signed url', function () {
27+
seo()->previewify('blog', 1);
28+
29+
expect(seo()->previewify('blog', ['title' => 'abc']))
30+
->toContain('?signature=' . hash_hmac('sha256', base64_encode(json_encode(['title' => 'abc'])), config('services.previewify.key')));
31+
});
32+
33+
test("previewify templates use default data when they're not passed any data explicitly", function () {
34+
seo()->previewify('blog', 1);
35+
36+
seo()->title('foo')->description('bar');
37+
38+
expect(seo()->previewify('blog'))
39+
->toContain('previewify.app/generate/templates/1')
40+
->toContain(base64_encode(json_encode(['title' => 'foo', 'description' => 'bar'])));
41+
});
42+
43+
test('previewify images are used as the cover images', function () {
44+
seo()->previewify('blog', 1);
45+
46+
seo()->title('foo')->description('bar');
47+
48+
expect(seo()->previewify('blog'))
49+
->toBe(seo('image'));
50+
});
51+
52+
test('the blade directive can be used with previewify', function () {
53+
seo()->previewify('blog', 1);
54+
55+
seo()->title('foo')->description('bar');
56+
57+
expect(blade("@seo('previewify', 'blog')"))->toBe(seo()->previewify('blog'));
58+
expect(blade("@seo('previewify', 'blog', ['title' => 'abc'])"))->toBe(seo()->previewify('blog', ['title' => 'abc']));
59+
});
60+
61+
test('previewify uses the raw title and description', function () {
62+
seo()->previewify('blog', 1);
63+
64+
seo()->title(modify: fn (string $title) => $title . ' - modified');
65+
seo()->title('foo')->description('bar');
66+
67+
expect(seo()->previewify('blog'))
68+
->toContain('previewify.app/generate/templates/1')
69+
->toContain(base64_encode(json_encode(['title' => 'foo', 'description' => 'bar'])));
70+
});
71+
72+
test('the @seo helper can be used for setting a previewify image', function () {
73+
seo()->previewify('blog', 1);
74+
blade("@seo(['previewify' => ['blog', ['title' => 'abc', 'excerpt' => 'def']]])");
75+
76+
expect(seo('image'))->toContain('previewify.app/generate/templates/1');
77+
});

0 commit comments

Comments
 (0)