Skip to content

PreviewLinks #37

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ By default, it uses `<title>` 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
Expand Down Expand Up @@ -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.
Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -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#'
26 changes: 13 additions & 13 deletions src/SEOManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -184,35 +184,35 @@ 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,
);
}

$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. */
Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
Expand Down
77 changes: 77 additions & 0 deletions tests/Pest/PreviewLinksTest.php
Original file line number Diff line number Diff line change
@@ -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');
});
77 changes: 0 additions & 77 deletions tests/Pest/PreviewifyTest.php

This file was deleted.

Loading