Issues (1)

Security Analysis    not enabled

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/TranslatableBootForm.php (1 issue)

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 namespace Propaganistas\LaravelTranslatableBootForms;
2
3
use Closure;
4
5
class TranslatableBootForm
6
{
7
8
    /**
9
     * BootForm implementation.
10
     *
11
     * @var \AdamWathan\BootForms\BootForm
12
     */
13
    protected $form;
14
15
    /**
16
     * Array holding config values.
17
     *
18
     * @var array
19
     */
20
    protected $config;
21
22
    /**
23
     * Array of locale keys.
24
     *
25
     * @var array
26
     */
27
    protected $locales;
28
29
    /**
30
     * The current element type this class is working on.
31
     *
32
     * @var string
33
     */
34
    protected $element;
35
36
    /**
37
     * The array of arguments to pass in when creating the element.
38
     *
39
     * @var array
40
     */
41
    protected $arguments = [];
42
43
    /**
44
     * A keyed array of method => arguments to call on the created input.
45
     *
46
     * @var array
47
     */
48
    protected $methods = [];
49
50
    /**
51
     * Boolean indicating if the element should be cloned with corresponding translation name attributes.
52
     *
53
     * @var bool
54
     */
55
    protected $cloneElement = false;
56
57
    /**
58
     * Boolean indicating if the element should have an indication that is it a translation.
59
     *
60
     * @var bool
61
     */
62
    protected $translatableIndicator = false;
63
64
    /**
65
     * Array holding the mappable element arguments.
66
     *
67
     * @var array
68
     */
69
    private $mappableArguments = [
70
        'text'           => ['label', 'name', 'value'],
71
        'textarea'       => ['label', 'name'],
72
        'password'       => ['label', 'name'],
73
        'date'           => ['label', 'name', 'value'],
74
        'email'          => ['label', 'name', 'value'],
75
        'file'           => ['label', 'name', 'value'],
76
        'inputGroup'     => ['label', 'name', 'value'],
77
        'radio'          => ['label', 'name', 'value'],
78
        'inlineRadio'    => ['label', 'name', 'value'],
79
        'checkbox'       => ['label', 'name'],
80
        'inlineCheckbox' => ['label', 'name'],
81
        'select'         => ['label', 'name', 'options'],
82
        'button'         => ['label', 'name', 'type'],
83
        'submit'         => ['value', 'type'],
84
        'hidden'         => ['name'],
85
        'label'          => ['label'],
86
        'open'           => [],
87
        'openHorizontal' => ['columnSizes'],
88
        'bind'           => ['model'],
89
        'close'          => [],
90
    ];
91
92
    /**
93
     * Array holding the methods to call during element behavior processing.
94
     *
95
     * @var array
96
     */
97
    private $elementBehaviors = [
98
        'text'           => ['cloneElement', 'translatableIndicator'],
99
        'textarea'       => ['cloneElement', 'translatableIndicator'],
100
        'password'       => ['cloneElement', 'translatableIndicator'],
101
        'date'           => ['cloneElement', 'translatableIndicator'],
102
        'email'          => ['cloneElement', 'translatableIndicator'],
103
        'file'           => ['cloneElement', 'translatableIndicator'],
104
        'inputGroup'     => ['cloneElement', 'translatableIndicator'],
105
        'radio'          => ['cloneElement', 'translatableIndicator'],
106
        'inlineRadio'    => ['cloneElement', 'translatableIndicator'],
107
        'checkbox'       => ['cloneElement', 'translatableIndicator'],
108
        'inlineCheckbox' => ['cloneElement', 'translatableIndicator'],
109
        'select'         => ['cloneElement', 'translatableIndicator'],
110
        'button'         => ['cloneElement'],
111
        'submit'         => ['cloneElement'],
112
        'hidden'         => ['cloneElement'],
113
        'label'          => [],
114
        'open'           => [],
115
        'openHorizontal' => [],
116
        'close'          => [],
117
    ];
118
119
    /**
120
     * Form constructor.
121
     *
122
     * @param object $form
123
     */
124 45
    public function __construct($form)
125
    {
126 45
        $this->form = $form;
127 45
        $this->config = config('translatable-bootforms');
128 45
    }
129
130
    /**
131
     * Magic __call method.
132
     *
133
     * @param string $method
134
     * @param array  $parameters
135
     * @return \Propaganistas\LaravelTranslatableBootForms\TranslatableBootForm
136
     */
137 42
    public function __call($method, $parameters)
138
    {
139
        // New translatable form element.
140 42
        if (is_null($this->element())) {
141 42
            $this->element($method);
142 42
            $this->arguments($this->mapArguments($parameters));
143 28
        } // Calling methods on the translatable form element.
144
        else {
145 12
            $this->addMethod($method, $parameters);
146
        }
147
148
        // Execute bind or close immediately.
149 42
        if (in_array($method, ['bind', 'close'])) {
150 15
            return $this->render();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->render(); (string) is incompatible with the return type documented by Propaganistas\LaravelTra...latableBootForm::__call of type Propaganistas\LaravelTra...ms\TranslatableBootForm.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
151
        }
152
153 36
        return $this;
154
    }
155
156
    /**
157
     * Magic __toString method.
158
     *
159
     * @return string
160
     */
161 3
    public function __toString()
162
    {
163 3
        return $this->render();
164
    }
165
166
    /**
167
     * Resets the properties.
168
     *
169
     * @return $this
170
     */
171 42
    protected function reset()
172
    {
173 42
        $this->element = null;
174 42
        $this->arguments = [];
175 42
        $this->methods = [];
176 42
        $this->cloneElement = false;
177 42
        $this->translatableIndicator = false;
178
179 42
        return $this;
180
    }
181
182
    /**
183
     * Get or set the available locales.
184
     *
185
     * @param array|null $locales
186
     * @return array
187
     */
188 45
    public function locales(array $locales = null)
189
    {
190 45
        return is_null($locales)
191 38
            ? $this->locales
192 45
            : ($this->locales = $locales);
193
    }
194
195
    /**
196
     * Get or set the current element.
197
     *
198
     * @param string|null $element
199
     * @return string
200
     */
201 42
    protected function element($element = null)
202
    {
203 42
        return is_null($element)
204 42
            ? $this->element
205 42
            : ($this->element = $element);
206
    }
207
208
    /**
209
     * Get or set the arguments.
210
     *
211
     * @param array|null $arguments
212
     * @return array
213
     */
214 42
    protected function arguments(array $arguments = null)
215
    {
216 42
        return is_null($arguments)
217 42
            ? $this->arguments
218 42
            : ($this->arguments = $arguments);
219
    }
220
221
    /**
222
     * Get or set the methods.
223
     *
224
     * @param array|null $methods
225
     * @return array
226
     */
227 39
    protected function methods(array $methods = null)
228
    {
229 39
        return is_null($methods)
230 39
            ? $this->methods
231 39
            : ($this->methods = $methods);
232
    }
233
234
    /**
235
     * Get or set the current element.
236
     *
237
     * @param bool|null $clone
238
     * @return bool
239
     */
240 42
    protected function cloneElement($clone = null)
241
    {
242 42
        return is_null($clone)
243 42
            ? $this->cloneElement
244 42
            : ($this->cloneElement = (bool) $clone);
245
    }
246
247
    /**
248
     * Get or set the translatable indicator boolean.
249
     *
250
     * @param bool|null $add
251
     * @return bool
252
     */
253 24
    protected function translatableIndicator($add = null)
254
    {
255 24
        return is_null($add)
256 24
            ? $this->translatableIndicator
257 24
            : ($this->translatableIndicator = (bool) $add);
258
    }
259
260
    /**
261
     * Overwrites an argument.
262
     *
263
     * @param string       $argument
264
     * @param string|array $value
265
     */
266 24
    protected function overwriteArgument($argument, $value)
267
    {
268 24
        $arguments = $this->arguments();
269
270 24
        $arguments[$argument] = $value;
271
272 24
        $this->arguments($arguments);
273 24
    }
274
275
    /**
276
     * Adds a method.
277
     *
278
     * @param string       $name
279
     * @param string|array $parameters
280
     */
281 24
    protected function addMethod($name, $parameters)
282
    {
283 24
        $methods = $this->methods();
284
        
285 24
        $parameters = is_array($parameters) ? $parameters : [$parameters];
286
287 24
        $methods[] = compact('name', 'parameters');
288
289 24
        $this->methods($methods);
290 24
    }
291
292
    /**
293
     * Renders the current translatable form element.
294
     *
295
     * @return string
296
     */
297 42
    public function render()
298
    {
299 42
        $this->applyElementBehavior();
300
301 42
        $elements = [];
302
303 42
        if ($this->cloneElement()) {
304 24
            $originalArguments = $this->arguments();
305 24
            $originalMethods = $this->methods();
306
307 24
            $locales = $this->locales();
308
            // Check if a custom locale set is requested.
309 24
            if ($count = func_num_args()) {
310 3
                $args = ($count == 1 ? head(func_get_args()) : func_get_args());
311 3
                $locales = array_intersect($locales, (array) $args);
312 2
            }
313
314 24
            foreach ($locales as $locale) {
315 24
                $this->arguments($originalArguments);
316 24
                $this->methods($originalMethods);
317
                
318 24
                $name = str_contains($originalArguments['name'], '%locale') ? $originalArguments['name'] : '%locale[' . $originalArguments['name'] . ']';
319 24
                $this->overwriteArgument('name', str_replace('%locale', $locale, $name));
320
                
321 24
                if ($this->translatableIndicator()) {
322 24
                    $this->setTranslatableLabelIndicator($locale);
323 16
                }
324 24
                if (!empty($this->config['form-group-class'])) {
325 24
                    $this->addMethod('addGroupClass', str_replace('%locale', $locale, $this->config['form-group-class']));
326 16
                }
327 24
                if (!empty($this->config['input-locale-attribute'])) {
328 24
                    $this->addMethod('attribute', [$this->config['input-locale-attribute'], $locale]);
329 16
                }
330 24
                $elements[] = $this->createInput($locale);
331 16
            }
332 16
        } else {
333 42
            $elements[] = $this->createInput();
334
        }
335
336 42
        $this->reset();
337
338 42
        return implode('', $elements);
339
    }
340
341
    /**
342
     * Shortcut method for locale-specific rendering.
343
     *
344
     * @return string
345
     */
346 3
    public function renderLocale()
347
    {
348 3
        return call_user_func_array([$this, 'render'], func_get_args());
349
    }
350
351
    /**
352
     * Creates an input element using the supplied arguments and methods.
353
     *
354
     * @param string|null $currentLocale
355
     * @return mixed
356
     */
357 42
    protected function createInput($currentLocale = null)
358
    {
359
        // Create element using arguments.
360 42
        $element = call_user_func_array([$this->form, $this->element()], array_values($this->arguments()));
361
362
        // Elements such as 'bind' do not return renderable stuff and do not accept methods.
363 42
        if ($element) {
364
            // Apply requested methods.
365 39
            foreach ($this->methods() as $method) {
366 24
                $methodName = $method['name'];
367 24
                $methodParameters = $method['parameters'];
368
369
                // Check if method is locale-specific.
370 24
                if (ends_with($methodName, 'ForLocale')) {
371 3
                    $methodName = strstr($methodName, 'ForLocale', true);
372 3
                    $locales = array_shift($methodParameters);
373 3
                    $locales = is_array($locales) ? $locales : [$locales];
374 3
                    if (!is_null($currentLocale) && !in_array($currentLocale, $locales)) {
375
                        // Method should not be applied for this locale.
376 3
                        continue;
377
                    }
378 2
                }
379
380
                // Call method.
381 24
                if (!empty($methodParameters)) {
382
                    // Convert closures to values.
383 24
                    $methodParameters = array_map(function($parameter) use ($currentLocale) {
384 24
                        return ($parameter instanceof Closure) ? $parameter($currentLocale) : $parameter;
385 24
                    }, $methodParameters);
386
                    
387 24
                    $methodParameters = $this->replacePlaceholdersRecursively($methodParameters, $currentLocale);
388
                    
389 24
                    call_user_func_array([$element, $methodName], $methodParameters);
390 16
                } else {
391 14
                    $element->{$methodName}();
392
                }
393
394 26
            }
395 26
        }
396
397 42
        return $element;
398
    }
399
400
    /**
401
     * Replaces %name recursively with the proper input name.
402
     *
403
     * @param mixed $parameter
404
     * @param string $currentLocale
405
     * @return mixed
406
     */
407 24
    protected function replacePlaceholdersRecursively($parameter, $currentLocale)
408
    {
409 24
        if (is_array($parameter)) {
410 24
            foreach ($parameter as $param) {
411 24
                $this->replacePlaceholdersRecursively($param, $currentLocale);
412 16
            }
413 16
        }
414
        
415 24
        $replacements = ['locale' => $currentLocale];
416
        
417 24
        if ($name = array_get($this->arguments(), 'name')) {
418 24
            array_set($replacements, 'name', $name);
419 16
        }
420
421 24
        return str_replace(array_keys($replacements), array_values($replacements), $parameter);
422
    }
423
424
    /**
425
     * Add specific element behavior to the current translatable form element.
426
     */
427 42
    protected function applyElementBehavior()
428
    {
429 42
        $behaviors = isset($this->elementBehaviors[$this->element()]) ? $this->elementBehaviors[$this->element()] : [];
430
431 42
        foreach ($behaviors as $behavior) {
432 24
            $this->{$behavior}(true);
433 28
        }
434 42
    }
435
436
    /**
437
     * Maps the form element arguments to their name.
438
     *
439
     * @param array $arguments
440
     * @return array
441
     */
442 42
    protected function mapArguments(array $arguments)
443
    {
444 42
        $keys = isset($this->mappableArguments[$this->element()]) ? $this->mappableArguments[$this->element()] : [];
445
446 42
        return array_combine(array_slice($keys, 0, count($arguments)), $arguments);
447
    }
448
449
    /**
450
     * Add a locale indicator to the label.
451
     *
452
     * @param string $locale
453
     */
454 24
    protected function setTranslatableLabelIndicator($locale)
455
    {
456 24
        $localizedLabel = str_replace('%label', $this->arguments()['label'], $this->config['label-locale-indicator']);
457 24
        $this->overwriteArgument('label', str_replace('%locale', $locale, $localizedLabel));
458 24
    }
459
460
}
461