Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 74b632d

Browse files
authoredAug 4, 2023
Keyword checks (#36)
* Create check and test file * wip * Fix styling * wip * wip * wip * wip * Fix styling * wip * wip * wip * wip * Check if keywords are present in the title * Fix styling * Add test * Remove focus from check name * Add keyword in first paragraph check * Fix styling * Fix Http class * Fix Http class --------- Co-authored-by: Baspa <[email protected]>
1 parent 475066e commit 74b632d

File tree

8 files changed

+281
-2
lines changed

8 files changed

+281
-2
lines changed
 

‎README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,8 @@ These checks are available in the package. You can add or remove checks in the c
241241
✅ The page title is not longer than 60 characters. <br>
242242
✅ The page has an Open Graph image.<br>
243243
✅ The lang attribute is set on the html tag.<br>
244+
✅ The title contains one or more keywords.<br>
245+
✅ One or more keywords are present in the first paragraph.<br>
244246

245247
### Performance
246248

‎config/seo.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,5 +153,21 @@
153153
'headers' => [
154154
'User-Agent' => 'Laravel SEO Scanner/1.0',
155155
],
156+
157+
/*
158+
|--------------------------------------------------------------------------
159+
| Http throttle
160+
|--------------------------------------------------------------------------
161+
|
162+
| Here you can specify the throttle options of the http client. This
163+
| will throttle the requests to the same domain. This is useful
164+
| when you have a lot of routes to check.
165+
|
166+
*/
167+
'throttle' => [
168+
'enabled' => false,
169+
'max_requests' => 10,
170+
'seconds' => 1,
171+
],
156172
],
157173
];

