custom/plugins/AbmAdjustments/src/Subscriber/FrontendSubscriber.php line 352

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace AbmAdjustments\Subscriber;
  3. use Doctrine\DBAL\Connection;
  4. use Psr\Cache\InvalidArgumentException;
  5. use Shopware\Core\Checkout\Cart\AbstractCartPersister;
  6. use Shopware\Core\Checkout\Cart\Address\Error\AddressValidationError;
  7. use Shopware\Core\Checkout\Cart\Address\Error\ProfileSalutationMissingError;
  8. use Shopware\Core\Checkout\Cart\Cart;
  9. use Shopware\Core\Checkout\Cart\CartPersister;
  10. use Shopware\Core\Checkout\Cart\Event\AfterLineItemAddedEvent;
  11. use Shopware\Core\Checkout\Cart\Event\BeforeLineItemAddedEvent;
  12. use Shopware\Core\Checkout\Cart\Event\CartChangedEvent;
  13. use Shopware\Core\Checkout\Cart\Event\CartCreatedEvent;
  14. use Shopware\Core\Checkout\Cart\Event\CartVerifyPersistEvent;
  15. use Shopware\Core\Checkout\Cart\LineItem\LineItem;
  16. use Shopware\Core\Checkout\Cart\Price\Struct\QuantityPriceDefinition;
  17. use Shopware\Core\Checkout\Cart\SalesChannel\CartService;
  18. use Shopware\Core\Checkout\Cart\Tax\Struct\CalculatedTax;
  19. use Shopware\Core\Checkout\Cart\Tax\Struct\CalculatedTaxCollection;
  20. use Shopware\Core\Content\Product\Events\ProductListingCriteriaEvent;
  21. use Shopware\Core\Content\Product\Events\ProductSuggestCriteriaEvent;
  22. use Shopware\Core\Content\Product\ProductEvents;
  23. use Shopware\Core\Content\Product\SalesChannel\SalesChannelProductEntity;
  24. use Shopware\Core\Framework\Context;
  25. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  26. use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityLoadedEvent;
  27. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  28. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  29. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\MultiFilter;
  30. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\NotFilter;
  31. use Shopware\Core\Framework\Event\DataMappingEvent;
  32. use Shopware\Core\Framework\Validation\DataBag\RequestDataBag;
  33. use Shopware\Core\System\SalesChannel\Entity\SalesChannelEntityLoadedEvent;
  34. use Shopware\Core\System\SystemConfig\Event\SystemConfigChangedEvent;
  35. use Shopware\Storefront\Framework\Routing\RequestTransformer;
  36. use Shopware\Core\System\SalesChannel\Context\SalesChannelContextFactory;
  37. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  38. use Shopware\Core\System\SystemConfig\SystemConfigService;
  39. use Shopware\Core\System\Tax\Aggregate\TaxRule\TaxRuleEntity;
  40. use Shopware\Core\System\Tax\TaxEntity;
  41. use Shopware\Storefront\Event\StorefrontRenderEvent;
  42. use Shopware\Storefront\Page\Checkout\Cart\CheckoutCartPageLoadedEvent;
  43. use Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPageLoadedEvent;
  44. use Shopware\Storefront\Page\PageLoadedEvent;
  45. use Shopware\Storefront\Page\Product\ProductPageLoadedEvent;
  46. use Shopware\Storefront\Page\Search\SearchPageLoadedEvent;
  47. use Symfony\Component\DependencyInjection\ContainerInterface;
  48. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  49. use Symfony\Component\HttpFoundation\RedirectResponse;
  50. use Symfony\Component\HttpFoundation\RequestStack;
  51. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  52. use Symfony\Component\HttpKernel\Event\ControllerEvent;
  53. use Symfony\Component\HttpKernel\Event\KernelEvent;
  54. use Symfony\Component\HttpKernel\Event\RequestEvent;
  55. use Symfony\Component\HttpKernel\Event\ResponseEvent;
  56. use Symfony\Component\HttpKernel\Event\TerminateEvent;
  57. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  58. use Symfony\Component\HttpKernel\KernelEvents;
  59. use Symfony\Config\Framework\UidConfig;
  60. use Symfony\Contracts\Cache\CacheInterface;
  61. use Symfony\Contracts\Cache\ItemInterface;
  62. use Symfony\Component\Cache\Adapter\FilesystemAdapter;
  63. use Shopware\Core\System\SalesChannel\Context\SalesChannelContextService;
  64. use Shopware\Core\Framework\Util\StringHelper;
  65. use Shopware\Core\Checkout\Order\Aggregate\OrderLineItem\OrderLineItemEntity;
  66. use Shopware\Core\Checkout\Order\OrderEntity;
  67. use Shopware\Core\Framework\Uuid\Uuid;
  68. use Shopware\Core\Checkout\Cart\Tax\Struct\TaxRuleCollection;
  69. use Symfony\Component\HttpKernel\Event\ExceptionEvent;
  70. use Symfony\Component\ErrorHandler\Exception\FlattenException;
  71. use Shopware\Storefront\Controller\StorefrontController;
  72. use Shopware\Core\Content\Product\SalesChannel\Price\ProductPriceCalculator;
  73. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsAnyFilter;
  74. use Shopware\Core\Framework\Struct\ArrayStruct;
  75. /**
  76.  * @property SystemConfigService systemConfigService
  77.  */
  78. // extends StorefrontController
  79. class FrontendSubscriber extends StorefrontController implements EventSubscriberInterface
  80. {
  81.     const ALLOWED_DEMO_IPS 'AkkusysTools.config.allowedDemoIps';
  82.     const TAX_DEMO_MODE 'AkkusysTools.config.demoMode';
  83.     private $systemConfigService;
  84.     private $requestStack;
  85.     private $productRepository;
  86.     private $customFieldRepository;
  87.     private $connection;
  88.     private $cache;
  89.     private $cacheStage;
  90.     const ALL_ZERO_TAX_GROSS_NET_STG 'all-zero-tax-gross-net-stg';
  91.     const ALL_ZERO_TAX_GROSS_NET_LIVE 'all-zero-tax-gross-net-live';
  92.     private SalesChannelContextService $salesChannelContextService;
  93.     private CartService $cartService;
  94.     /**
  95.      * @param SystemConfigService $systemConfigService
  96.      * @param RequestStack $requestStack
  97.      * @param ContainerInterface $container
  98.      */
  99.     public function __construct(
  100.         SystemConfigService $systemConfigService,
  101.         RequestStack $requestStack,
  102.         ContainerInterface $container,
  103.         EntityRepositoryInterface $productRepository,
  104.         EntityRepositoryInterface $customFieldRepository,
  105.         Connection $connection,
  106.         SalesChannelContextService $salesChannelContextService,
  107.         CartService $cartService
  108.     )
  109.     {
  110.         $this->systemConfigService $systemConfigService;
  111.         $this->requestStack $requestStack;
  112.         $this->container $container;
  113.         $this->productRepository $productRepository;
  114.         $this->customFieldRepository $customFieldRepository;
  115.         $this->connection $connection;
  116.         $this->cacheStage = new FilesystemAdapter();
  117.         $this->cache = new FilesystemAdapter();
  118.         $this->salesChannelContextService $salesChannelContextService;
  119.         $this->cartService $cartService;
  120.     }
  121.     /**
  122.      * @return array
  123.      */
  124.     public static function getSubscribedEvents()
  125.     {
  126.         return [
  127.             ProductEvents::PRODUCT_LOADED_EVENT => "onProductLoaded",
  128.             CartChangedEvent::class => 'onCartChanged',
  129.             CartVerifyPersistEvent::class => 'onCartVerifyPersist',
  130.             SystemConfigChangedEvent::class => 'onConfigChanged',
  131.             'sales_channel.product.loaded' => 'onSalesChannelProductLoaded',
  132.         ];
  133.     }
  134.     /**
  135.      * @param SalesChannelEntityLoadedEvent $event
  136.      */
  137.     public function onSalesChannelProductLoaded(SalesChannelEntityLoadedEvent $event): void
  138.     {
  139.         foreach ($event->getEntities() as $productEntity) {
  140.             /* @var \Shopware\Core\Content\Product\SalesChannel\SalesChannelProductEntity $productEntity */
  141.             $bZeroTax $productEntity->getTranslated()['customFields']['taxrate'] ?? 1;
  142.             if (!empty($bZeroTax)) {
  143.                 continue;
  144.             }
  145.             $oReferencePrice = @$productEntity->getCalculatedPrice()->getReferencePrice() ?? null;
  146.             if(is_null($oReferencePrice)) {
  147.                 continue;
  148.             }
  149.             $fReferencePrice $oReferencePrice->getPrice();
  150.             $fCalculatedUnit null;
  151.             if (!empty($productEntity->getCalculatedPrice()->getReferencePrice()->getPurchaseUnit())) {
  152.                 $fCalculatedUnit $productEntity->getCheapestPrice()->getPrice()->first()->getNet() / $productEntity->getCalculatedPrice()->getReferencePrice()->getPurchaseUnit();
  153.             }
  154.             if (!empty($fReferencePrice)) {
  155.                 $customFields $productEntity->getCustomFields();
  156.                 $customFields['netReferencePrice'] = (int) ( ($fCalculatedUnit / (1.00)) * 100) / 100 ?? (int) ( ($productEntity->getCalculatedPrice()->getReferencePrice()->getPrice() / (1.19)) * 100) / 100;
  157.                 $productEntity->setCustomFields($customFields);
  158.             }
  159.         }
  160.     }
  161.     /**
  162.      * @param SystemConfigChangedEvent $event
  163.      */
  164.     public function onConfigChanged(SystemConfigChangedEvent $event)
  165.     {
  166.         if ($event->getKey()  === 'zeroTaxMode') {
  167.             $currentRequest $this->requestStack->getCurrentRequest();
  168.             $oCurrentSession $currentRequest->getSession();
  169.             $oCurrentSession->clear();
  170.         }
  171.     }
  172.     /**
  173.      * @param CartChangedEvent $event
  174.      */
  175.     public function onCartChanged(CartChangedEvent $event): void
  176.     {
  177.         $oCart $event->getCart();
  178.         $context $event->getContext();
  179.         $currentRequest $this->requestStack->getCurrentRequest();
  180.         if ($currentRequest == null) {
  181.             return;
  182.         }
  183.         $oCurrentSession $currentRequest->getSession();
  184.         $sRoute $currentRequest->attributes->get('_route');
  185.         $oCustomer $context->getCustomer();
  186.         $bLoggedIn = !is_null($oCustomer);
  187.         $bZeroTaxConsent $oCurrentSession->get('zero_tax_consent');
  188.         $bAcceptDiscount = !empty($oCurrentSession->get('zero_tax_waiver'));
  189.         $bGroupPrices = ($bZeroTaxConsent && $bAcceptDiscount && $bLoggedIn) || !$bLoggedIn;
  190.         $groupedTotalAmounts = [];
  191.         foreach ($oCart->getLineItems() as $lineItem) {
  192. //            if ($this->isZeroTaxRate($lineItem) && $bGroupPrices && !$this->systemConfigService->get(self::FORCE_BRUT_TAX_SETTING)) {
  193.             if ($this->isZeroTaxRate($lineItem) && $bGroupPrices) {
  194.                 $taxBase 0;
  195.             } else {
  196.                 $taxBase $lineItem->getPrice()->getTaxRules()->first()->getTaxRate();
  197.             }
  198.             if (!isset($groupedTotalAmounts[$taxBase])) {
  199.                 $groupedTotalAmounts[$taxBase] = 0;
  200.             }
  201.             $groupedTotalAmounts[$taxBase] += $lineItem->getPrice()->getTotalPrice();
  202.         }
  203.         if (!empty($groupedTotalAmounts)) {
  204.             $aCartExtension $oCart->getExtensions();
  205.             $aCartExtension['groupedTaxes'] = $groupedTotalAmounts;
  206.             $oCart->setExtensions($aCartExtension);
  207.             $this->cartService->recalculate($oCart$context);
  208.             if (!empty($groupedTotalAmounts[0]) && $sRoute !== 'frontend.checkout.confirm.page') {
  209.                 $oCurrentSession->set('zero_tax_consent'true);
  210.             }
  211.         }
  212.     }
  213.     /**
  214.      * @param CartVerifyPersistEvent $event
  215.      */
  216.     public function onCartVerifyPersist(CartVerifyPersistEvent $event): void
  217.     {
  218.         $oCart $event->getCart();
  219.         $salesChannelContext $event->getSalesChannelContext();
  220.         $currentRequest $this->requestStack->getCurrentRequest();
  221.         if ($currentRequest == null) {
  222.             return;
  223.         }
  224.         $oCurrentSession $currentRequest->getSession();
  225.         $sRoute $currentRequest->attributes->get('_route');
  226.         $oCustomer $salesChannelContext->getCustomer();
  227.         $bLoggedIn = !is_null($oCustomer);
  228.         $bZeroTaxConsent $oCurrentSession->get('zero_tax_consent');
  229.         $bAcceptDiscount = empty($oCurrentSession->get('zero_tax_waiver'));
  230.         $bGroupPrices = ($bZeroTaxConsent && $bAcceptDiscount && $bLoggedIn) || !$bLoggedIn;
  231.         $groupedTotalAmounts = [];
  232.         foreach ($oCart->getLineItems() as $lineItem) {
  233.             if ($this->isZeroTaxRate($lineItem) && $bGroupPrices && $bAcceptDiscount) {
  234.                 $taxBase 0;
  235.             } else {
  236.                 $taxBase $lineItem->getPrice()->getTaxRules()->first()->getTaxRate();
  237.             }
  238.             if (!isset($groupedTotalAmounts[$taxBase])) {
  239.                 $groupedTotalAmounts[$taxBase] = 0;
  240.             }
  241.             $groupedTotalAmounts[$taxBase] += $lineItem->getPrice()->getTotalPrice();
  242.         }
  243.         if (!empty($groupedTotalAmounts)) {
  244.             $aCartExtension $oCart->getExtensions();
  245.             $aCartExtension['groupedTaxes'] = $groupedTotalAmounts;
  246.             $oCart->setExtensions($aCartExtension);
  247.             if (!empty($groupedTotalAmounts[0]) && $sRoute !== 'frontend.checkout.confirm.page') {
  248.                 $oCurrentSession->set('zero_tax_consent'true);
  249.             }
  250.         }
  251.     }
  252.     /**
  253.      * @param LineItem $lineItem
  254.      * @return bool
  255.      */
  256.     private function isZeroTaxRate(LineItem $lineItem): bool
  257.     {
  258.         return isset($lineItem->getPayload()['customFields']['taxrate']) && $lineItem->getPayload()['customFields']['taxrate'] === "0";
  259.     }
  260.     /**
  261.      * @param EntityLoadedEvent $event
  262.      * @throws InvalidArgumentException
  263.      */
  264.     public function onProductLoaded(EntityLoadedEvent $event): void
  265.     {
  266.         if(php_sapi_name() === 'cli') {
  267.             return;
  268.         }
  269.         $currentRequest $this->requestStack->getCurrentRequest();
  270.         if ($currentRequest == null) {
  271.             return;
  272.         }
  273.         $sRoute $currentRequest->attributes->get('_route');
  274.         $oCurrentSession $currentRequest->getSession();
  275.         $aAllowedDemoIps = [];
  276.         if (!empty($this->systemConfigService->get(self::ALLOWED_DEMO_IPS))) {
  277.             try {
  278.                 $aAllowedDemoIps explode(','$this->systemConfigService->get(self::ALLOWED_DEMO_IPS)) ?? null;
  279.             } catch (\Exception $e) {
  280.                 $aAllowedDemoIps = [];
  281.             }
  282.         }
  283.         $isDemosMode$this->systemConfigService->get(self::TAX_DEMO_MODE) ?? null;
  284.         $bApplyZeroTaxFeature = !$isDemosMode ||
  285.             ( $isDemosMode &&
  286.                 (
  287.                     in_array($_SERVER['REMOTE_ADDR'], $aAllowedDemoIps ) ||
  288.                     in_array(@$_SERVER['HTTP_X_FORWARDED_FOR'], $aAllowedDemoIps )
  289.                 )
  290.             );
  291.         if ($bApplyZeroTaxFeature) {
  292.             $bFirstZeroTaxHit  false;
  293.             /** @var  SalesChannelProductEntity $productEntity */
  294.             foreach ($event->getEntities() as $productEntity) {
  295.                 $customFields $productEntity->getCustomFields();
  296.                 $bZeroTax $productEntity->getTranslated()['customFields']['taxrate'] ?? 1;
  297.                 if (empty($bZeroTax)) {
  298.                     if (!empty($productEntity->getPrices()->first())) {
  299.                         $customFields['net'] = (float) $productEntity->getPrices()->first()->getPrice()->first()->getNet();
  300.                         $customFields['brut'] = (float) $productEntity->getPrices()->first()->getPrice()->first()->getGross();
  301.                         $productEntity->setCustomFields($customFields);
  302.                         $productEntity->setCustomFields($customFields);
  303.                         if(!$oCurrentSession->get('first_zero_tax_hit')) {
  304.                             $oCurrentSession->set('first_zero_tax_hit'true);
  305.                             $bFirstZeroTaxHit true;
  306.                         }
  307.                     }
  308.                 }
  309.             }
  310.             if($bFirstZeroTaxHit) {
  311.                 $oCurrentSession->set('zero_tax_consent'true);
  312.                 $oCurrentSession->set('zero_tax_waiver'false);
  313.             }
  314.         } else {
  315.             // $oCurrentSession->set('zero_tax_consent', false);
  316.         }
  317.         if ($sRoute == 'frontend.detail.page') {
  318.             foreach ( $event->getEntities() as $productEntity ) {
  319.                 $iTaxRate intval($productEntity->getTax()->getTaxRate());
  320.                 if (empty($iTaxRate)) {
  321.                     $customFields $productEntity->getCustomFields();
  322.                     $customFields['zeroTax'] = true;
  323.                     $productEntity->setCustomFields($customFields);
  324.                 }
  325.             }
  326.         }
  327.         if($sRoute == 'frontend.detail.page' || $sRoute == 'frontend.expert.search'){
  328.             return;
  329.         }
  330.         if ( !($event->getContext()->getSource() instanceof \Shopware\Core\Framework\Api\Context\SalesChannelApiSource) ) {
  331.             return;
  332.         }
  333.     }
  334.     /**
  335.      * @param $key
  336.      * @param $value
  337.      * @param \DateInterval $expiresAfter
  338.      * @throws InvalidArgumentException
  339.      */
  340.     public function cacheObject($key$value, \DateInterval $expiresAfter)
  341.     {
  342.         $cache $this->cache;
  343.         if( (bool)getenv('APP_STAGE')) {
  344.             $cache $this->cacheStage;
  345.         }
  346.         $item $cache->getItem($key);
  347.         $item->set($value);
  348.         $item->expiresAfter($expiresAfter);
  349.         $cache->save($item);
  350.     }
  351.     /**
  352.      * @param $key
  353.      * @return mixed|null
  354.      * @throws InvalidArgumentException
  355.      */
  356.     public function retrieveObject($key$bReturn false)
  357.     {
  358.         $cache $this->cache;
  359.         if( (bool)getenv('APP_STAGE')) {
  360.             $cache $this->cacheStage;
  361.         }
  362.         $item $cache->getItem($key);
  363.         if ($item->isHit()) {
  364.             return $item->get();
  365.         }
  366.         return $this->getAllProductPrices($bReturn);
  367.     }
  368.     /**
  369.      * @param false $bReturn
  370.      * @return array
  371.      * @throws \Doctrine\DBAL\Exception
  372.      */
  373.     private function getAllProductPrices(bool $bReturn false): array
  374.     {
  375.         $sql = <<<SQL
  376.                     SELECT                       
  377.                         HEX(p.id) as id, 
  378.                         product_number,
  379.                         JSON_EXTRACT(p.price,  CONCAT('$.', JSON_UNQUOTE(JSON_EXTRACT(JSON_KEYS(p.price, '$[0]'), "$[0]")), '.gross')) AS gross_pp,
  380.                         JSON_EXTRACT(p.price,  CONCAT('$.', JSON_UNQUOTE(JSON_EXTRACT(JSON_KEYS(p.price, '$[0]'), "$[0]")), '.net')) AS net_pp,
  381.                         JSON_EXTRACT(pp.price, CONCAT('$.', JSON_UNQUOTE(JSON_EXTRACT(JSON_KEYS(pp.price, '$[0]'), "$[0]")), '.gross')) AS gross,
  382.                         JSON_EXTRACT(pp.price, CONCAT('$.', JSON_UNQUOTE(JSON_EXTRACT(JSON_KEYS(pp.price, '$[0]'), "$[0]")), '.net')) AS net                                            
  383.                     FROM product p, product_translation pt, product_price pp                     
  384.                     WHERE 
  385.                         p.id =pt.product_id 
  386.                     AND p.version_id = pt.product_version_id 
  387.                     AND p.id =pp.product_id 
  388.                     AND p.version_id = pp.product_version_id 
  389.                     AND JSON_EXTRACT(pt.custom_fields, '$.taxrate') = 0
  390. SQL;
  391.         $aResultsObject = [];
  392.         //get the number of affected rows
  393.         $aResults $this->connection->executeQuery($sql)->fetchAll();
  394.         foreach ($aResults as $aResult) {
  395.             $aResultsObject[strtolower($aResult['id'])] = [
  396.                 "product_number" => $aResult['product_number'],
  397.                 "gross" => (float) $aResult['gross'],
  398.                 "net" => (float) $aResult['net']
  399.             ];
  400.         }
  401.         if($bReturn) {
  402.             return $aResultsObject;
  403.         }
  404.         $sRedisProductsCacheKey self::ALL_ZERO_TAX_GROSS_NET_LIVE;
  405.         if( (bool)getenv('APP_STAGE')) {
  406.             $sRedisProductsCacheKey self::ALL_ZERO_TAX_GROSS_NET_STG;
  407.         }
  408.         $this->cacheObject($sRedisProductsCacheKey$aResultsObject, new \DateInterval('PT10S'));
  409.         return $aResultsObject;
  410.     }
  411.     private function getGoogleBotIps()
  412.     {
  413.         try {
  414.             $item $this->cache->getItem('sGoogleBotIps');
  415.             if (!$item->isHit()) {
  416.                 $client = new \GuzzleHttp\Client();
  417.                 $response $client->get(self::GOOGLE_BOT_IP_RANGES);
  418.                 /** @var \GuzzleHttp\Psr7\Stream $rBody */
  419.                 $aBotLoads json_decode($response->getBody()->getContents(), true)['prefixes'] ;
  420.                 $aOutputIps array_map(
  421.                     function (array $elem) {
  422.                         if(array_key_exists('ipv4Prefix'$elem)) {
  423.                             $range = array();
  424.                             $cidr explode('/'reset($elem));
  425.                             $range[0] = long2ip((ip2long($cidr[0])) & ((-<< (32 - (int)$cidr[1]))));
  426.                             $range[1] = long2ip((ip2long($range[0])) + pow(2, (32 - (int)$cidr[1])) - 1);
  427.                             return self::generateIps('ipv4Prefix'$range);
  428.                         }
  429.                     },
  430.                     $aBotLoads
  431.                 );
  432.                 $aOutputIps array_filter($aOutputIps);
  433.                 $aOutputIps array_values($aOutputIps);
  434.                 $aIps = [];
  435.                 foreach($aOutputIps as $key=>&$aIpBlock){
  436.                     $aIps array_merge($aIps$aIpBlock);
  437.                 }
  438.                 $this->cacheObject('sGoogleBotIps'$aIps, new \DateInterval('P1D'));
  439.                 return $aIps;
  440.             }
  441.             return $item;
  442.         } catch (InvalidArgumentException $e) {
  443.         }
  444.         return true;
  445.     }
  446.     /**
  447.      * @param string $sType
  448.      * @param array $range
  449.      * @return array
  450.      */
  451.     public static function generateIps(string $sType, array $range): array
  452.     {
  453.         $aIps = [];
  454.         $aStart explode('.'$range[0]);
  455.         $aEnd explode('.'$range[1]);
  456.         $iStart end($aStart);
  457.         $iEnd end($aEnd);
  458.         array_pop($aStart);
  459.         $sBase implode('.'$aStart);
  460.         for($i=$iStart$i<=$iEnd$i++) {
  461.             $aIps[] = $sBase.'.'.$i;
  462.         }
  463.         return $aIps;
  464.     }
  465. }