This commit is contained in:
Yuri Kuznetsov
2024-02-03 13:52:37 +02:00
parent 94351646b1
commit 33072072c4
70 changed files with 286 additions and 301 deletions
@@ -30,6 +30,7 @@
namespace Espo\Core\Api;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\Conflict;
use Espo\Core\Exceptions\Error;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\NotFound;
@@ -59,6 +60,7 @@ class ActionHandler implements RequestHandlerInterface
* @throws Forbidden
* @throws Error
* @throws NotFound
* @throws Conflict
*/
public function handle(ServerRequestInterface $request): ResponseInterface
{
@@ -93,6 +95,7 @@ class ActionHandler implements RequestHandlerInterface
$response->setHeader('X-App-Timestamp', (string) ($this->config->get('appTimestamp') ?? '0'));
/** @noinspection PhpConditionAlreadyCheckedInspection */
return $response instanceof ResponseWrapper ?
$response->toPsr7() :
self::responseToPsr7($response);
@@ -33,7 +33,6 @@ use Espo\Core\InjectableFactory;
class AuthBuilderFactory
{
public function __construct(private InjectableFactory $injectableFactory)
{}
@@ -156,7 +156,6 @@ class ControllerActionProcessor
$type = $params[0]->getType();
if (
!$type ||
!$type instanceof ReflectionNamedType ||
$type->isBuiltin()
) {
+2 -2
View File
@@ -90,7 +90,7 @@ class ErrorOutput
?string $route = null
): void {
$this->processInternal($request, $response, $exception, $route, false);
$this->processInternal($request, $response, $exception, $route);
}
public function processWithBodyPrinting(
@@ -133,7 +133,7 @@ class ErrorOutput
$logMessageItemList = [];
if ($message) {
$logMessageItemList[] = "{$message}";
$logMessageItemList[] = "$message";
}
$logMessageItemList[] = $request->getMethod() . ' ' . $request->getResourcePath();
+1 -1
View File
@@ -32,7 +32,6 @@ namespace Espo\Core\Api;
use Espo\Core\Api\Request as ApiRequest;
use Psr\Http\Message\UriInterface;
use Slim\Psr7\Factory\UriFactory;
use stdClass;
@@ -49,6 +48,7 @@ class RequestNull implements ApiRequest
/**
* @return null
* @noinspection PhpDocSignatureInspection
*/
public function getQueryParam(string $name): ?string
{
@@ -275,6 +275,7 @@ class RequestWrapper implements ApiRequest
return $this->getMethod() === 'PUT';
}
/** @noinspection PhpUnused */
public function isUpdate(): bool
{
return $this->getMethod() === 'UPDATE';
+1 -1
View File
@@ -202,7 +202,7 @@ class RouteProcessor
$action = $crudMethodActionMap[strtolower($method)] ?? null;
if (!$action) {
throw new BadRequest("No action for method `{$method}`.");
throw new BadRequest("No action for method `$method`.");
}
}
@@ -55,7 +55,7 @@ class RunnerRunner
public function run(string $className, ?Params $params = null): void
{
if (!class_exists($className)) {
$this->log->error("Application runner '{$className}' does not exist.");
$this->log->error("Application runner '$className' does not exist.");
throw new RunnerException();
}
@@ -38,14 +38,8 @@ use Espo\Core\Utils\Config;
*/
class Client implements Runner
{
private ClientManager $clientManager;
private Config $config;
public function __construct(ClientManager $clientManager, Config $config)
{
$this->clientManager = $clientManager;
$this->config = $config;
}
public function __construct(private ClientManager $clientManager, private Config $config)
{}
public function run(): void
{
@@ -60,6 +60,9 @@ class PortalClient implements RunnerParameterized
private ErrorOutput $errorOutput
) {}
/**
* @throws BadRequest
*/
public function run(Params $params): void
{
$id = $params->get('id') ??
@@ -43,6 +43,9 @@ class Preload implements Runner
{
use Cli;
/**
* @throws Throwable
*/
public function run(): void
{
$preload = new PreloadUtil();
@@ -59,7 +62,7 @@ class Preload implements Runner
$count = $preload->getCount();
echo "Success." . PHP_EOL;
echo "Files loaded: " . (string) $count . "." . PHP_EOL;
echo "Files loaded: " . $count . "." . PHP_EOL;
}
protected function processException(Throwable $e): void
@@ -69,13 +72,13 @@ class Preload implements Runner
$msg = $e->getMessage();
if ($msg) {
echo "Message: {$msg}" . PHP_EOL;
echo "Message: $msg" . PHP_EOL;
}
$file = $e->getFile();
if ($file) {
echo "File: {$file}" . PHP_EOL;
echo "File: $file" . PHP_EOL;
}
echo "Line: " . $e->getLine() . PHP_EOL;
@@ -41,6 +41,8 @@ use RuntimeException;
* in another storage. E.g. a single Redis data store can be utilized with
* multiple Espo replicas (for scalability purposes).
* Defined at metadata > app > containerServices > authTokenManager.
*
* @noinspection PhpUnused
*/
class EspoManager implements Manager
{
@@ -56,8 +58,7 @@ class EspoManager implements Manager
public function get(string $token): ?AuthToken
{
/** @var ?AuthTokenEntity $authToken */
$authToken = $this->repository
return $this->repository
->select([
'id',
'isActive',
@@ -72,13 +73,10 @@ class EspoManager implements Manager
])
->where(['token' => $token])
->findOne();
return $authToken;
}
public function create(Data $data): AuthToken
{
/** @var AuthTokenEntity $authToken */
$authToken = $this->repository->getNew();
$authToken
@@ -102,6 +100,7 @@ class EspoManager implements Manager
public function inactivate(AuthToken $authToken): void
{
/** @noinspection PhpConditionAlreadyCheckedInspection */
if (!$authToken instanceof AuthTokenEntity) {
throw new RuntimeException();
}
@@ -115,6 +114,7 @@ class EspoManager implements Manager
public function renew(AuthToken $authToken): void
{
/** @noinspection PhpConditionAlreadyCheckedInspection */
if (!$authToken instanceof AuthTokenEntity) {
throw new RuntimeException();
}
@@ -159,11 +159,11 @@ class EspoManager implements Manager
$length = self::TOKEN_RANDOM_LENGTH;
if (function_exists('random_bytes')) {
/** @noinspection PhpUnhandledExceptionInspection */
return bin2hex(random_bytes($length));
}
if (function_exists('openssl_random_pseudo_bytes')) {
/** @var string $randomValue */
$randomValue = openssl_random_pseudo_bytes($length);
return bin2hex($randomValue);
@@ -35,7 +35,7 @@ namespace Espo\Core\Authentication\AuthToken;
interface Manager
{
/**
* Get an auth token. If does not exist then returns NULL.
* Get an auth token. If it does not exist, then returns NULL.
*/
public function get(string $token): ?AuthToken;
@@ -34,20 +34,13 @@ namespace Espo\Core\Authentication;
*/
class AuthenticationData
{
private ?string $username;
private ?string $password;
private ?string $method;
private bool $byTokenOnly = false;
public function __construct(
?string $username = null,
?string $password = null,
?string $method = null
) {
$this->username = $username;
$this->password = $password;
$this->method = $method;
}
private ?string $username = null,
private ?string $password = null,
private ?string $method = null
) {}
public static function create(): self
{
@@ -34,21 +34,13 @@ use Espo\Core\Authentication\Logins\Espo;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Metadata;
use RuntimeException;
class ConfigDataProvider
{
private const FAILED_ATTEMPTS_PERIOD = '60 seconds';
private const MAX_FAILED_ATTEMPT_NUMBER = 10;
private Config $config;
private Metadata $metadata;
public function __construct(Config $config, Metadata $metadata)
{
$this->config = $config;
$this->metadata = $metadata;
}
public function __construct(private Config $config, private Metadata $metadata)
{}
/**
* A period for max failed attempts checking.
@@ -155,16 +147,4 @@ class ConfigDataProvider
return $list;
}
public function getMethodLoginMetadataParams(string $method): MetadataParams
{
/** @var ?array<string, mixed> $data */
$data = $this->metadata->get(['authenticationMethods', $method]);
if ($data === null) {
throw new RuntimeException();
}
return MetadataParams::fromRaw($method, $data);
}
}
@@ -32,60 +32,46 @@ namespace Espo\Core\Authentication\Helper;
use Espo\Core\Authentication\Logins\ApiKey;
use Espo\Core\Authentication\Logins\Hmac;
use Espo\ORM\EntityManager;
use Espo\Entities\User;
class UserFinder
{
private EntityManager $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function __construct(private EntityManager $entityManager)
{}
public function find(string $username, string $hash): ?User
{
/** @var ?User $user */
$user = $this->entityManager
->getRDBRepository(User::ENTITY_TYPE)
return $this->entityManager
->getRDBRepositoryByClass(User::class)
->where([
'userName' => $username,
'password' => $hash,
'type!=' => [User::TYPE_API, User::TYPE_SYSTEM],
])
->findOne();
return $user;
}
public function findApiHmac(string $apiKey): ?User
{
/** @var ?User $user */
$user = $this->entityManager
->getRDBRepository(User::ENTITY_TYPE)
return $this->entityManager
->getRDBRepositoryByClass(User::class)
->where([
'type' => User::TYPE_API,
'apiKey' => $apiKey,
'authMethod' => Hmac::NAME,
])
->findOne();
return $user;
}
public function findApiApiKey(string $apiKey): ?User
{
/** @var ?User $user */
$user = $this->entityManager
->getRDBRepository(User::ENTITY_TYPE)
return $this->entityManager
->getRDBRepositoryByClass(User::class)
->where([
'type' => User::TYPE_API,
'apiKey' => $apiKey,
'authMethod' => ApiKey::NAME,
])
->findOne();
return $user;
}
}
@@ -40,7 +40,12 @@ use Espo\ORM\EntityManager;
use Espo\Entities\AuthLogRecord;
use DateTime;
use Exception;
use RuntimeException;
/**
* @noinspection PhpUnused
*/
class FailedAttemptsLimit implements BeforeLogin
{
public function __construct(
@@ -70,7 +75,12 @@ class FailedAttemptsLimit implements BeforeLogin
$requestTime = intval($request->getServerParam('REQUEST_TIME_FLOAT'));
$requestTimeFrom = (new DateTime('@' . $requestTime))->modify('-' . $failedAttemptsPeriod);
try {
$requestTimeFrom = (new DateTime('@' . $requestTime))->modify('-' . $failedAttemptsPeriod);
}
catch (Exception $e) {
throw new RuntimeException($e->getMessage());
}
$ip = $this->util->obtainIpFromRequest($request);
@@ -99,7 +109,7 @@ class FailedAttemptsLimit implements BeforeLogin
return;
}
$this->log->warning("AUTH: Max failed login attempts exceeded for IP '{$ip}'.");
$this->log->warning("AUTH: Max failed login attempts exceeded for IP '$ip'.");
throw new Forbidden("Max failed login attempts exceeded.");
}
@@ -31,21 +31,15 @@ namespace Espo\Core\Authentication\Hook;
use Espo\Core\Utils\Metadata;
use Espo\Core\InjectableFactory;
use Espo\Core\Authentication\AuthenticationData;
use Espo\Core\Api\Request;
use Espo\Core\Authentication\Result;
class Manager
{
private Metadata $metadata;
private InjectableFactory $injectableFactory;
public function __construct(Metadata $metadata, InjectableFactory $injectableFactory)
{
$this->metadata = $metadata;
$this->injectableFactory = $injectableFactory;
}
public function __construct(private Metadata $metadata, private InjectableFactory $injectableFactory)
{}
public function processBeforeLogin(AuthenticationData $data, Request $request): void
{
@@ -63,7 +63,7 @@ class Hmac implements SignatureVerifier
$this->key = $key;
if (!in_array($algorithm, self::SUPPORTED_ALGORITHM_LIST)) {
throw new RuntimeException("Unsupported algorithm {$algorithm}.");
throw new RuntimeException("Unsupported algorithm $algorithm.");
}
}
@@ -70,7 +70,7 @@ class Rsa implements SignatureVerifier
$this->keys = $keys;
if (!in_array($algorithm, self::SUPPORTED_ALGORITHM_LIST)) {
throw new RuntimeException("Unsupported algorithm {$algorithm}.");
throw new RuntimeException("Unsupported algorithm $algorithm.");
}
}
@@ -72,7 +72,7 @@ class Header
try {
$parsed = Json::decode($raw);
}
catch (JsonException $e) {}
catch (JsonException) {}
if (!$parsed instanceof stdClass) {
throw new RuntimeException();
@@ -88,23 +88,25 @@ class Header
);
}
/** @noinspection PhpSameParameterValueInspection */
private static function obtainFromParsedString(stdClass $parsed, string $name): string
{
$value = $parsed->$name ?? null;
if (!is_string($value)) {
throw new RuntimeException("No or bad `{$name}` in JWT header.");
throw new RuntimeException("No or bad `$name` in JWT header.");
}
return $value;
}
/** @noinspection PhpSameParameterValueInspection */
private static function obtainFromParsedStringNull(stdClass $parsed, string $name): ?string
{
$value = $parsed->$name ?? null;
if ($value !== null && !is_string($value)) {
throw new RuntimeException("Bad `{$name}` in JWT header.");
throw new RuntimeException("Bad `$name` in JWT header.");
}
return $value;
@@ -123,6 +123,7 @@ class Payload
return $this->authTime;
}
/** @noinspection PhpUnused */
public function getSid(): ?string
{
return $this->sid;
@@ -143,7 +144,7 @@ class Payload
try {
$parsed = Json::decode($raw);
}
catch (JsonException $e) {}
catch (JsonException) {}
if (!$parsed instanceof stdClass) {
throw new RuntimeException();
@@ -29,4 +29,6 @@
namespace Espo\Core\Authentication\Ldap;
class Client extends \Laminas\Ldap\Ldap {}
use Laminas\Ldap\Ldap;
class Client extends Ldap {}
@@ -29,11 +29,13 @@
namespace Espo\Core\Authentication\Ldap;
use Laminas\Ldap\Exception\LdapException;
class ClientFactory
{
/**
* @param array<string, mixed> $options
* @throws \Laminas\Ldap\Exception\LdapException
* @throws LdapException
*/
public function create(array $options): Client
{
@@ -52,7 +52,12 @@ use Espo\Core\Utils\Log;
use Espo\Core\Utils\PasswordHash;
use Espo\Entities\User;
use Exception;
use Laminas\Ldap\Exception\LdapException;
use Laminas\Ldap\Ldap;
/**
* @noinspection PhpUnused
*/
class LdapLogin implements Login
{
private LDAPUtils $utils;
@@ -81,6 +86,7 @@ class LdapLogin implements Login
/**
* @var array<string, string>
* @noinspection PhpUnusedPrivateFieldInspection
*/
private $ldapFieldMap = [
'userName' => 'userNameAttribute',
@@ -93,6 +99,7 @@ class LdapLogin implements Login
/**
* @var array<string, string>
* @noinspection PhpUnusedPrivateFieldInspection
*/
private $userFieldMap = [
'teamsIds' => 'userTeamsIds',
@@ -101,12 +108,16 @@ class LdapLogin implements Login
/**
* @var array<string, string>
* @noinspection PhpUnusedPrivateFieldInspection
*/
private $portalUserFieldMap = [
'portalsIds' => 'portalUserPortalsIds',
'portalRolesIds' => 'portalUserRolesIds',
];
/**
* @throws LdapException
*/
public function login(Data $data, Request $request): Result
{
$username = $data->getUsername();
@@ -219,7 +230,7 @@ class LdapLogin implements Login
if (!isset($user)) {
if (!$this->utils->getOption('createEspoUser')) {
$this->log->warning(
"LDAP: Authentication success for user {$username}, but user is not created in EspoCRM."
"LDAP: Authentication success for user $username, but user is not created in EspoCRM."
);
return Result::fail(FailReason::USER_NOT_FOUND);
@@ -369,13 +380,14 @@ class LdapLogin implements Login
$user->setAsNotNew();
$user->updateFetchedValues();
/** @var ?User */
return $this->entityManager->getEntityById(User::ENTITY_TYPE, $user->getId());
}
/**
* Find LDAP user DN by his username.
*
* @throws \Laminas\Ldap\Exception\LdapException
* @throws LdapException
*/
private function findLdapUserDnByUsername(string $username): ?string
{
@@ -395,7 +407,8 @@ class LdapLogin implements Login
$loginFilterString . ')';
/** @var array<int, array{dn: string}> $result */
$result = $ldapClient->search($searchString, null, Client::SEARCH_SCOPE_SUB);
/** @noinspection PhpRedundantOptionalArgumentInspection */
$result = $ldapClient->search($searchString, null, Ldap::SEARCH_SCOPE_SUB);
$this->log->debug('LDAP: user search string: "' . $searchString . '"');
@@ -413,11 +426,11 @@ class LdapLogin implements Login
{
$filter = trim($filter);
if (substr($filter, 0, 1) != '(') {
if (!str_starts_with($filter, '(')) {
$filter = '(' . $filter;
}
if (substr($filter, -1) != ')') {
if (!str_ends_with($filter, ')')) {
$filter = $filter . ')';
}
@@ -188,8 +188,6 @@ class Utils
{
$options = $this->getOptions();
$zendOptions = array_diff_key($options, array_flip($this->permittedEspoOptions));
return $zendOptions;
return array_diff_key($options, array_flip($this->permittedEspoOptions));
}
}
@@ -30,7 +30,6 @@
namespace Espo\Core\Authentication;
use Espo\Core\Api\Request;
use Espo\Core\Authentication\Result;
use Espo\Core\Authentication\Login\Data;
/**
@@ -43,14 +43,8 @@ class Hmac implements Login
{
public const NAME = 'Hmac';
private UserFinder $userFinder;
private ApiKey $apiKeyUtil;
public function __construct(UserFinder $userFinder, ApiKey $apiKeyUtil)
{
$this->userFinder = $userFinder;
$this->apiKeyUtil = $apiKeyUtil;
}
public function __construct(private UserFinder $userFinder, private ApiKey $apiKeyUtil)
{}
public function login(Data $data, Request $request): Result
{
@@ -36,14 +36,8 @@ use RuntimeException;
class LogoutFactory
{
private InjectableFactory $injectableFactory;
private Metadata $metadata;
public function __construct(InjectableFactory $injectableFactory, Metadata $metadata)
{
$this->injectableFactory = $injectableFactory;
$this->metadata = $metadata;
}
public function __construct(private InjectableFactory $injectableFactory, private Metadata $metadata)
{}
public function create(string $method): Logout
{
@@ -81,7 +81,7 @@ class BackchannelLogout
$username = $token->getPayload()->get($usernameClaim);
if (!$username) {
throw new Invalid("No username claim `{$usernameClaim}` in token.");
throw new Invalid("No username claim `$usernameClaim` in token.");
}
$user = $this->userRepository->findByUsername($username);
@@ -64,7 +64,7 @@ class DefaultSignatureVerifierFactory implements SignatureVerifierFactory
$className = self::ALGORITHM_VERIFIER_CLASS_NAME_MAP[$algorithm] ?? null;
if (!$className) {
throw new RuntimeException("Not supported algorithm {$algorithm}.");
throw new RuntimeException("Not supported algorithm $algorithm.");
}
if ($className === Rsa::class) {
@@ -67,7 +67,7 @@ class KeysProvider
try {
$list[] = $this->factory->create($raw);
}
catch (UnsupportedKey $e) {
catch (UnsupportedKey) {
$this->log->debug("OIDC: Unsupported key " . print_r($raw, true));
}
}
@@ -127,7 +127,7 @@ class KeysProvider
}
if ($error) {
throw new RuntimeException("OIDC: JWKS request error. Status: {$status}.");
throw new RuntimeException("OIDC: JWKS request error. Status: $status.");
}
$parsedResponse = null;
@@ -282,10 +282,10 @@ class Login implements LoginInterface
private static function composeLogMessage(string $text, ?int $status = null, ?string $response = null): string
{
if ($status === null) {
return "OIDC: {$text}";
return "OIDC: $text";
}
return "OIDC: {$text}; Status: {$status}; Response: {$response}";
return "OIDC: $text; Status: $status; Response: $response";
}
/**
@@ -34,6 +34,9 @@ use Espo\Core\Authentication\Logout as LogoutInterface;
use Espo\Core\Authentication\Logout\Params;
use Espo\Core\Authentication\Logout\Result;
/**
* @noinspection PhpUnused
*/
class Logout implements LogoutInterface
{
public function __construct(
@@ -53,7 +53,7 @@ class TokenValidator
$allowedAlgorithmList = $this->configDataProvider->getJwtSignatureAlgorithmList();
if (!in_array($algorithm, $allowedAlgorithmList)) {
throw new Invalid("JWT signing algorithm `{$algorithm}` not allowed.");
throw new Invalid("JWT signing algorithm `$algorithm` not allowed.");
}
$verifier = $this->signatureVerifierFactory->create($algorithm);
@@ -71,7 +71,7 @@ class DefaultUserProvider implements UserProvider
$username = $payload->get($usernameClaim);
if (!$username) {
throw new RuntimeException("No username claim `{$usernameClaim}` in token.");
throw new RuntimeException("No username claim `$usernameClaim` in token.");
}
$username = $this->sync->normalizeUsername($username);
@@ -91,13 +91,13 @@ class DefaultUserProvider implements UserProvider
$isPortal = $this->applicationState->isPortal();
if (!$isPortal && !$user->isRegular() && !$user->isAdmin()) {
$this->log->info("Oidc: User {$userId} found but it's neither regular user not admin.");
$this->log->info("Oidc: User $userId found but it's neither regular user not admin.");
return null;
}
if ($isPortal && !$user->isPortal()) {
$this->log->info("Oidc: User {$userId} found but it's not portal user.");
$this->log->info("Oidc: User $userId found but it's not portal user.");
return null;
}
@@ -106,20 +106,20 @@ class DefaultUserProvider implements UserProvider
$portalId = $this->applicationState->getPortalId();
if (!$user->getPortals()->hasId($portalId)) {
$this->log->info("Oidc: User {$userId} found but it's not related to current portal.");
$this->log->info("Oidc: User $userId found but it's not related to current portal.");
return null;
}
}
if ($user->isSuperAdmin()) {
$this->log->info("Oidc: User {$userId} found but it's super-admin, not allowed.");
$this->log->info("Oidc: User $userId found but it's super-admin, not allowed.");
return null;
}
if ($user->isAdmin() && !$this->configDataProvider->allowAdminUser()) {
$this->log->info("Oidc: User {$userId} found but it's admin, not allowed.");
$this->log->info("Oidc: User $userId found but it's admin, not allowed.");
return null;
}
@@ -238,7 +238,7 @@ class Sync
$username = strtolower($username);
/** @var string $result */
$result = preg_replace("/{$regExp}/", '_', $username);
$result = preg_replace("/$regExp/", '_', $username);
/** @var string */
return str_replace(' ', '_', $result);
@@ -46,7 +46,7 @@ class UsernameValidator
->getLength();
if ($maxLength && $maxLength < strlen($username)) {
throw new RuntimeException("Value in username claim exceeds max length of `{$maxLength}`. " .
throw new RuntimeException("Value in username claim exceeds max length of `$maxLength`. " .
"Increase maxLength parameter for User.userName field (up to 255).");
}
}
@@ -45,13 +45,13 @@ class Result
public const STATUS_SECOND_STEP_REQUIRED = 'secondStepRequired';
public const STATUS_FAIL = 'fail';
private ?User $user = null;
private ?User $user;
private string $status;
private ?string $message = null;
private ?string $token = null;
private ?string $view = null;
private ?string $failReason = null;
private ?Data $data = null;
private ?Data $data;
private function __construct(string $status, ?User $user = null, ?Data $data = null)
{
@@ -173,7 +173,7 @@ class Result
*/
public function getData(): ?stdClass
{
return $this->data ? $this->data->getData() : null;
return $this->data?->getData();
}
/**
@@ -36,24 +36,16 @@ use stdClass;
*/
class Data
{
private ?string $message = null;
private ?string $token = null;
private ?string $view = null;
private ?string $failReason = null;
/** @var array<string, mixed> */
private array $data = [];
/** @noinspection PhpSameParameterValueInspection */
private function __construct(
?string $message = null,
?string $failReason = null,
?string $token = null,
?string $view = null
) {
$this->message = $message;
$this->failReason = $failReason;
$this->token = $token;
$this->view = $view;
}
private ?string $message = null,
private ?string $failReason = null,
private ?string $token = null,
private ?string $view = null
) {}
public static function create(): self
{
@@ -103,6 +95,7 @@ class Data
return $obj;
}
/** @noinspection PhpUnused */
public function withFailReason(?string $failReason): self
{
$obj = clone $this;
@@ -111,6 +104,7 @@ class Data
return $obj;
}
/** @noinspection PhpUnused */
public function withToken(?string $token): self
{
$obj = clone $this;
@@ -119,6 +113,7 @@ class Data
return $obj;
}
/** @noinspection PhpUnused */
public function withView(?string $view): self
{
$obj = clone $this;
@@ -40,7 +40,6 @@ use stdClass;
*/
class EmailUserSetup implements UserSetup
{
public function __construct(private Util $util)
{}
@@ -36,14 +36,8 @@ use LogicException;
class LoginFactory
{
private InjectableFactory $injectableFactory;
private Metadata $metadata;
public function __construct(InjectableFactory $injectableFactory, Metadata $metadata)
{
$this->injectableFactory = $injectableFactory;
$this->metadata = $metadata;
}
public function __construct(private InjectableFactory $injectableFactory, private Metadata $metadata)
{}
public function create(string $method): Login
{
@@ -51,7 +45,7 @@ class LoginFactory
$className = $this->metadata->get(['app', 'authentication2FAMethods', $method, 'loginClassName']);
if (!$className) {
throw new LogicException("No login-class class for '{$method}'.");
throw new LogicException("No login-class class for '$method'.");
}
return $this->injectableFactory->create($className);
@@ -36,7 +36,6 @@ use RuntimeException;
class UserSetupFactory
{
public function __construct(
private InjectableFactory $injectableFactory,
private Metadata $metadata
+1
View File
@@ -104,6 +104,7 @@ class Binder
* @template T of object
* @param class-string<T>|NamedClassKey<T> $key An interface or interface with a parameter name.
* @param T $instance An instance.
* @noinspection PhpDocSignatureInspection
*/
public function bindInstance(string|NamedClassKey $key, object $instance): self
{
@@ -89,11 +89,11 @@ class BindingContainer
public function getByInterface(string $interfaceName): Binding
{
if (!$this->hasByInterface($interfaceName)) {
throw new LogicException("Binding for interface `{$interfaceName}` does not exist.");
throw new LogicException("Binding for interface `$interfaceName` does not exist.");
}
if (!interface_exists($interfaceName) && !class_exists($interfaceName)) {
throw new LogicException("Interface `{$interfaceName}` does not exist.");
throw new LogicException("Interface `$interfaceName` does not exist.");
}
return $this->data->getGlobal($interfaceName);
@@ -62,6 +62,7 @@ class BindingContainerBuilder
*
* @param class-string<object>|NamedClassKey<object> $key An interface or interface with a parameter name.
* @param string $serviceName A service name.
* @noinspection PhpUnused
*/
public function bindService(string|NamedClassKey $key, string $serviceName): self
{
@@ -77,6 +78,7 @@ class BindingContainerBuilder
* @param class-string<T>|NamedClassKey<T> $key An interface or interface with a parameter name.
* @param Closure $callback A callback that will resolve a dependency.
* @todo Change to Closure(...): T Once https://github.com/phpstan/phpstan/issues/8214 is implemented.
* @noinspection PhpUnused
*/
public function bindCallback(string|NamedClassKey $key, Closure $callback): self
{
@@ -91,6 +93,7 @@ class BindingContainerBuilder
* @template T of object
* @param class-string<T>|NamedClassKey<T> $key An interface or interface with a parameter name.
* @param T $instance An instance.
* @noinspection PhpDocSignatureInspection
*/
public function bindInstance(string|NamedClassKey $key, object $instance): self
{
@@ -105,6 +108,7 @@ class BindingContainerBuilder
* @template T of object
* @param class-string<T>|NamedClassKey<T> $key An interface or interface with a parameter name.
* @param class-string<Factory<T>> $factoryClassName A factory class name.
* @noinspection PhpUnused
*/
public function bindFactory(string|NamedClassKey $key, string $factoryClassName): self
{
@@ -117,6 +117,7 @@ class ContextualBinder
* @template T of object
* @param class-string<T>|NamedClassKey<T> $key An interface or interface with a parameter name.
* @param T $instance An instance.
* @noinspection PhpDocSignatureInspection
*/
public function bindInstance(string|NamedClassKey $key, object $instance): self
{
@@ -36,6 +36,7 @@ interface Factory
{
/**
* @return T
* @noinspection PhpDocSignatureInspection
*/
public function create(): object;
}
@@ -41,17 +41,11 @@ use Espo\Core\Console\Command\Params;
*/
class CommandManager
{
private InjectableFactory $injectableFactory;
private Metadata $metadata;
private const DEFAULT_COMMAND = 'Help';
private const DEFAULT_COMMAND_FLAG = 'help';
public function __construct(InjectableFactory $injectableFactory, Metadata $metadata)
{
$this->injectableFactory = $injectableFactory;
$this->metadata = $metadata;
}
public function __construct(private InjectableFactory $injectableFactory, private Metadata $metadata)
{}
/**
* @param array<int, string> $argv
@@ -40,6 +40,8 @@ use Espo\Core\Portal\Application as PortalApplication;
/**
* Checks access for websocket topic subscription. Prints `true` if access allowed.
*
* @noinspection PhpUnused
*/
class AclCheck implements Command
{
@@ -62,6 +64,7 @@ class AclCheck implements Command
$container = $this->container;
$entityManager = $this->container->getByClass(EntityManager::class);
/** @var ?User $user */
$user = $entityManager->getEntityById(User::ENTITY_TYPE, $userId);
if (!$user) {
@@ -69,7 +72,6 @@ class AclCheck implements Command
}
if ($user->isPortal()) {
/** @var string[] $portalIdList */
$portalIdList = $user->getLinkMultipleIdList('portals');
foreach ($portalIdList as $portalId) {
@@ -86,7 +88,7 @@ class AclCheck implements Command
$result = $this->check($user, $scope, $id, $action, $containerPortal);
if ($result) {
$io->write('true');;
$io->write('true');
return;
}
@@ -36,6 +36,9 @@ use Espo\Core\InjectableFactory;
use Espo\Core\Utils\File\Manager as FileManager;
use Espo\Core\Utils\Util;
/**
* @noinspection PhpUnused
*/
class AppInfo implements Command
{
public function __construct(private InjectableFactory $injectableFactory, private FileManager $fileManager)
@@ -36,16 +36,13 @@ use Espo\Core\Console\Command\Params;
use Espo\Core\Console\IO;
use Espo\Core\ORM\EntityManager;
/**
* @noinspection PhpUnused
*/
class AuthTokenCheck implements Command
{
private EntityManager $entityManager;
private AuthTokenManager $authTokenManager;
public function __construct(EntityManager $entityManager, AuthTokenManager $authTokenManager)
{
$this->entityManager = $entityManager;
$this->authTokenManager = $authTokenManager;
}
public function __construct(private EntityManager $entityManager, private AuthTokenManager $authTokenManager)
{}
public function run(Params $params, IO $io): void
{
@@ -35,6 +35,9 @@ use Espo\Core\Console\IO;
use Espo\Core\DataManager;
use Espo\Core\Exceptions\Error;
/**
* @noinspection PhpUnused
*/
class ClearCache implements Command
{
public function __construct(private DataManager $dataManager)
@@ -33,6 +33,4 @@ namespace Espo\Core\Console\Commands;
* @deprecated
*/
interface Command
{
}
{}
@@ -36,14 +36,13 @@ use Espo\Core\Console\IO;
use Espo\Tools\EntityManager\Rename\Renamer;
use Espo\Tools\EntityManager\Rename\FailReason as RenameFailReason;
/**
* @noinspection PhpUnused
*/
class EntityUtil implements Command
{
private Renamer $renamer;
public function __construct(Renamer $renamer)
{
$this->renamer = $renamer;
}
public function __construct(private Renamer $renamer)
{}
public function run(Params $params, IO $io): void
{
@@ -57,8 +56,6 @@ class EntityUtil implements Command
if ($subCommand === 'rename') {
$this->runRename($params, $io);
return;
}
}
@@ -125,13 +122,13 @@ class EntityUtil implements Command
}
if ($failReason === RenameFailReason::DOES_NOT_EXIST) {
$io->writeLine("Entity type `{$entityType}` does not exist.");
$io->writeLine("Entity type `$entityType` does not exist.");
return;
}
if ($failReason === RenameFailReason::NOT_CUSTOM) {
$io->writeLine("Entity type `{$entityType}` is not custom, hence can't be renamed.");
$io->writeLine("Entity type `$entityType` is not custom, hence can't be renamed.");
return;
}
@@ -150,8 +147,6 @@ class EntityUtil implements Command
if ($failReason === RenameFailReason::ERROR) {
$io->writeLine("Error occurred.");
return;
}
}
}
@@ -32,29 +32,29 @@ namespace Espo\Core\Console\Commands;
use Espo\Core\Console\Command;
use Espo\Core\Console\Command\Params;
use Espo\Core\Console\IO;
use Espo\Core\Exceptions\Error;
use Espo\Entities\Extension as ExtensionEntity;
use Espo\ORM\EntityManager;
use Espo\Core\Upgrades\ExtensionManager;
use Espo\Core\Container;
use Espo\Core\Utils\File\Manager as FileManager;
use Throwable;
/**
* @noinspection PhpUnused
*/
class Extension implements Command
{
private Container $container;
private EntityManager $entityManager;
private FileManager $fileManager;
public function __construct(Container $container, EntityManager $entityManager, FileManager $fileManager)
{
$this->container = $container;
$this->entityManager = $entityManager;
$this->fileManager = $fileManager;
}
public function __construct(
private Container $container,
private EntityManager $entityManager,
private FileManager $fileManager
) {}
/**
* @throws Error
*/
public function run(Params $params, IO $io): void
{
if ($params->hasFlag('l') || $params->hasFlag('list')) {
@@ -104,6 +104,9 @@ class Extension implements Command
$this->runInstall($file, $io);
}
/**
* @throws Error
*/
private function runInstall(string $file, IO $io): void
{
$manager = $this->createExtensionManager();
@@ -155,7 +158,7 @@ class Extension implements Command
}
$io->writeLine("");
$io->writeLine("Extension '{$name}' v{$version} is installed.\nExtension ID: '{$id}'.");
$io->writeLine("Extension '$name' v$version is installed.\nExtension ID: '$id'.");
}
protected function runUninstall(Params $params, IO $io): void
@@ -174,7 +177,7 @@ class Extension implements Command
->findOne();
if (!$record) {
$io->writeLine("Extension with ID '{$id}' is not installed.");
$io->writeLine("Extension with ID '$id' is not installed.");
$io->setExitStatus(1);
return;
@@ -199,7 +202,7 @@ class Extension implements Command
->findOne();
if (!$record) {
$io->writeLine("Extension '{$name}' is not installed.");
$io->writeLine("Extension '$name' is not installed.");
$io->setExitStatus(1);
return;
@@ -226,7 +229,7 @@ class Extension implements Command
$io->writeLine("");
if ($toKeep) {
$io->writeLine("Extension '{$name}' is uninstalled.");
$io->writeLine("Extension '$name' is uninstalled.");
$io->setExitStatus(1);
return;
@@ -237,14 +240,12 @@ class Extension implements Command
}
catch (Throwable $e) {
$io->writeLine($e->getMessage());
$io->writeLine("Extension '{$name}' is uninstalled but could not be deleted.");
$io->writeLine("Extension '$name' is uninstalled but could not be deleted.");
return;
}
$io->writeLine("Extension '{$name}' is uninstalled and deleted.");
return;
$io->writeLine("Extension '$name' is uninstalled and deleted.");
}
private function printList(IO $io): void
@@ -253,8 +254,10 @@ class Extension implements Command
->getRDBRepository(ExtensionEntity::ENTITY_TYPE)
->find();
/** @noinspection PhpParamsInspection */
$count = is_countable($collection) ? count($collection) : iterator_count($collection);
/** @noinspection PhpIfWithCommonPartsInspection */
if ($count === 0) {
$io->writeLine("");
$io->writeLine("No extensions.");
@@ -36,14 +36,13 @@ use Espo\Core\Console\IO;
use Espo\Core\Utils\Metadata;
use Espo\Core\Utils\Util;
/**
* @noinspection PhpUnused
*/
class Help implements Command
{
private $metadata;
public function __construct(Metadata $metadata)
{
$this->metadata = $metadata;
}
public function __construct(private Metadata $metadata)
{}
public function run(Params $params, IO $io): void
{
@@ -36,6 +36,9 @@ use Espo\Core\DataManager;
use Espo\Core\Exceptions\Error;
use Espo\Core\Utils\Database\Schema\RebuildMode;
/**
* @noinspection PhpUnused
*/
class Rebuild implements Command
{
public function __construct(private DataManager $dataManager)
@@ -40,9 +40,11 @@ use Espo\Entities\Job;
use Throwable;
/**
* @noinspection PhpUnused
*/
class RunJob implements Command
{
public function __construct(private JobManager $jobManager, private EntityManager $entityManager)
{}
@@ -79,6 +81,7 @@ class RunJob implements Command
$entityManager = $this->entityManager;
/** @var Job $job */
$job = $entityManager->createEntity(Job::ENTITY_TYPE, [
'name' => $jobName,
'job' => $jobName,
@@ -92,7 +95,7 @@ class RunJob implements Command
$this->jobManager->runJob($job);
}
catch (Throwable $e) {
$message = "Error: Job '{$jobName}' failed to execute.";
$message = "Error: Job '$jobName' failed to execute.";
if ($e->getMessage()) {
$message .= ' ' . $e->getMessage();
@@ -104,6 +107,6 @@ class RunJob implements Command
return;
}
$io->writeLine("Job '{$jobName}' has been executed.");
$io->writeLine("Job '$jobName' has been executed.");
}
}
@@ -36,6 +36,9 @@ use Espo\Core\Console\IO;
use Espo\Core\ORM\EntityManager;
use Espo\Core\Utils\PasswordHash;
/**
* @noinspection PhpUnused
*/
class SetPassword implements Command
{
public function __construct(private EntityManager $entityManager, private PasswordHash $passwordHash)
@@ -59,7 +62,7 @@ class SetPassword implements Command
->findOne();
if (!$user) {
$io->writeLine("User '{$userName}' not found.");
$io->writeLine("User '$userName' not found.");
$io->setExitStatus(1);
return;
@@ -75,7 +78,7 @@ class SetPassword implements Command
];
if (!in_array($userType, $allowedTypes)) {
$io->writeLine("Can't set password for a user of the type '{$userType}'.");
$io->writeLine("Can't set password for a user of the type '$userType'.");
$io->setExitStatus(1);
return;
@@ -96,6 +99,6 @@ class SetPassword implements Command
$em->saveEntity($user);
$io->writeLine("Password for user '{$userName}' has been changed.");
$io->writeLine("Password for user '$userName' has been changed.");
}
}
@@ -46,13 +46,22 @@ use Exception;
use Throwable;
use stdClass;
use const CURLOPT_FILE;
use const CURLOPT_RETURNTRANSFER;
use const CURLOPT_SSL_VERIFYPEER;
use const CURLOPT_TIMEOUT;
use const CURLOPT_URL;
use const FILTER_VALIDATE_URL;
use const STDOUT;
/**
* @noinspection PhpUnused
*/
class Upgrade implements Command
{
private ?UpgradeManager $upgradeManager = null;
/**
* @var string[]
*/
/** @var string[] */
private $upgradeStepList = [
'copyBefore',
'rebuild',
@@ -66,9 +75,7 @@ class Upgrade implements Command
'rebuild',
];
/**
* @var array<string, string>
*/
/** @var array<string, string> */
private $upgradeStepLabels = [
'init' => 'Initialization',
'copyBefore' => 'Copying before upgrade files',
@@ -81,17 +88,15 @@ class Upgrade implements Command
'revert' => 'Reverting',
];
private FileManager $fileManager;
private Config $config;
private Log $log;
public function __construct(FileManager $fileManager, Config $config, Log $log)
{
$this->fileManager = $fileManager;
$this->config = $config;
$this->log = $log;
}
public function __construct(
private FileManager $fileManager,
private Config $config,
private Log $log
) {}
/**
* @throws Error
*/
public function run(Params $params, IO $io): void
{
$options = $params->getOptions();
@@ -122,10 +127,10 @@ class Upgrade implements Command
$nextVersion = $manifest['version'];
}
fwrite(\STDOUT, "Current version is {$fromVersion}.\n");
fwrite(STDOUT, "Current version is $fromVersion.\n");
if (!$upgradeParams->skipConfirmation) {
fwrite(\STDOUT, "EspoCRM will be upgraded to version {$nextVersion} now. Enter [Y] to continue.\n");
fwrite(STDOUT, "EspoCRM will be upgraded to version $nextVersion now. Enter [Y] to continue.\n");
if (!$this->confirm()) {
echo "Upgrade canceled.\n";
@@ -134,17 +139,17 @@ class Upgrade implements Command
}
}
fwrite(\STDOUT, "This may take a while. Do not close the terminal.\n");
fwrite(STDOUT, "This may take a while. Do not close the terminal.\n");
if (filter_var($packageFile, \FILTER_VALIDATE_URL)) {
fwrite(\STDOUT, "Downloading...");
if (filter_var($packageFile, FILTER_VALIDATE_URL)) {
fwrite(STDOUT, "Downloading...");
$packageFile = $this->downloadFile($packageFile);
fwrite(\STDOUT, "\n");
fwrite(STDOUT, "\n");
if (!$packageFile) {
fwrite(\STDOUT, "Error: Unable to download upgrade package.\n");
fwrite(STDOUT, "Error: Unable to download upgrade package.\n");
return;
}
@@ -152,7 +157,7 @@ class Upgrade implements Command
$upgradeId = $upgradeId ?? $this->upload($packageFile);
fwrite(\STDOUT, "Upgrading...");
fwrite(STDOUT, "Upgrading...");
try {
$this->runUpgradeProcess($upgradeId, $upgradeParams);
@@ -162,7 +167,7 @@ class Upgrade implements Command
$errorMessage = $e->getMessage();
}
fwrite(\STDOUT, "\n");
fwrite(STDOUT, "\n");
if (!$upgradeParams->keepPackageFile) {
$this->fileManager->unlink($packageFile);
@@ -171,25 +176,23 @@ class Upgrade implements Command
if (isset($errorMessage)) {
$errorMessage = !empty($errorMessage) ? $errorMessage : "Error: An unexpected error occurred.";
fwrite(\STDOUT, $errorMessage . "\n");
fwrite(STDOUT, $errorMessage . "\n");
return;
}
$currentVersion = $this->getCurrentVersion();
fwrite(\STDOUT, "Upgrade is complete. Current version is {$currentVersion}.\n");
fwrite(STDOUT, "Upgrade is complete. Current version is $currentVersion.\n");
if ($lastVersion && $lastVersion !== $currentVersion && $fromVersion !== $currentVersion) {
fwrite(\STDOUT, "Newer version is available. Run command again to upgrade.\n");
fwrite(STDOUT, "Newer version is available. Run command again to upgrade.\n");
return;
}
if ($lastVersion && $lastVersion === $currentVersion) {
fwrite(\STDOUT, "You have the latest version.\n");
return;
fwrite(STDOUT, "You have the latest version.\n");
}
}
@@ -203,7 +206,8 @@ class Upgrade implements Command
* @param array<string, string> $options
* @param string[] $flagList
* @param string[] $argumentList
* @return \stdClass
* @return stdClass
* @noinspection PhpUnusedParameterInspection
*/
private function normalizeParams(array $options, array $flagList, array $argumentList): object
{
@@ -253,19 +257,19 @@ class Upgrade implements Command
if (!$params->localMode) {
if (empty($versionInfo)) {
fwrite(\STDOUT, "Error: Upgrade server is currently unavailable. Please try again later.\n");
fwrite(STDOUT, "Error: Upgrade server is currently unavailable. Please try again later.\n");
return null;
}
if (!isset($versionInfo->nextVersion)) {
fwrite(\STDOUT, "There are no available upgrades.\n");
fwrite(STDOUT, "There are no available upgrades.\n");
return null;
}
if (!isset($versionInfo->nextPackage)) {
fwrite(\STDOUT, "Error: Upgrade package is not found.\n");
fwrite(STDOUT, "Error: Upgrade package is not found.\n");
return null;
}
@@ -274,7 +278,7 @@ class Upgrade implements Command
}
if (!$packageFile || !file_exists($packageFile)) {
fwrite(\STDOUT, "Error: Upgrade package is not found.\n");
fwrite(STDOUT, "Error: Upgrade package is not found.\n");
return null;
}
@@ -310,7 +314,7 @@ class Upgrade implements Command
$stepList = !empty($params->step) ? [$params->step] : $this->upgradeStepList;
array_unshift($stepList, 'init');
array_push($stepList, 'finalize');
$stepList[] = 'finalize';
if (!$useSingleProcess && $this->isShellEnabled()) {
$this->runSteps($upgradeId, $stepList);
@@ -342,7 +346,7 @@ class Upgrade implements Command
try {
$this->log->error('Upgrade Error: ' . $e->getMessage());
}
catch (Throwable $t) {}
catch (Throwable) {}
throw new Error($e->getMessage());
}
@@ -380,7 +384,7 @@ class Upgrade implements Command
{
$stepLabel = $this->upgradeStepLabels[$stepName] ?? "";
fwrite(\STDOUT, "\n {$stepLabel}...");
fwrite(STDOUT, "\n $stepLabel...");
}
private function confirm(): bool
@@ -435,9 +439,9 @@ class Upgrade implements Command
$ch = curl_init();
curl_setopt($ch, \CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, \CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, \CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_URL, $url);
$result = curl_exec($ch);
@@ -446,7 +450,7 @@ class Upgrade implements Command
try {
$data = json_decode($result); /** @phpstan-ignore-line */
}
catch (Exception $e) { /** @phpstan-ignore-line */
catch (Exception) { /** @phpstan-ignore-line */
echo "Could not parse info about next version.\n";
return null;
@@ -472,9 +476,9 @@ class Upgrade implements Command
}
else {
$options = [
\CURLOPT_FILE => fopen($localFilePath, 'w'),
\CURLOPT_TIMEOUT => 3600,
\CURLOPT_URL => $url,
CURLOPT_FILE => fopen($localFilePath, 'w'),
CURLOPT_TIMEOUT => 3600,
CURLOPT_URL => $url,
];
$ch = curl_init();
@@ -37,6 +37,9 @@ use Espo\Core\Upgrades\UpgradeManager;
use Exception;
/**
* @noinspection PhpUnused
*/
class UpgradeStep implements Command
{
public function __construct() {}
@@ -33,5 +33,4 @@ use RuntimeException;
class CommandNotSpecified extends RuntimeException
{
}
@@ -94,6 +94,7 @@ class ContainerBuilder
/**
* @param array<string, class-string<Loader>> $classNames
* @noinspection PhpUnused
*/
public function withLoaderClassNames(array $classNames): self
{
@@ -136,6 +137,7 @@ class ContainerBuilder
/**
* @param class-string $fileManagerClassName
* @noinspection PhpUnused
*/
public function withFileManagerClassName(string $fileManagerClassName): self
{
@@ -146,6 +148,7 @@ class ContainerBuilder
/**
* @param class-string $dataCacheClassName
* @noinspection PhpUnused
*/
public function withDataCacheClassName(string $dataCacheClassName): self
{
@@ -40,6 +40,7 @@ class ContainerConfiguration implements Configuration
/**
* Log must be loaded before anything.
* @phpstan-ignore-next-line
* @noinspection PhpPropertyOnlyWrittenInspection
*/
private Log $log;
@@ -141,6 +141,8 @@ class RecordBase extends Base implements
* @throws NotFoundSilent
* @throws ForbiddenSilent
* @throws BadRequest
* @throws Forbidden
* @noinspection PhpUnusedParameterInspection
*/
public function getActionRead(Request $request, Response $response): stdClass
{
@@ -188,6 +190,7 @@ class RecordBase extends Base implements
* @throws NotFound
* @throws Forbidden
* @throws Conflict
* @noinspection PhpUnused
*/
public function patchActionUpdate(Request $request, Response $response): stdClass
{
@@ -201,6 +204,7 @@ class RecordBase extends Base implements
* @throws NotFound
* @throws Forbidden
* @throws Conflict
* @noinspection PhpUnusedParameterInspection
*/
public function putActionUpdate(Request $request, Response $response): stdClass
{
@@ -254,6 +258,7 @@ class RecordBase extends Base implements
* @throws Forbidden
* @throws BadRequest
* @throws NotFound
* @noinspection PhpUnusedParameterInspection
*/
public function deleteActionDelete(Request $request, Response $response): bool
{
@@ -288,6 +293,7 @@ class RecordBase extends Base implements
* @throws Forbidden
* @throws NotFound
* @throws ForbiddenSilent
* @noinspection PhpUnused
*/
public function postActionGetDuplicateAttributes(Request $request): stdClass
{
@@ -304,6 +310,7 @@ class RecordBase extends Base implements
* @throws BadRequest
* @throws Forbidden
* @throws NotFound
* @noinspection PhpUnused
*/
public function postActionRestoreDeleted(Request $request): bool
{
@@ -33,6 +33,8 @@ use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\Error;
use Espo\Core\Exceptions\NotFound;
use Espo\Core\ORM\Entity;
use Espo\Services\RecordTree as Service;
use Espo\Core\Api\Request;
@@ -51,7 +53,8 @@ class RecordTree extends Record
* @throws BadRequest
* @throws Error
* @throws Forbidden
* @throws \Espo\Core\Exceptions\NotFound
* @throws NotFound
* @noinspection PhpUnused
*/
public function getActionListTree(Request $request): stdClass
{
@@ -96,6 +99,8 @@ class RecordTree extends Record
/**
* @return string[]
* @throws Forbidden
* @throws BadRequest
* @noinspection PhpUnused
*/
public function getActionLastChildrenIdList(Request $request): array
{
@@ -109,7 +114,7 @@ class RecordTree extends Record
}
/**
* @return Service<\Espo\Core\ORM\Entity>
* @return Service<Entity>
*/
protected function getRecordTreeService(): Service
{
@@ -47,7 +47,7 @@ class Factory
$className = $this->metadata->get(['app', 'fileStorage', 'implementationClassNameMap', $name]);
if (!$className) {
throw new RuntimeException("Unknown file storage '{$name}'.");
throw new RuntimeException("Unknown file storage '$name'.");
}
return $this->injectableFactory->create($className);
@@ -42,23 +42,20 @@ use RuntimeException;
*/
class Manager
{
/** @var array<string, Storage> */
private array $implHash = [];
private const DEFAULT_STORAGE = 'EspoUploadDir';
private Factory $factory;
/** @var array<string, Storage> */
private array $implHash = [];
/**
* @var array<string, resource>
* @phpstan-ignore-next-line Used to prevent deleting from memory.
* @noinspection PhpPropertyOnlyWrittenInspection
*/
private $resourceMap = [];
public function __construct(Factory $factory)
{
$this->factory = $factory;
}
public function __construct(private Factory $factory)
{}
/**
* Whether a file exists in a storage.
@@ -36,19 +36,15 @@ use Espo\Core\Utils\File\Manager as FileManager;
use Espo\Core\Utils\File\Exceptions\FileError;
use Psr\Http\Message\StreamInterface;
use GuzzleHttp\Psr7\Stream;
class EspoUploadDir implements Storage, Local
{
protected FileManager $fileManager;
public const NAME = 'EspoUploadDir';
public function __construct(FileManager $fileManager)
{
$this->fileManager = $fileManager;
}
public function __construct(protected FileManager $fileManager)
{}
public function unlink(Attachment $attachment): void
{
@@ -69,7 +65,7 @@ class EspoUploadDir implements Storage, Local
$filePath = $this->getFilePath($attachment);
if (!$this->exists($attachment)) {
throw new FileError("Could not get size for non-existing file '{$filePath}'.");
throw new FileError("Could not get size for non-existing file '$filePath'.");
}
return $this->fileManager->getSize($filePath);
@@ -80,13 +76,13 @@ class EspoUploadDir implements Storage, Local
$filePath = $this->getFilePath($attachment);
if (!$this->exists($attachment)) {
throw new FileError("Could not get stream for non-existing '{$filePath}'.");
throw new FileError("Could not get stream for non-existing '$filePath'.");
}
$resource = fopen($filePath, 'r');
if ($resource === false) {
throw new FileError("Could not open '{$filePath}'.");
throw new FileError("Could not open '$filePath'.");
}
return new Stream($resource);
@@ -104,7 +100,7 @@ class EspoUploadDir implements Storage, Local
$result = $this->fileManager->putContents($filePath, $contents);
if (!$result) {
throw new FileError("Could not store a file '{$filePath}'.");
throw new FileError("Could not store a file '$filePath'.");
}
}