Issues (283)

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/UI/Component/CellWrapper.php (3 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
/*
4
 * This file is part of the webmozart/console package.
5
 *
6
 * (c) Bernhard Schussek <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Webmozart\Console\UI\Component;
13
14
use Webmozart\Console\Api\Formatter\Formatter;
15
use Webmozart\Console\Util\StringUtil;
16
17
/**
18
 * Wraps cells to fit a given screen width with a given number of columns.
19
 *
20
 * You can add data cells with {@link addCell()}. Call {@link fit()} to fit
21
 * the cells into a given maximum width and number of columns.
22
 *
23
 * You can access the rows with the wrapped cells with {@link getWrappedRows()}.
24
 *
25
 * @since  1.0
26
 *
27
 * @author Bernhard Schussek <[email protected]>
28
 */
29
class CellWrapper
30
{
31
    /**
32
     * @var string[]
33
     */
34
    private $cells = array();
35
36
    /**
37
     * @var int[][]
38
     */
39
    private $cellLengths = array();
40
41
    /**
42
     * @var string[][]
43
     */
44
    private $wrappedRows = array();
45
46
    /**
47
     * @var int
48
     */
49
    private $nbColumns = 0;
50
51
    /**
52
     * @var int[]
53
     */
54
    private $columnLengths;
55
56
    /**
57
     * @var bool
58
     */
59
    private $wordWraps = false;
60
61
    /**
62
     * @var bool
63
     */
64
    private $wordCuts = false;
65
66
    /**
67
     * @var int
68
     */
69
    private $maxTotalWidth = 0;
70
71
    /**
72
     * @var int
73
     */
74
    private $totalWidth = 0;
75
76
    /**
77
     * Adds a cell to the wrapper.
78
     *
79
     * @param string $cell The data cell.
80
     */
81 21
    public function addCell($cell)
82
    {
83 21
        $this->cells[] = rtrim($cell);
84 21
    }
85
86
    /**
87
     * Adds cells to the wrapper.
88
     *
89
     * @param string[] $cells The data cells.
90
     */
91
    public function addCells(array $cells)
92
    {
93
        foreach ($cells as $cell) {
94
            $this->cells[] = rtrim($cell);
95
        }
96
    }
97
98
    /**
99
     * Sets the data cells in the wrapper.
100
     *
101
     * @param string[] $cells The data cells.
102
     */
103
    public function setCells(array $cells)
104
    {
105
        $this->cells = array();
106
107
        $this->addCells($cells);
108
    }
109
110
    /**
111
     * Returns the data cells in the wrapper.
112
     *
113
     * @return string[] The data cells.
114
     */
115
    public function getCells()
116
    {
117
        return $this->cells;
118
    }
119
120
    /**
121
     * Returns the wrapped cells organized by rows and columns.
122
     *
123
     * The method {@link fit()} should be called before accessing this method.
124
     * Otherwise, an empty array is returned.
125
     *
126
     * @return string[][] An array of arrays. The first level represents rows,
127
     *                    the second level the cells in each row.
128
     */
129 21
    public function getWrappedRows()
130
    {
131 21
        return $this->wrappedRows;
132
    }
133
134
    /**
135
     * Returns the lengths of the wrapped columns.
136
     *
137
     * The method {@link fit()} should be called before accessing this method.
138
     * Otherwise, an empty array is returned.
139
     *
140
     * @return int[] The lengths of each column.
141
     */
142 21
    public function getColumnLengths()
143
    {
144 21
        return $this->columnLengths;
145
    }
146
147
    /**
148
     * Returns the number of wrapped columns.
149
     *
150
     * The method {@link fit()} should be called before accessing this method.
151
     * Otherwise this method returns zero.
152
     *
153
     * @return int The number of columns.
154
     */
155
    public function getNbColumns()
156
    {
157
        return $this->nbColumns;
158
    }
159
160
    /**
161
     * Returns an estimated number of columns for the given maximum width.
162
     *
163
     * @param int $maxTotalWidth The maximum total width of the columns.
164
     *
165
     * @return int The estimated number of columns.
166
     */
167 10
    public function getEstimatedNbColumns($maxTotalWidth)
168
    {
169 10
        $i = 0;
170 10
        $rowWidth = 0;
171
172 10
        while (isset($this->cells[$i])) {
173 10
            $rowWidth += StringUtil::getLength($this->cells[$i]);
174
175 10
            if ($rowWidth > $maxTotalWidth) {
176
                // Return previous number of columns
177 9
                return $i;
178
            }
179
180 10
            ++$i;
181
        }
182
183 1
        return $i;
184
    }
185
186
    /**
187
     * Returns the maximum allowed total width of the columns.
188
     *
189
     * The method {@link fit()} should be called before accessing this method.
190
     * Otherwise this method returns zero.
191
     *
192
     * @return int The maximum allowed total width.
193
     */
194
    public function getMaxTotalWidth()
195
    {
196
        return $this->maxTotalWidth;
197
    }
198
199
    /**
200
     * Returns the actual total column width.
201
     *
202
     * The method {@link fit()} should be called before accessing this method.
203
     * Otherwise this method returns zero.
204
     *
205
     * @return int The actual total column width.
206
     */
207
    public function getTotalWidth()
208
    {
209
        return $this->totalWidth;
210
    }
211
212
    /**
213
     * Returns whether any of the cells needed to be wrapped into multiple
214
     * lines.
215
     *
216
     * The method {@link fit()} should be called before accessing this method.
217
     * Otherwise this method returns `false`.
218
     *
219
     * @return bool Returns `true` if a cell was wrapped into multiple lines
220
     *              and `false` otherwise.
221
     */
222
    public function hasWordWraps()
223
    {
224
        return $this->wordWraps;
225
    }
226
227
    /**
228
     * Returns whether any of the cells contains words cut in two.
229
     *
230
     * The method {@link fit()} should be called before accessing this method.
231
     * Otherwise this method returns `false`.
232
     *
233
     * @return bool Returns `true` if a cell contains words cut in two and
234
     *              `false` otherwise.
235
     */
236 10
    public function hasWordCuts()
237
    {
238 10
        return $this->wordCuts;
239
    }
240
241
    /**
242
     * Fits the added cells into the given maximum total width with the given
243
     * number of columns.
244
     *
245
     * @param int       $maxTotalWidth The maximum total width of the columns.
246
     * @param int       $nbColumns     The number of columns to use.
247
     * @param Formatter $formatter     The formatter used to remove style tags.
248
     */
249 21
    public function fit($maxTotalWidth, $nbColumns, Formatter $formatter)
250
    {
251 21
        $this->resetState($maxTotalWidth, $nbColumns);
252 21
        $this->initRows($formatter);
253
254
        // If the cells fit within the max width we're good
255 21
        if ($this->totalWidth <= $maxTotalWidth) {
256 7
            return;
257
        }
258
259 14
        $this->wrapColumns($formatter);
260 14
    }
261
262 21
    private function resetState($maxTotalWidth, $nbColumns)
263
    {
264 21
        $this->wrappedRows = array();
265 21
        $this->nbColumns = $nbColumns;
266 21
        $this->cellLengths = array();
267 21
        $this->columnLengths = array_fill(0, $nbColumns, 0);
268 21
        $this->wordWraps = false;
269 21
        $this->wordCuts = false;
270 21
        $this->maxTotalWidth = $maxTotalWidth;
271 21
        $this->totalWidth = 0;
272 21
    }
273
274 21
    private function initRows(Formatter $formatter)
275
    {
276 21
        $row = null;
277 21
        $col = 0;
278
279 21
        foreach ($this->cells as $i => $cell) {
280 21
            if (0 === $col) {
281 21
                $this->wrappedRows[] = array();
282 21
                $this->cellLengths[] = array();
283
284 21
                $row = &$this->wrappedRows[count($this->wrappedRows) - 1];
285 21
                $cellLengths = &$this->cellLengths[count($this->cellLengths) - 1];
286
            }
287
288 21
            $row[$col] = $cell;
289 21
            $cellLengths[$col] = StringUtil::getLength($cell, $formatter);
0 ignored issues
show
The variable $cellLengths does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
290 21
            $this->columnLengths[$col] = max($this->columnLengths[$col], $cellLengths[$col]);
291
292 21
            $col = ($col + 1) % $this->nbColumns;
293
        }
294
295
        // Fill last row up
296 21
        if ($col > 0) {
297 7
            while ($col < $this->nbColumns) {
298 7
                $row[$col] = '';
299 7
                $cellLengths[$col] = 0;
300 7
                ++$col;
301
            }
302
        }
303
304 21
        $this->totalWidth = array_sum($this->columnLengths);
0 ignored issues
show
Documentation Bug introduced by
It seems like array_sum($this->columnLengths) can also be of type double. However, the property $totalWidth is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
305 21
    }
306
307 14
    private function wrapColumns(Formatter $formatter)
308
    {
309 14
        $availableWidth = $this->maxTotalWidth;
310 14
        $longColumnLengths = $this->columnLengths;
311
312
        // Filter "short" column, i.e. columns that are not wrapped
313
        // We distribute the available screen width by the number of columns
314
        // and decide that all columns that are shorter than their share are
315
        // "short".
316
        // This process is repeated until no more "short" columns are found.
317
        do {
318 14
            $threshold = $availableWidth / count($longColumnLengths);
319 14
            $repeat = false;
320
321 14
            foreach ($longColumnLengths as $col => $length) {
322 14
                if ($length <= $threshold) {
323 12
                    $availableWidth -= $length;
324 12
                    unset($longColumnLengths[$col]);
325 14
                    $repeat = true;
326
                }
327
            }
328 14
        } while ($repeat);
329
330
        // Calculate actual and available width
331 14
        $actualWidth = 0;
332 14
        $lastAdaptedCol = 0;
333
334
        // "Long" columns, i.e. columns that need to be wrapped, are added to
335
        // the actual width
336 14
        foreach ($longColumnLengths as $col => $length) {
337 14
            $actualWidth += $length;
338 14
            $lastAdaptedCol = $col;
339
        }
340
341
        // Fit columns into available width
342 14
        foreach ($longColumnLengths as $col => $length) {
343
            // Keep ratios of column lengths and distribute them among the
344
            // available width
345 14
            $this->columnLengths[$col] = round(($length / $actualWidth) * $availableWidth);
346
347 14
            if ($col === $lastAdaptedCol) {
348
                // Fix rounding errors
349 14
                $this->columnLengths[$col] += $this->maxTotalWidth - array_sum($this->columnLengths);
350
            }
351
352 14
            $this->wrapColumn($col, $this->columnLengths[$col], $formatter);
353
354
            // Recalculate the column length based on the actual wrapped length
355 14
            $this->refreshColumnLength($col);
356
357
            // Recalculate the actual width based on the changed length.
358 14
            $actualWidth = $actualWidth - $length + $this->columnLengths[$col];
359
        }
360
361 14
        $this->totalWidth = array_sum($this->columnLengths);
0 ignored issues
show
Documentation Bug introduced by
It seems like array_sum($this->columnLengths) can also be of type double. However, the property $totalWidth is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
362 14
    }
363
364 14
    private function wrapColumn($col, $columnLength, Formatter $formatter)
365
    {
366 14
        foreach ($this->wrappedRows as $i => $row) {
367 14
            $cell = $row[$col];
368 14
            $cellLength = $this->cellLengths[$i][$col];
369
370 14
            if ($cellLength > $columnLength) {
371 14
                $this->wordWraps = true;
372
373 14
                if (!$this->wordCuts) {
374 14
                    $minLengthWithoutCut = StringUtil::getMaxWordLength($cell, $formatter);
375
376 14
                    if ($minLengthWithoutCut > $columnLength) {
377 7
                        $this->wordCuts = true;
378
                    }
379
                }
380
381
                // TODO use format aware wrapper
382
                // true: Words may be cut in two
383 14
                $wrappedCell = wordwrap($cell, $columnLength, "\n", true);
384
385 14
                $this->wrappedRows[$i][$col] = $wrappedCell;
386
387
                // Refresh cell length
388 14
                $this->cellLengths[$i][$col] = StringUtil::getMaxLineLength($wrappedCell, $formatter);
389
            }
390
        }
391 14
    }
392
393 14
    private function refreshColumnLength($col)
394
    {
395 14
        $this->columnLengths[$col] = 0;
396
397 14
        foreach ($this->wrappedRows as $i => $row) {
398 14
            $this->columnLengths[$col] = max($this->columnLengths[$col], $this->cellLengths[$i][$col]);
399
        }
400 14
    }
401
}
402