<commit_before><?php

declare(strict_types=1);

namespace App\Entity\Repository;

use App\Doctrine\ReloadableEntityManagerInterface;
use App\Entity;
use App\Exception;
use App\Radio\AutoDJ;
use App\Radio\Frontend\Blocklist\BlocklistParser;
use App\Service\DeviceDetector;
use Carbon\CarbonImmutable;
use Carbon\CarbonInterface;

/**
 * @extends AbstractStationBasedRepository<Entity\StationRequest>
 */
final class StationRequestRepository extends AbstractStationBasedRepository
{
    public function __construct(
        ReloadableEntityManagerInterface $em,
        private readonly StationMediaRepository $mediaRepo,
        private readonly DeviceDetector $deviceDetector,
        private readonly BlocklistParser $blocklistParser,
        private readonly AutoDJ\DuplicatePrevention $duplicatePrevention,
    ) {
        parent::__construct($em);
    }

    public function getPendingRequest(int|string $id, Entity\Station $station): ?Entity\StationRequest
    {
        return $this->repository->findOneBy(
            [
                'id' => $id,
                'station' => $station,
                'played_at' => 0,
            ]
        );
    }

    public function clearPendingRequests(Entity\Station $station): void
    {
        $this->em->createQuery(
            <<<'DQL'
                DELETE FROM App\Entity\StationRequest sr
                WHERE sr.station = :station
                AND sr.played_at = 0
            DQL
        )->setParameter('station', $station)
            ->execute();
    }

    public function submit(
        Entity\Station $station,
        string $trackId,
        bool $isAuthenticated,
        string $ip,
        string $userAgent
    ): int {
        // Verify that the station supports requests.
        if (!$station->getEnableRequests()) {
            throw new Exception(__('This station does not accept requests currently.'));
        }

        // Forbid web crawlers from using this feature.
        $dd = $this->deviceDetector->parse($userAgent);

        if ($dd->isBot) {
            throw new Exception(__('Search engine crawlers are not permitted to use this feature.'));
        }

        // Check frontend blocklist and apply it to requests.
        if (!$this->blocklistParser->isAllowed($ip, $userAgent, $station)) {
            throw new Exception(__('You are not permitted to submit requests.'));
        }

        // Verify that Track ID exists with station.
        $media_item = $this->mediaRepo->requireByUniqueId($trackId, $station);

        if (!$media_item->isRequestable()) {
            throw new Exception(__('The song ID you specified cannot be requested for this station.'));
        }

        // Check if the song is already enqueued as a request.
        $this->checkPendingRequest($media_item, $station);

        // Check the most recent song history.
        $this->checkRecentPlay($media_item, $station);

        if (!$isAuthenticated) {
            // Check for any request (on any station) within the last $threshold_seconds.
            $thresholdMins = $station->getRequestThreshold() ?? 5;
            $thresholdSeconds = $thresholdMins * 60;

            // Always have a minimum threshold to avoid flooding.
            if ($thresholdSeconds < 60) {
                $thresholdSeconds = 15;
            }

            $recent_requests = $this->em->createQuery(
                <<<'DQL'
                    SELECT sr FROM App\Entity\StationRequest sr
                    WHERE sr.ip = :user_ip
                    AND sr.timestamp >= :threshold
                DQL
            )->setParameter('user_ip', $ip)
                ->setParameter('threshold', time() - $thresholdSeconds)
                ->getArrayResult();

            if (count($recent_requests) > 0) {
                throw new Exception(
                    __('You have submitted a request too recently! Please wait before submitting another one.')
                );
            }
        }

        // Save request locally.
        $record = new Entity\StationRequest($station, $media_item, $ip);
        $this->em->persist($record);
        $this->em->flush();

        return $record->getIdRequired();
    }

