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

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.     public function get(VisitorInfo $visitorInfostring $scopestring $name$default null)
  85.     {
  86.         $this->loadData($visitorInfo$scope);
  87.         if (isset($this->data[$scope][$name])) {
  88.             return $this->data[$scope][$name];
  89.         }
  90.         return $default;
  91.     }
  92.     public function set(VisitorInfo $visitorInfostring $scopestring $name$value)
  93.     {
  94.         $this->loadData($visitorInfo$scope);
  95.         $this->data[$scope][$name] = $value;
  96.         $this->updateTimestamps($scope);
  97.         $this->addSaveListener($visitorInfo);
  98.     }
  99.     public function clear(VisitorInfo $visitorInfostring $scope null)
  100.     {
  101.         if (null === $scope) {
  102.             $this->data = [];
  103.         } else {
  104.             if (isset($this->data[$scope])) {
  105.                 unset($this->data[$scope]);
  106.             }
  107.         }
  108.         $this->addSaveListener($visitorInfo);
  109.     }
  110.     public function migrateFromStorage(TargetingStorageInterface $storageVisitorInfo $visitorInfostring $scope)
  111.     {
  112.         $values $storage->all($visitorInfo$scope);
  113.         $this->loadData($visitorInfo$scope);
  114.         foreach ($values as $name => $value) {
  115.             $this->data[$scope][$name] = $value;
  116.         }
  117.         // update created/updated at from storage
  118.         $this->updateTimestamps(
  119.             $scope,
  120.             $storage->getCreatedAt($visitorInfo$scope),
  121.             $storage->getUpdatedAt($visitorInfo$scope)
  122.         );
  123.         $this->addSaveListener($visitorInfo);
  124.     }
  125.     public function getCreatedAt(VisitorInfo $visitorInfostring $scope)
  126.     {
  127.         $this->loadData($visitorInfo$scope);
  128.         if (!isset($this->data[$scope][self::STORAGE_KEY_CREATED_AT])) {
  129.             return null;
  130.         }
  131.         return \DateTimeImmutable::createFromFormat('U', (string)$this->data[$scope][self::STORAGE_KEY_CREATED_AT]);
  132.     }
  133.     public function getUpdatedAt(VisitorInfo $visitorInfostring $scope)
  134.     {
  135.         $this->loadData($visitorInfo$scope);
  136.         if (!isset($this->data[$scope][self::STORAGE_KEY_UPDATED_AT])) {
  137.             return null;
  138.         }
  139.         return \DateTimeImmutable::createFromFormat('U', (string)$this->data[$scope][self::STORAGE_KEY_CREATED_AT]);
  140.     }
  141.     private function loadData(VisitorInfo $visitorInfostring $scope): array
  142.     {
  143.         if (!isset($this->scopeCookieMapping[$scope])) {
  144.             throw new \InvalidArgumentException(sprintf('Scope "%s" is not supported'$scope));
  145.         }
  146.         if (isset($this->data[$scope]) && null !== $this->data[$scope]) {
  147.             return $this->data[$scope];
  148.         }
  149.         $request $visitorInfo->getRequest();
  150.         $this->data[$scope] = $this->saveHandler->load($request$scope$this->scopeCookieMapping[$scope]);
  151.         return $this->data[$scope];
  152.     }
  153.     private function addSaveListener(VisitorInfo $visitorInfo)
  154.     {
  155.         if ($this->changed) {
  156.             return;
  157.         }
  158.         $this->changed true;
  159.         // adds a response listener setting the storage cookie
  160.         $listener = function (ResponseEvent $event) use ($visitorInfo) {
  161.             // only handle event for the visitor info which triggered the save
  162.             if ($event->getRequest() !== $visitorInfo->getRequest()) {
  163.                 return;
  164.             }
  165.             $response $event->getResponse();
  166.             foreach (array_keys($this->scopeCookieMapping) as $scope) {
  167.                 $this->saveHandler->save(
  168.                     $response,
  169.                     $scope,
  170.                     $this->scopeCookieMapping[$scope],
  171.                     $this->expiryFor($scope),
  172.                     $this->data[$scope] ?? null
  173.                 );
  174.             }
  175.         };
  176.         $this->eventDispatcher->addListener(KernelEvents::RESPONSE$listener);
  177.     }
  178.     private function updateTimestamps(
  179.         string $scope,
  180.         \DateTimeInterface $createdAt null,
  181.         \DateTimeInterface $updatedAt null
  182.     ) {
  183.         $timestamps $this->normalizeTimestamps($createdAt$updatedAt);
  184.         if (!isset($this->data[$scope][self::STORAGE_KEY_CREATED_AT])) {
  185.             $this->data[$scope][self::STORAGE_KEY_CREATED_AT] = $timestamps['createdAt']->getTimestamp();
  186.             $this->data[$scope][self::STORAGE_KEY_UPDATED_AT] = $timestamps['updatedAt']->getTimestamp();
  187.         } else {
  188.             $this->data[$scope][self::STORAGE_KEY_UPDATED_AT] = $timestamps['updatedAt']->getTimestamp();
  189.         }
  190.     }
  191.     protected function expiryFor(string $scope)
  192.     {
  193.         $expiry 0;
  194.         if (self::SCOPE_VISITOR === $scope) {
  195.             $expiry = new \DateTime('+1 year');
  196.         } elseif (self::SCOPE_SESSION === $scope) {
  197.             $expiry = new \DateTime('+30 minutes');
  198.         }
  199.         return $expiry;
  200.     }
  201. }