Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
2 changes: 1 addition & 1 deletion .phpstan/phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ includes:
parameters:
bootstrapFiles:
- bootstrap.php
level: 2
level: 3
inferPrivatePropertyTypeFromConstructor: true
paths:
- ../public
Expand Down
9 changes: 5 additions & 4 deletions .phpstan/stubs/laravel-gaps.stub
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,16 @@ namespace Illuminate\Database;
*/
interface ConnectionInterface {}

namespace Illuminate\Cache;
namespace Illuminate\Contracts\Cache;

/**
* Repository forwards lock() via __call to its underlying store when that store is a
* LockProvider (Redis/File/Database/...). StartSession uses it for session locking.
* The concrete Illuminate\Cache\Repository (what Cache::store() returns) forwards lock() via
* __call to its underlying store when that store is a LockProvider (Redis/File/Database/...).
* StartSession::cache() is typed against this contract and uses it for session locking.
*
* @method \Illuminate\Cache\Lock lock(string $name, int $seconds = 0, ?string $owner = null)
*/
class Repository {}
interface Repository {}

namespace Illuminate\Contracts\Filesystem;

Expand Down
2 changes: 0 additions & 2 deletions app/Core/Bootloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ class Bootloader

/**
* Bootloader instance
*
* @var static
*/
protected static ?Bootloader $instance = null;

Expand Down
4 changes: 2 additions & 2 deletions app/Core/Controller/Frontcontroller.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class Frontcontroller
public function __construct(IncomingRequest $request, private PermissionEnforcer $permissionEnforcer)
{
$this->incomingRequest = $request;
$this->config = config();
$this->config = app(Environment::class);
}

