This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace Gabievi\Promocodes; |
||
4 | |||
5 | use Carbon\Carbon; |
||
6 | use Gabievi\Promocodes\Models\Promocode; |
||
7 | use Gabievi\Promocodes\Exceptions\AlreadyUsedException; |
||
8 | use Gabievi\Promocodes\Exceptions\UnauthenticatedException; |
||
9 | use Gabievi\Promocodes\Exceptions\InvalidPromocodeException; |
||
10 | |||
11 | class Promocodes |
||
12 | { |
||
13 | /** |
||
14 | * Prefix for code generation |
||
15 | * |
||
16 | * @var string |
||
17 | */ |
||
18 | protected $prefix; |
||
19 | |||
20 | /** |
||
21 | * Suffix for code generation |
||
22 | * |
||
23 | * @var string |
||
24 | */ |
||
25 | protected $suffix; |
||
26 | |||
27 | /** |
||
28 | * Number of codes to be generated |
||
29 | * |
||
30 | * @var int |
||
31 | */ |
||
32 | protected $amount = 1; |
||
33 | |||
34 | /** |
||
35 | * Reward value which will be sticked to code |
||
36 | * |
||
37 | * @var null |
||
38 | */ |
||
39 | protected $reward = null; |
||
40 | |||
41 | /** |
||
42 | * Additional data to be returned with code |
||
43 | * |
||
44 | * @var array |
||
45 | */ |
||
46 | protected $data = []; |
||
47 | |||
48 | /** |
||
49 | * Number of days of code expiration |
||
50 | * |
||
51 | * @var null|int |
||
52 | */ |
||
53 | protected $expires_in = null; |
||
54 | |||
55 | /** |
||
56 | * Maximum number of available usage of code |
||
57 | * |
||
58 | * @var null|int |
||
59 | */ |
||
60 | protected $quantity = null; |
||
61 | |||
62 | /** |
||
63 | * If code should automatically invalidate after first use |
||
64 | * |
||
65 | * @var bool |
||
66 | */ |
||
67 | protected $disposable = false; |
||
68 | |||
69 | /** |
||
70 | * Generated codes will be saved here |
||
71 | * to be validated later. |
||
72 | * |
||
73 | * @var array |
||
74 | */ |
||
75 | private $codes = []; |
||
76 | |||
77 | /** |
||
78 | * Length of code will be calculated from asterisks you have |
||
79 | * set as mask in your config file. |
||
80 | * |
||
81 | * @var int |
||
82 | */ |
||
83 | private $length; |
||
84 | |||
85 | /** |
||
86 | * Promocodes constructor. |
||
87 | */ |
||
88 | public function __construct() |
||
89 | { |
||
90 | $this->codes = Promocode::pluck('code')->toArray(); |
||
91 | $this->length = substr_count(config('promocodes.mask'), '*'); |
||
92 | |||
93 | $this->prefix = (bool)config('promocodes.prefix') |
||
94 | ? config('promocodes.prefix') . config('promocodes.separator') |
||
95 | : ''; |
||
96 | |||
97 | $this->suffix = (bool)config('promocodes.suffix') |
||
98 | ? config('promocodes.separator') . config('promocodes.suffix') |
||
99 | : ''; |
||
100 | } |
||
101 | |||
102 | /** |
||
103 | * Save one-time use promocodes into database |
||
104 | * Successful insert returns generated promocodes |
||
105 | * Fail will return empty collection. |
||
106 | * |
||
107 | * @param int $amount |
||
108 | * @param float|null $reward |
||
109 | * @param array $data |
||
110 | * @param int|null $expires_in |
||
111 | * @param int|null $quantity |
||
112 | * |
||
113 | * @return \Illuminate\Support\Collection |
||
114 | */ |
||
115 | public function createDisposable( |
||
116 | $amount = null, |
||
117 | $reward = null, |
||
118 | $data = null, |
||
119 | $expires_in = null, |
||
120 | $quantity = null |
||
121 | ) |
||
122 | { |
||
123 | return $this->create($amount, $reward, $data, $expires_in, $quantity, true); |
||
0 ignored issues
–
show
|
|||
124 | } |
||
125 | |||
126 | /** |
||
127 | * Save promocodes into database |
||
128 | * Successful insert returns generated promocodes |
||
129 | * Fail will return empty collection. |
||
130 | * |
||
131 | * @param int $amount |
||
132 | * @param null $reward |
||
133 | * @param array $data |
||
134 | * @param int|null $expires_in |
||
135 | * @param bool $is_disposable |
||
136 | * @param int|null $quantity |
||
137 | * |
||
138 | * @return \Illuminate\Support\Collection |
||
139 | */ |
||
140 | public function create( |
||
141 | $amount = null, |
||
142 | $reward = null, |
||
143 | $data = null, |
||
144 | $expires_in = null, |
||
145 | $quantity = null, |
||
146 | $is_disposable = null |
||
147 | ) |
||
148 | { |
||
149 | $records = []; |
||
150 | |||
151 | foreach ($this->output($amount) as $code) { |
||
152 | $records[] = [ |
||
153 | 'code' => $code, |
||
154 | 'reward' => $this->getReward($reward), |
||
155 | 'data' => json_encode($this->getData($data)), |
||
156 | 'expires_at' => $this->getExpiresIn($expires_in) ? Carbon::now()->addDays($this->getExpiresIn($expires_in)) : null, |
||
157 | 'is_disposable' => $this->getDisposable($is_disposable), |
||
158 | 'quantity' => $this->getQuantity($quantity), |
||
159 | ]; |
||
160 | } |
||
161 | |||
162 | if (Promocode::insert($records)) { |
||
163 | return collect($records)->map(function ($record) { |
||
164 | $record['data'] = json_decode($record['data'], true); |
||
165 | |||
166 | return $record; |
||
167 | }); |
||
168 | } |
||
169 | |||
170 | return collect([]); |
||
171 | } |
||
172 | |||
173 | /** |
||
174 | * Generates promocodes as many as you wish. |
||
175 | * |
||
176 | * @param int $amount |
||
177 | * |
||
178 | * @return array |
||
179 | */ |
||
180 | public function output($amount = null) |
||
181 | { |
||
182 | $collection = []; |
||
183 | |||
184 | for ($i = 1; $i <= $this->getAmount($amount); $i++) { |
||
185 | $random = $this->generate(); |
||
186 | |||
187 | while (!$this->validate($collection, $random)) { |
||
188 | $random = $this->generate(); |
||
189 | } |
||
190 | |||
191 | array_push($collection, $random); |
||
192 | } |
||
193 | |||
194 | return $collection; |
||
195 | } |
||
196 | |||
197 | /** |
||
198 | * Get number of codes to be generated |
||
199 | * |
||
200 | * @param null|int $request |
||
201 | * @return null|int |
||
202 | */ |
||
203 | public function getAmount($request) |
||
204 | { |
||
205 | return $request !== null ? $request : $this->amount; |
||
206 | } |
||
207 | |||
208 | /** |
||
209 | * Set how much code you want to be generated |
||
210 | * |
||
211 | * @param int $amount |
||
212 | * @return $this |
||
213 | */ |
||
214 | public function setAmount($amount) |
||
215 | { |
||
216 | $this->amount = $amount; |
||
217 | return $this; |
||
218 | } |
||
219 | |||
220 | /** |
||
221 | * Here will be generated single code using your parameters from config. |
||
222 | * |
||
223 | * @return string |
||
224 | */ |
||
225 | private function generate() |
||
226 | { |
||
227 | $characters = config('promocodes.characters'); |
||
228 | $mask = config('promocodes.mask'); |
||
229 | $promocode = ''; |
||
230 | $random = []; |
||
231 | |||
232 | for ($i = 1; $i <= $this->length; $i++) { |
||
233 | $character = $characters[rand(0, strlen($characters) - 1)]; |
||
234 | $random[] = $character; |
||
235 | } |
||
236 | |||
237 | shuffle($random); |
||
238 | $length = count($random); |
||
239 | |||
240 | $promocode .= $this->prefix; |
||
241 | |||
242 | for ($i = 0; $i < $length; $i++) { |
||
243 | $mask = preg_replace('/\*/', $random[$i], $mask, 1); |
||
244 | } |
||
245 | |||
246 | $promocode .= $mask; |
||
247 | $promocode .= $this->suffix; |
||
248 | |||
249 | return $promocode; |
||
250 | } |
||
251 | |||
252 | /** |
||
253 | * Your code will be validated to be unique for one request. |
||
254 | * |
||
255 | * @param $collection |
||
256 | * @param $new |
||
257 | * |
||
258 | * @return bool |
||
259 | */ |
||
260 | private function validate($collection, $new) |
||
261 | { |
||
262 | return !in_array($new, array_merge($collection, $this->codes)); |
||
263 | } |
||
264 | |||
265 | /** |
||
266 | * Get custom set reward value |
||
267 | * |
||
268 | * @param null|int $request |
||
269 | * @return null|int |
||
270 | */ |
||
271 | public function getReward($request) |
||
272 | { |
||
273 | return $request !== null ? $request : $this->reward; |
||
274 | } |
||
275 | |||
276 | /** |
||
277 | * Set custom reward value |
||
278 | * |
||
279 | * @param int $reward |
||
280 | * @return $this |
||
281 | */ |
||
282 | public function setReward($reward) |
||
283 | { |
||
284 | $this->reward = $reward; |
||
0 ignored issues
–
show
It seems like
$reward of type integer is incompatible with the declared type null of property $reward .
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property. Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.. ![]() |
|||
285 | return $this; |
||
286 | } |
||
287 | |||
288 | /** |
||
289 | * Get custom set data value |
||
290 | * |
||
291 | * @param null|array $data |
||
0 ignored issues
–
show
There is no parameter named
$data . Was it maybe removed?
This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. Consider the following example. The parameter /**
* @param array $germany
* @param array $island
* @param array $italy
*/
function finale($germany, $island) {
return "2:1";
}
The most likely cause is that the parameter was removed, but the annotation was not. ![]() |
|||
292 | * @return null|array |
||
293 | */ |
||
294 | public function getData($request) |
||
295 | { |
||
296 | return $request !== null ? $request : $this->data; |
||
297 | } |
||
298 | |||
299 | /** |
||
300 | * Set custom data value |
||
301 | * |
||
302 | * @param array $data |
||
303 | * @return $this |
||
304 | */ |
||
305 | public function setData($data) |
||
306 | { |
||
307 | $this->data = $data; |
||
308 | return $this; |
||
309 | } |
||
310 | |||
311 | /** |
||
312 | * Get custom set expiration days value |
||
313 | * |
||
314 | * @param null|int $request |
||
315 | * @return null|int |
||
316 | */ |
||
317 | public function getExpiresIn($request) |
||
318 | { |
||
319 | return $request !== null ? $request : $this->expires_in; |
||
320 | } |
||
321 | |||
322 | /** |
||
323 | * Set custom expiration days value |
||
324 | * |
||
325 | * @param int $expires_in |
||
326 | * @return $this |
||
327 | */ |
||
328 | public function setExpiresIn($expires_in) |
||
329 | { |
||
330 | $this->expires_in = $expires_in; |
||
331 | return $this; |
||
332 | } |
||
333 | |||
334 | /** |
||
335 | * Get custom disposable value |
||
336 | * |
||
337 | * @param null|bool $request |
||
338 | * @return null|bool |
||
339 | */ |
||
340 | public function getDisposable($request) |
||
341 | { |
||
342 | return $request !== null ? $request : $this->disposable; |
||
343 | } |
||
344 | |||
345 | /** |
||
346 | * Set custom disposable value |
||
347 | * |
||
348 | * @param bool $disposable |
||
349 | * @return $this |
||
350 | */ |
||
351 | public function setDisposable($disposable = true) |
||
352 | { |
||
353 | $this->disposable = $disposable; |
||
354 | return $this; |
||
355 | } |
||
356 | |||
357 | /** |
||
358 | * Get custom set quantity value |
||
359 | * |
||
360 | * @param null|int $request |
||
361 | * @return null|int |
||
362 | */ |
||
363 | public function getQuantity($request) |
||
364 | { |
||
365 | return $request !== null ? $request : $this->quantity; |
||
366 | } |
||
367 | |||
368 | /** |
||
369 | * Set custom quantity value |
||
370 | * |
||
371 | * @param int $quantity |
||
372 | * @return $this |
||
373 | */ |
||
374 | public function setQuantity($quantity) |
||
375 | { |
||
376 | $this->quantity = $quantity; |
||
377 | return $this; |
||
378 | } |
||
379 | |||
380 | /** |
||
381 | * Set custom prefix for next generation |
||
382 | * |
||
383 | * @param string $prefix |
||
384 | * @return $this |
||
385 | */ |
||
386 | public function setPrefix($prefix) |
||
387 | { |
||
388 | $this->prefix = $prefix; |
||
389 | return $this; |
||
390 | } |
||
391 | |||
392 | /** |
||
393 | * Set custom suffix for next generation |
||
394 | * |
||
395 | * @param string $suffix |
||
396 | * @return $this |
||
397 | */ |
||
398 | public function setSuffix($suffix) |
||
399 | { |
||
400 | $this->suffix = $suffix; |
||
401 | return $this; |
||
402 | } |
||
403 | |||
404 | /** |
||
405 | * Reedem promocode to user that it's used from now. |
||
406 | * |
||
407 | * @param string $code |
||
408 | * |
||
409 | * @return bool|Promocode |
||
410 | * @throws AlreadyUsedException |
||
411 | * @throws UnauthenticatedException |
||
412 | */ |
||
413 | public function redeem($code) |
||
414 | { |
||
415 | return $this->apply($code); |
||
416 | } |
||
417 | |||
418 | /** |
||
419 | * Apply promocode to user that it's used from now. |
||
420 | * |
||
421 | * @param string $code |
||
422 | * |
||
423 | * @return bool|Promocode |
||
424 | * @throws AlreadyUsedException |
||
425 | * @throws UnauthenticatedException |
||
426 | */ |
||
427 | public function apply($code) |
||
428 | { |
||
429 | if (!auth()->check()) { |
||
0 ignored issues
–
show
The method
check does only exist in Illuminate\Contracts\Auth\Guard , but not in Illuminate\Contracts\Auth\Factory .
It seems like the method you are trying to call exists only in some of the possible types. Let’s take a look at an example: class A
{
public function foo() { }
}
class B extends A
{
public function bar() { }
}
/**
* @param A|B $x
*/
function someFunction($x)
{
$x->foo(); // This call is fine as the method exists in A and B.
$x->bar(); // This method only exists in B and might cause an error.
}
Available Fixes
![]() |
|||
430 | throw new UnauthenticatedException; |
||
431 | } |
||
432 | |||
433 | if ($promocode = $this->check($code)) { |
||
434 | if ($this->isSecondUsageAttempt($promocode)) { |
||
0 ignored issues
–
show
It seems like
$promocode defined by $this->check($code) on line 433 can also be of type boolean ; however, Gabievi\Promocodes\Promo...:isSecondUsageAttempt() does only seem to accept object<Gabievi\Promocodes\Models\Promocode> , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue. ![]() |
|||
435 | throw new AlreadyUsedException; |
||
436 | } |
||
437 | |||
438 | $promocode->users()->attach(auth()->id(), [ |
||
0 ignored issues
–
show
The method
id does only exist in Illuminate\Contracts\Auth\Guard , but not in Illuminate\Contracts\Auth\Factory .
It seems like the method you are trying to call exists only in some of the possible types. Let’s take a look at an example: class A
{
public function foo() { }
}
class B extends A
{
public function bar() { }
}
/**
* @param A|B $x
*/
function someFunction($x)
{
$x->foo(); // This call is fine as the method exists in A and B.
$x->bar(); // This method only exists in B and might cause an error.
}
Available Fixes
![]() |
|||
439 | 'promocode_id' => $promocode->id, |
||
440 | 'used_at' => Carbon::now(), |
||
441 | ]); |
||
442 | |||
443 | if (!is_null($promocode->quantity)) { |
||
444 | $promocode->quantity -= 1; |
||
445 | $promocode->save(); |
||
446 | } |
||
447 | |||
448 | return $promocode->load('users'); |
||
449 | } |
||
450 | |||
451 | return false; |
||
452 | } |
||
453 | |||
454 | /** |
||
455 | * Check promocode in database if it is valid. |
||
456 | * |
||
457 | * @param string $code |
||
458 | * |
||
459 | * @return bool|Promocode |
||
460 | */ |
||
461 | public function check($code) |
||
462 | { |
||
463 | $promocode = Promocode::byCode($code)->first(); |
||
464 | |||
465 | if ($promocode === null || $promocode->isExpired() || ($promocode->isDisposable() && $promocode->users()->exists()) || $promocode->isOverAmount()) { |
||
466 | return false; |
||
467 | } |
||
468 | |||
469 | return $promocode; |
||
470 | } |
||
471 | |||
472 | /** |
||
473 | * Check if user is trying to apply code again. |
||
474 | * |
||
475 | * @param Promocode $promocode |
||
476 | * |
||
477 | * @return bool |
||
478 | */ |
||
479 | public function isSecondUsageAttempt(Promocode $promocode) |
||
480 | { |
||
481 | return $promocode->isDisposable() && $promocode->users()->wherePivot(config('promocodes.related_pivot_key', 'user_id'), |
||
482 | auth()->id())->exists(); |
||
0 ignored issues
–
show
The method
id does only exist in Illuminate\Contracts\Auth\Guard , but not in Illuminate\Contracts\Auth\Factory .
It seems like the method you are trying to call exists only in some of the possible types. Let’s take a look at an example: class A
{
public function foo() { }
}
class B extends A
{
public function bar() { }
}
/**
* @param A|B $x
*/
function someFunction($x)
{
$x->foo(); // This call is fine as the method exists in A and B.
$x->bar(); // This method only exists in B and might cause an error.
}
Available Fixes
![]() |
|||
483 | } |
||
484 | |||
485 | /** |
||
486 | * Expire code as it won't be usable anymore. |
||
487 | * |
||
488 | * @param string $code |
||
489 | * @return bool |
||
490 | * @throws InvalidPromocodeException |
||
491 | */ |
||
492 | public function disable($code) |
||
493 | { |
||
494 | $promocode = Promocode::byCode($code)->first(); |
||
495 | |||
496 | if ($promocode === null) { |
||
497 | throw new InvalidPromocodeException; |
||
498 | } |
||
499 | |||
500 | $promocode->expires_at = Carbon::now(); |
||
501 | $promocode->quantity = 0; |
||
502 | |||
503 | return $promocode->save(); |
||
504 | } |
||
505 | |||
506 | /** |
||
507 | * Clear all expired and used promotion codes |
||
508 | * that can not be used anymore. |
||
509 | * |
||
510 | * @return void |
||
511 | */ |
||
512 | public function clearRedundant() |
||
513 | { |
||
514 | Promocode::all()->each(function (Promocode $promocode) { |
||
515 | if ($promocode->isExpired() || ($promocode->isDisposable() && $promocode->users()->exists()) || $promocode->isOverAmount()) { |
||
516 | $promocode->users()->detach(); |
||
517 | $promocode->delete(); |
||
518 | } |
||
519 | }); |
||
520 | } |
||
521 | |||
522 | /** |
||
523 | * Get the list of valid promocodes |
||
524 | * |
||
525 | * @return Promocode[]|\Illuminate\Database\Eloquent\Collection |
||
526 | */ |
||
527 | public function all() |
||
528 | { |
||
529 | return Promocode::all()->filter(function (Promocode $promocode) { |
||
530 | return !$promocode->isExpired() && !($promocode->isDisposable() && $promocode->users()->exists()) && !$promocode->isOverAmount(); |
||
531 | }); |
||
532 | } |
||
533 | } |
||
534 |
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.