Issues (3)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Services/Calculator.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Ecocode\SyliusBasePricePlugin\Services;
6
7
use Ecocode\SyliusBasePricePlugin\Entity\Mapping;
8
use Ecocode\SyliusBasePricePlugin\Entity\Product\ProductVariantInterface;
9
use RuntimeException;
10
use Sylius\Bundle\MoneyBundle\Templating\Helper\ConvertMoneyHelper;
11
use Sylius\Bundle\MoneyBundle\Templating\Helper\ConvertMoneyHelperInterface;
12
use Sylius\Bundle\MoneyBundle\Templating\Helper\FormatMoneyHelper;
13
use Sylius\Component\Core\Model\ChannelInterface;
14
use Sylius\Component\Core\Model\ChannelPricingInterface;
15
use Sylius\Component\Core\Model\ProductVariantInterface as CoreModelProductVariantInterface;
16
use Symfony\Contracts\Translation\TranslatorInterface;
17
use Twig\Extension\AbstractExtension;
18
use UnitConverter\UnitConverter;
19
use function constant;
20
21
/**
22
 * Class Calculator
23
 * @package Ecocode\SyliusBasePricePlugin\Services
24
 */
25
class Calculator extends AbstractExtension
26
{
27
    /** @var array<string, array<string, array<string, string|float>>> */
28
    private $mappingConfig = [];
29
30
    /** @var bool */
31
    private $useShortUnitName = true;
32
33
    /** @var TranslatorInterface */
34
    private $translator;
35
36
    /** @var UnitConverter|null */
37
    private $unitConverter;
38
39
    /** @var FormatMoneyHelper */
40
    private $moneyFormatter;
41
42
    /** @var ConvertMoneyHelperInterface|null */
43
    private $moneyConverter;
44
45
    /**
46
     * Calculator constructor.
47
     *
48
     * @param TranslatorInterface $translator
49
     * @param UnitConverter       $unitConverter
50
     * @param FormatMoneyHelper   $formatMoneyHelper
51
     */
52 12
    public function __construct(
53
        TranslatorInterface $translator,
54
        UnitConverter $unitConverter,
55
        FormatMoneyHelper $formatMoneyHelper
56
    ) {
57 12
        $this->translator     = $translator;
58 12
        $this->moneyFormatter = $formatMoneyHelper;
59 12
        $this->setUnitConverter($unitConverter);
60 12
    }
61
62 12
    public function setMoneyConverter(ConvertMoneyHelper $convertMoneyHelper): self
63
    {
64 12
        $this->moneyConverter = $convertMoneyHelper;
65
66 12
        return $this;
67
    }
68
69 12
    public function setUnitConverter(UnitConverter $unitConverter): self
70
    {
71 12
        $this->unitConverter = $unitConverter;
72
73 12
        return $this;
74
    }
75
76 12
    private function getUnitConverter(): UnitConverter
77
    {
78 12
        if ($this->unitConverter === null) {
79
            throw new RuntimeException('Unit Converter class not set');
80
        }
81
82 12
        return $this->unitConverter;
83
    }
84
85
    /**
86
     * @param array<string, array<string, array<string, string|float>>> $mappingConfig
87
     *
88
     * @return $this
89
     */
90 12
    public function setMappingConfig(array $mappingConfig): self
91
    {
92 12
        $this->mappingConfig = $mappingConfig;
93
94 12
        return $this;
95
    }
96
97 12
    public function setUseShortUnitName(bool $useShortUnitName): self
98
    {
99 12
        $this->useShortUnitName = $useShortUnitName;
100
101 12
        return $this;
102
    }
103
104
    /**
105
     * @param ProductVariantInterface $productVariant
106
     * @param ChannelInterface        $channel
107
     * @param string                  $currencyCode current currency code
108
     *
109
     * @return string|null
110
     */
111 12
    public function calculate(
112
        ProductVariantInterface $productVariant,
113
        ChannelInterface $channel,
114
        string $currencyCode
115
    ): ?string {
116 12
        if (!$productVariant instanceof CoreModelProductVariantInterface) {
117
            return null;
118
        }
119
120 12
        $measurementMapping = $this->getMeasurementMapping($productVariant);
121
122 12
        foreach ($measurementMapping as $mappings) {
123
            $this
124 12
                ->getUnitConverter()
125 12
                ->from((string)$productVariant->getBasePriceUnit())
126 12
                ->convert((string)$productVariant->getBasePriceValue());
127
128 12
            foreach ($mappings as $mapping) {
129 12
                $symbol         = $mapping->getUnitClass()->getSymbol();
130 12
                $targetUnitSize = $this->getUnitConverter()->to((string)$symbol);
131
132 12
                if (!$this->isMappingUsable($mapping, $targetUnitSize)) {
133 4
                    continue;
134
                }
135
136 10
                $channelPricing = $productVariant->getChannelPricingForChannel($channel);
137 10
                if ($channelPricing === null) {
138
                    continue;
139
                }
140 10
                if ($channelPricing->getPrice() === null) {
141
                    continue;
142
                }
143
144 10
                $params = $this->getBasePriceFormatParams(
145 10
                    $channel,
146
                    $channelPricing,
147
                    $mapping,
148 10
                    (float)$targetUnitSize,
149
                    $currencyCode
150
                );
151
152 10
                return $this->translator->trans('ecocode_sylius_base_price_plugin.format', $params);
153
            }
154
        }
155
156 2
        return null;
157
    }
158
159
    /**
160
     * @param ProductVariantInterface $productVariant
161
     *
162
     * @return array<string, Mapping[]>
0 ignored issues
show
The doc-type array<string, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
163
     */
164 12
    public function getMeasurementMapping(ProductVariantInterface $productVariant): array
165
    {
166 12
        $basePriceUnitValue = $productVariant->getBasePriceUnit();
167 12
        if ($basePriceUnitValue === null || $productVariant->getBasePriceValue() === null) {
168
            return [];
169
        }
170
171 12
        $basePriceUnit = $this->getUnitConverter()->getRegistry()->loadUnit($basePriceUnitValue);
172
173 12
        if ($basePriceUnit === null) {
174
            return [];
175
        }
176
177 12
        $measurement = $basePriceUnit->getUnitOf();
178 12
        if ($measurement === null) {
179
            return [];
180
        }
181
182 12
        return $this->getMeasurementMappings($measurement);
183
    }
184
185 10
    private function getBasePriceFormatParams(
186
        ChannelInterface $channel,
187
        ChannelPricingInterface $channelPricing,
188
        Mapping $mapping,
189
        float $targetUnitSize,
190
        string $targetCurrency
191
    ): array {
192 10
        $symbol              = (string)$mapping->getUnitClass()->getSymbol();
193 10
        $format              = 'ecocode_sylius_base_price_plugin.%s.%s';
194 10
        $unitType            = sprintf($format, $this->useShortUnitName ? 'unit_short' : 'unit', $symbol);
195 10
        $unitTypeTranslation = $this->translator->trans($unitType);
196 10
        if ($unitTypeTranslation == $unitType) { // fallback if unit translation is missing
197 2
            $unitTypeTranslation = $this->useShortUnitName ? $symbol : $mapping->getUnitClass()->getName();
198
        }
199
200 10
        if ($this->moneyConverter === null) {
201
            throw new RuntimeException('Money converter not set');
202
        }
203
204 10
        $mod              = $mapping->getMod();
205 10
        $baseCurrency     = $channel->getBaseCurrency();
206 10
        $baseCurrencyCode = $baseCurrency !== null && $baseCurrency->getCode() !== null ? $baseCurrency->getCode() : $targetCurrency;
207 10
        $channelPrice     = (float)$channelPricing->getPrice();
208 10
        $price            = ($channelPrice / $targetUnitSize) * $mod;
209 10
        $convertedPrice   = $this->moneyConverter->convertAmount((int)$price, $baseCurrencyCode, $targetCurrency);
210 10
        $defaultLocale    = $channel->getDefaultLocale();
211 10
        $locale           = $defaultLocale !== null ? (string)$defaultLocale->getCode() : '';
212 10
        $formattedPrice   = $this->moneyFormatter->formatAmount((int)$convertedPrice, $targetCurrency, $locale);
213
214
        return [
215 10
            '%PRICE%' => $formattedPrice,
216 10
            '%VALUE%' => $mod > 1 ? $mod . ' ' : '',
217 10
            '%TYPE%'  => $unitTypeTranslation
218
        ];
219
    }
220
221
    /**
222
     * @param string|null $measurement
223
     *
224
     * @return array<string, Mapping[]>
0 ignored issues
show
The doc-type array<string, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
225
     */
226 12
    private function getMeasurementMappings(?string $measurement): array
227
    {
228 12
        $mappings = [];
229 12
        if ($measurement === null) {
230
            return $mappings;
231
        }
232
233 12
        foreach ($this->mappingConfig as $measurementsConst => $mappingsConfig) {
234 12
            $measurementConfig = (string)constant($measurementsConst);
235 12
            if ($measurement != $measurementConfig) {
236 5
                continue;
237
            }
238
239 12
            foreach ($mappingsConfig as $mappingData) {
240 12
                $mappings[$measurementConfig][] = new Mapping($mappingData);
241
            }
242
        }
243
244 12
        return $mappings;
245
    }
246
247
    /**
248
     * @param Mapping          $mapping
249
     * @param int|float|string $targetUnitValue
250
     *
251
     * @return bool
252
     */
253 12
    private function isMappingUsable(Mapping $mapping, $targetUnitValue): bool
254
    {
255 12
        if ($targetUnitValue <= 0) {
256
            return false;
257
        }
258
259 12
        if ($targetUnitValue > $mapping->getIfMoreThan()) {
260 10
            return true;
261
        }
262
263 4
        return false;
264
    }
265
}
266