PHP code refresh #8
|
@ -30,7 +30,7 @@ doctrine:
|
||||||
mappings:
|
mappings:
|
||||||
App:
|
App:
|
||||||
is_bundle: false
|
is_bundle: false
|
||||||
type: annotation
|
type: attribute
|
||||||
dir: '%kernel.project_dir%/src/Entity'
|
dir: '%kernel.project_dir%/src/Entity'
|
||||||
prefix: 'App\Entity'
|
prefix: 'App\Entity'
|
||||||
alias: App
|
alias: App
|
||||||
|
@ -39,7 +39,7 @@ doctrine:
|
||||||
mappings:
|
mappings:
|
||||||
Magnetico:
|
Magnetico:
|
||||||
is_bundle: false
|
is_bundle: false
|
||||||
type: annotation
|
type: attribute
|
||||||
dir: '%kernel.project_dir%/src/Magnetico/Entity'
|
dir: '%kernel.project_dir%/src/Magnetico/Entity'
|
||||||
prefix: 'App\Magnetico'
|
prefix: 'App\Magnetico'
|
||||||
alias: Magnetico
|
alias: Magnetico
|
||||||
|
|
|
@ -6,10 +6,9 @@ security:
|
||||||
class: App\Entity\User
|
class: App\Entity\User
|
||||||
property: username
|
property: username
|
||||||
manager_name: default
|
manager_name: default
|
||||||
encoders:
|
password_hashers:
|
||||||
App\Entity\User:
|
App\Entity\User:
|
||||||
# https://symfony.com/blog/new-in-symfony-4-3-native-password-encoder
|
algorithm: sodium
|
||||||
algorithm: 'auto'
|
|
||||||
firewalls:
|
firewalls:
|
||||||
dev:
|
dev:
|
||||||
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Api\V1\Controller;
|
namespace App\Api\V1\Controller;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Api\V1\Controller;
|
namespace App\Api\V1\Controller;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Api\V1\Controller;
|
namespace App\Api\V1\Controller;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Api\V1\Controller;
|
namespace App\Api\V1\Controller;
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Api\V1\DTO;
|
namespace App\Api\V1\DTO;
|
||||||
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Serializer\Annotation\{Groups, MaxDepth};
|
use Symfony\Component\Serializer\Annotation\Groups;
|
||||||
|
|
||||||
class ApiResponse
|
class ApiResponse
|
||||||
{
|
{
|
||||||
|
@ -12,33 +13,20 @@ class ApiResponse
|
||||||
public const STATUS_FAIL = 'fail';
|
public const STATUS_FAIL = 'fail';
|
||||||
public const STATUS_UNKNOWN = 'unknown';
|
public const STATUS_UNKNOWN = 'unknown';
|
||||||
|
|
||||||
/**
|
#[Groups(['api'])]
|
||||||
* @var int HTTP response status code
|
private int $code;
|
||||||
*
|
|
||||||
* @Groups({"api"})
|
|
||||||
*/
|
|
||||||
private $code;
|
|
||||||
|
|
||||||
/**
|
/** Status text: 'success' (1xx-3xx), 'error' (4xx), 'fail' (5xx) or 'unknown' */
|
||||||
* @var string Status text: 'success' (1xx-3xx), 'error' (4xx), 'fail' (5xx) or 'unknown'
|
#[Groups(['api'])]
|
||||||
*
|
private string $status;
|
||||||
* @Groups({"api"})
|
|
||||||
*/
|
|
||||||
private $status;
|
|
||||||
|
|
||||||
/**
|
/** Used for 'fail' and 'error') */
|
||||||
* @var string|null Used for 'fail' and 'error'
|
#[Groups(['api'])]
|
||||||
*
|
private ?string $message;
|
||||||
* @Groups({"api"})
|
|
||||||
*/
|
|
||||||
private $message;
|
|
||||||
|
|
||||||
/**
|
/** @Response body. In case of 'error' or 'fail' contains cause or exception name. */
|
||||||
* @var string|\object|array|null Response body. In case of 'error' or 'fail' contains cause or exception name.
|
#[Groups(['api'])]
|
||||||
*
|
private string|object|array|null $data;
|
||||||
* @Groups({"api"})
|
|
||||||
*/
|
|
||||||
private $data;
|
|
||||||
|
|
||||||
public function __construct($data = null, int $code = Response::HTTP_OK, string $message = null, string $status = '')
|
public function __construct($data = null, int $code = Response::HTTP_OK, string $message = null, string $status = '')
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,46 +1,27 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Api\V1\DTO;
|
namespace App\Api\V1\DTO;
|
||||||
|
|
||||||
use Pagerfanta\Pagerfanta;
|
use Pagerfanta\Pagerfanta;
|
||||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
use Symfony\Component\Serializer\Annotation\Groups;
|
||||||
|
|
||||||
class ListPage
|
class ListPage
|
||||||
{
|
{
|
||||||
/**
|
#[Groups(['api'])]
|
||||||
* @var int
|
private int $numberOfPages;
|
||||||
*
|
|
||||||
* @Serializer\Groups({"api"})
|
|
||||||
*/
|
|
||||||
private $numberOfPages;
|
|
||||||
|
|
||||||
/**
|
#[Groups(['api'])]
|
||||||
* @var int
|
private int $currentPage;
|
||||||
*
|
|
||||||
* @Serializer\Groups({"api"})
|
|
||||||
*/
|
|
||||||
private $currentPage;
|
|
||||||
|
|
||||||
/**
|
#[Groups(['api'])]
|
||||||
* @var int
|
private int $numberOfResults;
|
||||||
*
|
|
||||||
* @Serializer\Groups({"api"})
|
|
||||||
*/
|
|
||||||
private $numberOfResults;
|
|
||||||
|
|
||||||
/**
|
#[Groups(['api'])]
|
||||||
* @var int
|
private int $maxPerPage;
|
||||||
*
|
|
||||||
* @Serializer\Groups({"api"})
|
|
||||||
*/
|
|
||||||
private $maxPerPage;
|
|
||||||
|
|
||||||
/**
|
#[Groups(['api'])]
|
||||||
* @var \Traversable
|
protected \Traversable $items;
|
||||||
*
|
|
||||||
* @Serializer\Groups({"api"})
|
|
||||||
*/
|
|
||||||
protected $items;
|
|
||||||
|
|
||||||
public function __construct(\Traversable $items, int $numberOfResults, int $numberOfPages, int $currentPage, int $maxPerPage)
|
public function __construct(\Traversable $items, int $numberOfResults, int $numberOfPages, int $currentPage, int $maxPerPage)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Command;
|
namespace App\Command;
|
||||||
|
|
||||||
|
@ -11,22 +12,12 @@ use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
class AddInvitesCommand extends Command
|
class AddInvitesCommand extends Command
|
||||||
{
|
{
|
||||||
/** @var EntityManagerInterface */
|
public function __construct(
|
||||||
private $em;
|
private readonly EntityManagerInterface $em,
|
||||||
|
private readonly UserRepository $userRepo,
|
||||||
/** @var UserRepository */
|
private readonly InviteManager $inviteManager
|
||||||
private $userRepo;
|
) {
|
||||||
|
|
||||||
/** @var InviteManager */
|
|
||||||
private $inviteManager;
|
|
||||||
|
|
||||||
public function __construct(EntityManagerInterface $em, UserRepository $userRepo, InviteManager $inviteManager)
|
|
||||||
{
|
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
|
|
||||||
$this->em = $em;
|
|
||||||
$this->userRepo = $userRepo;
|
|
||||||
$this->inviteManager = $inviteManager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function configure()
|
protected function configure()
|
||||||
|
@ -38,15 +29,15 @@ class AddInvitesCommand extends Command
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function execute(InputInterface $input, OutputInterface $output)
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
{
|
{
|
||||||
$username = $input->getArgument('username');
|
$username = $input->getArgument('username');
|
||||||
$number = $input->getArgument('number');
|
$number = (int) $input->getArgument('number');
|
||||||
|
|
||||||
if (null === $user = $this->userRepo->findOneBy(['username' => $username])) {
|
if (null === $user = $this->userRepo->findOneBy(['username' => $username])) {
|
||||||
$output->writeln('<error>User not found.</error>');
|
$output->writeln('<error>User not found.</error>');
|
||||||
|
|
||||||
return 1;
|
return Command::FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->inviteManager->createInvitesForUser($user, $number);
|
$this->inviteManager->createInvitesForUser($user, $number);
|
||||||
|
@ -55,6 +46,6 @@ class AddInvitesCommand extends Command
|
||||||
|
|
||||||
$output->writeln(sprintf('<info>%d invites added to \'%s\'.</info>', $number, $user->getUsername()));
|
$output->writeln(sprintf('<info>%d invites added to \'%s\'.</info>', $number, $user->getUsername()));
|
||||||
|
|
||||||
return 0;
|
return Command::SUCCESS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Command;
|
namespace App\Command;
|
||||||
|
|
||||||
|
@ -13,26 +14,13 @@ use Symfony\Component\Console\Question\Question;
|
||||||
|
|
||||||
class AddUserCommand extends Command
|
class AddUserCommand extends Command
|
||||||
{
|
{
|
||||||
/** @var EntityManagerInterface */
|
public function __construct(
|
||||||
private $em;
|
private readonly EntityManagerInterface $em,
|
||||||
|
private readonly UserManager $userManager,
|
||||||
/** @var UserManager */
|
private readonly UserRepository $userRepo,
|
||||||
private $userManager;
|
private readonly InviteManager $inviteManager,
|
||||||
|
) {
|
||||||
/** @var UserRepository */
|
|
||||||
private $userRepo;
|
|
||||||
|
|
||||||
/** @var InviteManager */
|
|
||||||
private $inviteManager;
|
|
||||||
|
|
||||||
public function __construct(EntityManagerInterface $em, UserManager $userManager, UserRepository $userRepo, InviteManager $inviteManager)
|
|
||||||
{
|
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
|
|
||||||
$this->em = $em;
|
|
||||||
$this->userManager = $userManager;
|
|
||||||
$this->userRepo = $userRepo;
|
|
||||||
$this->inviteManager = $inviteManager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function configure()
|
protected function configure()
|
||||||
|
@ -47,7 +35,7 @@ class AddUserCommand extends Command
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function execute(InputInterface $input, OutputInterface $output)
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
{
|
{
|
||||||
$username = $input->getArgument('username');
|
$username = $input->getArgument('username');
|
||||||
$email = $input->getArgument('email');
|
$email = $input->getArgument('email');
|
||||||
|
@ -68,7 +56,7 @@ class AddUserCommand extends Command
|
||||||
if (!$password) {
|
if (!$password) {
|
||||||
$output->writeln('<error>User password cannot be empty.</error>');
|
$output->writeln('<error>User password cannot be empty.</error>');
|
||||||
|
|
||||||
return 1;
|
return Command::FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($roles) {
|
if ($roles) {
|
||||||
|
@ -88,7 +76,7 @@ class AddUserCommand extends Command
|
||||||
|
|
||||||
$output->writeln(sprintf('<info>User \'%s\' registered, %d invites added.</info>', $user->getUsername(), $invites));
|
$output->writeln(sprintf('<info>User \'%s\' registered, %d invites added.</info>', $user->getUsername(), $invites));
|
||||||
|
|
||||||
return 0;
|
return Command::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,11 +1,10 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
use App\Entity\ApiToken;
|
use App\Entity\{ApiToken, User};
|
||||||
use App\Entity\User;
|
use App\Repository\{ApiTokenRepository, InviteRepository};
|
||||||
use App\Repository\ApiTokenRepository;
|
|
||||||
use App\Repository\InviteRepository;
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Doctrine\ORM\AST;
|
namespace App\Doctrine\ORM\AST;
|
||||||
|
@ -14,14 +13,13 @@ use Doctrine\ORM\Query\{AST\Functions\FunctionNode, AST\Node, Lexer, Parser, Sql
|
||||||
*/
|
*/
|
||||||
abstract class BaseFunction extends FunctionNode
|
abstract class BaseFunction extends FunctionNode
|
||||||
{
|
{
|
||||||
/** @var string */
|
protected string $functionPrototype;
|
||||||
protected $functionPrototype;
|
|
||||||
|
|
||||||
/** @var string[] */
|
/** @var string[] */
|
||||||
protected $nodesMapping = [];
|
protected array $nodesMapping = [];
|
||||||
|
|
||||||
/** @var Node[] */
|
/** @var Node[] */
|
||||||
protected $nodes = [];
|
protected array $nodes = [];
|
||||||
|
|
||||||
abstract protected function customiseFunction(): void;
|
abstract protected function customiseFunction(): void;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Doctrine\ORM\AST;
|
namespace App\Doctrine\ORM\AST;
|
||||||
|
|
|
@ -1,40 +1,27 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Repository\ApiTokenRepository;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||||
|
|
||||||
/**
|
#[ORM\Table(name: 'api_tokens', schema: 'users')]
|
||||||
* @ORM\Table(name="api_tokens", schema="users")
|
#[ORM\Entity(repositoryClass: ApiTokenRepository::class, readOnly: true)]
|
||||||
* @ORM\Entity(repositoryClass="App\Repository\ApiTokenRepository", readOnly=true)
|
|
||||||
*/
|
|
||||||
class ApiToken
|
class ApiToken
|
||||||
{
|
{
|
||||||
/**
|
#[ORM\ManyToOne(targetEntity: User::class)]
|
||||||
* @var User
|
#[ORM\JoinColumn(name: 'user_id', nullable: false)]
|
||||||
*
|
private User $user;
|
||||||
* @ORM\ManyToOne(targetEntity="App\Entity\User")
|
|
||||||
* @ORM\JoinColumn(name="user_id", nullable=false)
|
|
||||||
*/
|
|
||||||
private $user;
|
|
||||||
|
|
||||||
/**
|
#[Serializer\Groups(['api', 'api_v1_login'])]
|
||||||
* @var string
|
#[ORM\Id]
|
||||||
*
|
#[ORM\Column(name: 'key', type: 'string', length: 32)]
|
||||||
* @Serializer\Groups({"api", "api_v1_login"})
|
private string $key;
|
||||||
*
|
|
||||||
* @ORM\Id()
|
|
||||||
* @ORM\Column(name="key", type="string", length=32)
|
|
||||||
*/
|
|
||||||
private $key;
|
|
||||||
|
|
||||||
/**
|
#[ORM\Column(name: 'created_at', type: 'datetime')]
|
||||||
* @var \DateTime
|
private \DateTime $createdAt;
|
||||||
*
|
|
||||||
* @ORM\Column(name="created_at", type="datetime")
|
|
||||||
*/
|
|
||||||
private $createdAt;
|
|
||||||
|
|
||||||
public function __construct(User $user)
|
public function __construct(User $user)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,51 +1,35 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Repository\InviteRepository;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
/**
|
#[ORM\Table(name: 'invites', schema: 'users')]
|
||||||
* @ORM\Table(name="invites", schema="users")
|
#[ORM\Entity(repositoryClass: InviteRepository::class)]
|
||||||
* @ORM\Entity(repositoryClass="App\Repository\InviteRepository")
|
|
||||||
*/
|
|
||||||
class Invite
|
class Invite
|
||||||
{
|
{
|
||||||
/**
|
#[ORM\Id]
|
||||||
* @var int
|
#[ORM\GeneratedValue(strategy: 'AUTO')]
|
||||||
*
|
#[ORM\Column(name: 'id', type: 'integer')]
|
||||||
* @ORM\Id()
|
private int $id;
|
||||||
* @ORM\GeneratedValue(strategy="AUTO")
|
|
||||||
* @ORM\Column(name="id", type="integer")
|
|
||||||
*/
|
|
||||||
private $id;
|
|
||||||
|
|
||||||
/**
|
#[ORM\ManyToOne(targetEntity: User::class, inversedBy: 'invites')]
|
||||||
* @var User
|
#[ORM\JoinColumn(name: 'user_id', nullable: false)]
|
||||||
*
|
private User $user;
|
||||||
* @ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="invites")
|
|
||||||
* @ORM\JoinColumn(name="user_id", nullable=false)
|
|
||||||
*/
|
|
||||||
private $user;
|
|
||||||
|
|
||||||
/**
|
#[ORM\Column(name: 'code', type: 'string', length: 32, unique: true)]
|
||||||
* @var string
|
private string $code;
|
||||||
*
|
|
||||||
* @ORM\Column(name="code", type="string", length=32, unique=true)
|
|
||||||
*/
|
|
||||||
private $code;
|
|
||||||
|
|
||||||
/**
|
#[ORM\ManyToOne(targetEntity: User::class)]
|
||||||
* @var User|null
|
#[ORM\JoinColumn(name: 'used_by_id', nullable: true)]
|
||||||
*
|
private ?User $usedBy;
|
||||||
* @ORM\ManyToOne(targetEntity="App\Entity\User")
|
|
||||||
* @ORM\JoinColumn(name="used_by_id", nullable=true)
|
|
||||||
*/
|
|
||||||
private $usedBy;
|
|
||||||
|
|
||||||
public function __construct(User $forUser)
|
public function __construct(User $forUser)
|
||||||
{
|
{
|
||||||
$this->user = $forUser;
|
$this->user = $forUser;
|
||||||
$this->code = md5(random_bytes(100));
|
$this->code = md5(\random_bytes(100));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): int
|
public function getId(): int
|
||||||
|
|
|
@ -1,38 +1,24 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
/**
|
#[ORM\Table(schema: 'users', name: 'password_reset_tokens')]
|
||||||
* @ORM\Table(schema="users", name="password_reset_tokens")
|
#[ORM\Entity(readOnly: true)]
|
||||||
* @ORM\Entity(readOnly=true)
|
|
||||||
*/
|
|
||||||
class PasswordResetToken
|
class PasswordResetToken
|
||||||
{
|
{
|
||||||
/**
|
#[ORM\ManyToOne(targetEntity: User::class, fetch: 'EAGER')]
|
||||||
* @var User
|
#[ORM\JoinColumn(name: 'user_id', nullable: false, onDelete: 'CASCADE')]
|
||||||
*
|
private User $user;
|
||||||
* @ORM\ManyToOne(targetEntity="User", fetch="EAGER")
|
|
||||||
* @ORM\JoinColumn(name="user_id", nullable=false, onDelete="CASCADE")
|
|
||||||
*/
|
|
||||||
private $user;
|
|
||||||
|
|
||||||
/**
|
#[ORM\Id]
|
||||||
* @var string
|
#[ORM\Column(name: 'code', type: 'text', nullable: false)]
|
||||||
*
|
private string $code;
|
||||||
* @ORM\Id()
|
|
||||||
* @ORM\Column(name="code", type="text", nullable=false)
|
|
||||||
*/
|
|
||||||
private $code;
|
|
||||||
|
|
||||||
/**
|
#[ORM\Column(name: 'valid_until', type: 'datetime', nullable: false)]
|
||||||
* @var \DateTime
|
private \DateTime $validUntil;
|
||||||
*
|
|
||||||
* @ORM\Column(name="valid_until", type="datetime", nullable=false)
|
|
||||||
*/
|
|
||||||
private $validUntil;
|
|
||||||
|
|
||||||
public function __construct(User $user, \DateInterval $validFor = null)
|
public function __construct(User $user, \DateInterval $validFor = null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,73 +1,46 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Repository\UserRepository;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface;
|
use Symfony\Component\PasswordHasher\PasswordHasherInterface;
|
||||||
use Symfony\Component\Security\Core\User\UserInterface;
|
use Symfony\Component\Security\Core\User\{PasswordAuthenticatedUserInterface, UserInterface};
|
||||||
|
|
||||||
/**
|
#[ORM\Table(name: 'users', schema: 'users')]
|
||||||
* @ORM\Table(name="users", schema="users")
|
#[ORM\Entity(repositoryClass: UserRepository::class)]
|
||||||
* @ORM\Entity(repositoryClass="App\Repository\UserRepository")
|
class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||||
*/
|
|
||||||
class User implements UserInterface, \Serializable
|
|
||||||
{
|
{
|
||||||
/**
|
#[ORM\Id]
|
||||||
* @var int
|
#[ORM\Column(name: 'id', type: 'integer')]
|
||||||
*
|
#[ORM\GeneratedValue(strategy: 'AUTO')]
|
||||||
* @ORM\Id
|
private int $id;
|
||||||
* @ORM\Column(name="id", type="integer")
|
|
||||||
* @ORM\GeneratedValue(strategy="AUTO")
|
|
||||||
*/
|
|
||||||
private $id;
|
|
||||||
|
|
||||||
/**
|
#[ORM\Column(name: 'username', type: 'string', length: 25, unique: true)]
|
||||||
* @var string
|
private string $username;
|
||||||
*
|
|
||||||
* @ORM\Column(name="username", type="string", length=25, unique=true)
|
|
||||||
*/
|
|
||||||
private $username;
|
|
||||||
|
|
||||||
/**
|
#[ORM\Column(name: 'password', type: 'text')]
|
||||||
* @var string
|
private string $password;
|
||||||
*
|
|
||||||
* @ORM\Column(name="password", type="text")
|
|
||||||
*/
|
|
||||||
private $password;
|
|
||||||
|
|
||||||
/**
|
#[ORM\Column(name: 'email', type: 'string', length: 254, unique: true)]
|
||||||
* @var string
|
private string $email;
|
||||||
*
|
|
||||||
* @ORM\Column(name="email", type="string", length=254, unique=true)
|
|
||||||
*/
|
|
||||||
private $email;
|
|
||||||
|
|
||||||
/**
|
#[ORM\Column(name: 'roles', type: 'json')]
|
||||||
* @var string[]
|
private array $roles = [];
|
||||||
*
|
|
||||||
* @ORM\Column(name="roles", type="json")
|
|
||||||
*/
|
|
||||||
private $roles = [];
|
|
||||||
|
|
||||||
/**
|
#[ORM\Column(name: 'created_at', type: 'datetime')]
|
||||||
* @var \DateTime
|
private \DateTime $createdAt;
|
||||||
*
|
|
||||||
* @ORM\Column(name="created_at", type="datetime")
|
|
||||||
*/
|
|
||||||
private $createdAt;
|
|
||||||
|
|
||||||
/**
|
/** @var Invite[]|ArrayCollection */
|
||||||
* @var Invite[]|ArrayCollection
|
#[ORM\OneToMany(targetEntity: Invite::class, mappedBy: 'user', fetch: 'EXTRA_LAZY')]
|
||||||
*
|
|
||||||
* @ORM\OneToMany(targetEntity="App\Entity\Invite", mappedBy="user", fetch="EXTRA_LAZY")
|
|
||||||
*/
|
|
||||||
private $invites;
|
private $invites;
|
||||||
|
|
||||||
public function __construct(string $username, PasswordEncoderInterface $encoder, string $rawPassword, string $email, array $roles = [])
|
public function __construct(string $username, PasswordHasherInterface $hasher, string $rawPassword, string $email, array $roles = [])
|
||||||
{
|
{
|
||||||
$this->username = $username;
|
$this->username = $username;
|
||||||
$this->password = $encoder->encodePassword($rawPassword, null);
|
$this->password = $hasher->hash($rawPassword);
|
||||||
$this->email = $email;
|
$this->email = $email;
|
||||||
$this->roles = $roles ?: ['ROLE_USER'];
|
$this->roles = $roles ?: ['ROLE_USER'];
|
||||||
$this->createdAt = new \DateTime();
|
$this->createdAt = new \DateTime();
|
||||||
|
@ -78,22 +51,29 @@ class User implements UserInterface, \Serializable
|
||||||
return $this->id;
|
return $this->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUsername()
|
public function getUserIdentifier(): string
|
||||||
{
|
{
|
||||||
return $this->username;
|
return $this->username;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPassword()
|
/** @deprecated since Symfony 5.3, use getUserIdentifier() instead */
|
||||||
|
public function getUsername(): string
|
||||||
|
{
|
||||||
|
return $this->username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPassword(): string
|
||||||
{
|
{
|
||||||
return $this->password;
|
return $this->password;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function changePassword(PasswordEncoderInterface $encoder, string $rawPassword): void
|
public function changePassword(PasswordHasherInterface $hasher, string $rawPassword): void
|
||||||
{
|
{
|
||||||
$this->password = $encoder->encodePassword($rawPassword, null);
|
$this->password = $hasher->hash($rawPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSalt()
|
/** @deprecated since Symfony 5.3 */
|
||||||
|
public function getSalt(): ?string
|
||||||
{
|
{
|
||||||
// Salt is not needed when using Argon2i
|
// Salt is not needed when using Argon2i
|
||||||
// @see https://symfony.com/doc/current/reference/configuration/security.html#using-the-argon2i-password-encoder
|
// @see https://symfony.com/doc/current/reference/configuration/security.html#using-the-argon2i-password-encoder
|
||||||
|
@ -132,7 +112,7 @@ class User implements UserInterface, \Serializable
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @see \Serializable::serialize() */
|
/** @see \Serializable::serialize() */
|
||||||
public function serialize()
|
public function serialize(): string
|
||||||
{
|
{
|
||||||
return serialize([
|
return serialize([
|
||||||
$this->id,
|
$this->id,
|
||||||
|
@ -142,7 +122,7 @@ class User implements UserInterface, \Serializable
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @see \Serializable::unserialize() */
|
/** @see \Serializable::unserialize() */
|
||||||
public function unserialize($serialized)
|
public function unserialize($serialized): void
|
||||||
{
|
{
|
||||||
[
|
[
|
||||||
$this->id,
|
$this->id,
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
|
||||||
namespace App\Feed;
|
namespace App\Feed;
|
||||||
|
|
||||||
use App\Magnet\MagnetGenerator;
|
use App\Magnet\MagnetGenerator;
|
||||||
|
@ -21,15 +20,12 @@ class RssGenerator
|
||||||
private const PER_PAGE = 1000;
|
private const PER_PAGE = 1000;
|
||||||
private const MIME_TYPE = 'application/x-bittorrent';
|
private const MIME_TYPE = 'application/x-bittorrent';
|
||||||
|
|
||||||
private TorrentRepository $repo;
|
public function __construct(
|
||||||
private RouterInterface $router;
|
private readonly TorrentRepository $repo,
|
||||||
private MagnetGenerator $magnetGenerator;
|
private readonly RouterInterface $router,
|
||||||
|
private readonly MagnetGenerator $magnetGenerator
|
||||||
|
) {
|
||||||
|
|
||||||
public function __construct(TorrentRepository $repo, RouterInterface $router, MagnetGenerator $magnetGenerator)
|
|
||||||
{
|
|
||||||
$this->repo = $repo;
|
|
||||||
$this->router = $router;
|
|
||||||
$this->magnetGenerator = $magnetGenerator;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function generateLast(int $page): string
|
public function generateLast(int $page): string
|
||||||
|
@ -43,8 +39,6 @@ class RssGenerator
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Torrent[] $torrents
|
* @param Torrent[] $torrents
|
||||||
*
|
|
||||||
* @return Feed
|
|
||||||
*/
|
*/
|
||||||
private function createFeedFromTorrents(array $torrents): Feed
|
private function createFeedFromTorrents(array $torrents): Feed
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Form\Data;
|
namespace App\Form\Data;
|
||||||
|
|
||||||
|
@ -6,12 +7,8 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
class PasswordResetData
|
class PasswordResetData
|
||||||
{
|
{
|
||||||
/**
|
#[Assert\NotBlank]
|
||||||
* @var string
|
#[Assert\Length(min: 8, max: 4096)]
|
||||||
*
|
#[Assert\NotCompromisedPassword(skipOnError: true)]
|
||||||
* @Assert\NotBlank
|
public string $password;
|
||||||
* @Assert\Length(min="8", max="4096")
|
|
||||||
* @Assert\NotCompromisedPassword(skipOnError=true)
|
|
||||||
*/
|
|
||||||
public $password;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Form\Data;
|
namespace App\Form\Data;
|
||||||
|
|
||||||
|
@ -7,18 +8,10 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
class PasswordResetRequestData
|
class PasswordResetRequestData
|
||||||
{
|
{
|
||||||
/**
|
#[Assert\Email]
|
||||||
* @var string
|
#[Assert\NotBlank]
|
||||||
*
|
public string $email;
|
||||||
* @Assert\Email()
|
|
||||||
* @Assert\NotBlank()
|
|
||||||
*/
|
|
||||||
public $email;
|
|
||||||
|
|
||||||
/**
|
#[ReCaptcha\IsTrueV3]
|
||||||
* @var string
|
public string $recaptcha;
|
||||||
*
|
|
||||||
* @ReCaptcha\IsTrueV3()
|
|
||||||
*/
|
|
||||||
public $recaptcha;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Form\Data;
|
namespace App\Form\Data;
|
||||||
|
|
||||||
|
@ -10,37 +11,21 @@ use App\Validator\Constraints as AppAssert;
|
||||||
*/
|
*/
|
||||||
class RegisterData
|
class RegisterData
|
||||||
{
|
{
|
||||||
/**
|
#[Assert\NotBlank]
|
||||||
* @var string
|
#[Assert\Length(min: 2, max: 25)]
|
||||||
*
|
public string $username;
|
||||||
* @Assert\NotBlank()
|
|
||||||
* @Assert\Length(min="2", max="25")
|
|
||||||
*/
|
|
||||||
public $username;
|
|
||||||
|
|
||||||
/**
|
#[Assert\NotBlank]
|
||||||
* @var string
|
#[Assert\Length(min: 8, max: 4096)]
|
||||||
*
|
public string $password;
|
||||||
* @Assert\NotBlank()
|
|
||||||
* @Assert\Length(min="8", max="4096")
|
|
||||||
*/
|
|
||||||
public $password;
|
|
||||||
|
|
||||||
/**
|
#[Assert\Email]
|
||||||
* @var string
|
public string $email;
|
||||||
*
|
|
||||||
* @Assert\Email()
|
|
||||||
*/
|
|
||||||
public $email;
|
|
||||||
|
|
||||||
/**
|
#[Assert\NotBlank]
|
||||||
* @var string
|
#[Assert\Length(min: 32, max: 32)]
|
||||||
*
|
#[AppAssert\ValidInvite()]
|
||||||
* @Assert\NotBlank()
|
public string $inviteCode;
|
||||||
* @Assert\Length(min="32", max="32")
|
|
||||||
* @AppAssert\ValidInvite()
|
|
||||||
*/
|
|
||||||
public $inviteCode;
|
|
||||||
|
|
||||||
public function __construct(string $inviteCode = null)
|
public function __construct(string $inviteCode = null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Form;
|
namespace App\Form;
|
||||||
|
|
||||||
|
@ -16,9 +17,9 @@ class LoginType extends AbstractType
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getBlockPrefix()
|
public function getBlockPrefix(): string
|
||||||
{
|
{
|
||||||
// Empty prefix for default UsernamePasswordFrormAuthenticationListener
|
// Empty prefix for default UsernamePasswordFormAuthenticationListener
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Form;
|
namespace App\Form;
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Form;
|
namespace App\Form;
|
||||||
|
|
||||||
use App\Form\Data\PasswordResetData;
|
use App\Form\Data\PasswordResetData;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\{HiddenType, PasswordType, RepeatedType};
|
use Symfony\Component\Form\Extension\Core\Type\{PasswordType, RepeatedType};
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Form;
|
namespace App\Form;
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Helper;
|
namespace App\Helper;
|
||||||
|
|
||||||
use App\Magnetico\Entity\Torrent;
|
use App\Magnetico\Entity\Torrent;
|
||||||
use App\View\Torrent\FileTreeNode;
|
use App\View\Torrent\FileTreeNode;
|
||||||
|
|
||||||
class BstreeviewFileTreeBuilder
|
class BsTreeviewFileTreeBuilder
|
||||||
{
|
{
|
||||||
private const DEFAULT_FILE_ICON = 'fas fa-file';
|
private const DEFAULT_FILE_ICON = 'fas fa-file';
|
||||||
private const DEFAULT_DIR_ICON = 'fas fa-folder';
|
private const DEFAULT_DIR_ICON = 'fas fa-folder';
|
|
@ -30,11 +30,7 @@ class FileSizeHumanizer
|
||||||
|
|
||||||
$maxSuffixIndex = count(self::SIZE_SUFFIXES) - 1;
|
$maxSuffixIndex = count(self::SIZE_SUFFIXES) - 1;
|
||||||
|
|
||||||
if ($maxSuffixIndex >= $factor) {
|
$suffixIndex = min($maxSuffixIndex, $factor);
|
||||||
$suffixIndex = $factor;
|
|
||||||
} else {
|
|
||||||
$suffixIndex = $maxSuffixIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
$suffix = self::SIZE_SUFFIXES[$suffixIndex];
|
$suffix = self::SIZE_SUFFIXES[$suffixIndex];
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App;
|
namespace App;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Magnet;
|
namespace App\Magnet;
|
||||||
|
|
||||||
|
|
|
@ -1,51 +1,32 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Magnetico\Entity;
|
namespace App\Magnetico\Entity;
|
||||||
|
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||||
|
|
||||||
/**
|
#[ORM\Entity(readOnly: true)]
|
||||||
* @ORM\Table(schema="magneticod", name="files", indexes={
|
#[ORM\Table(schema: 'magneticod', name: 'files')]
|
||||||
* @ORM\Index(name="file_info_hash_index", columns={"torrent_id"})
|
#[ORM\Index(name: 'file_info_hash_index', columns: ['torrent_id'])]
|
||||||
* })
|
|
||||||
* @ORM\Entity(readOnly=true)
|
|
||||||
*/
|
|
||||||
class File
|
class File
|
||||||
{
|
{
|
||||||
/**
|
#[ORM\Id]
|
||||||
* @var int
|
#[ORM\Column(name: 'id', type: 'integer')]
|
||||||
*
|
private int $id;
|
||||||
* @ORM\Column(name="id", type="integer")
|
|
||||||
* @ORM\Id
|
|
||||||
*/
|
|
||||||
private $id;
|
|
||||||
|
|
||||||
/**
|
#[ORM\ManyToOne(targetEntity: Torrent::class, inversedBy: 'files')]
|
||||||
* @var Torrent
|
#[ORM\JoinColumn(name: 'torrent_id')]
|
||||||
*
|
private Torrent $torrent;
|
||||||
* @ORM\ManyToOne(targetEntity="App\Magnetico\Entity\Torrent", inversedBy="files")
|
|
||||||
* @ORM\JoinColumn(name="torrent_id")
|
|
||||||
*/
|
|
||||||
private $torrent;
|
|
||||||
|
|
||||||
/**
|
/** File size in bytes */
|
||||||
* @var int File size in bytes
|
#[Serializer\Groups(['api_v1_show'])]
|
||||||
*
|
#[ORM\Column(name: 'size', type: 'bigint', nullable: false)]
|
||||||
* @Serializer\Groups({"api_v1_show"})
|
private int $size;
|
||||||
*
|
|
||||||
* @ORM\Column(name="size", type="bigint", nullable=false)
|
|
||||||
*/
|
|
||||||
private $size;
|
|
||||||
|
|
||||||
/**
|
#[Serializer\Groups(['api_v1_show'])]
|
||||||
* @var string
|
#[ORM\Column(name: 'path', type: 'text', nullable: false)]
|
||||||
*
|
private string $path;
|
||||||
* @Serializer\Groups({"api_v1_show"})
|
|
||||||
*
|
|
||||||
* @ORM\Column(name="path", type="text", nullable=false)
|
|
||||||
*/
|
|
||||||
private $path;
|
|
||||||
|
|
||||||
public function getId(): int
|
public function getId(): int
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,78 +1,49 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Magnetico\Entity;
|
namespace App\Magnetico\Entity;
|
||||||
|
|
||||||
|
use App\Magnetico\Repository\TorrentRepository;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||||
|
|
||||||
/**
|
#[ORM\Entity(repositoryClass: TorrentRepository::class, readOnly: true)]
|
||||||
* @ORM\Table(schema="magneticod", name="torrents", indexes={
|
#[ORM\Table(schema: 'magneticod', name: 'torrents')]
|
||||||
* @ORM\Index(name="discovered_on_index", columns={"discovered_on"}),
|
#[ORM\Index(name: 'discovered_on_index', columns: ['discovered_on'])]
|
||||||
* @ORM\Index(name="info_hash_index", columns={"info_hash"})
|
#[ORM\Index(name: 'info_hash_index', columns: ['info_hash'])]
|
||||||
* })
|
|
||||||
* @ORM\Entity(readOnly=true, repositoryClass="App\Magnetico\Repository\TorrentRepository")
|
|
||||||
*/
|
|
||||||
class Torrent
|
class Torrent
|
||||||
{
|
{
|
||||||
/**
|
#[Serializer\Groups(['api_v1_search', 'api_v1_show'])]
|
||||||
* @var int
|
#[ORM\Id]
|
||||||
*
|
#[ORM\Column(name: 'id', type: 'integer')]
|
||||||
* @Serializer\Groups({"api_v1_search", "api_v1_show"})
|
private int $id;
|
||||||
*
|
|
||||||
* @ORM\Column(name="id", type="integer")
|
|
||||||
* @ORM\Id
|
|
||||||
*/
|
|
||||||
private $id;
|
|
||||||
|
|
||||||
/**
|
/** @var resource Resource pointing to info-hash BLOB */
|
||||||
* @var resource Resource pointing to info-hash BLOB
|
#[Serializer\Groups(['api_v1_search', 'api_v1_show'])]
|
||||||
*
|
#[ORM\Column(name: 'info_hash', type: 'blob', nullable: false)]
|
||||||
* @Serializer\Groups({"api_v1_search", "api_v1_show"})
|
|
||||||
*
|
|
||||||
* @ORM\Column(name="info_hash", type="blob", nullable=false)
|
|
||||||
*/
|
|
||||||
private $infoHash;
|
private $infoHash;
|
||||||
|
|
||||||
/**
|
/** Cached value of self::infoHash in HEX string */
|
||||||
* @var string Cached value of self::infoHash in HEX string
|
private string $infoHashHexCache;
|
||||||
*/
|
|
||||||
private $infoHashHexCache;
|
|
||||||
|
|
||||||
/**
|
#[Serializer\Groups(['api_v1_search', 'api_v1_show'])]
|
||||||
* @var string Torrent name
|
#[ORM\Column(name: 'name', type: 'text', nullable: false)]
|
||||||
*
|
private string $name;
|
||||||
* @Serializer\Groups({"api_v1_search", "api_v1_show"})
|
|
||||||
*
|
|
||||||
* @ORM\Column(name="name", type="text", nullable=false)
|
|
||||||
*/
|
|
||||||
private $name;
|
|
||||||
|
|
||||||
/**
|
/** Torrent files total size in bytes */
|
||||||
* @var int Torrent files total size in bytes
|
#[Serializer\Groups(['api_v1_search', 'api_v1_show'])]
|
||||||
*
|
#[ORM\Column(name: 'total_size', type: 'bigint', nullable: false)]
|
||||||
* @Serializer\Groups({"api_v1_search", "api_v1_show"})
|
private int $totalSize;
|
||||||
*
|
|
||||||
* @ORM\Column(name="total_size", type="bigint", nullable=false)
|
|
||||||
*/
|
|
||||||
private $totalSize;
|
|
||||||
|
|
||||||
/**
|
/** Torrent discovery timestamp */
|
||||||
* @var int Torrent discovery timestamp
|
#[Serializer\Groups(['api_v1_search', 'api_v1_show'])]
|
||||||
*
|
#[ORM\Column(name: 'discovered_on', type: 'integer', nullable: false)]
|
||||||
* @Serializer\Groups({"api_v1_search", "api_v1_show"})
|
|
||||||
*
|
|
||||||
* @ORM\Column(name="discovered_on", type="integer", nullable=false)
|
|
||||||
*/
|
|
||||||
private $discoveredOn;
|
private $discoveredOn;
|
||||||
|
|
||||||
/**
|
/** @var File[]|ArrayCollection */
|
||||||
* @var File[]|ArrayCollection
|
#[Serializer\Groups(['api_v1_show'])]
|
||||||
*
|
#[ORM\OneToMany(targetEntity: File::class, fetch: 'EXTRA_LAZY', mappedBy: 'torrent')]
|
||||||
* @Serializer\Groups({"api_v1_show"})
|
|
||||||
*
|
|
||||||
* @ORM\OneToMany(targetEntity="App\Magnetico\Entity\File", fetch="EXTRA_LAZY", mappedBy="torrent")
|
|
||||||
*/
|
|
||||||
private $files;
|
private $files;
|
||||||
|
|
||||||
public function getId(): int
|
public function getId(): int
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Magnetico\Repository;
|
namespace App\Magnetico\Repository;
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Pager\View;
|
namespace App\Pager\View;
|
||||||
|
|
||||||
use Pagerfanta\Pagerfanta;
|
use Pagerfanta\Pagerfanta;
|
||||||
use Pagerfanta\PagerfantaInterface;
|
use Pagerfanta\PagerfantaInterface;
|
||||||
use Pagerfanta\View\Template\TemplateInterface;
|
use Pagerfanta\View\Template\{TemplateInterface, TwitterBootstrap4Template};
|
||||||
use Pagerfanta\View\Template\TwitterBootstrap4Template;
|
|
||||||
use Pagerfanta\View\ViewInterface;
|
use Pagerfanta\View\ViewInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -48,31 +46,31 @@ class TwitterBootstrap4PagelessView implements ViewInterface
|
||||||
return new TwitterBootstrap4Template();
|
return new TwitterBootstrap4Template();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function initializePagerfanta(PagerfantaInterface $pagerfanta)
|
private function initializePagerfanta(PagerfantaInterface $pagerfanta): void
|
||||||
{
|
{
|
||||||
$this->pagerfanta = $pagerfanta;
|
$this->pagerfanta = $pagerfanta;
|
||||||
$this->currentPage = $pagerfanta->getCurrentPage();
|
$this->currentPage = $pagerfanta->getCurrentPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function configureTemplate($routeGenerator, $options)
|
private function configureTemplate($routeGenerator, $options): void
|
||||||
{
|
{
|
||||||
$this->template->setRouteGenerator($routeGenerator);
|
$this->template->setRouteGenerator($routeGenerator);
|
||||||
$this->template->setOptions($options);
|
$this->template->setOptions($options);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generate()
|
private function generate(): array|string
|
||||||
{
|
{
|
||||||
$pages = $this->generatePages();
|
$pages = $this->generatePages();
|
||||||
|
|
||||||
return $this->generateContainer($pages);
|
return $this->generateContainer($pages);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generateContainer($pages)
|
private function generateContainer($pages): array|string
|
||||||
{
|
{
|
||||||
return str_replace('%pages%', $pages, $this->template->container());
|
return str_replace('%pages%', $pages, $this->template->container());
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generatePages()
|
private function generatePages(): string
|
||||||
{
|
{
|
||||||
return $this->previous().$this->currentPage().$this->next();
|
return $this->previous().$this->currentPage().$this->next();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Repository;
|
namespace App\Repository;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Repository;
|
namespace App\Repository;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Repository;
|
namespace App\Repository;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Repository;
|
namespace App\Repository;
|
||||||
|
|
||||||
|
|
|
@ -13,16 +13,11 @@ class TorrentSearcher
|
||||||
|
|
||||||
private const ORDER_DISABLED_FIELDS = ['infoHash'];
|
private const ORDER_DISABLED_FIELDS = ['infoHash'];
|
||||||
|
|
||||||
/** @var TorrentRepository */
|
public function __construct(
|
||||||
private $torrentRepo;
|
private readonly TorrentRepository $torrentRepo,
|
||||||
|
private readonly ClassMetadata $classMetadata
|
||||||
|
) {
|
||||||
|
|
||||||
/** @var ClassMetadata */
|
|
||||||
private $classMetadata;
|
|
||||||
|
|
||||||
public function __construct(TorrentRepository $torrentRepo, ClassMetadata $classMetadata)
|
|
||||||
{
|
|
||||||
$this->torrentRepo = $torrentRepo;
|
|
||||||
$this->classMetadata = $classMetadata;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createSearchQueryBuilder(string $query, string $orderBy = null, string $order = 'asc'): QueryBuilder
|
public function createSearchQueryBuilder(string $query, string $orderBy = null, string $order = 'asc'): QueryBuilder
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Twig;
|
namespace App\Twig;
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,22 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Twig;
|
namespace App\Twig;
|
||||||
|
|
||||||
use App\Helper\BstreeviewFileTreeBuilder;
|
use App\Helper\BsTreeviewFileTreeBuilder;
|
||||||
use Twig\Extension\AbstractExtension;
|
use Twig\Extension\AbstractExtension;
|
||||||
use Twig\{TwigFilter};
|
use Twig\{TwigFilter};
|
||||||
|
|
||||||
class FileTreeExtension extends AbstractExtension
|
class FileTreeExtension extends AbstractExtension
|
||||||
{
|
{
|
||||||
private BstreeviewFileTreeBuilder $builder;
|
private BsTreeviewFileTreeBuilder $builder;
|
||||||
|
|
||||||
public function __construct(BstreeviewFileTreeBuilder $builder)
|
public function __construct(BsTreeviewFileTreeBuilder $builder)
|
||||||
{
|
{
|
||||||
$this->builder = $builder;
|
$this->builder = $builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getFilters()
|
public function getFilters(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
new TwigFilter('file_tree', [$this->builder, 'buildFileTreeDataArray']),
|
new TwigFilter('file_tree', [$this->builder, 'buildFileTreeDataArray']),
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Twig;
|
namespace App\Twig;
|
||||||
|
|
||||||
|
@ -8,12 +9,10 @@ use Twig\{TwigFilter, TwigFunction};
|
||||||
|
|
||||||
class MagnetExtension extends AbstractExtension
|
class MagnetExtension extends AbstractExtension
|
||||||
{
|
{
|
||||||
/** @var MagnetGenerator */
|
public function __construct(
|
||||||
private $magnetGenerator;
|
private readonly MagnetGenerator $magnetGenerator
|
||||||
|
) {
|
||||||
|
|
||||||
public function __construct(MagnetGenerator $magnetGenerator)
|
|
||||||
{
|
|
||||||
$this->magnetGenerator = $magnetGenerator;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getFunctions(): array
|
public function getFunctions(): array
|
||||||
|
@ -23,7 +22,7 @@ class MagnetExtension extends AbstractExtension
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getFilters()
|
public function getFilters(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
new TwigFilter('magnet', [$this->magnetGenerator, 'generate']),
|
new TwigFilter('magnet', [$this->magnetGenerator, 'generate']),
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\User\Exception;
|
namespace App\User\Exception;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\User\Exception;
|
namespace App\User\Exception;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\User;
|
namespace App\User;
|
||||||
|
|
||||||
|
@ -7,16 +8,11 @@ use App\Repository\InviteRepository;
|
||||||
|
|
||||||
class InviteManager
|
class InviteManager
|
||||||
{
|
{
|
||||||
/** @var InviteRepository */
|
public function __construct(
|
||||||
private $inviteRepo;
|
private readonly InviteRepository $inviteRepo,
|
||||||
|
private readonly int $newUserInvites = 0
|
||||||
|
) {
|
||||||
|
|
||||||
/** @var int Which amount of invites we need to give to the new user */
|
|
||||||
private $newUserInvites;
|
|
||||||
|
|
||||||
public function __construct(InviteRepository $inviteRepo, int $newUserInvites = 0)
|
|
||||||
{
|
|
||||||
$this->inviteRepo = $inviteRepo;
|
|
||||||
$this->newUserInvites = $newUserInvites;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,7 +24,7 @@ class InviteManager
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$amount = (null !== $forceAmount) ? $forceAmount : $this->newUserInvites;
|
$amount = $forceAmount ?? $this->newUserInvites;
|
||||||
|
|
||||||
$invites = [];
|
$invites = [];
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\User;
|
namespace App\User;
|
||||||
|
|
||||||
|
@ -14,38 +14,15 @@ use Symfony\Component\Routing\{Generator\UrlGeneratorInterface, RouterInterface}
|
||||||
|
|
||||||
class PasswordResetManager
|
class PasswordResetManager
|
||||||
{
|
{
|
||||||
/** @var UserRepository */
|
|
||||||
private $userRepo;
|
|
||||||
|
|
||||||
/** @var PasswordResetTokenRepository */
|
|
||||||
private $tokenRepo;
|
|
||||||
|
|
||||||
/** @var EntityManagerInterface */
|
|
||||||
private $em;
|
|
||||||
|
|
||||||
/** @var MailerInterface */
|
|
||||||
private $mailer;
|
|
||||||
|
|
||||||
/** @var RouterInterface */
|
|
||||||
private $router;
|
|
||||||
|
|
||||||
/** @var string */
|
|
||||||
private $fromAddress;
|
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
UserRepository $userRepo,
|
private readonly UserRepository $userRepo,
|
||||||
PasswordResetTokenRepository $tokenRepo,
|
private readonly PasswordResetTokenRepository $tokenRepo,
|
||||||
EntityManagerInterface $em,
|
private readonly EntityManagerInterface $em,
|
||||||
MailerInterface $mailer,
|
private readonly MailerInterface $mailer,
|
||||||
RouterInterface $router,
|
private readonly RouterInterface $router,
|
||||||
string $fromAddress
|
private readonly string $fromAddress
|
||||||
) {
|
) {
|
||||||
$this->userRepo = $userRepo;
|
|
||||||
$this->tokenRepo = $tokenRepo;
|
|
||||||
$this->em = $em;
|
|
||||||
$this->mailer = $mailer;
|
|
||||||
$this->router = $router;
|
|
||||||
$this->fromAddress = $fromAddress;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function sendResetLink(string $address): void
|
public function sendResetLink(string $address): void
|
||||||
|
|
|
@ -1,37 +1,29 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\User;
|
namespace App\User;
|
||||||
|
|
||||||
use App\Entity\{Invite, User};
|
use App\Entity\{Invite, User};
|
||||||
use App\Repository\{InviteRepository, UserRepository};
|
use App\Repository\UserRepository;
|
||||||
use App\User\Exception\InvalidInviteException;
|
use App\User\Exception\InvalidInviteException;
|
||||||
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
|
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface;
|
||||||
|
|
||||||
class UserManager
|
class UserManager
|
||||||
{
|
{
|
||||||
private const DEFAULT_ROLES = ['ROLE_USER'];
|
private const DEFAULT_ROLES = ['ROLE_USER'];
|
||||||
|
|
||||||
/** @var UserRepository */
|
public function __construct(
|
||||||
private $userRepo;
|
private readonly PasswordHasherFactoryInterface $hasherFactory,
|
||||||
|
private readonly UserRepository $userRepo,
|
||||||
|
) {
|
||||||
|
|
||||||
/** @var InviteRepository */
|
|
||||||
private $inviteRepo;
|
|
||||||
|
|
||||||
/** @var EncoderFactoryInterface */
|
|
||||||
private $encoderFactory;
|
|
||||||
|
|
||||||
public function __construct(EncoderFactoryInterface $encoderFactory, UserRepository $userRepo, InviteRepository $inviteRepo)
|
|
||||||
{
|
|
||||||
$this->userRepo = $userRepo;
|
|
||||||
$this->inviteRepo = $inviteRepo;
|
|
||||||
$this->encoderFactory = $encoderFactory;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createUser(string $username, string $password, string $email, array $roles = self::DEFAULT_ROLES): User
|
public function createUser(string $username, string $password, string $email, array $roles = self::DEFAULT_ROLES): User
|
||||||
{
|
{
|
||||||
$user = new User(
|
$user = new User(
|
||||||
$username,
|
$username,
|
||||||
$this->encoderFactory->getEncoder(User::class),
|
$this->hasherFactory->getPasswordHasher(User::class),
|
||||||
$password,
|
$password,
|
||||||
$email,
|
$email,
|
||||||
$roles
|
$roles
|
||||||
|
@ -44,10 +36,7 @@ class UserManager
|
||||||
|
|
||||||
public function changePassword(User $user, string $rawPassword): void
|
public function changePassword(User $user, string $rawPassword): void
|
||||||
{
|
{
|
||||||
$user->changePassword(
|
$user->changePassword($this->hasherFactory->getPasswordHasher(User::class), $rawPassword);
|
||||||
$this->encoderFactory->getEncoder(User::class),
|
|
||||||
$rawPassword
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createUserByInvite(string $username, string $password, string $email, Invite $invite, array $roles = self::DEFAULT_ROLES): User
|
public function createUserByInvite(string $username, string $password, string $email, Invite $invite, array $roles = self::DEFAULT_ROLES): User
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Validator\Constraints;
|
namespace App\Validator\Constraints;
|
||||||
|
|
||||||
use Symfony\Component\Validator\Constraint;
|
use Symfony\Component\Validator\Constraint;
|
||||||
|
|
||||||
/**
|
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD)]
|
||||||
* @Annotation
|
|
||||||
*/
|
|
||||||
class ValidInvite extends Constraint
|
class ValidInvite extends Constraint
|
||||||
{
|
{
|
||||||
public $notFoundMessage = 'Invite {{ code }} not found.';
|
public $notFoundMessage = 'Invite {{ code }} not found.';
|
||||||
|
@ -14,6 +13,6 @@ class ValidInvite extends Constraint
|
||||||
|
|
||||||
public function validatedBy(): string
|
public function validatedBy(): string
|
||||||
{
|
{
|
||||||
return get_class($this).'Validator';
|
return static::class.'Validator';
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Validator\Constraints;
|
namespace App\Validator\Constraints;
|
||||||
|
|
||||||
|
@ -8,19 +9,16 @@ use Symfony\Component\Validator\{Constraint, ConstraintValidator};
|
||||||
|
|
||||||
class ValidInviteValidator extends ConstraintValidator
|
class ValidInviteValidator extends ConstraintValidator
|
||||||
{
|
{
|
||||||
/** @var InviteRepository */
|
public function __construct(
|
||||||
private $inviteRepo;
|
private readonly InviteRepository $inviteRepo
|
||||||
|
) {
|
||||||
|
|
||||||
public function __construct(InviteRepository $inviteRepo)
|
|
||||||
{
|
|
||||||
$this->inviteRepo = $inviteRepo;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param mixed $value
|
|
||||||
* @param ValidInvite $constraint
|
* @param ValidInvite $constraint
|
||||||
*/
|
*/
|
||||||
public function validate($value, Constraint $constraint)
|
public function validate(mixed $value, Constraint $constraint)
|
||||||
{
|
{
|
||||||
/** @var Invite $invite */
|
/** @var Invite $invite */
|
||||||
if (null === $invite = $this->inviteRepo->findOneBy(['code' => $value])) {
|
if (null === $invite = $this->inviteRepo->findOneBy(['code' => $value])) {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\View\Torrent;
|
namespace App\View\Torrent;
|
||||||
|
|
||||||
use App\Magnetico\Entity\File;
|
use App\Magnetico\Entity\{File, Torrent};
|
||||||
use App\Magnetico\Entity\Torrent;
|
|
||||||
|
|
||||||
class FileTreeNode
|
class FileTreeNode
|
||||||
{
|
{
|
||||||
|
@ -52,7 +52,7 @@ class FileTreeNode
|
||||||
|
|
||||||
// If we have file only file and not a tree.
|
// If we have file only file and not a tree.
|
||||||
if (1 === count($pathParts)) {
|
if (1 === count($pathParts)) {
|
||||||
$this->addChild($path, FileTreeNode::createFromFile($path, $file, $this));
|
$this->addChild($path, self::createFromFile($path, $file, $this));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -89,12 +89,7 @@ class FileTreeNode
|
||||||
return array_key_exists($name, $this->children);
|
return array_key_exists($name, $this->children);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function getChild(string $name): File|FileTreeNode
|
||||||
* @param string $name
|
|
||||||
*
|
|
||||||
* @return FileTreeNode|File
|
|
||||||
*/
|
|
||||||
public function getChild(string $name)
|
|
||||||
{
|
{
|
||||||
if (!array_key_exists($name, $this->children)) {
|
if (!array_key_exists($name, $this->children)) {
|
||||||
throw new \InvalidArgumentException(sprintf(
|
throw new \InvalidArgumentException(sprintf(
|
||||||
|
@ -119,7 +114,7 @@ class FileTreeNode
|
||||||
$files = [];
|
$files = [];
|
||||||
|
|
||||||
foreach ($this->children as $name => $child) {
|
foreach ($this->children as $name => $child) {
|
||||||
if ($child instanceof FileTreeNode) {
|
if ($child instanceof self) {
|
||||||
$dirs[$name] = $child;
|
$dirs[$name] = $child;
|
||||||
} elseif ($child instanceof File) {
|
} elseif ($child instanceof File) {
|
||||||
$files[] = $child;
|
$files[] = $child;
|
||||||
|
|
Loading…
Reference in a new issue