first commit

This commit is contained in:
2025-08-02 16:30:27 +02:00
commit 23646bfcee
14851 changed files with 1750626 additions and 0 deletions

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace KupShop\ProxyCacheBundle\Command;
use KupShop\ProxyCacheBundle\Util\CookiesFunctionalityTest;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Contracts\Service\Attribute\Required;
class CookiesFunctionalityTestCommand extends Command
{
#[Required]
public CookiesFunctionalityTest $test;
protected function configure()
{
$this
->setName('kupshop:test:cookies')
->setDescription('Test if cacheable responses return cookies');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$errors = $this->test->testInitialRequestCookiesAbsence();
$failed = false;
foreach ($errors as $error) {
$failed = true;
$output->writeln('<error>COOKIE VRÁCENA:</error> '.$error);
}
if ($failed) {
$output->writeln('<comment>Více o tom jak funguje proxy_cache a jak má být nastavená: </comment>'.CookiesFunctionalityTest::WIKI_LINK);
} else {
$output->writeln('<info>Úspěch: na úvodní požadavky nebyly vráceny cookies</info>');
}
return 0;
}
}

View File

@@ -0,0 +1,81 @@
<?php
namespace KupShop\ProxyCacheBundle\EventListener;
use KupShop\KupShopBundle\Context\CacheContext;
use KupShop\KupShopBundle\Util\Contexts;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class CacheKeyChangeListener implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
KernelEvents::RESPONSE => [
['changeCacheKeyCookie', 200],
['changeAdminCookie', 200],
],
];
}
protected string $cacheKeyword = 'cachekey';
protected string $adminLoggedKeyword = 'adminlogged';
public function changeCacheKeyCookie(ResponseEvent $event): void
{
/**
* @var $cacheContext CacheContext
*/
$cacheContext = Contexts::get(CacheContext::class);
$response = $event->getResponse();
$cacheKey = $this->getCacheKey();
$key = $this->hashKey($cacheContext->getKey($cacheKey));
$activeKey = $event->getRequest()->cookies->get($this->cacheKeyword);
if ($key == $activeKey) {
return;
}
$defaultKey = $this->hashKey($cacheContext->getDefaultKey($cacheKey));
if ($key == $defaultKey) {
if ($activeKey) {
$response->headers->clearCookie($this->cacheKeyword);
}
return;
}
$response->headers->setCookie(new Cookie($this->cacheKeyword, $key, strtotime('now +1 year')));
}
protected function hashKey(?string $key): ?string
{
if (is_null($key)) {
return null;
}
return $key;
}
protected function getCacheKey(): array
{
return [CacheContext::TYPE_FULL_PAGE];
}
public function changeAdminCookie(ResponseEvent $event): void
{
$adminLoggedCookie = $event->getRequest()->cookies->has($this->adminLoggedKeyword);
if (getAdminUser() && !$adminLoggedCookie) {
$event->getResponse()->headers->setCookie(new Cookie($this->adminLoggedKeyword, 1));
} elseif (!getAdminUser() && $adminLoggedCookie) {
$event->getResponse()->headers->clearCookie($this->adminLoggedKeyword);
}
}
}

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace KupShop\ProxyCacheBundle\EventListener;
use KupShop\KupShopBundle\Event\CronEvent;
use KupShop\KupShopBundle\Util\Logging\SentryLogger;
use KupShop\ProxyCacheBundle\Util\CookiesFunctionalityTest;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Contracts\Service\Attribute\Required;
class CookiesFunctionalityTestCron implements EventSubscriberInterface
{
#[Required]
public SentryLogger $sentryLogger;
#[Required]
public CookiesFunctionalityTest $test;
public static function getSubscribedEvents()
{
return [
CronEvent::RUN_EXPENSIVE => [
['testCookies', 200],
],
];
}
public function testCookies()
{
$errors = [];
foreach ($this->test->testInitialRequestCookiesAbsence() as $error) {
$errors[] = $error;
}
if (!empty($errors)) {
$this->sentryLogger->captureMessage('PROXY CACHE: kešovatelné odpověďi vrací cookies!!!', [], ['extra' => [
'cookies' => $errors,
'wiki_link' => CookiesFunctionalityTest::WIKI_LINK,
]]);
}
}
}

