Skip to content

Commit

Permalink
Add support for FAQPage schema (#77)
Browse files Browse the repository at this point in the history
* add support for FaqPage schema

* add documentation

* update readme

* Formatting

* Update README.md

* Update README.md

* WIP

---------

Co-authored-by: Ralph J. Smit <[email protected]>
  • Loading branch information
QuentinGab and ralphjsmit authored May 23, 2024
1 parent c51735f commit e25a2fa
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 14 deletions.
47 changes: 33 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ This package generates **valid and useful meta tags straight out-of-the-box**, w
2. Meta tags (author, description, image, robots, etc.)
3. OpenGraph Tags (Facebook, LinkedIn, etc.)
4. Twitter Tags
5. Structured data (Article and Breadcrumbs)
5. Structured data (Article, Breadcrumbs and FAQPage)
6. Favicon
7. Robots tag
8. Alternates links tag
Expand Down Expand Up @@ -333,6 +333,7 @@ This package can also **generate structured data** for you (also called schema m

1. `Article`
2. `BreadcrumbList`
3. `FAQPage`

After generating the structured data it is always a good idea to [test your website with Google's rich result validator](https://search.google.com/test/rich-results).

Expand Down Expand Up @@ -368,7 +369,7 @@ You can completely customize the schema markup by using the `->markup()` method
```php
use Illuminate\Support\Collection;

SchemaCollection::initialize()->addArticle(function(ArticleSchema $article): ArticleSchema {
SchemaCollection::initialize()->addArticle(function (ArticleSchema $article): ArticleSchema {
return $article->markup(function(Collection $markup): Collection {
return $markup->put('alternativeHeadline', $this->tagline);
});
Expand All @@ -382,18 +383,20 @@ At this point, I'm just unable to fluently support every possible version of the
You can also add `BreadcrumbList` schema markup by using the `->addBreadcrumbs()` function on the `SchemaCollection`:

```php
SchemaCollection::initialize()->addBreadcrumbs(
function(BreadcrumbListSchema $breadcrumbs): BreadcrumbListSchema {
return $breadcrumbs->prependBreadcrumbs([
'Homepage' => 'https://example.com',
'Category' => 'https://example.com/test',
])->appendBreadcrumbs([
'Subarticle' => 'https://example.com/test/article/2',
])->markup(function(Collection $markup): Collection {
// ...
});
}
);
SchemaCollection::initialize()
->addBreadcrumbs(function (BreadcrumbListSchema $breadcrumbs): BreadcrumbListSchema {
return $breadcrumbs
->prependBreadcrumbs([
'Homepage' => 'https://example.com',
'Category' => 'https://example.com/test',
])
->appendBreadcrumbs([
'Subarticle' => 'https://example.com/test/article/2',
])
->markup(function(Collection $markup): Collection {
// ...
});
});
```

This code will generate `BreadcrumbList` JSON-LD structured data with the following four pages:
Expand All @@ -403,6 +406,22 @@ This code will generate `BreadcrumbList` JSON-LD structured data with the follow
3. [Current page]
4. Subarticle

### FAQPage schema markup

You can also add `FAQPage` schema markup by using the `->addFaqPage()` function on the `SchemaCollection`:

```php
use RalphJSmit\Laravel\SEO\Schema\FaqPageSchema;
use RalphJSmit\Laravel\SEO\SchemaCollection;

SchemaCollection::initialize()
->addFaqPage(function (FaqPageSchema $faqPage): FaqPageSchema {
return $faqPage
->addQuestion(name: "Can this package add FaqPage to the schema?", acceptedAnswer: "Yes!")
->addQuestion(name: "Does it support multiple questions?", acceptedAnswer: "Of course.");
});
```

## Advanced usage

Sometimes you may have advanced needs, that require you to apply your own logic to the `SEOData` class, just before it is used to generate the tags.
Expand Down
50 changes: 50 additions & 0 deletions src/Schema/FaqPageSchema.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

namespace RalphJSmit\Laravel\SEO\Schema;

use Illuminate\Support\HtmlString;
use RalphJSmit\Laravel\SEO\Support\SEOData;

/**
* @see https://developers.google.com/search/docs/appearance/structured-data/faqpage
*/
class FaqPageSchema extends Schema
{
public string $type = 'FAQPage';

public array $questions = [];

public function addQuestion(
string $name,
string $acceptedAnswer
): static {
$this->questions[] = [
'@type' => 'Question',
'name' => $name,
'acceptedAnswer' => [
'@type' => 'Answer',
'text' => $acceptedAnswer,
],
];

return $this;
}

public function initializeMarkup(SEOData $SEOData, array $markupBuilders): void
{
//
}

public function generateInner(): HtmlString
{
$inner = collect([
'@context' => 'https://schema.org',
'@type' => $this->type,
'mainEntity' => $this->questions,
])
->pipeThrough($this->markupTransformers)
->toJson();

return new HtmlString($inner);
}
}
9 changes: 9 additions & 0 deletions src/SchemaCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
use Illuminate\Support\Collection;
use RalphJSmit\Laravel\SEO\Schema\ArticleSchema;
use RalphJSmit\Laravel\SEO\Schema\BreadcrumbListSchema;
use RalphJSmit\Laravel\SEO\Schema\FaqPageSchema;
use RalphJSmit\Laravel\SEO\Schema\Schema;

class SchemaCollection extends Collection
{
protected array $dictionary = [
'article' => ArticleSchema::class,
'breadcrumbs' => BreadcrumbListSchema::class,
'faqPage' => FaqPageSchema::class,
];

public array $markup = [];
Expand All @@ -31,6 +33,13 @@ public function addBreadcrumbs(?Closure $builder = null): static
return $this;
}

public function addFaqPage(?Closure $builder = null): static
{
$this->markup[$this->dictionary['faqPage']][] = $builder ?: fn (Schema $schema): Schema => $schema;

return $this;
}

public static function initialize(): static
{
return new static();
Expand Down
59 changes: 59 additions & 0 deletions tests/Feature/JSON-LD/FaqPageTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

use RalphJSmit\Laravel\SEO\Schema\FaqPageSchema;
use RalphJSmit\Laravel\SEO\SchemaCollection;
use RalphJSmit\Laravel\SEO\Tests\Fixtures\Page;

use function Pest\Laravel\get;

it('does not render by default the JSON-LD Schema markup: FaqPageTest', function () {
get(route('seo.test-plain'))
->assertDontSee('"application/ld+json"')
->assertDontSee('"@type": "FAQPage"');
});

it('can correctly render the JSON-LD Schema markup: FaqPageTest', function () {
config()->set('seo.title.suffix', ' | Laravel SEO');

$page = Page::create([]);

$page::$overrides = [
'title' => 'Test FAQ',
'enableTitleSuffix' => true,
'url' => 'https://example.com/test/faq',
'schema' => SchemaCollection::initialize()->addFaqPage(function (FaqPageSchema $faqPage): FaqPageSchema {
return $faqPage
->addQuestion(name: 'Can this package add FaqPage to the schema?', acceptedAnswer: 'Yes!')
->addQuestion(name: 'Does it support multiple questions?', acceptedAnswer: 'Of course.');
}),
];

get(route('seo.test-page', ['page' => $page]))
->assertSee('"application/ld+json"', false)
->assertSee(
'<script type="application/ld+json">' .
json_encode([
'@context' => 'https://schema.org',
'@type' => 'FAQPage',
'mainEntity' => [
[
'@type' => 'Question',
'name' => 'Can this package add FaqPage to the schema?',
'acceptedAnswer' => [
'@type' => 'Answer',
'text' => 'Yes!',
],
],
[
'@type' => 'Question',
'name' => 'Does it support multiple questions?',
'acceptedAnswer' => [
'@type' => 'Answer',
'text' => 'Of course.',
],
],
],
]) . '</script>',
false
);
});

0 comments on commit e25a2fa

Please sign in to comment.