allejo /
bzion
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'; |
||
|
0 ignored issues
–
show
|
|||
| 86 | $since = "in the $last " . $date->diffForHumans(null, true); |
||
|
0 ignored issues
–
show
|
|||
| 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); |
||
|
0 ignored issues
–
show
$date is of type string|null, but the function expects a boolean.
It seems like the type of the argument is not accepted by the function/method which you are calling. In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug. We suggest to add an explicit type cast like in the following example: function acceptsInteger($int) { }
$x = '123'; // string "123"
// Instead of
acceptsInteger($x);
// we recommend to use
acceptsInteger((integer) $x);
Loading history...
|
|||
| 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()) { |
||
|
0 ignored issues
–
show
It seems like you code against a concrete implementation and not the interface
Symfony\Component\Console\Output\OutputInterface as the method isVeryVerbose() does only exist in the following implementations of said interface: BZIon\Command\NullOutput, Symfony\Component\Console\Output\BufferedOutput, Symfony\Component\Console\Output\ConsoleOutput, Symfony\Component\Console\Output\NullOutput, Symfony\Component\Console\Output\Output, Symfony\Component\Console\Output\StreamOutput, Symfony\Component\Consol...ts\Fixtures\DummyOutput, Symfony\Component\Console\Tests\Output\TestOutput.
Let’s take a look at an example: interface User
{
/** @return string */
public function getPassword();
}
class MyUser implements User
{
public function getPassword()
{
// return something
}
public function getDisplayName()
{
// return some name.
}
}
class AuthSystem
{
public function authenticate(User $user)
{
$this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
// do something.
}
}
In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break. Available Fixes
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types
inside the if block in such a case.
Loading history...
|
|||
| 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!=, orswitchconditions), values of different types might be equal.For
stringvalues, the empty string''is a special case, in particular the following results might be unexpected: