From addb68ba01b17637d49b2ff56ca9d911a0ee9de9 Mon Sep 17 00:00:00 2001
From: George Steel <george@net-glue.co.uk>
Date: Mon, 21 Feb 2022 23:36:48 +0000
Subject: [PATCH] Deprecates and replaces the Gravatar helper with a simplified
 version that achieves the same goal.

Signed-off-by: George Steel <george@net-glue.co.uk>
---
 src/Helper/Gravatar.php           |   2 +
 src/Helper/GravatarImage.php      |  74 ++++++++++++++++
 src/HelperPluginManager.php       |   2 +
 src/Renderer/PhpRenderer.php      |   1 +
 test/Helper/GravatarImageTest.php | 140 ++++++++++++++++++++++++++++++
 test/Helper/GravatarTest.php      |   1 +
 6 files changed, 220 insertions(+)
 create mode 100644 src/Helper/GravatarImage.php
 create mode 100644 test/Helper/GravatarImageTest.php

diff --git a/src/Helper/Gravatar.php b/src/Helper/Gravatar.php
index 65a42ac36..2fed1d13e 100644
--- a/src/Helper/Gravatar.php
+++ b/src/Helper/Gravatar.php
@@ -21,6 +21,8 @@
 
 /**
  * Helper for retrieving avatars from gravatar.com
+ *
+ * @deprecated This helper has been deprecated in favour of {@link GravatarImage} and will be removed in version 3.0
  */
 class Gravatar extends AbstractHtmlElement
 {
diff --git a/src/Helper/GravatarImage.php b/src/Helper/GravatarImage.php
new file mode 100644
index 000000000..07a0818b2
--- /dev/null
+++ b/src/Helper/GravatarImage.php
@@ -0,0 +1,74 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Laminas\View\Helper;
+
+use Laminas\Escaper\Escaper;
+use Laminas\View\HtmlAttributesSet;
+
+use function md5;
+use function sprintf;
+use function strtolower;
+use function trim;
+
+/**
+ * @psalm-import-type AttributeSet from HtmlAttributesSet
+ */
+final class GravatarImage
+{
+    private const GRAVATAR_URL = '//www.gravatar.com/avatar';
+
+    public const RATINGS = [
+        'g',
+        'pg',
+        'r',
+        'x',
+    ];
+
+    public const DEFAULT_IMAGE_VALUES = [
+        '404',
+        'mm',
+        'identicon',
+        'monsterid',
+        'wavatar',
+    ];
+
+    private Escaper $escaper;
+
+    public function __construct(?Escaper $escaper = null)
+    {
+        $this->escaper = $escaper ?: new Escaper();
+    }
+
+    /**
+     * @param non-empty-string                                  $emailAddress
+     * @param positive-int                                      $imageSize
+     * @param AttributeSet                                      $imageAttributes
+     * @psalm-param value-of<self::DEFAULT_IMAGE_VALUES>|string $defaultImage
+     * @psalm-param value-of<self::RATINGS>                     $rating
+     */
+    public function __invoke(
+        string $emailAddress,
+        int $imageSize = 80,
+        array $imageAttributes = [],
+        string $defaultImage = 'mm',
+        string $rating = 'g'
+    ): string {
+        $imageAttributes['width'] = $imageAttributes['height'] = $imageSize;
+        $imageAttributes['alt']   = $imageAttributes['alt'] ?? '';
+        $imageAttributes['src']   = sprintf(
+            '%s/%s?s=%d&r=%s&d=%s',
+            self::GRAVATAR_URL,
+            md5(strtolower(trim($emailAddress))),
+            $imageSize,
+            $rating,
+            $this->escaper->escapeUrl($defaultImage)
+        );
+
+        return sprintf(
+            '<img%s />',
+            (string) new HtmlAttributesSet($this->escaper, $imageAttributes)
+        );
+    }
+}
diff --git a/src/HelperPluginManager.php b/src/HelperPluginManager.php
index 7de656e63..ca1c656f2 100644
--- a/src/HelperPluginManager.php
+++ b/src/HelperPluginManager.php
@@ -76,6 +76,7 @@ class HelperPluginManager extends AbstractPluginManager
         'FlashMessenger'      => Helper\FlashMessenger::class,
         'Gravatar'            => Helper\Gravatar::class,
         'gravatar'            => Helper\Gravatar::class,
+        'gravatarImage'       => Helper\GravatarImage::class,
         'headLink'            => Helper\HeadLink::class,
         'HeadLink'            => Helper\HeadLink::class,
         'headlink'            => Helper\HeadLink::class,
@@ -259,6 +260,7 @@ class HelperPluginManager extends AbstractPluginManager
         Helper\EscapeCss::class           => InvokableFactory::class,
         Helper\EscapeUrl::class           => InvokableFactory::class,
         Helper\Gravatar::class            => InvokableFactory::class,
+        Helper\GravatarImage::class       => InvokableFactory::class,
         Helper\HtmlTag::class             => InvokableFactory::class,
         Helper\HeadLink::class            => InvokableFactory::class,
         Helper\HeadMeta::class            => InvokableFactory::class,
diff --git a/src/Renderer/PhpRenderer.php b/src/Renderer/PhpRenderer.php
index 7e3aeee47..5e25c7871 100644
--- a/src/Renderer/PhpRenderer.php
+++ b/src/Renderer/PhpRenderer.php
@@ -86,6 +86,7 @@
  * @method \Laminas\View\Helper\Navigation\Links links($container = null)
  * @method \Laminas\View\Helper\Navigation\Menu menu($container = null)
  * @method \Laminas\View\Helper\Navigation\Sitemap sitemap($container = null)
+ * @method string gravatarImage(string $emailAddress, int $imageSize = 80, array $imageAttributes = [], string $defaultImage = 'mm', string $rating = 'g')
  */
 class PhpRenderer implements Renderer, TreeRendererInterface
 {
diff --git a/test/Helper/GravatarImageTest.php b/test/Helper/GravatarImageTest.php
new file mode 100644
index 000000000..ed6e0cba8
--- /dev/null
+++ b/test/Helper/GravatarImageTest.php
@@ -0,0 +1,140 @@
+<?php
+
+declare(strict_types=1);
+
+namespace LaminasTest\View\Helper;
+
+use Laminas\View\Helper\GravatarImage;
+use PHPUnit\Framework\TestCase;
+
+use function md5;
+
+class GravatarImageTest extends TestCase
+{
+    private GravatarImage $helper;
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $this->helper = new GravatarImage();
+    }
+
+    public function testThatTheGivenEmailAddressWillBeHashed(): string
+    {
+        $image = ($this->helper)('me@example.com');
+
+        self::assertStringContainsString(
+            md5('me@example.com'),
+            $image
+        );
+
+        return $image;
+    }
+
+    /** @depends testThatTheGivenEmailAddressWillBeHashed  */
+    public function testTheRatingWillDefaultToG(string $markup): void
+    {
+        self::assertStringContainsString(
+            'r&#x3D;g&amp;',
+            $markup
+        );
+    }
+
+    /** @depends testThatTheGivenEmailAddressWillBeHashed  */
+    public function testAnEmptyAltAttributeWillBeAddedByDefault(string $markup): void
+    {
+        self::assertStringContainsString(
+            'alt=""',
+            $markup
+        );
+    }
+
+    /** @depends testThatTheGivenEmailAddressWillBeHashed  */
+    public function testTheImageSizeWillBe80(string $markup): void
+    {
+        self::assertStringContainsString(
+            's&#x3D;80',
+            $markup
+        );
+    }
+
+    /** @depends testThatTheGivenEmailAddressWillBeHashed  */
+    public function testWidthAndHeightAttributesWillBePresent(string $markup): void
+    {
+        self::assertStringContainsString(
+            'width="80"',
+            $markup
+        );
+        self::assertStringContainsString(
+            'height="80"',
+            $markup
+        );
+    }
+
+    /** @depends testThatTheGivenEmailAddressWillBeHashed  */
+    public function testTheDefaultFallbackImageImageSizeWillBeMM(string $markup): void
+    {
+        self::assertStringContainsString(
+            'd&#x3D;mm',
+            $markup
+        );
+    }
+
+    public function testThatAttributesCanBeAddedToTheImageMarkup(): void
+    {
+        $image = ($this->helper)('me@example.com', 80, ['data-foo' => 'bar']);
+        self::assertStringContainsString(
+            'data-foo="bar"',
+            $image
+        );
+    }
+
+    public function testThatTheImageSizeCanBeAltered(): string
+    {
+        $image = ($this->helper)('me@example.com', 123);
+        self::assertStringContainsString(
+            's&#x3D;123',
+            $image
+        );
+
+        return $image;
+    }
+
+    /** @depends testThatTheImageSizeCanBeAltered  */
+    public function testWidthAndHeightAttributesWillMatchCustomValue(string $markup): void
+    {
+        self::assertStringContainsString(
+            'width="123"',
+            $markup
+        );
+        self::assertStringContainsString(
+            'height="123"',
+            $markup
+        );
+    }
+
+    public function testThatTheRatingCanBeAltered(): void
+    {
+        $image = ($this->helper)('me@example.com', 80, [], 'mm', 'x');
+        self::assertStringContainsString(
+            'r&#x3D;x&amp;',
+            $image
+        );
+    }
+
+    public function testThatTheDefaultImageCanBeAltered(): void
+    {
+        $image = ($this->helper)('me@example.com', 80, [], 'wavatar');
+        self::assertStringContainsString(
+            'd&#x3D;wavatar',
+            $image
+        );
+    }
+
+    public function testThatTheDefaultImageCanBeAnUrl(): void
+    {
+        $image  = ($this->helper)('me@example.com', 80, [], 'https://example.com/someimage');
+        $expect = 'https&#x25;3A&#x25;2F&#x25;2Fexample.com&#x25;2Fsomeimage';
+        self::assertStringContainsString($expect, $image);
+    }
+}
diff --git a/test/Helper/GravatarTest.php b/test/Helper/GravatarTest.php
index c78e82b78..04c6076a9 100644
--- a/test/Helper/GravatarTest.php
+++ b/test/Helper/GravatarTest.php
@@ -14,6 +14,7 @@
 use function strtoupper;
 use function urlencode;
 
+/** @psalm-suppress DeprecatedClass */
 class GravatarTest extends TestCase
 {
     /** @var Gravatar */