254 lines
10 KiB
PHP
254 lines
10 KiB
PHP
<?php
|
|
|
|
namespace KupShop\FeedsBundle\Controller;
|
|
|
|
use KupShop\AdminBundle\Util\ActivityLog;
|
|
use KupShop\FeedsBundle\Feed\IFeed;
|
|
use KupShop\FeedsBundle\FeedLocator;
|
|
use KupShop\FeedsBundle\FeedsBundle;
|
|
use KupShop\FeedsBundle\Utils\FeedRenderer;
|
|
use KupShop\FeedsBundle\Utils\FeedUtils;
|
|
use KupShop\KupShopBundle\Context\CountryContext;
|
|
use KupShop\KupShopBundle\Context\CurrencyContext;
|
|
use KupShop\KupShopBundle\Context\DomainContext;
|
|
use KupShop\KupShopBundle\Context\LanguageContext;
|
|
use KupShop\KupShopBundle\Util\CachingStreamedResponse;
|
|
use KupShop\KupShopBundle\Util\Logging\SentryLogger;
|
|
use Psr\Log\LoggerInterface;
|
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|
use Symfony\Component\HttpFoundation\Request;
|
|
use Symfony\Component\HttpFoundation\RequestStack;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
use Symfony\Component\HttpFoundation\Session\Session;
|
|
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
|
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
|
use Symfony\Component\Routing\Annotation\Route;
|
|
|
|
class FeedController extends AbstractController
|
|
{
|
|
use \DatabaseCommunication;
|
|
|
|
public function __construct(
|
|
private FeedLocator $feedLocator,
|
|
private LanguageContext $languageContext,
|
|
private CurrencyContext $currencyContext,
|
|
private CountryContext $countryContext,
|
|
private DomainContext $domainContext,
|
|
private FeedUtils $feedUtils,
|
|
private FeedRenderer $feedRenderer,
|
|
private LoggerInterface $logger,
|
|
private RequestStack $requestStack,
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* @Route("/feed/{feedID}/{hash}", requirements={"feedID":"[0-9]+", "hash":"[0-9a-zA-Z_-]+"})
|
|
* @Route("/feed/{feedID}/{hash}.{format}", requirements={"feedID":"[0-9]+", "hash":".*", "format":"xml|json|csv|xlsx"})
|
|
*
|
|
* @return Response
|
|
*
|
|
* @throws \KupShop\FeedsBundle\Exceptions\UnknownFeedTypeException
|
|
*/
|
|
public function exportAction(SentryLogger $sentryLogger, Request $request)
|
|
{
|
|
$feedID = $request->get('feedID');
|
|
$hash = $request->get('hash');
|
|
$limit = $request->get('limit');
|
|
// output feed as a file (simple presence of this parameter means yes - might be empty)
|
|
$file = $request->get('file');
|
|
$format = $request->get('format', 'xml');
|
|
$pretty = $request->get('pretty');
|
|
$date_from = $request->get('date_from');
|
|
$date_to = $request->get('date_to');
|
|
$updateDownload = in_array($request->getRealMethod(), ['GET', 'POST']);
|
|
|
|
$feedRow = sqlQueryBuilder()->select('*')->from('feeds')
|
|
->where('id = :id')->setParameter('id', (int) $feedID)
|
|
->execute()->fetch();
|
|
if ($feedRow && $feedRow['hash'] === $hash) {
|
|
if (!$feedRow['active'] && !getAdminUser()) {
|
|
throw new NotFoundHttpException('Feed not active');
|
|
}
|
|
$feedRow['data'] = json_decode($feedRow['data'] ?? '{}', true);
|
|
$feedRow['pretty'] = (($feedRow['data']['format'] ?? 'xml') == 'xml_pretty') || $pretty || getAdminUser();
|
|
|
|
increaseMemoryLimit(1500);
|
|
ini_set('max_execution_time', 600);
|
|
ignore_user_abort(true);
|
|
|
|
// clean & disable output buffering
|
|
while (ob_get_level()) {
|
|
ob_end_clean();
|
|
}
|
|
|
|
return $this->outputFeed(
|
|
$this->feedLocator->getServiceByType($feedRow['type']),
|
|
$feedRow,
|
|
$limit,
|
|
isset($file),
|
|
(bool) getVal('skip_cache', null, false),
|
|
$format,
|
|
$updateDownload,
|
|
$date_from,
|
|
$date_to
|
|
);
|
|
} else {
|
|
$data = ['Feed ID' => $feedID, 'Hash' => $hash, 'IP' => $_SERVER['REMOTE_ADDR']];
|
|
if ($feedRow) {
|
|
$data['Type'] = $feedRow['type'];
|
|
$data['Name'] = $feedRow['name'];
|
|
$data['Active'] = $feedRow['active'];
|
|
$data['Language'] = $feedRow['id_language'] ?? 'independent';
|
|
$data['Currency'] = $feedRow['id_currency'] ?? 'independent';
|
|
}
|
|
throw new NotFoundHttpException('Feed not found');
|
|
}
|
|
}
|
|
|
|
public function outputFeed(
|
|
IFeed $feed,
|
|
array $feedRow,
|
|
?int $limit = null,
|
|
bool $file = false,
|
|
bool $skipCache = false,
|
|
$format = 'xml',
|
|
$updateDownload = true,
|
|
$date_from = null,
|
|
$date_to = null,
|
|
): Response {
|
|
if ($feed::getType() == 'deprecated') {
|
|
addActivityLog(ActivityLog::SEVERITY_ERROR, ActivityLog::TYPE_IMPORT,
|
|
"Stáhl se již ukončený (nekonfigurovatelný) feed '{$feedRow['name']}' (ID = {$feedRow['id']}).
|
|
Je nutné předělat feed na konfigurovatelný, stávající feed už se neaktualizuje.",
|
|
tags: [FeedsBundle::LOG_TAG_FEED]);
|
|
}
|
|
|
|
// use mocked session storage for feeds to avoid errors "Headers already sent by ..."
|
|
$this->requestStack->getCurrentRequest()?->setSession(
|
|
new Session(new MockArraySessionStorage())
|
|
);
|
|
|
|
$feedName = ['feed', $feedRow['id']];
|
|
|
|
$this->feedUtils->prepareContexts($feedRow);
|
|
|
|
$feedName[] = $this->languageContext->getActiveId();
|
|
$feedName[] = $this->currencyContext->getActiveId();
|
|
$feedName[] = $this->countryContext->getActiveId();
|
|
$feedName[] = $this->domainContext->getActiveId();
|
|
|
|
$feedName = implode('_', $feedName).'.'.$format;
|
|
|
|
// dates will differ between requests so we cant use cache
|
|
if ($date_from || $date_to) {
|
|
$skipCache = true;
|
|
}
|
|
|
|
// update feed info
|
|
if (!getAdminUser() && !$skipCache && $updateDownload) {
|
|
$this->updateSQL('feeds', ['last_download' => (new \DateTime())->format('Y-m-d H:i:s')], ['id' => $feedRow['id']]);
|
|
}
|
|
|
|
$response = new CachingStreamedResponse(function () use ($feed, $feedRow, $feedName, $limit, $format, $skipCache, $updateDownload) {
|
|
$uid = '';
|
|
if (is_null($limit)) {
|
|
$uid = uniqid('', true);
|
|
$this->logger->notice('Feed started generating ['.$feedRow['id'].'] "'.$feedName.'"', [
|
|
'uid' => $uid,
|
|
'id' => $feedRow['id'],
|
|
'cacheName' => $feedName,
|
|
'name' => $feedRow['name'],
|
|
'isAdmin' => getAdminUser() ? 'true' : 'false',
|
|
'skipCache' => $skipCache ? 'true' : 'false',
|
|
]);
|
|
}
|
|
$timeStart = getScriptTime();
|
|
|
|
$this->feedRenderer->render($feed, $feedRow, $limit, $format);
|
|
|
|
// update feed info
|
|
$timestamp = (new \DateTime())->format('Y-m-d H:i:s');
|
|
$updateData = [];
|
|
// if not cached and without limit
|
|
if (is_null($limit)) {
|
|
$updateData['regenerated'] = $timestamp;
|
|
}
|
|
if (!getAdminUser() && !$skipCache && $updateDownload) {
|
|
$updateData['last_download'] = $timestamp;
|
|
}
|
|
if (count($updateData)) {
|
|
$this->updateSQL('feeds', $updateData, ['id' => $feedRow['id']]);
|
|
}
|
|
|
|
if (is_null($limit)) {
|
|
$this->logger->notice('Feed finished ['.$feedRow['id'].'] "'.$feedName.'"', [
|
|
'uid' => $uid,
|
|
'id' => $feedRow['id'],
|
|
'cacheName' => $feedName,
|
|
'name' => $feedRow['name'],
|
|
'isAdmin' => getAdminUser() ? 'true' : 'false',
|
|
'skipCache' => $skipCache ? 'true' : 'false',
|
|
'format' => $format,
|
|
'duration' => getScriptTime() - $timeStart,
|
|
'peakMemory' => memory_get_peak_usage(true), // in bytes
|
|
]);
|
|
}
|
|
});
|
|
|
|
if ($limit) {
|
|
$response->setSkipCache(1);
|
|
$response->setIsLimited(true);
|
|
}
|
|
|
|
if ($skipCache) {
|
|
$response->setSkipCache(1);
|
|
}
|
|
|
|
if ($feedRow['expensive'] ?? false) {
|
|
$response->setExpensive();
|
|
} elseif (getAdminUser()) {
|
|
// skip cache for admins with non-expensive feeds only
|
|
$response->setSkipCache(1);
|
|
}
|
|
|
|
$response->setCacheName($feedName);
|
|
if (isset($feedRow['data']['ttl']) && is_numeric($feedRow['data']['ttl'])) {
|
|
// min TTL is 30 minutes
|
|
$ttl = max(60 * 30, 60 * $feedRow['data']['ttl']);
|
|
} else {
|
|
$ttl = $feed->getTTL();
|
|
}
|
|
$response->setTtl($ttl);
|
|
|
|
// get correct content type from formatter
|
|
$contentType = $this->feedUtils->getFeedFormatter($format)?->getContentType() ?: "text/{$format}";
|
|
|
|
// cannot use symfony Request::getMethod() nor getRealMethod() because Symfony\Component\HttpKernel\HttpCache\HttpCache::fetch() replaces HEAD with GET for some reason
|
|
if (($_SERVER['REQUEST_METHOD'] ?? '') === 'HEAD') {
|
|
$headResponse = new Response();
|
|
$headResponse->headers->set('Content-Type', $contentType);
|
|
$cacheFileName = $response->getCacheName();
|
|
if (file_exists($cacheFileName) && (time() - filemtime($cacheFileName)) < $ttl) {
|
|
$headResponse->headers->set('Content-Length', filesize($cacheFileName));
|
|
$headResponse->headers->set('Last-Modified', gmdate('D, d M Y H:i:s ', filemtime($cacheFileName)).'GMT');
|
|
} else {
|
|
$headResponse->headers->set('Last-Modified', gmdate('D, d M Y H:i:s ', time()).'GMT');
|
|
}
|
|
|
|
return $headResponse;
|
|
}
|
|
|
|
$response->initialize();
|
|
|
|
$response->headers->set('Content-Type', $contentType);
|
|
if ($file) {
|
|
$response->headers->set('Content-Description', 'File Transfer');
|
|
$response->headers->set('Content-Disposition', "attachment; filename={$feedName}");
|
|
$response->headers->set('Connection', 'Keep-Alive');
|
|
$response->headers->set('Expires', '0');
|
|
}
|
|
|
|
return $response;
|
|
}
|
|
}
|