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 BZIon\Command; |
||
4 | |||
5 | use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; |
||
6 | use Symfony\Component\Console\Input\InputInterface; |
||
7 | use Symfony\Component\Console\Input\InputOption; |
||
8 | use Symfony\Component\Console\Output\OutputInterface; |
||
9 | use Symfony\Component\Yaml\Yaml; |
||
10 | |||
11 | class ChangesCommand extends ContainerAwareCommand |
||
12 | { |
||
13 | /** |
||
14 | * The date when the latest changes were most recently shown |
||
15 | * |
||
16 | * @var null|\TimeDate |
||
17 | */ |
||
18 | private $lastUpdateDate = null; |
||
19 | |||
20 | /** |
||
21 | * An array of the most recent changelog entries that were shown to the user |
||
22 | * on the last update |
||
23 | * |
||
24 | * Used to prevent showing the same changelog entries if the user updated |
||
25 | * two times in the same day |
||
26 | * |
||
27 | * @var array |
||
28 | */ |
||
29 | private $alreadyListedChanges = array(); |
||
30 | |||
31 | /** |
||
32 | * {@inheritdoc} |
||
33 | */ |
||
34 | protected function configure() |
||
35 | { |
||
36 | $this |
||
37 | ->setName('bzion:changes') |
||
38 | ->setDescription('List new features and bug fixes since the last update') |
||
39 | ->addOption( |
||
40 | 'changelog', |
||
41 | 'c', |
||
42 | InputOption::VALUE_OPTIONAL, |
||
43 | 'The path to the changelog file', |
||
44 | dirname(dirname(__DIR__)) . '/app/changelog.yml' |
||
45 | ) |
||
46 | ->addOption( |
||
47 | 'lastupdate', |
||
48 | 'l', |
||
49 | InputOption::VALUE_OPTIONAL, |
||
50 | 'The path to the file containing the date of the last update', |
||
51 | dirname(dirname(__DIR__)) . '/app/lastupdate.yml' |
||
52 | ) |
||
53 | ->addOption( |
||
54 | 'date', |
||
55 | 'd', |
||
56 | InputOption::VALUE_OPTIONAL, |
||
57 | 'Show all the changes made since the given date, overrides the lastupdate file' |
||
58 | ) |
||
59 | ->addOption( |
||
60 | 'read', |
||
61 | null, |
||
62 | InputOption::VALUE_NONE, |
||
63 | 'Mark all the changes made before the current date as read' |
||
64 | ); |
||
65 | } |
||
66 | |||
67 | /** |
||
68 | * {@inheritdoc} |
||
69 | */ |
||
70 | protected function execute(InputInterface $input, OutputInterface $output) |
||
71 | { |
||
72 | $lastUpdatePath = $input->getOption('lastupdate'); |
||
73 | $date = $input->getOption('date'); |
||
74 | $markRead = $input->getOption('read'); |
||
75 | $changelog = Yaml::parse($input->getOption('changelog')); |
||
76 | |||
77 | $this->parseOptions($lastUpdatePath, $date, $output); |
||
78 | |||
79 | // Make sure the changelog dates are properly sorted (more recent to older) |
||
80 | LogCommand::sort($changelog); |
||
81 | $listed = $this->parseChangelog($changelog); |
||
82 | |||
83 | if (!$markRead) { |
||
84 | if ($date) { |
||
0 ignored issues
–
show
|
|||
85 | $last = $date->isFuture() ? 'next' : 'last'; |
||
86 | $since = "in the $last " . $date->diffForHumans(null, true); |
||
87 | } else { |
||
88 | $since = 'since the last update'; |
||
89 | } |
||
90 | |||
91 | if ($this->isEmpty($listed)) { |
||
92 | $output->writeln("No significant changes $since."); |
||
93 | } else { |
||
94 | $output->writeln("Changes $since:"); |
||
95 | $this->renderChangeList($listed, $output); |
||
96 | } |
||
97 | } |
||
98 | |||
99 | $this->storeLastUpdate($lastUpdatePath, $date); |
||
100 | |||
101 | // Reset properties in case execute() is run again |
||
102 | $this->lastUpdateDate = null; |
||
103 | $this->alreadyListedChanges = array(); |
||
104 | } |
||
105 | |||
106 | /** |
||
107 | * Parse the command line options concerning the date of the last update |
||
108 | * |
||
109 | * @param string $lastUpdatePath The path to the last update file |
||
110 | * @param string|null $date The date command line argument |
||
111 | * @param OutputInterface $output The command's output |
||
112 | * @return void |
||
113 | */ |
||
114 | private function parseOptions($lastUpdatePath, &$date, $output) |
||
115 | { |
||
116 | $message = null; |
||
117 | |||
118 | if ($date) { |
||
0 ignored issues
–
show
The expression
$date of type string|null is loosely compared to true ; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.
In PHP, under loose comparison (like For '' == false // true
'' == null // true
'ab' == false // false
'ab' == null // false
// It is often better to use strict comparison
'' === false // false
'' === null // false
Loading history...
|
|||
119 | $this->lastUpdateDate = $date = \TimeDate::from($date)->startOfDay(); |
||
120 | } elseif (!file_exists($lastUpdatePath)) { |
||
121 | $message = "Last update file not found, a new one is going to be created"; |
||
122 | } else { |
||
123 | $message = $this->parseLastUpdate($lastUpdatePath); |
||
124 | } |
||
125 | |||
126 | if ($output->isVeryVerbose()) { |
||
127 | $output->writeln($message); |
||
128 | |||
129 | if ($this->lastUpdateDate) { |
||
130 | $formattedDate = $this->lastUpdateDate->toFormattedDateString(); |
||
131 | $output->writeln("Showing changes since <options=bold>$formattedDate</options=bold>"); |
||
132 | } |
||
133 | |||
134 | $output->writeln(""); |
||
135 | } |
||
136 | } |
||
137 | |||
138 | /** |
||
139 | * Parse the last update file |
||
140 | * |
||
141 | * @param string $path The path to the last update file |
||
142 | * @return string The message to show to the user |
||
143 | */ |
||
144 | private function parseLastUpdate($path) |
||
145 | { |
||
146 | $lastUpdate = Yaml::parse($path); |
||
147 | $this->lastUpdateDate = \TimeDate::from($lastUpdate['date']); |
||
148 | $this->alreadyListedChanges = $lastUpdate['changes']; |
||
149 | |||
150 | return "Using <options=bold>$path</options=bold>"; |
||
151 | } |
||
152 | |||
153 | /** |
||
154 | * Get a list of changes that will be shown to the user |
||
155 | * |
||
156 | * @param array[] $changelog The parsed changelog.yml file |
||
157 | * @return array[] The changes to show to the user |
||
158 | */ |
||
159 | private function parseChangelog($changelog) |
||
160 | { |
||
161 | $listed = array(); |
||
162 | $firstEntry = true; |
||
163 | $lastChangeDate = \TimeDate::now()->startOfDay(); |
||
164 | $lastChanges = array(); |
||
165 | |||
166 | foreach ($changelog as $date => $changes) { |
||
167 | $date = \TimeDate::from($date); |
||
168 | |||
169 | if ($firstEntry) { |
||
170 | // The array has been sorted, the first entry represents the |
||
171 | // most recent change. Store its date so that we don't show the |
||
172 | // same entry many times |
||
173 | $firstEntry = false; |
||
174 | |||
175 | if ($lastChangeDate >= $date) { |
||
176 | $lastChangeDate = $date; |
||
177 | $lastChanges = $changes; |
||
178 | } |
||
179 | } |
||
180 | |||
181 | // Don't list changes that we've listed before |
||
182 | if ($date == $this->lastUpdateDate) { |
||
183 | $this->filterAlreadyListedChanges($changes); |
||
184 | } elseif ($this->lastUpdateDate && $date < $this->lastUpdateDate) { |
||
185 | break; |
||
186 | } |
||
187 | |||
188 | $listed = array_merge_recursive($listed, $changes); |
||
189 | } |
||
190 | |||
191 | $this->alreadyListedChanges = $lastChanges; |
||
192 | $this->lastUpdateDate = $lastChangeDate; |
||
193 | |||
194 | return $listed; |
||
195 | } |
||
196 | |||
197 | /** |
||
198 | * Filter out the changes made today that have already been shown to the |
||
199 | * user |
||
200 | * @param array $changes Today's changes |
||
201 | * @return void |
||
202 | */ |
||
203 | private function filterAlreadyListedChanges(&$changes) |
||
204 | { |
||
205 | $alreadyListed = $this->alreadyListedChanges; |
||
206 | |||
207 | foreach ($changes as $type => &$changelist) { |
||
208 | $changelist = array_filter($changelist, function ($change) use ($type, $alreadyListed) { |
||
209 | if (!isset($alreadyListed[$type])) { |
||
210 | return true; |
||
211 | } |
||
212 | |||
213 | return !in_array($change, $alreadyListed[$type]); |
||
214 | }); |
||
215 | } |
||
216 | } |
||
217 | |||
218 | /** |
||
219 | * Show a list of changes in a user-readable format |
||
220 | * |
||
221 | * @param array[] $listed The changes that should be listed |
||
222 | * @param OutputInterface $output The command's output |
||
223 | * @return void |
||
224 | */ |
||
225 | private function renderChangeList($listed, OutputInterface $output) |
||
226 | { |
||
227 | $types = array( |
||
228 | 'Features' => '<info>[+] %s</info>', |
||
229 | 'Bugfixes' => '<comment>[*] %s</comment>', |
||
230 | 'Notes' => '<bg=red;options=bold>[!] %s</bg=red;options=bold>', |
||
231 | ); |
||
232 | |||
233 | foreach ($types as $type => $format) { |
||
234 | if (isset($listed[$type])) { |
||
235 | foreach ($listed[$type] as $change) { |
||
236 | $output->writeln(sprintf($format, $change)); |
||
237 | } |
||
238 | } |
||
239 | } |
||
240 | } |
||
241 | |||
242 | /** |
||
243 | * Store the newest entry's date into the last update file, so that the user |
||
244 | * isn't shown the same changes in the future |
||
245 | * |
||
246 | * @param string $path The path to the last update file |
||
247 | * @param bool $date The date command line argument (used to determine |
||
248 | * whether we should store the last update or not) |
||
249 | * @return void |
||
250 | */ |
||
251 | private function storeLastUpdate($path, $date) |
||
252 | { |
||
253 | if ($date !== null) { |
||
254 | // The user probably run the command to see old changes, we don't |
||
255 | // consider this a result of an update |
||
256 | return; |
||
257 | } |
||
258 | |||
259 | $data = array( |
||
260 | 'date' => $this->lastUpdateDate->toFormattedDateString(), |
||
261 | 'changes' => $this->alreadyListedChanges |
||
262 | ); |
||
263 | |||
264 | file_put_contents($path, Yaml::dump($data, 3)); |
||
265 | } |
||
266 | |||
267 | /** |
||
268 | * Recursively find out if an array is empty |
||
269 | * |
||
270 | * @param array $array The array to test |
||
271 | * @return bool|null |
||
272 | */ |
||
273 | private function isEmpty(array $array) |
||
274 | { |
||
275 | if (empty($array)) { |
||
276 | return true; |
||
277 | } |
||
278 | |||
279 | foreach ($array as $child) { |
||
280 | if (!is_array($child)) { |
||
281 | return false; |
||
282 | } |
||
283 | |||
284 | return self::isEmpty($child); |
||
285 | } |
||
286 | |||
287 | return null; |
||
288 | } |
||
289 | } |
||
290 |
In PHP, under loose comparison (like
==
, or!=
, orswitch
conditions), values of different types might be equal.For
string
values, the empty string''
is a special case, in particular the following results might be unexpected: