Skip to content

Commit 18d7afc

Browse files
committed
Init
0 parents  commit 18d7afc

File tree

5 files changed

+328
-0
lines changed

5 files changed

+328
-0
lines changed

.github/workflows/main.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: Integrity check
2+
3+
on: [push]
4+
5+
jobs:
6+
build:
7+
runs-on: ubuntu-latest
8+
9+
steps:
10+
- uses: actions/checkout@master
11+
12+
- name: Install PHP
13+
uses: shivammathur/setup-php@master
14+
with:
15+
php-version: 7.4
16+
17+
- name: Install composer deps
18+
run: |
19+
composer create-project nette/code-checker temp/code-checker ^3 --no-progress
20+
composer create-project nette/coding-standard temp/coding-standard ^2 --no-progress
21+
22+
# Install app deps
23+
composer install --no-interaction --prefer-dist
24+
25+
# Check code checker and coding standards
26+
- name: Check coding standards
27+
run: |
28+
php temp/code-checker/code-checker --short-arrays --strict-types --fix --no-progress
29+
php temp/coding-standard/ecs check src --config temp/coding-standard/coding-standard-php71.yml
30+
31+
- name: Check PHPStan rules
32+
run: composer phpstan

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2021 Baraja packages
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

composer.json

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"name": "baraja-core/html-header",
3+
"description": "Renders the complete valid header HTML content based on MetaBuilder.",
4+
"homepage": "https://github.com/baraja-core/html-header",
5+
"authors": [
6+
{
7+
"name": "Jan Barášek",
8+
"homepage": "https://baraja.cz"
9+
}
10+
],
11+
"require": {
12+
"php": ">=7.4.0"
13+
},
14+
"require-dev": {
15+
"phpstan/phpstan": "^0.12.18",
16+
"tracy/tracy": "^2.7",
17+
"phpstan/phpstan-nette": "^0.12.6",
18+
"symplify/easy-coding-standard": "^7.2"
19+
},
20+
"autoload": {
21+
"classmap": [
22+
"src/"
23+
]
24+
},
25+
"scripts": {
26+
"phpstan": [
27+
"vendor/bin/phpstan analyse src -c phpstan.neon --level 8 --no-progress"
28+
]
29+
},
30+
"minimum-stability": "stable"
31+
}

phpstan.neon

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
includes:
2+
- vendor/phpstan/phpstan-nette/extension.neon
3+
- vendor/phpstan/phpstan-nette/rules.neon

src/HtmlHeader.php

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Baraja\HtmlHeader;
6+
7+
8+
/**
9+
* Renders the complete valid header HTML content based on MetaBuilder.
10+
* Simply define individual tags by calling methods and then get the entire contents of the header:
11+
*
12+
* $header = new HtmlHeader;
13+
* $header->title('My webpage');
14+
* $header->meta('description', 'Awesome page...');
15+
* // or shortcut:
16+
* $header->metaDescription('Awesome page...');
17+
*
18+
* $header->link('canonical', 'https://baraja.cz');
19+
* $header->link('alternate', [
20+
* 'hreflang' => 'cs',
21+
* 'href' => 'https://baraja.cz',
22+
* ]);
23+
*
24+
* $header->jsonld([
25+
* '@context' => 'http://schema.org',
26+
* '@type' => 'Person',
27+
* 'name' => 'Jan Barášek',
28+
* ]);
29+
*/
30+
final class HtmlHeader
31+
{
32+
/** @var string[] */
33+
private array $order = ['title', 'meta', 'og', 'twitter', 'link', 'json-ld'];
34+
35+
/** @var string[][]|string[][][] */
36+
private array $tags = [];
37+
38+
39+
public function __toString(): string
40+
{
41+
return $this->render();
42+
}
43+
44+
45+
/**
46+
* Render all or a specific group of HTML meta tags.
47+
*
48+
* @param string[] $groups render specific groups or keep empty for all records.
49+
*/
50+
public function render(?array $groups = null): string
51+
{
52+
$items = [];
53+
foreach (($groups ?? $this->order) as $group) {
54+
$items[] = $this->renderGroup($group);
55+
}
56+
57+
return trim(implode('', $items));
58+
}
59+
60+
61+
/**
62+
* @param string[] $order
63+
*/
64+
public function setCustomOrderingStrategy(array $order): void
65+
{
66+
$this->order = $order;
67+
}
68+
69+
70+
/**
71+
* Build an HTML link tag.
72+
*
73+
* @param string|string[]|null $value
74+
*/
75+
public function link(string $key, $value): void
76+
{
77+
if (!empty($value)) {
78+
$attributes = ['rel' => $key];
79+
if (is_array($value)) {
80+
foreach ($value as $valueKey => $v) {
81+
$attributes[$valueKey] = $v;
82+
}
83+
} else {
84+
$attributes['href'] = $value;
85+
}
86+
87+
$this->addToTagsGroup('link', $key, $this->createTag('link', $attributes));
88+
}
89+
}
90+
91+
92+
public function metaDescription(string $content): void
93+
{
94+
$this->meta('description', $content);
95+
}
96+
97+
98+
/**
99+
* Build an HTML meta tag.
100+
*
101+
* @param string|string[]|null $value
102+
*/
103+
public function meta(string $key, $value): void
104+
{
105+
if (!empty($value)) {
106+
$attributes = ['name' => $key];
107+
if (is_array($value)) {
108+
foreach ($value as $valueKey => $v) {
109+
$attributes[$valueKey] = $v;
110+
}
111+
} else {
112+
$attributes['content'] = $value;
113+
}
114+
115+
$this->addToTagsGroup('meta', $key, $this->createTag('meta', $attributes));
116+
}
117+
}
118+
119+
120+
/** Build an Open Graph meta tag. */
121+
public function og(string $key, string $value, bool $prefixed = true): void
122+
{
123+
if (!empty($value)) {
124+
$key = $prefixed ? 'og:' . $key : $key;
125+
$this->addToTagsGroup('og', $key, $this->createTag('meta', [
126+
'property' => $key,
127+
'content' => $value,
128+
]));
129+
}
130+
}
131+
132+
133+
/**
134+
* Build an JSON linked data meta tag.
135+
*
136+
* @param mixed[] $schema
137+
*/
138+
public function jsonld(array $schema): void
139+
{
140+
if ($schema === []) {
141+
return;
142+
}
143+
144+
try {
145+
$json = json_encode($schema, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR);
146+
} catch (\Throwable $e) {
147+
throw new \InvalidArgumentException('Invalid json: ' . $e->getMessage(), $e->getCode(), $e);
148+
}
149+
$this->tags['json-ld'][] = '<script type="application/ld+json">' . "\n" . $json . "\n" . '</script>';
150+
}
151+
152+
153+
/** Build a Title HTML tag. */
154+
public function title(?string $value): void
155+
{
156+
if ($value === null) {
157+
return;
158+
}
159+
if (($value = trim($value)) !== '') {
160+
$this->tags['title'][] = '<title>' . htmlspecialchars($value, ENT_NOQUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</title>';
161+
}
162+
}
163+
164+
165+
/** Build a Twitter Card meta tag. */
166+
public function twitter(string $key, string $value, bool $prefixed = true): void
167+
{
168+
if (!empty($value)) {
169+
$key = $prefixed ? 'twitter:' . $key : $key;
170+
$this->addToTagsGroup('twitter', $key, $this->createTag('meta', [
171+
'name' => $key,
172+
'content' => $value,
173+
]));
174+
}
175+
}
176+
177+
178+
/** Render all HTML meta tags from a specific group. */
179+
private function renderGroup(string $group): string
180+
{
181+
if (!isset($this->tags[$group])) {
182+
return '';
183+
}
184+
185+
$html = [];
186+
foreach ($this->tags[$group] as $tag) {
187+
if (is_array($tag)) {
188+
foreach ($tag as $t) {
189+
$html[] = $t;
190+
}
191+
} else {
192+
$html[] = $tag;
193+
}
194+
}
195+
196+
return count($html) > 0
197+
? implode("\n", $html) . "\n"
198+
: '';
199+
}
200+
201+
202+
/** Add single HTML element to tags group. */
203+
private function addToTagsGroup(string $group, string $key, string $tag): void
204+
{
205+
if (isset($this->tags[$group][$key])) {
206+
if (is_array($this->tags[$group][$key])) {
207+
$this->tags[$group][$key][] = $tag;
208+
} else {
209+
$this->tags[$group][$key] = [$this->tags[$group][$key], $tag];
210+
}
211+
} else {
212+
$this->tags[$group][$key] = $tag;
213+
}
214+
}
215+
216+
217+
/**
218+
* Build an HTML tag
219+
*
220+
* @param string[] $attributes
221+
*/
222+
private function createTag(string $tagName, array $attributes = []): string
223+
{
224+
$escapeAttr = static function (string $s): string {
225+
if (strpos($s, '`') !== false && strpbrk($s, ' <>"\'') === false) {
226+
$s .= ' '; // protection against innerHTML mXSS vulnerability nette/nette#1496
227+
}
228+
229+
return htmlspecialchars($s, ENT_QUOTES | ENT_HTML5 | ENT_SUBSTITUTE, 'UTF-8', true);
230+
};
231+
232+
$attrItems = [];
233+
foreach ($attributes as $key => $value) {
234+
if ($value !== null) {
235+
$attrItems[] = $escapeAttr((string) $key) . '="' . $escapeAttr($value) . '"';
236+
}
237+
}
238+
239+
return '<' . $tagName . (count($attrItems) > 0 ? ' ' . implode(' ', $attrItems) : '') . '>';
240+
}
241+
}

0 commit comments

Comments
 (0)