    /**
     * Check if the song is already enqueued as a request.
     *
     * @param Entity\StationMedia $media
     * @param Entity\Station $station
     *
     * @throws Exception
     */
    public function checkPendingRequest(Entity\StationMedia $media, Entity\Station $station): bool
    {
        $pending_request_threshold = time() - (60 * 10);
<commit_msg>Fixes #5550 -- Fix variable used in recent request threshold check.<commit_after><?php

declare(strict_types=1);

namespace App\Entity\Repository;

use App\Doctrine\ReloadableEntityManagerInterface;
use App\Entity;
use App\Exception;
use App\Radio\AutoDJ;
use App\Radio\Frontend\Blocklist\BlocklistParser;
use App\Service\DeviceDetector;
use Carbon\CarbonImmutable;
use Carbon\CarbonInterface;

/**
 * @extends AbstractStationBasedRepository<Entity\StationRequest>
 */
final class StationRequestRepository extends AbstractStationBasedRepository
{
    public function __construct(
        ReloadableEntityManagerInterface $em,
        private readonly StationMediaRepository $mediaRepo,
        private readonly DeviceDetector $deviceDetector,
        private readonly BlocklistParser $blocklistParser,
        private readonly AutoDJ\DuplicatePrevention $duplicatePrevention,
    ) {
        parent::__construct($em);
    }

    public function getPendingRequest(int|string $id, Entity\Station $station): ?Entity\StationRequest
    {
        return $this->repository->findOneBy(
            [
                'id' => $id,
                'station' => $station,
                'played_at' => 0,
            ]
        );
    }

    public function clearPendingRequests(Entity\Station $station): void
    {
        $this->em->createQuery(
            <<<'DQL'
                DELETE FROM App\Entity\StationRequest sr
                WHERE sr.station = :station
                AND sr.played_at = 0
            DQL
        )->setParameter('station', $station)
            ->execute();
    }

    public function submit(
        Entity\Station $station,
        string $trackId,
        bool $isAuthenticated,
        string $ip,
        string $userAgent
    ): int {
        // Verify that the station supports requests.
        if (!$station->getEnableRequests()) {
            throw new Exception(__('This station does not accept requests currently.'));
        }

        // Forbid web crawlers from using this feature.
        $dd = $this->deviceDetector->parse($userAgent);

        if ($dd->isBot) {
            throw new Exception(__('Search engine crawlers are not permitted to use this feature.'));
        }

        // Check frontend blocklist and apply it to requests.
        if (!$this->blocklistParser->isAllowed($ip, $userAgent, $station)) {
            throw new Exception(__('You are not permitted to submit requests.'));
        }

        // Verify that Track ID exists with station.
        $media_item = $this->mediaRepo->requireByUniqueId($trackId, $station);

        if (!$media_item->isRequestable()) {
            throw new Exception(__('The song ID you specified cannot be requested for this station.'));
        }

        // Check if the song is already enqueued as a request.
        $this->checkPendingRequest($media_item, $station);

        // Check the most recent song history.
        $this->checkRecentPlay($media_item, $station);

        if (!$isAuthenticated) {
            // Check for any request (on any station) within the last $threshold_seconds.
            $thresholdMins = $station->getRequestDelay() ?? 5;
            $thresholdSeconds = $thresholdMins * 60;

            // Always have a minimum threshold to avoid flooding.
            if ($thresholdSeconds < 60) {
                $thresholdSeconds = 15;
            }

            $recentRequests = (int)$this->em->createQuery(
                <<<'DQL'
                    SELECT COUNT(sr.id) FROM App\Entity\StationRequest sr
                    WHERE sr.ip = :user_ip
                    AND sr.timestamp >= :threshold
                DQL
            )->setParameter('user_ip', $ip)
                ->setParameter('threshold', time() - $thresholdSeconds)
                ->getSingleScalarResult();

            if ($recentRequests > 0) {
                throw new Exception(
                    __('You have submitted a request too recently! Please wait before submitting another one.')
                );
            }
        }

        // Save request locally.
        $record = new Entity\StationRequest($station, $media_item, $ip);
        $this->em->persist($record);
        $this->em->flush();

        return $record->getIdRequired();
    }

    /**
     * Check if the song is already enqueued as a request.
     *
     * @param Entity\StationMedia $media
     * @param Entity\Station $station
     *
     * @throws Exception
     */
    public function checkPendingRequest(Entity\StationMedia $media, Entity\Station $station): bool
    {
        $pending_request_threshold = time() - (60 * 10);

{'fraction_non_alphanumeric': 0.10385497433657312, 'fraction_numerical': 0.0037130064431582397, 'mean_word_length': 2.1330824495381457, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 71, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7385570', 'n_tokens_mistral': 2405, 'n_tokens_neox': 2304, 'n_words': 647}