View File

@@ -0,0 +1,9 @@
<?php
namespace KupShop\ProxyCacheBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class ProxyCacheBundle extends Bundle
{
}

View File

@@ -0,0 +1,15 @@
services:
_defaults:
autowire: true
autoconfigure: true
KupShop\ProxyCacheBundle\:
resource: ../../{EventListener,Inspection}
KupShop\ProxyCacheBundle\Command\CookiesFunctionalityTestCommand:
tags:
- { name: console.command }
KupShop\ProxyCacheBundle\Util\CookiesFunctionalityTest:
arguments:
$domains: '%shop.domains%'

View File

@@ -0,0 +1,121 @@
<?php
declare(strict_types=1);
namespace KupShop\ProxyCacheBundle\Util;
use KupShop\I18nBundle\Translations\SectionsTranslation;
use KupShop\KupShopBundle\Context\DomainContext;
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
use KupShop\KupShopBundle\Util\Contexts;
use KupShop\KupShopBundle\Util\StringUtil;
use Query\Operator;
use Query\QueryBuilder;
use Query\Translation;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
class CookiesFunctionalityTest
{
public const WIKI_LINK = 'https://gitlab.wpj.cz/kupshop/engine/-/wikis/Reverzn%C3%AD-ke%C5%A1ovac%C3%AD-proxy-%E2%80%93-full-page-cache';
public function __construct(
private HttpClientInterface $client,
private UrlGeneratorInterface $urlGenerator,
private array $domains,
) {
}
public function testInitialRequestCookiesAbsence()
{
$scheme = Contexts::get(DomainContext::class)->getActiveWithScheme();
$domains = $this->getActiveDomains();
if (empty($domains)) { // if "shop.domains" config is not set on the shop
$domains = [Contexts::get(DomainContext::class)->getActiveId()];
}
foreach ($domains as $domain) {
$requestPaths = [
'/',
$this->getSectionPath($domain),
];
foreach (array_filter($requestPaths) as $requestPath) {
$cookies = $this->makeRequest($scheme, $requestPath, $domain);
foreach ($cookies as $cookie) {
yield $domain.$requestPath.': '.$cookie;
}
}
}
}
private function getSectionPath($domain): string|false
{
$lang = $this->domains[$domain]['language'] ?? false;
$someSectionId = sqlQueryBuilder()->select('s.id, s.figure')->from('sections', 's')
->andWhere(Translation::joinTranslations([$lang],
ServiceContainer::getService(SectionsTranslation::class),
function (QueryBuilder $qb, $columnName, $translatedField) {
$qb->andWhere(Operator::coalesce($translatedField, 's.figure').' = "Y" ');
return false;
},
['figure']))
->andWhere('s.id > 0')
->orderBy('RAND()')->setMaxResults(1)->execute()->fetchColumn();
if (!$someSectionId) {
return false;
}
return $this->urlGenerator->generate('kupshop_catalog_catalog_section',
['parent_sections' => '', 'url_section' => 'test', 'id_section' => $someSectionId]);
}
private function makeRequest($scheme, $path, $domain): array
{
$bustCacheParam = '?cache_bust='.time();
$response = $this->client->request('GET', $scheme.$path.$bustCacheParam, [
'verify_host' => false,
'verify_peer' => false,
'headers' => [
'X-WPJ-SOURCE' => 'PROXY_CACHE:CookiesFunctionalityTest',
'Host' => $domain,
]]);
if ($response->getStatusCode() !== 200) {
return [];
}
$responseHeaders = $response->getHeaders();
$cookies = array_filter($responseHeaders['set-cookie'] ?? [], function ($cookie) {
return !str_starts_with($cookie, 'sf_redirect');
});
return $cookies;
}
private function getActiveDomains(): array
{
$domains = array_keys($this->domains);
if (isProduction()) {
$domains = array_filter($domains, function ($domain) {
return !StringUtil::endsWith($domain, '.wpjshop.cz');
});
}
if (!isLocalDevelopment()) {
$domains = array_filter($domains, function ($domain) {
return !StringUtil::endsWith($domain, 'kupshop.local');
});
}
return $domains;
}
}