/**
Expand Down Expand Up @@ -269,7 +269,7 @@ public function getValidControllerCall(string $moduleName, string $actionName, s
*
* @param string $controllerType The type of controller. Possible values are 'Controllers' or 'Hxcontrollers'.
**/
public function getClassPath(string $controllerType, string $moduleName, string $actionName): string
public function getClassPath(string $controllerType, string $moduleName, string $actionName): string|false
{

Comment on lines 275 to 279

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in da42e23: getValidControllerCall now guards $classPath === false and throws NotFoundHttpException (a 404, consistent with getValidControllerMethod) before passing it on — no more TypeError on the disabled-plugin / missing-controller path.

$controllerNs = 'Domain';
Expand Down
2 changes: 1 addition & 1 deletion app/Core/Db/Db.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public function __construct($app, ?string $connection = null)
/**
* Get the PDO connection (lazily retrieved from Laravel's connection pool)
*
* @return PDO
* @return \PDO|null
*/
public function __get($name)
{
Expand Down
8 changes: 6 additions & 2 deletions app/Core/Events/EventDispatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -781,15 +781,19 @@ public function listen($events, $listener = null)
{

if ($events instanceof \Closure) {
return collect($this->firstClosureParameterTypes($events))
collect($this->firstClosureParameterTypes($events))
->each(function ($event) use ($events) {
$this->listen($event, $events);
});

return;
} elseif ($events instanceof QueuedClosure) {
return collect($this->firstClosureParameterTypes($events->closure))
collect($this->firstClosureParameterTypes($events->closure))
->each(function ($event) use ($events) {
$this->listen($event, $events->resolve());
});

return;
} elseif ($listener instanceof QueuedClosure) {
$listener = $listener->resolve();
}
Expand Down
4 changes: 2 additions & 2 deletions app/Core/Exceptions/ExceptionHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ public function register()
/**
* Register a reportable callback.
*
* @return \Illuminate\Foundation\Exceptions\ReportableHandler
* @return \Leantime\Core\Exceptions\ReportableHandler
*/
public function reportable(callable $reportUsing)
{
Expand Down Expand Up @@ -435,7 +435,7 @@ protected function renderExceptionWithWhoops(Throwable $e)
/**
* Get the Whoops handler for the application.
*
* @return \Whoops\Handler\Handler
* @return \Whoops\Handler\HandlerInterface
*/
protected function whoopsHandler()
{
Expand Down
2 changes: 1 addition & 1 deletion app/Core/Exceptions/HandleExceptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class HandleExceptions
/**
* The application instance.
*
* @var \Leantime\Core\Application
* @var \Leantime\Core\Application|null
*/
protected static $app;

Expand Down
2 changes: 1 addition & 1 deletion app/Core/Middleware/StartSession.php
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ protected function sessionIsPersistent(?array $config = null)
* Resolve the given cache driver.
*
* @param string $driver
* @return \Illuminate\Cache\Repository
* @return \Illuminate\Contracts\Cache\Repository
*/
protected function cache($driver)
{
Expand Down
2 changes: 1 addition & 1 deletion app/Core/Middleware/TrustProxies.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class TrustProxies
/**
* The headers that should be used to detect proxies.
*
* @var string
* @var int
*/
protected $headers = IncomingRequest::HEADER_X_FORWARDED_FOR |
IncomingRequest::HEADER_X_FORWARDED_HOST |
Expand Down
4 changes: 2 additions & 2 deletions app/Core/Sessions/PathManifestRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public function loadManifest(string $manifestName)
}
}

return false;
return null;
}

/**
Expand All @@ -70,7 +70,7 @@ public function loadManifest(string $manifestName)
*/
public function shouldRefresh($manifest, $paths)
{
return is_null($manifest) || $manifest[$manifest] != $paths;
return is_null($manifest) || $manifest != $paths;
Comment on lines 69 to +73

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in da42e23: shouldRefresh @param $manifestarray|null.

}

/**
Expand Down
2 changes: 1 addition & 1 deletion app/Core/Support/Cast.php
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ public static function castEnum(mixed $value, string $enumClass): mixed
* Casts a string value into a datetime object.
*
* @param string $value The value to be casted into a datetime object.
* @return \DateTime The datetime object.
* @return \Carbon\CarbonImmutable The datetime object.
*
* @throws \InvalidArgumentException If the value is not a valid datetime string.
**/
Expand Down
4 changes: 2 additions & 2 deletions app/Core/Support/Format.php
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ public function isoDate(): string
public function timestamp(): int|bool
{
if (empty($this->value) || ! $this->value instanceof CarbonImmutable) {
return '';
return false;
}

return $this->value->getTimestamp();
Expand All @@ -219,7 +219,7 @@ public function timestamp(): int|bool
public function jsTimestamp(): int|bool
{
if (empty($this->value) || ! $this->value instanceof CarbonImmutable) {
return '';
return false;
}

return $this->value->getTimestampMs();
Expand Down
4 changes: 1 addition & 3 deletions app/Core/UI/Template.php
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,6 @@ public function assign(string $name, mixed $value): void

/**
* get - get assigned values
*
* @return array
*/
public function get(string $name): mixed
{
Expand Down Expand Up @@ -692,7 +690,7 @@ public function setNotification(string $msg, string $type, string $event_id = ''
*
Comment on lines 688 to 690

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in da42e23: getToggleState docblock @returnstring|false.

* @deprecated this should be in a component
*/
public function getToggleState(string $name): string
public function getToggleState(string $name): string|false
{
if (session()->exists('usersettings.submenuToggle.'.$name)) {
return session('usersettings.submenuToggle.'.$name);
Expand Down
11 changes: 3 additions & 8 deletions app/Domain/Auth/Guards/ApiGuard.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Leantime\Core\Http\ApiRequest;
use Leantime\Core\Http\IncomingRequest;
use Leantime\Domain\Api\Services\Api;
use Leantime\Domain\Auth\Models\AuthenticatableUser;

class ApiGuard implements Guard
{
Expand Down Expand Up @@ -64,20 +65,14 @@ public function user()
return $this->user;
}

$this->user = (object) $apiUser;
$this->user = new AuthenticatableUser((array) $apiUser);

return $this->user;
}

public function id()
{
// user() currently returns a stdClass of API-key user data (not yet a real
// Authenticatable — that conversion is tracked for the level-3 auth pass), so read
// the id off the object rather than calling getAuthIdentifier().
/** @var \stdClass|null $user */
$user = $this->user();

return $user?->id;
return $this->user()?->getAuthIdentifier();
}

public function validate(array $credentials = [])
Expand Down
8 changes: 1 addition & 7 deletions app/Domain/Auth/Guards/WebGuard.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,7 @@ public function hasUser()

public function id()
{
// user() currently returns a stdClass (AuthUser::retrieveById casts to object; the real
// Authenticatable conversion is tracked for the level-3 auth pass), so read the id off
// the object rather than calling getAuthIdentifier().
/** @var \stdClass|null $user */
$user = $this->user();

return $user?->id;
return $this->user()?->getAuthIdentifier();
}

public function validate(array $credentials = [])
Expand Down
63 changes: 63 additions & 0 deletions app/Domain/Auth/Models/AuthenticatableUser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

namespace Leantime\Domain\Auth\Models;

use Illuminate\Contracts\Auth\Authenticatable;

/**
* Lightweight Authenticatable wrapper around a user-data row.
*
* Replaces the `(object) $userRow` stdClass casts in AuthUser/ApiGuard so the provider/guard
* methods satisfy their `?Authenticatable` contracts. It uses dynamic properties on purpose so it
* stays a behavioural drop-in for the old stdClass cast — same property reads, same json/array
* serialization, truthy even when empty — and merely ADDS the Authenticatable accessor methods.
*/
#[\AllowDynamicProperties]
class AuthenticatableUser implements Authenticatable
{
/**
* @param array<string, mixed> $attributes A user row (column => value).
*/
public function __construct(array $attributes = [])
{
foreach ($attributes as $key => $value) {
$this->{$key} = $value;
}
}

public function getAuthIdentifierName(): string
{
return 'id';
}

public function getAuthIdentifier(): mixed
{
return $this->id ?? null;
}

public function getAuthPasswordName(): string
{
return 'password';
}

public function getAuthPassword(): string
{
return $this->password ?? '';
}

public function getRememberToken(): string
{
return $this->remember_token ?? '';
}

public function setRememberToken($value): void
{
// No-op: Leantime does not persist remember tokens (mirrors Auth::setRememberToken and
// AuthUser::updateRememberToken, which are likewise not implemented).
}

public function getRememberTokenName(): string
{
return 'remember_token';
}
}
2 changes: 1 addition & 1 deletion app/Domain/Auth/Services/Auth.php
Original file line number Diff line number Diff line change
Expand Up @@ -698,7 +698,7 @@ public function getAuthPasswordName()

public function getRememberToken()
{
return null; // Not implemented yet
return ''; // Not implemented yet (Authenticatable::getRememberToken is contractually a string)
}

public function setRememberToken($value)
Expand Down
19 changes: 11 additions & 8 deletions app/Domain/Auth/Services/AuthUser.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;
use Laravel\Sanctum\HasApiTokens;
use Leantime\Domain\Auth\Models\AuthenticatableUser;
use Leantime\Domain\Auth\Services\Auth as AuthService;

class AuthUser implements UserProvider
Expand All @@ -26,12 +27,12 @@ public function __construct(

public function retrieveById($identifier)
{
return (object) $this->userRepo->getUser($identifier);
return new AuthenticatableUser((array) $this->userRepo->getUser($identifier));
}

public function retrieveByToken($identifier, $token)
{
return (object) $this->authService->getUserByToken($token);
return new AuthenticatableUser((array) $this->authService->getUserByToken($token));
}
Comment on lines 28 to 50

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in da42e23: retrieveById/retrieveByToken now return null when the lookup fails and only build an AuthenticatableUser from a real row, so a 'not found' can't yield a non-null (authenticated-looking) user.


public function updateRememberToken(Authenticatable $user, $token)
Expand Down Expand Up @@ -63,10 +64,12 @@ public function rehashPasswordIfRequired(Authenticatable $user, array $credentia

public function getOrCreateUser($user, $source)
{
// Look up the existing account in a separate variable — the $user param holds the
// external/OAuth profile data we need to create the account from, so it must not be
// overwritten by the lookup result (doing so previously built new users with empty fields).
$existingUser = $this->authRepo->getUserByEmail($user['email']);

$user = $this->authRepo->getUserByEmail($user['email']);

if (empty($user) && config()->get('auth.create_user')) {
if (empty($existingUser) && config()->get('auth.create_user')) {

$userArray = [
'firstname' => $user['firstname'],
Expand All @@ -83,11 +86,11 @@ public function getOrCreateUser($user, $source)
'status' => 'a',
];

$userId = $this->userRepo->addUser($userArray);
$user = $this->authRepo->getUserByEmail($user['email']);
$this->userRepo->addUser($userArray);
$existingUser = $this->authRepo->getUserByEmail($user['email']);
}

return $user;
return $existingUser;
}

public function setUser($userId)
Expand Down
2 changes: 1 addition & 1 deletion app/Domain/Blueprints/Repositories/Blueprints.php
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,7 @@ public function copyCanvas(int $projectId, int $canvasId, int $authorId, string

$this->connection->table('zp_canvas_items')->insertUsing($columns, $selectQuery);

return $newCanvasId;
return (int) $newCanvasId;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion app/Domain/Clients/Controllers/ShowClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public function get(array $params): Response

return Frontcontroller::redirect(BASE_URL.'/clients/showClient/'.$id.'#files');
} else {
$this->tpl->setNotification($result['msg'], 'error');
$this->tpl->setNotification(is_array($result) ? ($result['msg'] ?? '') : '', 'error');
}
}

Expand Down
2 changes: 1 addition & 1 deletion app/Domain/CsvImport/Services/CsvImport.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public function getEntities(): array
}

/**
* @return void
* @return array<int, mixed>|false
*/
public function getValues(Entity $Entity): mixed
{
Expand Down
Loading
Loading