Add fallback to regular variant & Add the Strict fetch user preference

This commit is contained in:
Bubka 2025-06-16 08:44:42 +02:00
parent 48cc7d76e4
commit c71464b763
5 changed files with 70 additions and 7 deletions

View File

@ -43,12 +43,20 @@ abstract class AbstractLogoLib implements LogoLibInterface
$this->setVariant($variant); $this->setVariant($variant);
$logoFilename = $this->getLogo(strval($serviceName)); $logoFilename = $this->getLogo(strval($serviceName));
if (!$logoFilename) { if (! $logoFilename && $this->format != 'png') {
// maybe the svg is not available, we try to get the png format // maybe the svg is not available, we try to get the png format
$this->format = 'png'; $this->format = 'png';
$logoFilename = $this->getLogo(strval($serviceName)); $logoFilename = $this->getLogo(strval($serviceName));
} }
if (! $logoFilename && $this->variant != 'regular' && ! $this->strictFetch()) {
// maybe the variant is not available, we try to get the regular svg variant
$this->format = 'svg';
if ($icon = $this->getIcon($serviceName)) {
return $icon;
}
}
if ($logoFilename) { if ($logoFilename) {
$iconFilename = \Illuminate\Support\Str::random(40) . '.' . $this->format; $iconFilename = \Illuminate\Support\Str::random(40) . '.' . $this->format;
@ -72,6 +80,16 @@ abstract class AbstractLogoLib implements LogoLibInterface
} }
} }
/**
* Should we fallback to the regular variant
*/
protected function strictFetch() : bool
{
return Auth::user()
? (bool)Auth::user()->preferences['iconVariantStrictFetch']
: false;
}
/** /**
* Return the logo's filename for a given service * Return the logo's filename for a given service
* *

View File

@ -15,6 +15,7 @@ $preferences = [
'showAccountsIcons' => envUnlessEmpty('USERPREF_DEFAULT__SHOW_ACCOUNTS_ICONS', true), 'showAccountsIcons' => envUnlessEmpty('USERPREF_DEFAULT__SHOW_ACCOUNTS_ICONS', true),
'iconCollection' => envUnlessEmpty('USERPREF_DEFAULT__ICON_COLLECTION', 'selfh'), 'iconCollection' => envUnlessEmpty('USERPREF_DEFAULT__ICON_COLLECTION', 'selfh'),
'iconVariant' => envUnlessEmpty('USERPREF_DEFAULT__ICON_VARIANT', 'regular'), 'iconVariant' => envUnlessEmpty('USERPREF_DEFAULT__ICON_VARIANT', 'regular'),
'iconVariantStrictFetch' => envUnlessEmpty('USERPREF_DEFAULT__ICON_VARIANT_STRICT_FETCH', false),
'kickUserAfter' => envUnlessEmpty('USERPREF_DEFAULT__KICK_USER_AFTER', 15), 'kickUserAfter' => envUnlessEmpty('USERPREF_DEFAULT__KICK_USER_AFTER', 15),
'activeGroup' => 0, 'activeGroup' => 0,
'rememberActiveGroup' => envUnlessEmpty('USERPREF_DEFAULT__REMEMBER_ACTIVE_GROUP', true), 'rememberActiveGroup' => envUnlessEmpty('USERPREF_DEFAULT__REMEMBER_ACTIVE_GROUP', true),

View File

@ -38,7 +38,10 @@
{ text: 'commons.regular', value: 'regular' }, { text: 'commons.regular', value: 'regular' },
{ text: 'settings.forms.light', value: 'light' }, { text: 'settings.forms.light', value: 'light' },
{ text: 'settings.forms.dark', value: 'dark' }, { text: 'settings.forms.dark', value: 'dark' },
] ],
tfa: [
{ text: 'commons.regular', value: 'regular' },
],
} }
const passwordFormats = [ const passwordFormats = [
{ text: '12 34 56', value: 2, legend: 'settings.forms.pair', title: 'settings.forms.pair_legend' }, { text: '12 34 56', value: 2, legend: 'settings.forms.pair', title: 'settings.forms.pair_legend' },
@ -199,7 +202,9 @@
</a> </a>
</FormSelect> </FormSelect>
<!-- icon variant --> <!-- icon variant -->
<FormSelect v-if="iconCollectionVariants[user.preferences.iconCollection]" v-model="user.preferences.iconVariant" @update:model-value="val => savePreference('iconVariant', val)" :options="iconCollectionVariants[user.preferences.iconCollection]" fieldName="iconVariant" :isLocked="appSettings.lockedPreferences.includes('iconVariant')" :isDisabled="!user.preferences.getOfficialIcons" label="settings.forms.icon_variant.label" help="settings.forms.icon_variant.help" :isIndented="true" /> <FormSelect v-model="user.preferences.iconVariant" @update:model-value="val => savePreference('iconVariant', val)" :options="iconCollectionVariants[user.preferences.iconCollection]" fieldName="iconVariant" :isLocked="appSettings.lockedPreferences.includes('iconVariant')" :isDisabled="!user.preferences.getOfficialIcons" label="settings.forms.icon_variant.label" help="settings.forms.icon_variant.help" :isIndented="true" />
<!-- icon variant strict fetch -->
<FormCheckbox v-model="user.preferences.iconVariantStrictFetch" @update:model-value="val => savePreference('iconVariantStrictFetch', val)" fieldName="iconVariantStrictFetch" :isLocked="appSettings.lockedPreferences.includes('iconVariantStrictFetch')" :isDisabled="user.preferences.iconVariant == 'regular'" label="settings.forms.icon_variant_strict_fetch.label" help="settings.forms.icon_variant_strict_fetch.help" :isIndented="true" />
<!-- password format --> <!-- password format -->
<FormCheckbox v-model="user.preferences.formatPassword" @update:model-value="val => savePreference('formatPassword', val)" fieldName="formatPassword" :isLocked="appSettings.lockedPreferences.includes('formatPassword')" label="settings.forms.password_format.label" help="settings.forms.password_format.help" /> <FormCheckbox v-model="user.preferences.formatPassword" @update:model-value="val => savePreference('formatPassword', val)" fieldName="formatPassword" :isLocked="appSettings.lockedPreferences.includes('formatPassword')" label="settings.forms.password_format.label" help="settings.forms.password_format.help" />
<FormToggle v-model="user.preferences.formatPasswordBy" @update:model-value="val => savePreference('formatPasswordBy', val)" :choices="passwordFormats" fieldName="formatPasswordBy" :isLocked="appSettings.lockedPreferences.includes('formatPasswordBy')" :isDisabled="!user.preferences.formatPassword" /> <FormToggle v-model="user.preferences.formatPasswordBy" @update:model-value="val => savePreference('formatPasswordBy', val)" :choices="passwordFormats" fieldName="formatPasswordBy" :isLocked="appSettings.lockedPreferences.includes('formatPasswordBy')" :isDisabled="!user.preferences.formatPassword" />

View File

@ -131,7 +131,11 @@ return [
], ],
'icon_variant' => [ 'icon_variant' => [
'label' => 'Icon variant', 'label' => 'Icon variant',
'help' => 'Some icons may be available in several flavors to best suit dark or light UIs. Set the one you want to look for first.' 'help' => 'Some icons are available in different flavors to best suit dark or light user interfaces. Set the one you want to look for first. The regular variant will automatically be fetched as a fallback.'
],
'icon_variant_strict_fetch' => [
'label' => 'Strict fetch',
'help' => 'Narrow the fetch to the specified variant. If the variant is missing, 2FAuth will not try to fallback to the regular variant.'
], ],
'auto_lock' => [ 'auto_lock' => [
'label' => 'Auto lock', 'label' => 'Auto lock',

View File

@ -122,6 +122,41 @@ class LogoLibsTest extends FeatureTestCase
$this->assertStringEndsWith('.png', $icon); $this->assertStringEndsWith('.png', $icon);
} }
#[Test]
public function test_getIcon_fallbacks_to_regular_when_requested_variant_is_not_available()
{
Http::preventStrayRequests();
Http::fake([
CommonDataProvider::SELFH_URL_ROOT . 'svg/myservice-dark.svg' => Http::response('not found', 404),
CommonDataProvider::SELFH_URL_ROOT . 'png/myservice-dark.png' => Http::response('not found', 404),
CommonDataProvider::SELFH_URL_ROOT . 'svg/myservice.svg' => Http::response(HttpRequestTestData::SVG_LOGO_BODY_VARIANT_REGULAR, 200),
]);
$this->logoLib = $this->app->make(SelfhLogoLib::class);
$icon = $this->logoLib->getIcon('myservice', 'dark');
$this->assertEquals(trim(Storage::disk('icons')->get($icon)), '<?xml version="1.0" encoding="UTF-8"?> ' . HttpRequestTestData::SVG_LOGO_BODY_VARIANT_REGULAR);
}
#[Test]
public function test_getIcon_does_not_fallback_to_regular_when_requested_variant_is_not_available()
{
$user = User::factory()->create();
$user['preferences->iconVariantStrictFetch'] = true;
$user->save();
Http::preventStrayRequests();
Http::fake([
CommonDataProvider::SELFH_URL_ROOT . 'svg/myservice-dark.svg' => Http::response('not found', 404),
CommonDataProvider::SELFH_URL_ROOT . 'png/myservice-dark.png' => Http::response('not found', 404),
]);
$this->logoLib = $this->app->make(SelfhLogoLib::class);
$icon = $this->actingAs($user)->logoLib->getIcon('myservice', 'dark');
$this->assertNull($icon);
}
#[Test] #[Test]
public function test_getIcon_fallbacks_to_user_preferences_when_variant_is_omitted() public function test_getIcon_fallbacks_to_user_preferences_when_variant_is_omitted()
{ {
@ -186,7 +221,7 @@ class LogoLibsTest extends FeatureTestCase
$this->logoLib = $this->app->make(TfalogoLib::class); $this->logoLib = $this->app->make(TfalogoLib::class);
$icon = $this->logoLib->getIcon('service'); $icon = $this->logoLib->getIcon('service');
$this->assertEquals(null, $icon); $this->assertNull($icon);
} }
#[Test] #[Test]
@ -204,7 +239,7 @@ class LogoLibsTest extends FeatureTestCase
$this->logoLib = $this->app->make($iconProvider['class']); $this->logoLib = $this->app->make($iconProvider['class']);
$icon = $this->logoLib->getIcon('service'); $icon = $this->logoLib->getIcon('service');
$this->assertEquals(null, $icon); $this->assertNull($icon);
} }
#[Test] #[Test]
@ -219,7 +254,7 @@ class LogoLibsTest extends FeatureTestCase
$this->logoLib = $this->app->make(TfalogoLib::class); $this->logoLib = $this->app->make(TfalogoLib::class);
$icon = $this->logoLib->getIcon('no_logo_should_exists_with_this_name'); $icon = $this->logoLib->getIcon('no_logo_should_exists_with_this_name');
$this->assertEquals(null, $icon); $this->assertNull($icon);
} }
#[Test] #[Test]