Skip to content
Open
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
19 changes: 15 additions & 4 deletions app/Auth.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

namespace Fisharebest\Webtrees;

use Fisharebest\Webtrees\Cli\Console;
use Fisharebest\Webtrees\Contracts\UserInterface;
use Fisharebest\Webtrees\Http\Exceptions\HttpAccessDeniedException;
use Fisharebest\Webtrees\Http\Exceptions\HttpNotFoundException;
Expand Down Expand Up @@ -140,14 +141,15 @@ public static function accessLevel(Tree $tree, UserInterface|null $user = null):
{
$user ??= self::user();

if (self::isAdmin($user)) {
return self::PRIV_HIDE;
}
if (self::isManager($tree, $user)) {
return self::PRIV_NONE;
}

if (self::isMember($tree, $user)) {
return self::PRIV_USER;
}

return self::PRIV_PRIVATE;
}

Expand All @@ -157,7 +159,6 @@ public static function accessLevel(Tree $tree, UserInterface|null $user = null):
public static function id(): int|null
{
$wt_user = Session::get('wt_user');

return is_int($wt_user) ? $wt_user : null;
}

Expand All @@ -169,10 +170,20 @@ public static function id(): int|null
public static function user(): UserInterface
{
$user_service = Registry::container()->get(UserService::class);
return $user_service->find(self::id()) ?? self::newVolatileUser();
}

return $user_service->find(self::id()) ?? new GuestUser();
private static function newVolatileUser(): UserInterface
{
return self::isCliActive() ? new CliUser() : new GuestUser();
}

private static function isCliActive(): bool
{
return php_sapi_name() === 'cli' && Session::get(Console::CLI_SESSION) === '1';
}


/**
* Login directly as an explicit user - for masquerading.
*
Expand Down
26 changes: 24 additions & 2 deletions app/Cli/Console.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use Fisharebest\Webtrees\DB;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Registry;
use Fisharebest\Webtrees\Session;
use Fisharebest\Webtrees\Webtrees;
use Symfony\Component\Console\Application;
use Throwable;
Expand All @@ -30,6 +31,8 @@

final class Console extends Application
{
public const string CLI_SESSION = 'cli_session';

private const array COMMANDS = [
Commands\CompilePoFiles::class,
Commands\ConfigIni::class,
Expand All @@ -52,7 +55,16 @@ public function __construct()
parent::__construct(name: Webtrees::NAME, version: Webtrees::VERSION);
}

public function loadCommands(): self
public function bootstrap(): self
{
return $this
->loadCommands()
->initI18N()
->connectDatabase()
->initSession();
}

private function loadCommands(): self
{
foreach (self::COMMANDS as $command) {
$this->addCommand(command: Registry::container()->get($command));
Expand All @@ -61,10 +73,14 @@ public function loadCommands(): self
return $this;
}

public function bootstrap(): self
private function initI18N(): self
{
I18N::init(code: 'en-US', setup: true);
return $this;
}

private function connectDatabase(): self
{
try {
$config = parse_ini_file(filename: Webtrees::CONFIG_FILE) ?: [];

Expand All @@ -84,7 +100,13 @@ public function bootstrap(): self
} catch (Throwable) {
// Ignore errors
}
return $this;
}

private function initSession(): self
{
Session::put(self::CLI_SESSION, '1');
return $this;
}

}
42 changes: 42 additions & 0 deletions app/CliUser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

/**
* webtrees: online genealogy
* Copyright (C) 2025 webtrees development team
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

declare(strict_types=1);

namespace Fisharebest\Webtrees;

use Fisharebest\Webtrees\Contracts\UserInterface;

/**
* User for command-line invoked functions.
*/
class CliUser extends VolatileUser
{
public function __construct(string $real_name = 'CLI_USER')
{
parent::__construct('_CLI_', $real_name);
}

public function getPreference(string $setting_name, string $default = ''): string
{
if ($setting_name === UserInterface::PREF_IS_ADMINISTRATOR) {
return '1';
}
return parent::getPreference($setting_name, $default);
}

}
81 changes: 3 additions & 78 deletions app/GuestUser.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,90 +19,15 @@

namespace Fisharebest\Webtrees;

use Fisharebest\Webtrees\Contracts\UserInterface;

use function is_string;

/**
* A site visitor.
*/
class GuestUser implements UserInterface
class GuestUser extends VolatileUser
{
private string $email;

private string $real_name;

/**
* @param string $email
* @param string $real_name
*/
public function __construct(string $email = 'GUEST_USER', string $real_name = 'GUEST_USER')
{
$this->email = $email;
$this->real_name = $real_name;
}

/**
* The user‘s internal identifier.
*
* @return int
*/
public function id(): int
public function __construct(string $real_name = 'GUEST_USER')
{
return 0;
parent::__construct('_GUEST_', $real_name);
}

/**
* The users email address.
*
* @return string
*/
public function email(): string
{
return $this->email;
}

/**
* The user‘s real name.
*
* @return string
*/
public function realName(): string
{
return $this->real_name;
}

/**
* The user‘s login name.
*
* @return string
*/
public function userName(): string
{
return '';
}

/**
* @param string $setting_name
* @param string $default
*
* @return string
*/
public function getPreference(string $setting_name, string $default = ''): string
{
$preference = Session::get('_GUEST_' . $setting_name);

return is_string($preference) ? $preference : $default;
}

/**
* @param string $setting_name
* @param string $setting_value
*
* @return void
*/
public function setPreference(string $setting_name, string $setting_value): void
{
Session::put('_GUEST_' . $setting_name, $setting_value);
}
}
73 changes: 73 additions & 0 deletions app/VolatileUser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

