<?php
namespace App\Entity\System;
use App\Entity\Profile\Comment\CommentByCustomer;
use App\Repository\IpAddressRepository;
use Carbon\CarbonImmutable;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\Mapping as ORM;
use IPTools\Network;
#[ORM\Table(name: 'ip_addresses')]
#[ORM\Entity(repositoryClass: IpAddressRepository::class)]
class IpAddress
{
#[ORM\Id]
#[ORM\Column(name: 'id', type: 'integer')]
#[ORM\GeneratedValue(strategy: 'AUTO')]
protected int $id;
/**
* Human-readable representation of the ip address or range
*
* Use binary range representation for ip lookup.
*/
#[ORM\Column(name: 'ip', type: 'string', length: 128)]
protected string $ip;
/**
* Binary representation of the first ip address in range
*
* @internal Don't use it outside of entity or repository queries.
*/
#[ORM\Column(name: 'ip_range_first', type: 'binary', length: 16, nullable: true)]
private $ipRangeFirst;
/**
* Binary representation of the last ip address in range
*
* @internal Don't use it outside of entity or repository queries.
*/
#[ORM\Column(name: 'ip_range_last', type: 'binary', length: 16, nullable: true)]
private $ipRangeLast;
/**
* Ip range subnet for lookup order from large subnet to smaller
*
* @internal Don't use it outside of entity or repository queries.
*/
#[ORM\Column(name: 'ip_range_subnet', type: 'smallint', nullable: true)]
private ?int $ipRangeSubnet;
/** @var IpAddressDailyCommentBan[] */
#[ORM\OneToMany(targetEntity: IpAddressDailyCommentBan::class, mappedBy: 'ipAddress', cascade: ['all'], orphanRemoval: true)]
private Collection $dailyCommentBans;
/** @var IpAddressPermanentCommentBan[] */
#[ORM\OneToMany(targetEntity: IpAddressPermanentCommentBan::class, mappedBy: 'ipAddress', cascade: ['all'], orphanRemoval: true)]
protected Collection $permanentCommentBans;
/** @var CommentByCustomer[] */
#[ORM\OneToMany(targetEntity: CommentByCustomer::class, mappedBy: 'ipAddress')]
private Collection $profileComments;
/** @var \App\Entity\Saloon\Comment\CommentByCustomer[] */
#[ORM\OneToMany(targetEntity: \App\Entity\Saloon\Comment\CommentByCustomer::class, mappedBy: 'ipAddress')]
private Collection $saloonComments;
public static function normalize(string $raw, bool $withSubnet = true): string
{
$singleAddressSubnet = 32;
// Определяем маску подсети: 172.22.19.1/19, 172.22.10.5/32
if (str_contains($raw, '/')) {
[$ip, $subnet] = explode('/', $raw, 2);
$subnet = filter_var($subnet, FILTER_VALIDATE_INT, ['options' => [
'default' => $singleAddressSubnet,
'min_range' => 0,
'max_range' => $singleAddressSubnet,
]]);
} else {
$ip = $raw;
$subnet = $singleAddressSubnet;
}
// Если подсеть была указана в виде маски: 172.22., 172.22.*, 172.22.10.*
$ipParts = preg_split('/\./', $ip, -1, PREG_SPLIT_NO_EMPTY);
for ($pad = 4 - count($ipParts); $pad > 0; $pad--) {
$ipParts[] = '*';
}
for ($i = 3; $i >= 0; $i--) {
if ('*' === $ipParts[$i]) {
$ipParts[$i] = '0';
$subnet = $i * 8;
}
}
$normalized = implode('.', $ipParts);
if ($withSubnet) {
$normalized .= '/'.$subnet;
}
return $normalized;
}
public function __construct(string $ip = null)
{
if (null !== $ip) {
$this->setIp($ip);
}
$this->dailyCommentBans = new ArrayCollection();
$this->permanentCommentBans = new ArrayCollection();
$this->profileComments = new ArrayCollection();
$this->saloonComments = new ArrayCollection();
}
public function setIp(string $raw): void
{
$normalized = self::normalize($raw, true);
$network = Network::parse($normalized);
$range = $network->getHosts();
$this->ip = $normalized;
$this->ipRangeFirst = $range->getFirstIP()->inAddr();
$this->ipRangeLast = $range->getLastIP()->inAddr();
$this->ipRangeSubnet = $network->getPrefixLength();
}
public function getId(): ?int
{
return $this->id;
}
public function getIp(): ?string
{
return $this->ip;
}
public function getTodayCommentCount(): int
{
return $this->getTodayProfileCommentCount() + $this->getTodaySaloonCommentCount();
}
public function getTodaySaloonCommentCount(): int
{
$criteria = Criteria::create()
->where(Criteria::expr()->gte("createdAt", CarbonImmutable::today()));
return $this->saloonComments->matching($criteria)->count();
}
public function getTodayProfileCommentCount(): int
{
$criteria = Criteria::create()
->where(Criteria::expr()->gte("createdAt", CarbonImmutable::today()));
return $this->profileComments->matching($criteria)->count();
}
public function getProfileComments(): Collection
{
return $this->profileComments;
}
public function getSaloonComments(): Collection
{
return $this->saloonComments;
}
public function getDailyCommentBansCount(): int
{
return $this->dailyCommentBans->count();
}
public function getActiveDailyCommentBan(): ?IpAddressDailyCommentBan
{
$criteria = Criteria::create()
->where(Criteria::expr()->gte("date", CarbonImmutable::today()))
->andWhere(Criteria::expr()->eq("isReset", false));
$result = $this->dailyCommentBans->matching($criteria);
return $result->count() ? $result->first() : null;
}
public function getTodayDailyCommentBansCount(): int
{
$criteria = Criteria::create()
->where(Criteria::expr()->gte("date", CarbonImmutable::today()));
return $this->dailyCommentBans->matching($criteria)->count();
}
public function setDailyCommentBan(): void
{
if(null !== $this->getActiveDailyCommentBan()) {
throw new \DomainException('Daily ban for today for this IP already set.');
}
$this->dailyCommentBans->add(new IpAddressDailyCommentBan($this, CarbonImmutable::now()));
}
public function getPermanentCommentBansCount(): int
{
return $this->permanentCommentBans->count();
}
public function getActivePermanentCommentBan(): ?IpAddressPermanentCommentBan
{
$criteria = Criteria::create()
->andWhere(Criteria::expr()->eq("isReset", false));
$result = $this->permanentCommentBans->matching($criteria);
return $result->count() ? $result->first() : null;
}
public function getPermanentDailyCommentBans(): Collection
{
$criteria = Criteria::create()
->where(Criteria::expr()->gte("date", CarbonImmutable::today()));
return $this->permanentCommentBans->matching($criteria);
}
public function setPermanentCommentBan(): void
{
if(null !== $this->getActivePermanentCommentBan()) {
throw new \DomainException('Permanent ban for this IP already set.');
}
$this->permanentCommentBans->add(new IpAddressPermanentCommentBan($this, CarbonImmutable::now()));
}
/**
* @inheritDoc
*/
public function __toString(): string
{
return (string) $this->ip;
}
}