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; } }