/**
* webtrees: online genealogy
* Copyright (C) 2025 webtrees development team
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

declare(strict_types=1);

namespace Fisharebest\Webtrees;

use Fisharebest\Webtrees\Contracts\UserInterface;

use function is_string;

/**
* A volatile user is a user which is not persisted, eg a guest visitor.
* Their user preferences are stored in the session and have no email address.
*/
abstract class VolatileUser implements UserInterface
{
private string $user_name;
private string $real_name;

public function __construct(string $user_name, string $real_name = 'N/A')
{
$this->user_name = $user_name;
$this->real_name = $real_name;
}

public function id(): int
{
return 0;
}

public function userName(): string
{
return $this->user_name;
}

public function realName(): string
{
return $this->real_name;
}

public function email(): string
{
return 'N/A';
}

public function getPreference(string $setting_name, string $default = ''): string
{
$preference = Session::get($this->userName() . $setting_name);

return is_string($preference) ? $preference : $default;
}

public function setPreference(string $setting_name, string $setting_value): void
{
Session::put($this->userName() . $setting_name, $setting_value);
}

}
2 changes: 1 addition & 1 deletion app/Webtrees.php
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ public function cliRequest(): int
{
$console = new Console();

return $console->loadCommands()->bootstrap()->run();
return $console->bootstrap()->run();
}

/**
Expand Down
37 changes: 37 additions & 0 deletions tests/app/AuthTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,50 @@

namespace Fisharebest\Webtrees;

use Fisharebest\Webtrees\Cli\Console;
use Fisharebest\Webtrees\Services\UserService;
use PHPUnit\Framework\Attributes\CoversClass;

#[CoversClass(Auth::class)]
class AuthTest extends TestCase
{
private UserService $user_service;

protected static bool $uses_database = true;

protected function setup(): void
{
parent::setup();

$this->user_service = self::createStub(UserService::class);
Registry::container()->set(UserService::class, $this->user_service);
}

public function testClass(): void
{
self::assertTrue(class_exists(Auth::class));
}

public function testLoginLogout(): void
{
$user = new DefaultUser();
$this->user_service->method('find')->willReturn(null, $user, null);

Check failure on line 49 in tests/app/AuthTest.php

View workflow job for this annotation

GitHub Actions / phpstan (ubuntu-latest, 8.4)

Call to an undefined method Fisharebest\Webtrees\Services\UserService::method().

Check failure on line 49 in tests/app/AuthTest.php

View workflow job for this annotation

GitHub Actions / phpstan (ubuntu-latest, 8.5)

Call to an undefined method Fisharebest\Webtrees\Services\UserService::method().

Check failure on line 49 in tests/app/AuthTest.php

View workflow job for this annotation

GitHub Actions / phpstan (ubuntu-latest, 8.6)

Call to an undefined method Fisharebest\Webtrees\Services\UserService::method().

Check failure on line 49 in tests/app/AuthTest.php

View workflow job for this annotation

GitHub Actions / phpstan (ubuntu-latest, 8.3)

Call to an undefined method Fisharebest\Webtrees\Services\UserService::method().

self::assertInstanceOf('Fisharebest\Webtrees\GuestUser', Auth::user());

Auth::login($user);
self::assertInstanceOf('Fisharebest\Webtrees\DefaultUser', Auth::user());

Auth::logout();
$this->user_service->method('find');

Check failure on line 57 in tests/app/AuthTest.php

View workflow job for this annotation

GitHub Actions / phpstan (ubuntu-latest, 8.4)

Call to an undefined method Fisharebest\Webtrees\Services\UserService::method().

Check failure on line 57 in tests/app/AuthTest.php

View workflow job for this annotation

GitHub Actions / phpstan (ubuntu-latest, 8.5)

Call to an undefined method Fisharebest\Webtrees\Services\UserService::method().

Check failure on line 57 in tests/app/AuthTest.php

View workflow job for this annotation

GitHub Actions / phpstan (ubuntu-latest, 8.6)

Call to an undefined method Fisharebest\Webtrees\Services\UserService::method().

Check failure on line 57 in tests/app/AuthTest.php

View workflow job for this annotation

GitHub Actions / phpstan (ubuntu-latest, 8.3)

Call to an undefined method Fisharebest\Webtrees\Services\UserService::method().
self::assertInstanceOf('Fisharebest\Webtrees\GuestUser', Auth::user());
}

public function testCli(): void
{
self::assertInstanceOf('Fisharebest\Webtrees\GuestUser', Auth::user());
Session::put(Console::CLI_SESSION, '1');
self::assertInstanceOf('Fisharebest\Webtrees\CLiUser', Auth::user());
}

}
Loading
Loading