spatie /
period
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 Spatie\Period; |
||
| 4 | |||
| 5 | class Visualizer |
||
| 6 | { |
||
| 7 | /** |
||
| 8 | * Options used in configuring the visualization. |
||
| 9 | * |
||
| 10 | * - int width: |
||
| 11 | * Determines the output size of the visualization |
||
| 12 | * Note: This controls the width of the bars only. |
||
| 13 | * |
||
| 14 | * @var array |
||
| 15 | */ |
||
| 16 | private $options; |
||
| 17 | |||
| 18 | /** |
||
| 19 | * Create a new visualizer. |
||
| 20 | * |
||
| 21 | * @param array $options |
||
| 22 | */ |
||
| 23 | public function __construct(array $options = []) |
||
| 24 | { |
||
| 25 | $this->options = $options; |
||
| 26 | } |
||
| 27 | |||
| 28 | /** |
||
| 29 | * Builds a string to visualize one or more |
||
| 30 | * periods and/or collections in a more |
||
| 31 | * human readable / parsable manner. |
||
| 32 | * |
||
| 33 | * Keys are used as identifiers in the output |
||
| 34 | * and the periods are represented with bars. |
||
| 35 | * |
||
| 36 | * This visualizer is capable of generating |
||
| 37 | * output like the following: |
||
| 38 | * |
||
| 39 | * A [========] |
||
| 40 | * B [==] |
||
| 41 | * C [=====] |
||
| 42 | * CURRENT [===============] |
||
| 43 | * OVERLAP [=] [==] [=] |
||
| 44 | * |
||
| 45 | * @param array|Period[]|PeriodCollection[] $blocks |
||
| 46 | * @return string |
||
| 47 | */ |
||
| 48 | public function visualize(array $blocks): string |
||
| 49 | { |
||
| 50 | $matrix = $this->matrix($blocks); |
||
| 51 | |||
| 52 | $nameLength = max(...array_map('strlen', array_keys($matrix))); |
||
| 53 | |||
| 54 | $lines = []; |
||
| 55 | |||
| 56 | foreach ($matrix as $name => $row) { |
||
| 57 | $lines[] = vsprintf('%s %s', [ |
||
| 58 | str_pad($name, $nameLength, ' '), |
||
| 59 | $this->toBars($row), |
||
| 60 | ]); |
||
| 61 | } |
||
| 62 | |||
| 63 | return implode("\n", $lines); |
||
| 64 | } |
||
| 65 | |||
| 66 | /** |
||
| 67 | * Build a 2D table such that: |
||
| 68 | * - There's one row for every block. |
||
| 69 | * - There's one column for every unit of width. |
||
| 70 | * - Each cell is true when a period is active for that unit. |
||
| 71 | * - Each cell is false when a period is not active for that unit. |
||
| 72 | * |
||
| 73 | * @param array $blocks |
||
| 74 | * @return array |
||
| 75 | */ |
||
| 76 | private function matrix(array $blocks): array |
||
| 77 | { |
||
| 78 | $width = $this->options['width']; |
||
| 79 | |||
| 80 | $matrix = array_fill(0, count($blocks), array_fill(0, $width, false)); |
||
| 81 | $matrix = array_combine(array_keys($blocks), array_values($matrix)); |
||
| 82 | |||
| 83 | $bounds = $this->bounds($blocks); |
||
| 84 | |||
| 85 | foreach ($blocks as $name => $block) { |
||
| 86 | if ($block instanceof Period) { |
||
| 87 | $matrix[$name] = $this->populateRow($matrix[$name], $block, $bounds); |
||
| 88 | } elseif ($block instanceof PeriodCollection) { |
||
| 89 | foreach ($block as $period) { |
||
| 90 | $matrix[$name] = $this->populateRow($matrix[$name], $period, $bounds); |
||
| 91 | } |
||
| 92 | } |
||
| 93 | } |
||
| 94 | |||
| 95 | return $matrix; |
||
| 96 | } |
||
| 97 | |||
| 98 | /** |
||
| 99 | * Get the start / end coordinates for a given period. |
||
| 100 | * |
||
| 101 | * @param Period $period |
||
| 102 | * @param Period $bounds |
||
| 103 | * @param int $width |
||
| 104 | * @return array |
||
| 105 | */ |
||
| 106 | private function coords(Period $period, Period $bounds, int $width): array |
||
| 107 | { |
||
| 108 | $boundsStart = $bounds->getStart()->getTimestamp(); |
||
| 109 | $boundsEnd = $bounds->getEnd()->getTimestamp(); |
||
| 110 | $boundsLength = $boundsEnd - $boundsStart; |
||
| 111 | |||
| 112 | // Get the bounds |
||
| 113 | $start = $period->getStart()->getTimestamp() - $boundsStart; |
||
| 114 | $end = $period->getEnd()->getTimestamp() - $boundsStart; |
||
| 115 | |||
| 116 | // Rescale from timestamps to width units |
||
| 117 | $start *= $width / $boundsLength; |
||
| 118 | $end *= $width / $boundsLength; |
||
| 119 | |||
| 120 | // Cap at integer intervals |
||
| 121 | $start = floor($start); |
||
| 122 | $end = ceil($end); |
||
| 123 | |||
| 124 | return [$start, $end]; |
||
| 125 | } |
||
| 126 | |||
| 127 | /** |
||
| 128 | * Populate a row with true values |
||
| 129 | * where periods are active. |
||
| 130 | * |
||
| 131 | * @param array $row |
||
| 132 | * @param Period $period |
||
| 133 | * @param Period $bounds |
||
| 134 | * @return array |
||
| 135 | */ |
||
| 136 | private function populateRow(array $row, Period $period, Period $bounds): array |
||
| 137 | { |
||
| 138 | $width = $this->options['width']; |
||
| 139 | |||
| 140 | [$startIndex, $endIndex] = $this->coords($period, $bounds, $width); |
||
|
0 ignored issues
–
show
|
|||
| 141 | |||
| 142 | for ($i = 0; $i < $width; $i++) { |
||
| 143 | if ($startIndex <= $i && $i < $endIndex) { |
||
| 144 | $row[$i] = true; |
||
| 145 | } |
||
| 146 | } |
||
| 147 | |||
| 148 | return $row; |
||
| 149 | } |
||
| 150 | |||
| 151 | /** |
||
| 152 | * Get the bounds encompassing all visualized periods. |
||
| 153 | * |
||
| 154 | * @param array $blocks |
||
| 155 | * @return Period|null |
||
| 156 | */ |
||
| 157 | private function bounds(array $blocks): ?Period |
||
| 158 | { |
||
| 159 | $periods = new PeriodCollection(); |
||
| 160 | |||
| 161 | foreach ($blocks as $block) { |
||
| 162 | if ($block instanceof Period) { |
||
| 163 | $periods[] = $block; |
||
| 164 | } elseif ($block instanceof PeriodCollection) { |
||
| 165 | foreach ($block as $period) { |
||
| 166 | $periods[] = $period; |
||
| 167 | } |
||
| 168 | } |
||
| 169 | } |
||
| 170 | |||
| 171 | return $periods->boundaries(); |
||
| 172 | } |
||
| 173 | |||
| 174 | /** |
||
| 175 | * Turn a series of true/false values into bars |
||
| 176 | * representing the start/end of periods. |
||
| 177 | * |
||
| 178 | * @param array $row |
||
| 179 | * @return string |
||
| 180 | */ |
||
| 181 | private function toBars(array $row): string |
||
| 182 | { |
||
| 183 | $tmp = ''; |
||
| 184 | |||
| 185 | for ($i = 0, $l = count($row); $i < $l; $i++) { |
||
| 186 | $prev = $row[$i - 1] ?? null; |
||
| 187 | $curr = $row[$i]; |
||
| 188 | $next = $row[$i + 1] ?? null; |
||
| 189 | |||
| 190 | // Small state machine to build the string |
||
| 191 | switch (true) { |
||
| 192 | // The current period is only one unit long so display a "=" |
||
| 193 | case $curr && $curr !== $prev && $curr !== $next: |
||
| 194 | $tmp .= '='; |
||
| 195 | break; |
||
| 196 | |||
| 197 | // We've hit the start of a period |
||
| 198 | case $curr && $curr !== $prev && $curr === $next: |
||
| 199 | $tmp .= '['; |
||
| 200 | break; |
||
| 201 | |||
| 202 | // We've hit the end of the period |
||
| 203 | case $curr && $curr !== $next: |
||
| 204 | $tmp .= ']'; |
||
| 205 | break; |
||
| 206 | |||
| 207 | // We're adding segments to the current period |
||
| 208 | case $curr && $curr === $prev: |
||
| 209 | $tmp .= '='; |
||
| 210 | break; |
||
| 211 | |||
| 212 | // Otherwise it's just empty space |
||
| 213 | default: |
||
| 214 | $tmp .= ' '; |
||
| 215 | break; |
||
| 216 | } |
||
| 217 | } |
||
| 218 | |||
| 219 | return $tmp; |
||
| 220 | } |
||
| 221 | } |
||
| 222 |
This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.