‎resources/lang/en.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
"failed.content.no_title": "The page does not contain a title tag, while it should.",
1616
"failed.content.title_length": "The page title is :actualValue characters long. It should be max :expectedValue characters long.",
1717
"failed.meta.description": "The page does not contain a description meta tag, while it should.",
18+
"failed.meta.keyword_in_title_check": "The page title does not contain the focus keyword, while it should.",
19+
"failed.meta.keyword_in_first_paragraph_check": "The page does not contain the focus keyword in the first paragraph, while it should.",
1820
"failed.meta.no_lang": "The page does not contain a lang attribute, while it should.",
1921
"failed.meta.open_graph_image": "The page does not contain an open graph image, while it should.",
2022
"failed.meta.open_graph_image.broken": "The page contains a broken open graph image. This image was found: :actualValue.",
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php
2+
3+
namespace Vormkracht10\Seo\Checks\Meta;
4+
5+
use Illuminate\Http\Client\Response;
6+
use Illuminate\Support\Str;
7+
use Symfony\Component\DomCrawler\Crawler;
8+
use Vormkracht10\Seo\Interfaces\Check;
9+
use Vormkracht10\Seo\Traits\PerformCheck;
10+
11+
class KeywordInFirstParagraphCheck implements Check
12+
{
13+
use PerformCheck;
14+
15+
public string $title = 'The page has the focus keyword in the first paragraph';
16+
17+
public string $priority = 'medium';
18+
19+
public int $timeToFix = 10;
20+
21+
public int $scoreWeight = 5;
22+
23+
public bool $continueAfterFailure = true;
24+
25+
public ?string $failureReason;
26+
27+
public mixed $actualValue = null;
28+
29+
public mixed $expectedValue = null;
30+
31+
public function check(Response $response, Crawler $crawler): bool
32+
{
33+
if (! $this->validateContent($crawler)) {
34+
$this->failureReason = __('failed.meta.keyword_in_first_paragraph_check');
35+
36+
return false;
37+
}
38+
39+
return true;
40+
}
41+
42+
public function validateContent(Crawler $crawler): bool
43+
{
44+
$keywords = $this->getKeywords($crawler);
45+
46+
if (! $keywords) {
47+
return false;
48+
}
49+
50+
$firstParagraph = $this->getFirstParagraphContent($crawler);
51+
52+
if (! $firstParagraph) {
53+
return false;
54+
}
55+
56+
if (! Str::contains($firstParagraph, $keywords)) {
57+
return false;
58+
}
59+
60+
return true;
61+
}
62+
63+
public function getFirstParagraphContent(Crawler $crawler): ?string
64+
{
65+
$node = $crawler->filterXPath('//p')->getNode(0);
66+
67+
if (! $node) {
68+
return null;
69+
}
70+
71+
return $crawler->filterXPath('//p')->text();
72+
}
73+
74+
public function getKeywords(Crawler $crawler): array
75+
{
76+
$node = $crawler->filterXPath('//meta[@name="keywords"]')->getNode(0);
77+
78+
if (! $node) {
79+
return [];
80+
}
81+
82+
$keywords = $crawler->filterXPath('//meta[@name="keywords"]')->attr('content');
83+
84+
if (! $keywords) {
85+
return [];
86+
}
87+
88+
return explode(', ', $keywords);
89+
}
90+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
namespace Vormkracht10\Seo\Checks\Meta;
4+
5+
use Illuminate\Http\Client\Response;
6+
use Illuminate\Support\Str;
7+
use Symfony\Component\DomCrawler\Crawler;
8+
use Vormkracht10\Seo\Interfaces\Check;
9+
use Vormkracht10\Seo\Traits\PerformCheck;
10+
11+
class KeywordInTitleCheck implements Check
12+
{
13+
use PerformCheck;
14+
15+
public string $title = 'The page has the focus keyword in the title';
16+
17+
public string $priority = 'medium';
18+
19+
public int $timeToFix = 1;
20+
21+
public int $scoreWeight = 5;
22+
23+
public bool $continueAfterFailure = true;
24+
25+
public ?string $failureReason;
26+
27+
public mixed $actualValue = null;
28+
29+
public mixed $expectedValue = null;
30+
31+
public function check(Response $response, Crawler $crawler): bool
32+
{
33+
if (! $this->validateContent($crawler)) {
34+
$this->failureReason = __('failed.meta.keyword_in_title_check');
35+
36+
return false;
37+
}
38+
39+
return true;
40+
}
41+
42+
public function validateContent(Crawler $crawler): bool
43+
{
44+
$keywords = $this->getKeywords($crawler);
45+
46+
if (! $keywords) {
47+
return false;
48+
}
49+
50+
$title = $crawler->filterXPath('//title')->text();
51+
52+
if (! $title) {
53+
return false;
54+
}
55+
56+
if (! Str::contains($title, $keywords)) {
57+
return false;
58+
}
59+
60+
return true;
61+
}
62+
63+
public function getKeywords(Crawler $crawler): array
64+
{
65+
$node = $crawler->filterXPath('//meta[@name="keywords"]')->getNode(0);
66+
67+
if (! $node) {
68+
return [];
69+
}
70+
71+
$keywords = $crawler->filterXPath('//meta[@name="keywords"]')->attr('content');
72+
73+
if (! $keywords) {
74+
return [];
75+
}
76+
77+
return explode(', ', $keywords);
78+
}
79+
}

‎src/Http.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,15 @@ public function withHeaders(array $headers): self
4040

4141
public function get(): object
4242
{
43-
return HttpFacade::withOptions([
43+
$http = HttpFacade::withOptions([
4444
...config('seo.http.options', []),
4545
...$this->options,
4646
])->withHeaders([
4747
...config('seo.http.headers', []),
4848
...$this->headers,
49-
])->get($this->url);
49+
]);
50+
51+
return $http->get($this->url);
5052
}
5153

5254
public function getRemoteResponse(): object
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
use Illuminate\Support\Facades\Http;
4+
use Symfony\Component\DomCrawler\Crawler;
5+
use Vormkracht10\Seo\Checks\Meta\KeywordInFirstParagraphCheck;
6+
7+
it('can perform the keyword in first paragraph check on a page with the keyword in the first paragraph', function () {
8+
$check = new KeywordInFirstParagraphCheck();
9+
$crawler = new Crawler();
10+
11+
Http::fake([
12+
'vormkracht10.nl' => Http::response('<html><head><meta name="keywords" content="vormkracht10, seo, laravel, package"></head><body><p>vormkracht10 is a great company that specializes in SEO and Laravel packages.</p></body></html>', 200),
13+
]);
14+
15+
$crawler->addHtmlContent(Http::get('vormkracht10.nl')->body());
16+
17+
$this->assertTrue($check->check(Http::get('vormkracht10.nl'), $crawler));
18+
});
19+
20+
it('can perform the keyword in first paragraph check on a page without the keyword in the first paragraph', function () {
21+
$check = new KeywordInFirstParagraphCheck();
22+
$crawler = new Crawler();
23+
24+
Http::fake([
25+
'vormkracht10.nl' => Http::response('<html><head><meta name="keywords" content="seo, laravel, package"></head><body><p>Lorem ipsum dolor sit amet.</p></body></html>', 200),
26+
]);
27+
28+
$crawler->addHtmlContent(Http::get('vormkracht10.nl')->body());
29+
30+
$this->assertFalse($check->check(Http::get('vormkracht10.nl'), $crawler));
31+
});
32+
33+
it('can perform the keyword in first paragraph check on a page without keywords', function () {
34+
$check = new KeywordInFirstParagraphCheck();
35+
$crawler = new Crawler();
36+
37+
Http::fake([
38+
'vormkracht10.nl' => Http::response('<html><head></head><body></body></html>', 200),
39+
]);
40+
41+
$crawler->addHtmlContent(Http::get('vormkracht10.nl')->body());
42+
43+
$this->assertFalse($check->check(Http::get('vormkracht10.nl'), $crawler));
44+
});
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
use Illuminate\Support\Facades\Http;
4+
use Symfony\Component\DomCrawler\Crawler;
5+
use Vormkracht10\Seo\Checks\Meta\KeywordInTitleCheck;
6+
7+
it('can perform the keyword in title check on a page with the keyword in the title', function () {
8+
$check = new KeywordInTitleCheck();
9+
$crawler = new Crawler();
10+
11+
Http::fake([
12+
'vormkracht10.nl' => Http::response('<html><head><title>vormkracht10</title><meta name="keywords" content="vormkracht10, seo, laravel, package"></head><body></body></html>', 200),
13+
]);
14+
15+
$crawler->addHtmlContent(Http::get('vormkracht10.nl')->body());
16+
17+
$this->assertTrue($check->check(Http::get('vormkracht10.nl'), $crawler));
18+
});
19+
20+
it('can perform the keyword in title check on a page without the keyword in the title', function () {
21+
$check = new KeywordInTitleCheck();
22+
$crawler = new Crawler();
23+
24+
Http::fake([
25+
'vormkracht10.nl' => Http::response('<html><head><title>vormkracht10</title><meta name="keywords" content="seo, laravel, package"></head><body></body></html>', 200),
26+
]);
27+
28+
$crawler->addHtmlContent(Http::get('vormkracht10.nl')->body());
29+
30+
$this->assertFalse($check->check(Http::get('vormkracht10.nl'), $crawler));
31+
});
32+
33+
it('can perform the keyword in title check on a page without keywords', function () {
34+
$check = new KeywordInTitleCheck();
35+
$crawler = new Crawler();
36+
37+
Http::fake([
38+
'vormkracht10.nl' => Http::response('<html><head></head><body></body></html>', 200),
39+
]);
40+
41+
$crawler->addHtmlContent(Http::get('vormkracht10.nl')->body());
42+
43+
$this->assertFalse($check->check(Http::get('vormkracht10.nl'), $crawler));
44+
});

0 commit comments

Comments
 (0)