vendor/pimcore/pimcore/lib/Targeting/Storage/CookieStorage.php line 214

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4.  * Pimcore
  5.  *
  6.  * This source file is available under two different licenses:
  7.  * - GNU General Public License version 3 (GPLv3)
  8.  * - Pimcore Enterprise License (PEL)
  9.  * Full copyright and license information is available in
  10.  * LICENSE.md which is distributed with this source code.
  11.  *
  12.  * @copyright  Copyright (c) Pimcore GmbH (http://www.pimcore.org)
  13.  * @license    http://www.pimcore.org/license     GPLv3 and PEL
  14.  */
  15. namespace Pimcore\Targeting\Storage;
  16. use Pimcore\Targeting\Model\VisitorInfo;
  17. use Pimcore\Targeting\Storage\Cookie\CookieSaveHandlerInterface;
  18. use Pimcore\Targeting\Storage\Traits\TimestampsTrait;
  19. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  20. use Symfony\Component\HttpFoundation\RequestStack;
  21. use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
  22. use Symfony\Component\HttpKernel\KernelEvents;
  23. /**
  24.  * Stores data as cookie in the client's browser
  25.  *
  26.  * NOTE: using this storage without signed cookies is inherently insecure and can open vulnerabilities by injecting
  27.  * malicious data into the client cookie. Use only for testing!
  28.  */
  29. class CookieStorage implements TargetingStorageInterface
  30. {
  31.     use TimestampsTrait;
  32.     const COOKIE_NAME_SESSION '_pc_tss'// tss = targeting session storage
  33.     const COOKIE_NAME_VISITOR '_pc_tvs'// tvs = targeting visitor storage
  34.     const STORAGE_KEY_CREATED_AT '_c';
  35.     const STORAGE_KEY_UPDATED_AT '_u';
  36.     /**
  37.      * @var CookieSaveHandlerInterface
  38.      */
  39.     private $saveHandler;
  40.     /**
  41.      * @var RequestStack
  42.      */
  43.     private $requestStack;
  44.     /**
  45.      * @var EventDispatcherInterface
  46.      */
  47.     private $eventDispatcher;
  48.     /**
  49.      * @var array
  50.      */
  51.     private $data = [];
  52.     /**
  53.      * @var bool
  54.      */
  55.     private $changed false;
  56.     /**
  57.      * @var array
  58.      */
  59.     private $scopeCookieMapping = [
  60.         self::SCOPE_SESSION => self::COOKIE_NAME_SESSION,
  61.         self::SCOPE_VISITOR => self::COOKIE_NAME_VISITOR,
  62.     ];
  63.     public function __construct(
  64.         CookieSaveHandlerInterface $saveHandler,
  65.         RequestStack $requestHelper,
  66.         EventDispatcherInterface $eventDispatcher
  67.     ) {
  68.         $this->saveHandler $saveHandler;
  69.         $this->requestStack $requestHelper;
  70.         $this->eventDispatcher $eventDispatcher;
  71.     }
  72.     public function all(VisitorInfo $visitorInfostring $scope): array
  73.     {
  74.         $this->loadData($visitorInfo$scope);
  75.         $blacklist = [
  76.             self::STORAGE_KEY_CREATED_AT,
  77.             self::STORAGE_KEY_UPDATED_AT,
  78.             self::STORAGE_KEY_META_ENTRY,
  79.         ];
  80.         // filter internal values
  81.         $result array_filter($this->data[$scope], function ($key) use ($blacklist) {
  82.             return !in_array($key$blacklisttrue);
  83.         }, ARRAY_FILTER_USE_KEY);
  84.         return $result;
  85.     }
  86.     public function has(VisitorInfo $visitorInfostring $scopestring $name): bool
  87.     {
  88.         $this->loadData($visitorInfo$scope);
  89.         return isset($this->data[$scope][$name]);
  90.     }
  91.     public function get(VisitorInfo $visitorInfostring $scopestring $name$default null)
  92.     {
  93.         $this->loadData($visitorInfo$scope);
  94.         if (isset($this->data[$scope][$name])) {
  95.             return $this->data[$scope][$name];
  96.         }
  97.         return $default;
  98.     }
  99.     public function set(VisitorInfo $visitorInfostring $scopestring $name$value)
  100.     {
  101.         $this->loadData($visitorInfo$scope);
  102.         $this->data[$scope][$name] = $value;
  103.         $this->updateTimestamps($scope);
  104.         $this->addSaveListener($visitorInfo);
  105.     }
  106.     public function clear(VisitorInfo $visitorInfostring $scope null)
  107.     {
  108.         if (null === $scope) {
  109.             $this->data null;
  110.         } else {
  111.             if (isset($this->data[$scope])) {
  112.                 unset($this->data[$scope]);
  113.             }
  114.         }
  115.         $this->addSaveListener($visitorInfo);
  116.     }
  117.     public function migrateFromStorage(TargetingStorageInterface $storageVisitorInfo $visitorInfostring $scope)
  118.     {
  119.         $values $storage->all($visitorInfo$scope);
  120.         $this->loadData($visitorInfo$scope);
  121.         foreach ($values as $name => $value) {
  122.             $this->data[$scope][$name] = $value;
  123.         }
  124.         // update created/updated at from storage
  125.         $this->updateTimestamps(
  126.             $scope,
  127.             $storage->getCreatedAt($visitorInfo$scope),
  128.             $storage->getUpdatedAt($visitorInfo$scope)
  129.         );
  130.         $this->addSaveListener($visitorInfo);
  131.     }
  132.     public function getCreatedAt(VisitorInfo $visitorInfostring $scope)
  133.     {
  134.         $this->loadData($visitorInfo$scope);
  135.         if (!isset($this->data[$scope][self::STORAGE_KEY_CREATED_AT])) {
  136.             return null;
  137.         }
  138.         return \DateTimeImmutable::createFromFormat('U', (string)$this->data[$scope][self::STORAGE_KEY_CREATED_AT]);
  139.     }
  140.     public function getUpdatedAt(VisitorInfo $visitorInfostring $scope)
  141.     {
  142.         $this->loadData($visitorInfo$scope);
  143.         if (!isset($this->data[$scope][self::STORAGE_KEY_UPDATED_AT])) {
  144.             return null;
  145.         }
  146.         return \DateTimeImmutable::createFromFormat('U', (string)$this->data[$scope][self::STORAGE_KEY_CREATED_AT]);
  147.     }
  148.     private function loadData(VisitorInfo $visitorInfostring $scope): array
  149.     {
  150.         if (!isset($this->scopeCookieMapping[$scope])) {
  151.             throw new \InvalidArgumentException(sprintf('Scope "%s" is not supported'$scope));
  152.         }
  153.         if (isset($this->data[$scope]) && null !== $this->data[$scope]) {
  154.             return $this->data[$scope];
  155.         }
  156.         $request $visitorInfo->getRequest();
  157.         $this->data[$scope] = $this->saveHandler->load($request$scope$this->scopeCookieMapping[$scope]);
  158.         return $this->data[$scope];
  159.     }
  160.     private function addSaveListener(VisitorInfo $visitorInfo)
  161.     {
  162.         if ($this->changed) {
  163.             return;
  164.         }
  165.         $this->changed true;
  166.         // adds a response listener setting the storage cookie
  167.         $listener = function (FilterResponseEvent $event) use ($visitorInfo) {
  168.             // only handle event for the visitor info which triggered the save
  169.             if ($event->getRequest() !== $visitorInfo->getRequest()) {
  170.                 return;
  171.             }
  172.             $response $event->getResponse();
  173.             foreach (array_keys($this->scopeCookieMapping) as $scope) {
  174.                 $this->saveHandler->save(
  175.                     $response,
  176.                     $scope,
  177.                     $this->scopeCookieMapping[$scope],
  178.                     $this->expiryFor($scope),
  179.                     $this->data[$scope] ?? null
  180.                 );
  181.             }
  182.         };
  183.         $this->eventDispatcher->addListener(KernelEvents::RESPONSE$listener);
  184.     }
  185.     private function updateTimestamps(
  186.         string $scope,
  187.         \DateTimeInterface $createdAt null,
  188.         \DateTimeInterface $updatedAt null
  189.     ) {
  190.         $timestamps $this->normalizeTimestamps($createdAt$updatedAt);
  191.         if (!isset($this->data[$scope][self::STORAGE_KEY_CREATED_AT])) {
  192.             $this->data[$scope][self::STORAGE_KEY_CREATED_AT] = $timestamps['createdAt']->getTimestamp();
  193.             $this->data[$scope][self::STORAGE_KEY_UPDATED_AT] = $timestamps['updatedAt']->getTimestamp();
  194.         } else {
  195.             $this->data[$scope][self::STORAGE_KEY_UPDATED_AT] = $timestamps['updatedAt']->getTimestamp();
  196.         }
  197.     }
  198.     protected function expiryFor(string $scope)
  199.     {
  200.         $expiry 0;
  201.         if (self::SCOPE_VISITOR === $scope) {
  202.             $expiry = new \DateTime('+1 year');
  203.         } elseif (self::SCOPE_SESSION === $scope) {
  204.             $expiry = new \DateTime('+30 minutes');
  205.         }
  206.         return $expiry;
  207.     }
  208. }