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
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
9 changes: 7 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 @@ -257,6 +257,11 @@ public function getValidControllerCall(string $moduleName, string $actionName, s
}

$classPath = $this->getClassPath($controllerType, $moduleName, $actionName);

if ($classPath === false) {
throw new NotFoundHttpException("Can't find a valid controller for ".strip_tags($moduleName).'/'.strip_tags($actionName));
}

$classMethod = $this->getValidControllerMethod($classPath, $methodName);

Cache::store('installation')->set('routes.'.$routepath.'.'.($classMethod == 'run' ? $methodNameLower : $classMethod), ['class' => $classPath, 'method' => $classMethod]);
Expand All @@ -269,7 +274,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
6 changes: 3 additions & 3 deletions app/Core/Sessions/PathManifestRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,19 +58,19 @@ public function loadManifest(string $manifestName)
}
}

return false;
return null;
}

/**
* Determine if the manifest should be compiled.
*
* @param array $manifest
* @param array|null $manifest
* @param array $paths
* @return bool
*/
public function shouldRefresh($manifest, $paths)
{
return is_null($manifest) || $manifest[$manifest] != $paths;
return is_null($manifest) || $manifest != $paths;
}

/**
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
6 changes: 2 additions & 4 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 @@ -688,11 +686,11 @@ public function setNotification(string $msg, string $type, string $event_id = ''
* getToggleState - retrieves the toggle state of a submenu by name from the session
*
* @param string $name - the name of the submenu toggle
* @return string - the toggle state of the submenu (either "true" or "false")
* @return string|false - the toggle state of the submenu ("true"/"false"), or false if unset
*
* @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
Loading
Loading