Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 27 additions & 5 deletions Sources/Avatar.php
Original file line number Diff line number Diff line change
Expand Up @@ -501,14 +501,14 @@ public function __construct(

break;

// Is the file in the prepackaged avatar directory?
// Is the file in the avatar gallery directory?
case 2:
if (
!empty($this->filename)
&& !empty(Config::$modSettings['avatar_url'])
&& is_file(Config::$boarddir . '/avatars/' . ltrim($this->filename, '\\/'))
&& is_file(self::getGalleryDir() . '/' . ltrim($this->filename, '\\/'))
&& Utils::checkMimeType(
Config::$boarddir . '/avatars/' . ltrim($this->filename, '\\/'),
self::getGalleryDir() . '/' . ltrim($this->filename, '\\/'),
'image/',
true,
)
Expand Down Expand Up @@ -558,7 +558,7 @@ public function __construct(
fn($path) => Sapi::canonicalPath($path) . DIRECTORY_SEPARATOR,
array_filter([
Config::$modSettings['custom_avatar_dir'] ?? '',
Config::$boarddir . '/avatars',
self::getGalleryDir(),
]),
);

Expand All @@ -579,7 +579,7 @@ public function __construct(
case 5:
if (
!empty(Config::$modSettings['avatar_url'])
&& is_file(Config::$boarddir . '/avatars/default.png')
&& is_file(self::getGalleryDir() . '/default.png')
) {
$url = new Url(Config::$modSettings['avatar_url'] . '/default.png');
}
Expand Down Expand Up @@ -804,4 +804,26 @@ private function getExternal(): string
default => 'http://',
};
}

/*************************
* Internal static methods
*************************/

/**
* The directory that holds the gallery of avatars members can choose from.
*
* This is the directory the admin nominated, which is the one the gallery
* is listed from and the one a chosen avatar is checked against before it
* is saved. Config::$modSettings['avatar_url'] is the address of the same
* directory, so the two have to name the same place or a member's choice
* resolves to an address with nothing behind it.
*
* @return string Path to the avatar gallery directory.
*/
private static function getGalleryDir(): string
{
return !empty(Config::$modSettings['avatar_directory'])
? Config::$modSettings['avatar_directory']
: Config::$boarddir . '/avatars';
}
}
130 changes: 130 additions & 0 deletions tests/Unit/AvatarGalleryDirectoryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<?php

declare(strict_types=1);

namespace SMF\Tests\Unit;

use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use SMF\Avatar;
use SMF\Config;

/**
* Covers which directory SMF\Avatar looks in for a gallery avatar.
*
* The gallery lives wherever Config::$modSettings['avatar_directory'] says it
* does. Themes/default/images stands in for an admin who moved it: it is full
* of images, it is not the directory SMF ships the gallery in, and nothing has
* to be written to disk to use it.
*/
#[CoversClass(Avatar::class)]
class AvatarGalleryDirectoryTest extends TestCase
{
/*********************
* Internal properties
*********************/

/**
* @var array The settings this class reads, as they were before the test.
*/
private array $backup = [];

/**
* @var string Config::$boardurl as it was before the test.
*/
private string $boardurl = '';

/****************
* Public methods
****************/

/**
* The admin can move the avatar gallery, and the admin panel offers the
* setting and warns when the directory it names is not there. Profile
* lists the gallery out of that directory and refuses to save a choice
* from anywhere else, so it is the directory a stored avatar is a path
* into, and it is where the file has to be looked for.
*/
public function testAGalleryAvatarIsLookedForInTheConfiguredDirectory(): void
{
$this->setUpForum(Config::$boarddir . '/Themes/default/images');

$avatar = new Avatar(url: 'cake.png', id_member: 1);

$this->assertSame('https://example.com/gallery/cake.png', (string) $avatar->url);
}

public function testTheSameForAFileInASubdirectoryOfTheGallery(): void
{
$this->setUpForum(Config::$boarddir . '/Themes/default/images');

$avatar = new Avatar(url: 'icons/bell.png', id_member: 1);

$this->assertSame('https://example.com/gallery/icons/bell.png', (string) $avatar->url);
}

/**
* A forum whose admin never touched the setting keeps the gallery SMF
* ships, so the value the installer would have written is the fallback.
*/
public function testTheShippedGalleryIsUsedWhenNothingSaysOtherwise(): void
{
$this->setUpForum(null);

$avatar = new Avatar(url: 'Oxygen/beagle.png', id_member: 1);

$this->assertSame('https://example.com/gallery/Oxygen/beagle.png', (string) $avatar->url);
}

/******************
* Internal methods
******************/

protected function setUp(): void
{
$this->boardurl = Config::$boardurl ?? '';

foreach (['avatar_url', 'avatar_directory', 'gravatarEnabled'] as $key) {
if (isset(Config::$modSettings[$key])) {
$this->backup[$key] = Config::$modSettings[$key];
}
}
}

/**
* PHPUnit does not reset SMF's statics between tests, so a setting left
* behind here would leak into every test that follows.
*/
protected function tearDown(): void
{
Config::$boardurl = $this->boardurl;

foreach (['avatar_url', 'avatar_directory', 'gravatarEnabled'] as $key) {
unset(Config::$modSettings[$key]);

if (isset($this->backup[$key])) {
Config::$modSettings[$key] = $this->backup[$key];
}
}

$this->backup = [];
}

/**
* Puts the gallery in $directory, or leaves the setting off entirely when
* it is null. The URL is deliberately not the shipped one, so that an
* address built from it cannot be mistaken for a lucky guess.
*/
private function setUpForum(?string $directory): void
{
Config::$boardurl = 'https://example.com';
Config::$modSettings['avatar_url'] = 'https://example.com/gallery';
Config::$modSettings['gravatarEnabled'] = false;

if ($directory === null) {
unset(Config::$modSettings['avatar_directory']);
} else {
Config::$modSettings['avatar_directory'] = $directory;
}
}
}
Loading