src/Services/ABTestManager.php line 288

Open in your IDE?
  1. <?php
  2. /**
  3.  * Created by PhpStorm.
  4.  * User: jibi
  5.  * Date: 19/04/2018
  6.  * Time: 15:46
  7.  */
  8. namespace App\Services;
  9. use App\Entity\Main\Cart;
  10. use App\Entity\Main\Site;
  11. use App\Entity\Main\SiteABTest;
  12. use App\Entity\Main\SitePackTemplate;
  13. use App\Entity\Main\Tracking;
  14. use App\Entity\Main\TrackingHasABTest;
  15. use App\Entity\Main\User;
  16. use App\EventListener\Main\DebugListener;
  17. use Doctrine\Common\Collections\ArrayCollection;
  18. use Doctrine\Common\Collections\Collection;
  19. use Doctrine\ORM\EntityManagerInterface;
  20. use Doctrine\ORM\EntityRepository;
  21. use Psr\Log\LoggerInterface;
  22. use Symfony\Component\HttpFoundation\Request;
  23. use Symfony\Component\HttpFoundation\RequestStack;
  24. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  25. class ABTestManager
  26. {
  27.     const
  28.         SESSION_IDENTIFIER 'client_test'
  29.     ;
  30.     private $activeTests;
  31.     private mixed $clientTests null;
  32.     private ?Request $request;
  33.     private EntityRepository $siteABTestsRepo;
  34.     /**
  35.      * @var array|mixed
  36.      */
  37.     private mixed $forcedABTests;
  38.     public function __construct(
  39.         private RequestStack $requestStack,
  40.         private EntityManagerInterface $entityManager,
  41.         private LoggerInterface $logger,
  42.         private $redisClient
  43.     ) {
  44.         $this->request $this->requestStack->getCurrentRequest();
  45.         if ($this->request) {
  46.             $this->clientTests $this->requestStack->getSession()->get(self::SESSION_IDENTIFIER);
  47.             $this->forcedABTests $this->requestStack->getSession()->get(DebugListener::DEBUG_DATA_SESSION)[DebugListener::DEBUG_FORCED_ABTESTS] ?? [];
  48.         }
  49.         // If some AB tests are forced, we use them here.
  50.         $this->siteABTestsRepo $entityManager->getRepository(SiteABTest::class);
  51.     }
  52.     /**
  53.      * @param Tracking $tracking
  54.      * @return array|mixed|null
  55.      */
  56.     public function getTestsForTracking(Tracking $tracking)
  57.     {
  58.         if (null == $this->clientTests) {
  59.             try {
  60.                 $this->clientTests $tracking->getAbTests();
  61.             } catch (\Exception $e) {
  62.                 $this->logger->critical("Could not access tracking ABTESTS with the following exception : {$e->getMessage()}");
  63.                 $this->clientTests = [];
  64.             }
  65.         }
  66.         return $this->clientTests;
  67.     }
  68.     /**
  69.      * @return ArrayCollection|null
  70.      */
  71.     public function getActiveTests()
  72.     {
  73.         if (count($this->activeTests)) {
  74.             return $this->activeTests;
  75.         }
  76.     }
  77.     /**
  78.      * $registered will only create ab test for registered clients.
  79.      * it shouldn't be used for anonymous users.
  80.      *
  81.      * @param Tracking $tracking
  82.      * @param Site $site
  83.      * @param string $testType
  84.      * @param bool $save
  85.      * @throws \Exception
  86.      */
  87.     public function generateTestsForTracking(Tracking $trackingSite $site$testType SiteABTest::TYPE_GENERAL$save true)
  88.     {
  89.         if (count($tracking->getAbTests()) && !in_array($testType, [SiteABTest::TYPE_REGISTEREDSiteABTest::TYPE_CART_SWITCH])) {
  90.             $this->logger->critical("Trying to add new ab test to an existing tracking.");
  91.             return;
  92.         }
  93.         $this->loadActiveTests($site);
  94.         $needSave false;
  95.         // create new tests for this tracking
  96.         if (count($this->activeTests) && !in_array($testType, [SiteABTest::TYPE_REGISTEREDSiteABTest::TYPE_CART_SWITCH])) {
  97.             /** @var SiteABTest $activeTest */
  98.             foreach ($this->activeTests as $activeTest) {
  99.                 if ($activeTest->isActive() && $activeTest->isGeneral() && $this->isEligibleForTest($tracking$activeTest)) {
  100.                     $version $this->getVersionForTest($activeTest);
  101.                     // For the test for new design, Chris want us to keep the version of the test in the tracking o3 field.
  102.                     if ($activeTest->getAlias() == SiteABTest::BRU_DESIGN_V_ALIAS) {
  103.                         $tracking->setO3($version);
  104.                     }
  105.                     $newTest = new TrackingHasABTest();
  106.                     $newTest->setSiteAbTest($activeTest);
  107.                     $newTest->setTracking($tracking);
  108.                     $newTest->setVersion($version);
  109.                     $this->entityManager->persist($newTest);
  110.                     $tracking->addAbTest($newTest);
  111.                     $needSave true;
  112.                 }
  113.             }
  114.             if ($needSave && $save) {
  115.                 $this->entityManager->flush();
  116.             }
  117.             return;
  118.         }
  119.         // if this is for registered user, we create only registered abTests
  120.         // We create those test ONLY if there is not already a registered user for this tracking.
  121.         if (count($this->activeTests) && $testType == SiteABTest::TYPE_REGISTERED && count($tracking->getUsers()) < 2) {
  122.             /** @var SiteABTest $activeTest */
  123.             foreach ($this->activeTests as $activeTest) {
  124.                 if ($activeTest->isActive() && $activeTest->isForRegisteredUsers() && $this->isEligibleForTest($tracking$activeTest)) {
  125.                     $version $this->getVersionForTest($activeTest);
  126.                     $newTest = new TrackingHasABTest();
  127.                     $newTest->setSiteAbTest($activeTest);
  128.                     $newTest->setTracking($tracking);
  129.                     $newTest->setVersion($version);
  130.                     $this->entityManager->persist($newTest);
  131.                     $tracking->addAbTest($newTest);
  132.                     $needSave true;
  133.                 }
  134.             }
  135.             if ($needSave && $save) {
  136.                 $this->entityManager->flush();
  137.             }
  138.             return;
  139.         }
  140.         // if this is for cart switch, we create only cartSwitch abTests
  141.         // We create those test ONLY if there is not already a registered user for this tracking.
  142.         if (count($this->activeTests) && $testType == SiteABTest::TYPE_CART_SWITCH && count($tracking->getUsers()) < 2) {
  143.             /** @var SiteABTest $activeTest */
  144.             foreach ($this->activeTests as $activeTest) {
  145.                 if ($activeTest->isActive() && $activeTest->isForCartSwitch() && $this->isEligibleForTest($tracking$activeTest)) {
  146.                     $version $this->getVersionForTest($activeTest);
  147.                     $newTest = new TrackingHasABTest();
  148.                     $newTest->setSiteAbTest($activeTest);
  149.                     $newTest->setTracking($tracking);
  150.                     $newTest->setVersion($version);
  151.                     $this->entityManager->persist($newTest);
  152.                     $tracking->addAbTest($newTest);
  153.                     $needSave true;
  154.                 }
  155.             }
  156.             if ($needSave && $save) {
  157.                 $this->entityManager->flush();
  158.             }
  159.             return;
  160.         }
  161.     }
  162.     /**
  163.      * Check if a cart can receive some switch abtest OR a specific one if a siteABtest is provided.
  164.      *
  165.      * @param Cart $cart
  166.      * @param Site $site
  167.      * @param SiteABTest|null $siteABTest
  168.      * @return bool
  169.      */
  170.     public function cartCanReceiveSwitchTest(Cart $cartSite $siteSiteABTest $siteABTest null)
  171.     {
  172.         // if this cart has no user, he can't receive an ABtest.
  173.         if (!$cart->getUser() instanceof User) {
  174.             return false;
  175.         }
  176.         // Carts with custom packs don't receive any switch AB test.
  177.         if ($cart->isWithCustomPack()) {
  178.             return false;
  179.         }
  180.         // a user that already purchased, can't get a switch test
  181.         if ($cart->getUser() instanceof User && $cart->getUser()->getLastOrder() instanceof Cart) {
  182.             return false;
  183.         }
  184.         if ($siteABTest instanceof SiteABTest) {
  185.             if (!$siteABTest->isForCartSwitch() || !$siteABTest->isActive()) {
  186.                 return false;
  187.             }
  188.             $abTests = [$siteABTest];
  189.         } else {
  190.             $this->loadActiveTests($site);
  191.             // If not active Test, then no :)
  192.             if (!count($this->activeTests)) {
  193.                 return false;
  194.             }
  195.             $abTests $this->activeTests;
  196.         }
  197.         // ok so if there is an ABtest for cartSwitch that was created before the registration of this user, we can apply it.
  198.         /** @var SiteABTest $activeTest */
  199.         foreach ($abTests as $activeTest) {
  200.             if ($activeTest->isActive() && $activeTest->isForCartSwitch() && $cart->getUser()->getCreationStamp() >= $activeTest->getDateStarted()) {
  201.                 return true;
  202.             }
  203.         }
  204.         return false;
  205.     }
  206.     /**
  207.      * @param User $user
  208.      * @param Site $site
  209.      * @param SiteABTest|null $siteABTest
  210.      * @return bool
  211.      */
  212.     public function userCanReceiveRegisterTest(User $userSite $siteSiteABTest $siteABTest null)
  213.     {
  214.         if ($siteABTest instanceof SiteABTest) {
  215.             if (!$siteABTest->isForRegisteredUsers() || !$siteABTest->isActive()) {
  216.                 return false;
  217.             }
  218.             $abTests = [$siteABTest];
  219.         } else {
  220.             $this->loadActiveTests($site);
  221.             // If not active Test, then no :)
  222.             if (!count($this->activeTests)) {
  223.                 return false;
  224.             }
  225.             $abTests $this->activeTests;
  226.         }
  227.         // When a user is logged in, his tracking is ALWAYS the same than the current tracking, so we can use his tracking.
  228.         $tracking $user->getTracking();
  229.         // ok so if there is an ABtest for register that was created before the registration of this user, we can apply it.
  230.         /** @var SiteABTest $activeTest */
  231.         foreach ($abTests as $activeTest) {
  232.             if ($activeTest->isActive() && $activeTest->isForRegisteredUsers() && $user->getCreationStamp() >= $activeTest->getDateStarted() && $this->userIsEligibleForTest($user$activeTest)) {
  233.                 return true;
  234.             }
  235.         }
  236.         return false;
  237.     }
  238.     /**
  239.      * @param Tracking $tracking
  240.      * @param $version
  241.      * @return bool
  242.      */
  243.     public function hasTest(Tracking $tracking$version)
  244.     {
  245.         $hasVersion false;
  246.         $clientTests $this->getTestsForTracking($tracking);
  247.         // If some forced test are provided in the session, we use only those.
  248.         $debugData $this->requestStack->getSession()->get(DebugListener::DEBUG_DATA_SESSION);
  249.         if (!empty($debugData[DebugListener::DEBUG_FORCED_ABTESTS])) {
  250.             $hasTest false;
  251.             if (is_array($version)) {
  252.                 foreach ($version as $loopVersion) {
  253.                     if (in_array($loopVersion$debugData[DebugListener::DEBUG_FORCED_ABTESTS])) {
  254.                         $hasTest true;
  255.                         break;
  256.                     }
  257.                 }
  258.             } else {
  259.                 if (in_array($version$debugData[DebugListener::DEBUG_FORCED_ABTESTS])) {
  260.                     $hasTest true;
  261.                 }
  262.             }
  263.             return $hasTest;
  264.         }
  265.         if (count($this->clientTests) && is_string($version)) {
  266.             /** @var TrackingHasABTest $clientTest */
  267.             foreach ($clientTests as $clientTest) {
  268.                 if ($clientTest->getVersion() == $version && $clientTest->getSiteABTest()->isActive()) {
  269.                     $hasVersion true;
  270.                 }
  271.             }
  272.         } elseif (count($this->clientTests) && is_array($version) && count($version)) {
  273.             /** @var TrackingHasABTest $clientTest */
  274.             foreach ($clientTests as $clientTest) {
  275.                 foreach ($version as $string) {
  276.                     if ($clientTest->getVersion() == $string && $clientTest->getSiteABTest()->isActive()) {
  277.                         $hasVersion true;
  278.                     }
  279.                 }
  280.                 if ($hasVersion) {
  281.                     break;
  282.                 }
  283.             }
  284.         }
  285.         return $hasVersion;
  286.     }
  287.     /**
  288.      * @param Cart $cart
  289.      * @param $version
  290.      * @return bool
  291.      */
  292.     public function cartHasTest(Cart $cart$version)
  293.     {
  294.         $hasVersion false;
  295.         $cartTests $cart->getAbTests();
  296.         // If some forced test are provided in the session, we use only those.
  297.         $debugData $this->requestStack->getSession()->get(DebugListener::DEBUG_DATA_SESSION);
  298.         if (!empty($debugData[DebugListener::DEBUG_FORCED_ABTESTS])) {
  299.             $hasTest false;
  300.             if (is_array($version)) {
  301.                 foreach ($version as $loopVersion) {
  302.                     if (in_array($loopVersion$debugData[DebugListener::DEBUG_FORCED_ABTESTS])) {
  303.                         $hasTest true;
  304.                         break;
  305.                     }
  306.                 }
  307.             } else {
  308.                 if (in_array($version$debugData[DebugListener::DEBUG_FORCED_ABTESTS])) {
  309.                     $hasTest true;
  310.                 }
  311.             }
  312.             return $hasTest;
  313.         }
  314.         if (count($cartTests) && is_string($version)) {
  315.             /** @var TrackingHasABTest $cartTest */
  316.             foreach ($cartTests as $cartTest) {
  317.                 if ($cartTest->getVersion() == $version && $cartTest->getSiteABTest()->isActive()) {
  318.                     $hasVersion true;
  319.                 }
  320.             }
  321.         } elseif (count($cartTests) && is_array($version) && count($version)) {
  322.             /** @var TrackingHasABTest $clientTest */
  323.             foreach ($cartTests as $cartTest) {
  324.                 foreach ($version as $string) {
  325.                     if ($cartTest->getVersion() == $string && $cartTest->getSiteABTest()->isActive()) {
  326.                         $hasVersion true;
  327.                     }
  328.                 }
  329.                 if ($hasVersion) {
  330.                     break;
  331.                 }
  332.             }
  333.         }
  334.         return $hasVersion;
  335.     }
  336.     /**
  337.      * This is used for the free shipping AB test.
  338.      * @return mixed
  339.      */
  340.     public function getFreeShippingTestValues()
  341.     {
  342.         return json_decode('{"AT":{"0-+":0},"BE":{"0-+":0},"BL":{"0-+":0},"CA":{"0-+":0},"CH":{"0-+":0},"CZ":{"0-+":0},"DE":{"0-+":0},"DK":{"0-+":0},"ES":{"0-+":0},"FR":{"0-+":0},"GF":{"0-+":0},"GP":{"0-+":0},"GR":{"0-+":0},"HU":{"0-+":0},"IE":{"0-+":0},"IT":{"0-+":0},"LU":{"0-+":0},"MF":{"0-+":0},"MQ":{"0-+":0},"NC":{"0-+":0},"NL":{"0-+":0},"PF":{"0-+":0},"PL":{"0-+":0},"PM":{"0-+":0},"PT":{"0-+":0},"RE":{"0-+":0},"RO":{"0-+":0},"SE":{"0-+":0},"UK":{"0-+":0},"US":{"0-+":0},"WF":{"0-+":0},"YT":{"0-+":0}}'true);
  343.     }
  344.     /**
  345.      * @param Tracking $tracking
  346.      * @param array $packList
  347.      * @return array
  348.      */
  349.     public function getPacksForTracking(Tracking $tracking$packList = [])
  350.     {
  351.         $abTestsList $this->getTestsForTracking($tracking);
  352.         $abTestVersionArray $this->getABTestsVersionsFromList($abTestsList);
  353.         return $this->filterPacksWithABTests($abTestVersionArray$packList);
  354.     }
  355.     /**
  356.      * Get an array of the versions of ab tests of a tracking
  357.      * @param Tracking $tracking
  358.      * @param false $checkActiveTests
  359.      * @return array
  360.      */
  361.     public function getTrackingABTestsVersions(Tracking $tracking$checkActiveTests false)
  362.     {
  363.         $trackingABTestVersions $tracking->getAbTests();
  364.         return $this->getABTestsVersionsFromList($trackingABTestVersions$checkActiveTests);
  365.     }
  366.     /**
  367.      * @param $abTestAlias
  368.      * @return bool
  369.      */
  370.     public function siteABTestIsActive($abTestAlias): bool
  371.     {
  372.         $siteABTest =  $this->siteABTestsRepo->findOneBy(['alias' => $abTestAlias]);
  373.         if (!$siteABTest instanceof SiteABTest) {
  374.             return false;
  375.         }
  376.         if (!$siteABTest->isActive()) {
  377.             return false;
  378.         }
  379.         return true;
  380.     }
  381.     /**
  382.      * @param array $abTestsList
  383.      * @param array $packsList
  384.      * @return array
  385.      */
  386.     protected function filterPacksWithABTests(array $abTestsList, array $packsList)
  387.     {
  388.         $allPacks = [];
  389.         $exclusivePacks = [];
  390.         /** @var SitePackTemplate $pack */
  391.         foreach ($packsList as $pack) {
  392.             $packAbTests $pack->getAbTestVersion();
  393.             $found false;
  394.             foreach ($packAbTests as $abTestName => $abTestData) {
  395.                 if (in_array($abTestName$abTestsList)) {
  396.                     $found true;
  397.                     if (SitePackTemplate::AB_TEST_EXCLUSIVE === $abTestData['type']) {
  398.                         $exclusivePacks[] = $pack;
  399.                     } else {
  400.                         $allPacks[] = $pack;
  401.                     }
  402.                 }
  403.             }
  404.             if (!$found && empty($packAbTests)) {
  405.                 $allPacks[] = $pack;
  406.             }
  407.         }
  408.         // from here we choose what pack he will receive. If there is a single exclusive pack then we
  409.         // just take the exclusive ones.
  410.         if (count($exclusivePacks)) {
  411.             $packsToReturn $exclusivePacks;
  412.         } else {
  413.             $packsToReturn $allPacks;
  414.         }
  415.         return $packsToReturn;
  416.     }
  417.     /**
  418.      * @param Collection $abTestsList
  419.      * @param false $checkActiveTests
  420.      * @return array
  421.      */
  422.     protected function getABTestsVersionsFromList(Collection $abTestsList$checkActiveTests false)
  423.     {
  424.         $abTestVersionsArray = [];
  425.         if (count($abTestsList)) {
  426.             foreach ($abTestsList as $abTest) {
  427.                 if ($checkActiveTests) {
  428.                     if ($abTest->getSiteABTest()->isActive()) {
  429.                         $abTestVersionsArray[] = $abTest->getVersion();
  430.                     }
  431.                 } else {
  432.                     $abTestVersionsArray[] = $abTest->getVersion();
  433.                 }
  434.             }
  435.         }
  436.         return $abTestVersionsArray;
  437.     }
  438.     /**
  439.      * Choose and return the version for an AB test.
  440.      * this function update the test stats too.
  441.      *
  442.      * @param SiteABTest $siteABTest
  443.      * @return string
  444.      * @throws \Exception
  445.      */
  446.     private function getVersionForTest(SiteABTest $siteABTest)
  447.     {
  448.         $alias $siteABTest->getAlias();
  449.         $version $this->selectVersion($siteABTest);
  450.         $this->addSelectedClient($siteABTest$version);
  451.         return "{$alias}_{$version}";
  452.     }
  453.     /**
  454.      * This function will attribute a version for the test.
  455.      * There is two way to assign a version :
  456.      * 1 - random, we take the chances and we assign randomly
  457.      * 2 - deterministic, if the config is in "closing_mode" (put true in json config) we fill the missing options
  458.      * depending on the target %
  459.      *
  460.      * @param SiteABTest $siteABTest
  461.      * @return int|string
  462.      */
  463.     private function selectVersion(SiteABTest $siteABTest)
  464.     {
  465.         $actualStats $this->getStatsFromRedis($siteABTest);
  466.         $config $siteABTest->getConfig();
  467.         if (!isset($config['closing_mode']) or !$config['closing_mode']) {
  468.             $selectedVersion $this->getRandomVersion($actualStats$siteABTest);
  469.         } else {
  470.             $selectedVersion $this->getDeterministicVersion($actualStats$siteABTest);
  471.             // if we couldn't determine a version, choose a random one.
  472.             if (null === $selectedVersion) {
  473.                 $selectedVersion $this->getRandomVersion($actualStats$siteABTest);
  474.             }
  475.         }
  476.         return $selectedVersion;
  477.     }
  478.     /**
  479.      * @param SiteABTest $siteABTest
  480.      * @param $selectedVersion
  481.      * @param bool $save
  482.      * @throws \Exception
  483.      */
  484.     private function addSelectedClient(SiteABTest $siteABTest$selectedVersion$save true)
  485.     {
  486.         // get updated stats from redis.
  487.         $actualStats $this->getStatsFromRedis($siteABTest);
  488.         $actualStats['total']++;
  489.         $actualStats['options'][$selectedVersion]['count']++;
  490.         foreach ($actualStats['options'] as $version => $data) {
  491.             if (== $data['count']) {
  492.                 $actualStats['options'][$version]['percent'] = 0;
  493.             } else {
  494.                 $percent = ($actualStats['options'][$version]['count'] / $actualStats['total']) * 100;
  495.                 $actualStats['options'][$version]['percent'] = round($percent);
  496.             }
  497.         }
  498.         // save them back to redis just after.
  499.         $this->setStatsToRedis($actualStats$siteABTest);
  500.         // save every 20 counter in the db. (to prevent db overload we use redis as main counter.)
  501.         // or if test must be ended.
  502.         if (($save && == ($actualStats['total'] % 20)) or $this->checkTestIsEnded($actualStats$siteABTest)) {
  503.             $siteABTest->setStats($actualStats);
  504.             $this->entityManager->flush();
  505.         }
  506.     }
  507.     /**
  508.      * @param $actualStats
  509.      * @param SiteABTest $siteABTest
  510.      * @return bool
  511.      * @throws \Exception
  512.      */
  513.     private function checkTestIsEnded($actualStatsSiteABTest $siteABTest)
  514.     {
  515.         $now = new \DateTime();
  516.         $maxTest $siteABTest->getConfig()['count_end'];
  517.         $endDate $siteABTest->getConfig()['date_end'];
  518.         $ended false;
  519.         if (null != $maxTest && $actualStats['total'] >= $maxTest) {
  520.             $ended true;
  521.         } elseif (null != $endDate) {
  522.             $endDate = new \DateTime($endDate);
  523.             if ($now >= $endDate) {
  524.                 $ended true;
  525.             }
  526.         }
  527.         if (true === $ended) {
  528.             $siteABTest->setActive(false);
  529.         }
  530.         return $ended;
  531.     }
  532.     /**
  533.      * @param $actualStats
  534.      * @return mixed
  535.      * @throws \LogicException
  536.      */
  537.     private function getRandomVersion($actualStatsSiteABTest $siteABTest)
  538.     {
  539.         $randomValue mt_rand(1100);
  540.         $counter 0;
  541.         $possibleVersions $siteABTest->getPossibleVersions();
  542.         foreach ($actualStats['options'] as $option => $data) {
  543.             $counter += $data['target'];
  544.             if ($randomValue <= $counter) {
  545.                 if (in_array($option$possibleVersions)) {
  546.                     $selectedVersion $option;
  547.                 }
  548.                 break;
  549.             }
  550.         }
  551.         if (!isset($selectedVersion)) {
  552.             throw new \LogicException(
  553.                 "Unable to assign version, there must be a problem in your configuration, are you sure you have assigned 100% of votes ?"
  554.             );
  555.         }
  556.         return $selectedVersion;
  557.     }
  558.     /**
  559.      * @param $actualStats
  560.      * @return null|string
  561.      */
  562.     private function getDeterministicVersion($actualStatsSiteABTest $siteABTest)
  563.     {
  564.         $selectedVersion null;
  565.         $possibleVersions $siteABTest->getPossibleVersions();
  566.         foreach ($actualStats['options'] as $option => $data) {
  567.             if ($data['percent'] < $data['target']) {
  568.                 if (in_array($option$possibleVersions)) {
  569.                     $selectedVersion $option;
  570.                 }
  571.                 break;
  572.             }
  573.         }
  574.         if (!isset($selectedVersion)) {
  575.             throw new \LogicException(
  576.                 "Unable to assign version, there must be a problem in your configuration, are you sure you have assigned 100% of votes ?"
  577.             );
  578.         }
  579.         return $selectedVersion;
  580.     }
  581.     /**
  582.      * Get stats for a splitTest from redis
  583.      *
  584.      * @param SiteABTest $siteABTest
  585.      * @return mixed
  586.      */
  587.     private function getStatsFromRedis(SiteABTest $siteABTest)
  588.     {
  589.         $websiteIdentifier $siteABTest->getSite()->getSiteId();
  590.         $splitTestIdentifier $siteABTest->getRedisKey();
  591.         $key $websiteIdentifier "_" $splitTestIdentifier;
  592.         $stats $this->redisClient->get($key);
  593.         // no stats in redis, we need to initialise them
  594.         if (null == $stats) {
  595.             $stats $siteABTest->getStats();
  596.             $this->setStatsToRedis($stats$siteABTest);
  597.         } else {
  598.             $stats json_decode($statstrue);
  599.         }
  600.         return $stats;
  601.     }
  602.     /**
  603.      * Set stats to redis.
  604.      *
  605.      * @param $stats
  606.      * @param SiteABTest $siteABTest
  607.      * @return mixed
  608.      */
  609.     private function setStatsToRedis($statsSiteABTest $siteABTest)
  610.     {
  611.         $websiteIdentifier $siteABTest->getSite()->getSiteId();
  612.         $splitTestIdentifier $siteABTest->getRedisKey();
  613.         $key $websiteIdentifier "_" $splitTestIdentifier;
  614.         $stats json_encode($stats);
  615.         $result $this->redisClient->setex($key604800$stats);
  616.         return $result;
  617.     }
  618.     /**
  619.      * @param Site $site
  620.      */
  621.     private function loadActiveTests(Site $site)
  622.     {
  623.         $this->activeTests $site->getAbTests();
  624.     }
  625.     /**
  626.      * Check if the tracking is eligible for this AB test.
  627.      *
  628.      * @param Tracking $tracking
  629.      * @param SiteABTest $siteABTest
  630.      * @return bool
  631.      */
  632.     private function isEligibleForTest(Tracking $trackingSiteABTest $siteABTest)
  633.     {
  634.         // If this tracking already have this ABTest, don't add it again.
  635.         if ($tracking->alreadyHaveABTest($siteABTest)) {
  636.             return false;
  637.         }
  638.         // Check if this ABtest should not be applied to some specific affiliates.
  639.         if (array_key_exists(SiteABTest::TYPE_EXCLUDE_AFF$siteABTest->getConfig())) {
  640.             $affiliatesToExclude $siteABTest->getConfig()[SiteABTest::TYPE_EXCLUDE_AFF];
  641.             if (in_array($tracking->getAff()->getAffiliateId(), $affiliatesToExclude)) {
  642.                 return false;
  643.             }
  644.         }
  645.         // Check if this ABtest should be applied to some specific affiliates.
  646.         if (array_key_exists(SiteABTest::CONFIG_ALLOWED_AFF$siteABTest->getConfig()) && count($siteABTest->getConfig()[SiteABTest::CONFIG_ALLOWED_AFF])) {
  647.             $allowedAffiliates $siteABTest->getConfig()[SiteABTest::CONFIG_ALLOWED_AFF];
  648.             if (!in_array($tracking->getAff()->getAffiliateId(), $allowedAffiliates)) {
  649.                 return false;
  650.             }
  651.         }
  652.         return true;
  653.     }
  654.     /**
  655.      * Check if User is eligible for this AB test.
  656.      *
  657.      * @param User $user
  658.      * @param SiteABTest $siteABTest
  659.      * @return bool
  660.      */
  661.     private function userIsEligibleForTest(User $userSiteABTest $siteABTest)
  662.     {
  663.         // If this User already have this ABTest, don't add it again.
  664.         if ($user->alreadyHaveABTest($siteABTest)) {
  665.             return false;
  666.         }
  667.         // Check if this ABtest should not be applied to some specific affiliates.
  668.         if (array_key_exists(SiteABTest::TYPE_EXCLUDE_AFF$siteABTest->getConfig())) {
  669.             $affiliatesToExclude $siteABTest->getConfig()[SiteABTest::TYPE_EXCLUDE_AFF];
  670.             if (in_array($user->getTracking()->getAff()->getAffiliateId(), $affiliatesToExclude)) {
  671.                 return false;
  672.             }
  673.         }
  674.         return true;
  675.     }
  676. }