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

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