From 5c81fbf0d68ee3d12257fc2c2d73482ee6addafb Mon Sep 17 00:00:00 2001 From: Anton Komarev <1849174+antonkomarev@users.noreply.github.com> Date: Mon, 20 Jul 2020 22:17:33 +0300 Subject: [PATCH] Make badge style customizable (#8) * Add Poser badge generator * Make badge styles configurable * Make flat style as default --- README.md | 18 ++ composer.json | 1 + composer.lock | 319 +++++++++++++++++++++++++++++- public/file-repository.php | 17 +- public/pdo-repository.php | 19 +- resources/badge.svg | 11 -- src/BadgeImageRendererService.php | 60 ++---- 7 files changed, 377 insertions(+), 68 deletions(-) delete mode 100644 resources/badge.svg diff --git a/README.md b/README.md index 4f5f5af..8d8d48e 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,24 @@ You need to add counter in README.md file in your profile repository via Markdow > **NOTE**: Don't forget to replace example `username` parameter with real value. +### Customization + +#### Styles + +The following styles are available (`flat` is the default): + +| style | demo | +| ----- | ---- | +| `flat` | ![](https://img.shields.io/static/v1?label=Profile+views&message=1234567890&color=007ec6&style=flat) | +| `flat-square` | ![](https://img.shields.io/static/v1?label=Profile+views&message=1234567890&color=007ec6&style=flat-square) | +| `plastic` | ![](https://img.shields.io/static/v1?label=Profile+views&message=1234567890&color=007ec6&style=plastic) | + +```markdown +![](https://komarev.com/ghpvc/?username=your-github-username&style=flat-square) +``` + +For example + ## Is it safe to place tracking image? GitHub passes all images URLs through their proxy _camo_ service. URLs are look like [https://camo.githubusercontent.com/a9aa69b588bef281647d53091b4faa01ac126121/68747470733a2f2f6b6f6d617265762e636f6d2f67687076632f3f757365726e616d653d616e746f6e6b6f6d6172657626](https://camo.githubusercontent.com/a9aa69b588bef281647d53091b4faa01ac126121/68747470733a2f2f6b6f6d617265762e636f6d2f67687076632f3f757365726e616d653d616e746f6e6b6f6d6172657626). diff --git a/composer.json b/composer.json index 3c43944..71e59cd 100644 --- a/composer.json +++ b/composer.json @@ -27,6 +27,7 @@ }, "require": { "php": "^7.4", + "badges/poser": "^1.4", "vlucas/phpdotenv": "^5.0", "webmozart/assert": "^1.9" }, diff --git a/composer.lock b/composer.lock index 94a019e..36209e2 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,75 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1199b144b9fb9e5178e80602812dd8b2", + "content-hash": "268e668398fa52181cf35d8920a04ab9", "packages": [ + { + "name": "badges/poser", + "version": "v1.4.1", + "source": { + "type": "git", + "url": "https://github.com/badges/poser.git", + "reference": "4281890cbd58e0ea29c6550c4a4aaa16a15b3ce0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/badges/poser/zipball/4281890cbd58e0ea29c6550c4a4aaa16a15b3ce0", + "reference": "4281890cbd58e0ea29c6550c4a4aaa16a15b3ce0", + "shasum": "" + }, + "require": { + "ext-gd": "*", + "php": ">=5.3", + "symfony/console": "~2.0|~3.0|~4.0" + }, + "require-dev": { + "behat/behat": "^3.4", + "coduo/phpspec-data-provider-extension": "^1.0", + "friendsofphp/php-cs-fixer": "@stable", + "phpspec/phpspec": "^2.5" + }, + "bin": [ + "bin/poser" + ], + "type": "lib", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "psr-4": { + "PUGX\\Poser\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Giulio De Donato", + "email": "liuggio@gmail.com" + }, + { + "name": "PUGX Community", + "homepage": "https://github.com/PUGX/badge-poser/contributors" + }, + { + "name": "Shields Community", + "homepage": "https://github.com/badges/poser/contributors" + } + ], + "description": "Poser, add badges on your readme..", + "homepage": "https://github.com/PUGX", + "keywords": [ + "badge", + "composer", + "packagist", + "pin" + ], + "time": "2020-06-11T16:17:31+00:00" + }, { "name": "graham-campbell/result-type", "version": "v1.0.1", @@ -113,6 +180,132 @@ ], "time": "2020-06-07T10:40:07+00:00" }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14T16:28:37+00:00" + }, + { + "name": "symfony/console", + "version": "v4.4.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "326b064d804043005526f5a0494cfb49edb59bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/326b064d804043005526f5a0494cfb49edb59bb0", + "reference": "326b064d804043005526f5a0494cfb49edb59bb0", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/polyfill-php80": "^1.15", + "symfony/service-contracts": "^1.1|^2" + }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/event-dispatcher": "<4.3|>=5", + "symfony/lock": "<4.4", + "symfony/process": "<3.3" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/event-dispatcher": "^4.3", + "symfony/lock": "^4.4|^5.0", + "symfony/process": "^3.4|^4.0|^5.0", + "symfony/var-dumper": "^4.3|^5.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2020-05-30T20:06:45+00:00" + }, { "name": "symfony/polyfill-ctype", "version": "v1.17.1", @@ -238,6 +431,68 @@ ], "time": "2020-06-06T08:46:27+00:00" }, + { + "name": "symfony/polyfill-php73", + "version": "v1.18.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "fffa1a52a023e782cdcc221d781fe1ec8f87fcca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fffa1a52a023e782cdcc221d781fe1ec8f87fcca", + "reference": "fffa1a52a023e782cdcc221d781fe1ec8f87fcca", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2020-07-14T12:35:20+00:00" + }, { "name": "symfony/polyfill-php80", "version": "v1.17.1", @@ -304,6 +559,68 @@ ], "time": "2020-06-06T08:46:27+00:00" }, + { + "name": "symfony/service-contracts", + "version": "v2.1.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "58c7475e5457c5492c26cc740cc0ad7464be9442" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/58c7475e5457c5492c26cc740cc0ad7464be9442", + "reference": "58c7475e5457c5492c26cc740cc0ad7464be9442", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.0" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2020-07-06T13:23:11+00:00" + }, { "name": "vlucas/phpdotenv", "version": "v5.0.0", diff --git a/public/file-repository.php b/public/file-repository.php index e69f33a..c2dbb15 100644 --- a/public/file-repository.php +++ b/public/file-repository.php @@ -30,8 +30,6 @@ try { $dotEnv = Dotenv::createImmutable($basePath); $dotEnv->safeLoad(); - $badgeImagePath = $basePath . '/resources/badge.svg'; - $httpUserAgent = $_SERVER['HTTP_USER_AGENT']; if (!isset($_ENV['FILE_STORAGE_PATH']) || $_ENV['FILE_STORAGE_PATH'] === null) { @@ -40,12 +38,15 @@ try { $storagePath = $_ENV['FILE_STORAGE_PATH']; } - $style = $_GET['style'] ?? null; + $badgeStyle = $_GET['style'] ?? 'flat'; + if (!in_array($badgeStyle, ['flat', 'flat-square', 'plastic'])) { + $badgeStyle = 'flat'; + } $username = $_GET['username'] ?? ''; $username = trim($username); if ($username === '') { - echo $badgeImageRenderer->renderBadgeWithError($badgeImagePath, 'Invalid query parameter: username'); + echo $badgeImageRenderer->renderBadgeWithError($badgeStyle, 'Invalid query parameter: username'); exit; } @@ -57,16 +58,16 @@ try { $counterRepository->addViewByUsername($username); } - if ($style === 'pixel') { - echo ''; + if ($badgeStyle === 'pixel') { + echo $badgeImageRenderer->renderPixel(); exit; } $count = $counterRepository->getViewsCountByUsername($username); - echo $badgeImageRenderer->renderBadgeWithCount($badgeImagePath, $count); + echo $badgeImageRenderer->renderBadgeWithCount($badgeStyle, $count); exit; } catch (Exception $exception) { - echo $badgeImageRenderer->renderBadgeWithError($badgeImagePath, $exception->getMessage()); + echo $badgeImageRenderer->renderBadgeWithError($badgeStyle, $exception->getMessage()); exit; } diff --git a/public/pdo-repository.php b/public/pdo-repository.php index bfcdaec..6683ff3 100644 --- a/public/pdo-repository.php +++ b/public/pdo-repository.php @@ -40,16 +40,17 @@ try { 'DB_NAME', ]); - $badgeImagePath = $basePath . '/resources/badge.svg'; - $httpUserAgent = $_SERVER['HTTP_USER_AGENT']; - $style = $_GET['style'] ?? null; + $badgeStyle = $_GET['style'] ?? 'flat'; + if (!in_array($badgeStyle, ['flat', 'flat-square', 'plastic'])) { + $badgeStyle = 'flat'; + } $username = $_GET['username'] ?? ''; $username = trim($username); if ($username === '') { - echo $badgeImageRenderer->renderBadgeWithError($badgeImagePath, 'Invalid query parameter: username'); + echo $badgeImageRenderer->renderBadgeWithError($badgeStyle, 'Invalid query parameter: username'); exit; } @@ -70,19 +71,19 @@ try { $counterRepository->addViewByUsername($username); } - if ($style === 'pixel') { - echo ''; + if ($badgeStyle === 'pixel') { + echo $badgeImageRenderer->renderPixel(); exit; } $count = $counterRepository->getViewsCountByUsername($username); - echo $badgeImageRenderer->renderBadgeWithCount($badgeImagePath, $count); + echo $badgeImageRenderer->renderBadgeWithCount($badgeStyle, $count); exit; } catch (InvalidPathException $exception) { - echo $badgeImageRenderer->renderBadgeWithError($badgeImagePath, 'Application environment file is missing'); + echo $badgeImageRenderer->renderBadgeWithError($badgeStyle, 'Application environment file is missing'); exit; } catch (Exception $exception) { - echo $badgeImageRenderer->renderBadgeWithError($badgeImagePath, $exception->getMessage()); + echo $badgeImageRenderer->renderBadgeWithError($badgeStyle, $exception->getMessage()); exit; } diff --git a/resources/badge.svg b/resources/badge.svg deleted file mode 100644 index c154072..0000000 --- a/resources/badge.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - %LABEL% - %MESSAGE% - - diff --git a/src/BadgeImageRendererService.php b/src/BadgeImageRendererService.php index f74a26a..3796bfb 100644 --- a/src/BadgeImageRendererService.php +++ b/src/BadgeImageRendererService.php @@ -13,61 +13,43 @@ declare(strict_types=1); namespace Komarev\GitHubProfileViewsCounter; -use Contracts\Komarev\GitHubProfileViewsCounter\InvalidPathException; +use PUGX\Poser\Poser; +use PUGX\Poser\Render\SvgFlatRender; +use PUGX\Poser\Render\SvgFlatSquareRender; +use PUGX\Poser\Render\SvgRender; final class BadgeImageRendererService { - public function renderBadgeWithCount(string $imagePath, int $count): string + private Poser $poser; + + public function __construct() + { + $this->poser = new Poser([new SvgRender(), new SvgFlatRender(), new SvgFlatSquareRender()]); + } + + public function renderBadgeWithCount($badgeStyle, int $count): string { $message = (string) $count; - $messageBackgroundFill = '#007ec6'; + $messageBackgroundFill = '007ec6'; - return $this->renderBadge($imagePath, $message, $messageBackgroundFill); + return $this->renderBadge($badgeStyle, $message, $messageBackgroundFill); } - public function renderBadgeWithError(string $imagePath, string $message): string + public function renderBadgeWithError($badgeStyle, string $message): string { - $messageBackgroundFill = '#e05d44'; + $messageBackgroundFill = 'e05d44'; - return $this->renderBadge($imagePath, $message, $messageBackgroundFill); + return $this->renderBadge($badgeStyle, $message, $messageBackgroundFill); } - private function renderBadge(string $imagePath, string $message, string $messageBackgroundFill): string + private function renderBadge($badgeStyle, string $message, string $messageBackgroundFill): string { - if (!file_exists($imagePath)) { - throw new InvalidPathException('Badge image not found'); - } - - $image = file_get_contents($imagePath); - - - return $this->replaceImagePlaceholders($image, $message, $messageBackgroundFill); + return (string) $this->poser->generate('Page views', $message, $messageBackgroundFill, $badgeStyle); } - private function replaceImagePlaceholders(string $image, string $message, string $messageBackgroundFill): string + public function renderPixel(): string { - $messageLength = strlen($message); - - $minImageWidth = 98; - $minMessageBackgroundWidth = 17; - $minMessageTextLength = 70; - $minMessageTextMarginLeft = 885; - - $label = 'Profile views'; - $imageWidth = $minImageWidth + (8 * $messageLength); - $messageBackgroundWidth = $minMessageBackgroundWidth + (8 * $messageLength); - $messageTextLength = $minMessageTextLength + (80 * $messageLength); - $messageTextMarginLeft = $minMessageTextMarginLeft + (40 * $messageLength); - - $image = str_replace('%IMAGE_WIDTH%', $imageWidth, $image); - $image = str_replace('%LABEL%', $label, $image); - $image = str_replace('%MESSAGE%', $message, $image); - $image = str_replace('%MESSAGE_BACKGROUND_WIDTH%', $messageBackgroundWidth, $image); - $image = str_replace('%MESSAGE_BACKGROUND_FILL%', $messageBackgroundFill, $image); - $image = str_replace('%MESSAGE_TEXT_LENGTH%', $messageTextLength, $image); - $image = str_replace('%MESSAGE_TEXT_MARGIN_LEFT%', $messageTextMarginLeft, $image); - - return $image; + return ''; } }