Skip to content

Instantly share code, notes, and snippets.

@danlapteacru
Created November 22, 2024 08:45
Show Gist options
  • Save danlapteacru/0eac97c354051cb1abfff1b76a22dc1e to your computer and use it in GitHub Desktop.
Save danlapteacru/0eac97c354051cb1abfff1b76a22dc1e to your computer and use it in GitHub Desktop.
<?php
declare(strict_types=1);
namespace App\Extensions;
use function Roots\asset;
final class Favicons
{
private array $config;
private array $faviconConfig = [
'android' => [
'rel' => 'icon',
'type' => 'image/png',
'prefix' => 'android-chrome-'
],
'appleIcon' => [
'rel' => 'apple-touch-icon',
'type' => 'image/png',
'prefix' => 'apple-touch-icon-'
],
'appleStartup' => [
'rel' => 'apple-touch-startup-image',
'type' => 'image/png',
'prefix' => 'apple-touch-startup-image-'
],
'favicons' => [
'rel' => 'icon',
'type' => 'image/png',
'prefix' => 'favicon-'
],
'windows' => [
'rel' => 'msapplication-TileImage',
'type' => 'image/png',
'prefix' => 'mstile-'
],
'yandex' => [
'rel' => 'yandex-tableau-widget',
'type' => 'image/png',
'prefix' => 'yandex-browser-'
]
];
public function __construct(array $config)
{
$this->config = $config;
add_action('wp_head', [$this, 'addFavicons']);
}
public static function register(): ?self
{
$config = array_filter((array) config('favicons'));
if (empty($config)) {
return null;
}
return new self($config);
}
protected function getIconPlatforms(): array
{
return array_keys(
array_filter($this->config['favicons']['icons'] ?? [])
);
}
protected function getPublicPath(string $path): ?string
{
if (! asset($path)->exists()) {
return null;
}
return asset($path)->uri();
}
public function addFavicons(): void
{
$tags = $this->generateAllFaviconTags();
if (empty($tags)) {
return;
}
echo PHP_EOL . '<!-- Generated by Favicons -->' . PHP_EOL;
echo implode(PHP_EOL, $tags);
echo '<!-- /Generated by Favicons -->' . PHP_EOL;
}
/**
* Generate meta tags for all favicon types
*
* @return array
*/
public function generateAllFaviconTags(): array
{
$tags = [];
foreach ($this->faviconConfig as $type => $config) {
if (! in_array($type, $this->getIconPlatforms(), true)) {
continue;
}
$tags = array_merge($tags, $this->generateLinkTagsByType($type));
}
return array_filter(
array_merge(
$tags,
$this->generateManifestTags(),
$this->generatePwaMetaTags()
)
);
}
/**
* Generate meta tags for a specific favicon type
*
* @param string $type
* @return array
*/
protected function generateLinkTagsByType(string $type): array
{
$method = "generate{$type}MetaTags";
return method_exists($this, $method) ? $this->$method() : [];
}
/**
* Generate manifest related tags
*
* @return array
*/
protected function generateManifestTags(): array
{
$attributes = [
[
'rel' => 'manifest',
'href' => $this->getPublicPath('manifest.webmanifest'),
],
[
'rel' => 'shortcut icon',
'href' => $this->getPublicPath('favicon.ico'),
],
];
return array_map(
fn (array $attr): string => $this->buildMetaTag($attr),
$attributes
);
}
/**
* Generate PWA specific meta tags
*
* @return array
*/
protected function generatePwaMetaTags(): array
{
$attributes = [
[
'name' => 'application-name',
'content' => $this->getAppName(),
],
[
'name' => 'mobile-web-app-capable',
'content' => 'yes',
],
];
if (! empty($this->config['theme_color'])) {
$attributes[] = [
'name' => 'theme-color',
'content' => $this->config['theme_color'],
];
}
return array_map(
fn (array $attr): string => $this->buildMetaTag($attr, 'meta'),
$attributes
);
}
/**
* Generate Android favicon meta tags
*
* @return array
*/
protected function generateAndroidMetaTags(): array
{
$sizes = [36, 48, 72, 96, 144, 192, 256, 384, 512];
return $this->generateSizedMetaTags('android', $sizes);
}
/**
* Generate Apple icon meta tags
*
* @return array
*/
protected function generateAppleIconMetaTags(): array
{
$sizes = [57, 60, 72, 76, 114, 120, 144, 152, 167, 180, 1024];
$tags = $this->generateSizedMetaTags('appleIcon', $sizes);
// Add default apple-touch-icon tags.
$tags[] = $this->generateLinkTag('appleIcon', 'apple-touch-icon.png');
$tags[] = $this->generateLinkTag('appleIcon', 'apple-touch-icon-precomposed.png');
return $tags;
}
/**
* Generate Apple startup image meta tags
*
* @return array
*/
protected function generateAppleStartupMetaTags(): array
{
$dimensions = [
'1125x2436', '1242x2688', '1536x2048', '1668x2388', '2048x2732',
'640x1136', '750x1334', '828x1792', '1179x2556', '2556x1179',
'1290x2796', '2796x1290', '1488x2266', '2266x1488', '1640x2360',
'2360x1640',
];
$tags = array_map(
fn (string $dimension): string =>
$this->generateLinkTag(
'appleStartup',
"apple-touch-startup-image-{$dimension}.png",
$dimension
),
$dimensions
);
$attributes = [
[
'name' => 'apple-mobile-web-app-capable',
'content' => 'yes',
],
[
'name' => 'apple-mobile-web-app-title',
'content' => $this->getAppName(),
]
];
$appleStatusBarStyle = $this->config['appleStatusBarStyle'] ?? '';
if (! empty($appleStatusBarStyle)) {
$attributes[] = [
'name' => 'apple-mobile-web-app-status-bar-style',
'content' => $appleStatusBarStyle,
];
}
return array_merge(
$tags,
array_map(
fn (array $attr): string => $this->buildMetaTag($attr, 'meta'),
$attributes
)
);
}
/**
* Generate standard favicon meta tags
*
* @return array
*/
protected function generateFaviconsMetaTags(): array
{
$sizes = [16, 32, 48];
return $this->generateSizedMetaTags('favicons', $sizes);
}
/**
* Generate Windows tile meta tags
*
* @return array
*/
protected function generateWindowsMetaTags(): array
{
$sizes = ['70x70', '144x144', '150x150', '310x150', '310x310'];
$tags = array_map(
fn (string $size): string => $this->generateLinkTag('windows', "mstile-{$size}.png", $size),
$sizes
);
$attributes = [
[
'name' => 'msapplication-TileColor',
'content' => $this->config['msapplicationTileColor'] ?? '#ffffff',
],
[
'name' => 'msapplication-config',
'content' => $this->getPublicPath('browserconfig.xml'),
],
];
return array_merge(
$tags,
array_map(
fn (array $attr): string => $this->buildMetaTag($attr, 'meta'),
$attributes
)
);
}
/**
* Generate Yandex meta tags
*
* @return array
*/
protected function generateYandexMetaTags(): array
{
return [
$this->generateLinkTag('yandex', 'yandex-browser-50x50.png', '50x50'),
];
}
/**
* Generate meta tags for icons with specific sizes
*
* @param string $type
* @param array $sizes
* @return array
*/
protected function generateSizedMetaTags(string $type, array $sizes): array
{
return array_map(function (int|string $size) use ($type): string {
$dimension = "{$size}x{$size}";
return $this->generateLinkTag(
$type,
"{$this->faviconConfig[$type]['prefix']}{$dimension}.png",
$dimension
);
}, $sizes);
}
/**
* Generate a single meta link tag
*
* @param string $type
* @param string $filename
* @param string|null $size
* @return string
*/
protected function generateLinkTag(string $type, string $filename, ?string $size = null): string
{
$config = $this->faviconConfig[$type];
$attributes = [
'rel' => $config['rel'] ?? '',
'type' => $config['type'] ?? '',
'href' => $this->getPublicPath($filename)
];
if ($size) {
$attributes['sizes'] = $size;
}
return $this->buildMetaTag($attributes);
}
/**
* Generate a meta tag for a specific name and content
*
* @param string $name
* @param string $content
* @return string
*/
protected function generateMetaTag(string $name, string $content): string
{
return $this->buildMetaTag([
'name' => $name,
'content' => $content
], 'meta');
}
/**
* Build HTML meta tag from attributes
*
* @param array $attributes
* @param string $tag
* @return string
*/
protected function buildMetaTag(array $attributes, string $tag = 'link'): string
{
$attrs = [];
foreach ($attributes as $key => $value) {
if (empty($value)) {
continue;
}
$attrs[] = sprintf('%s="%s"', $key, htmlspecialchars($value, ENT_QUOTES, 'UTF-8'));
}
return sprintf('<%s %s>', $tag, implode(' ', $attrs));
}
/**
* Get the app name
*
* @return string
*/
protected function getAppName(): string
{
return $this->config['appName'] ?? get_bloginfo('name');
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment