julien-boudry /
Condorcet
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 | Condorcet PHP - Election manager and results calculator. |
||
| 4 | Designed for the Condorcet method. Integrating a large number of algorithms extending Condorcet. Expandable for all types of voting systems. |
||
| 5 | |||
| 6 | By Julien Boudry and contributors - MIT LICENSE (Please read LICENSE.txt) |
||
| 7 | https://github.com/julien-boudry/Condorcet |
||
| 8 | */ |
||
| 9 | declare(strict_types=1); |
||
| 10 | |||
| 11 | namespace CondorcetPHP\Condorcet; |
||
| 12 | |||
| 13 | use CondorcetPHP\Condorcet\ElectionProcess\VoteUtil; |
||
| 14 | use CondorcetPHP\Condorcet\Throwable\CondorcetException; |
||
| 15 | |||
| 16 | class Vote implements \Iterator |
||
| 17 | { |
||
| 18 | use Linkable, CondorcetVersion; |
||
| 19 | |||
| 20 | // Implement Iterator |
||
| 21 | |||
| 22 | private $position = 1; |
||
| 23 | |||
| 24 | 146 | public function rewind() : void { |
|
| 25 | 146 | $this->position = 1; |
|
| 26 | 146 | } |
|
| 27 | |||
| 28 | 3 | public function current() : array { |
|
| 29 | 3 | return $this->getRanking()[$this->position]; |
|
| 30 | } |
||
| 31 | |||
| 32 | 4 | public function key() : int { |
|
| 33 | 4 | return $this->position; |
|
| 34 | } |
||
| 35 | |||
| 36 | 4 | public function next() : void { |
|
| 37 | 4 | ++$this->position; |
|
| 38 | 4 | } |
|
| 39 | |||
| 40 | 4 | public function valid() : bool { |
|
| 41 | 4 | return isset($this->getRanking()[$this->position]); |
|
| 42 | } |
||
| 43 | |||
| 44 | // Vote |
||
| 45 | |||
| 46 | private $_ranking; |
||
| 47 | |||
| 48 | private $_lastTimestamp; |
||
| 49 | |||
| 50 | private $_counter; |
||
| 51 | |||
| 52 | private $_ranking_history = []; |
||
| 53 | |||
| 54 | private $_weight = 1; |
||
| 55 | |||
| 56 | private $_tags = []; |
||
| 57 | |||
| 58 | private $_hashCode; |
||
| 59 | |||
| 60 | private $_electionContext = null; |
||
| 61 | |||
| 62 | /// |
||
| 63 | |||
| 64 | 146 | public function __construct ($ranking, $tags = null, ?float $ownTimestamp = null, ?Election $electionContext = null) |
|
| 65 | { |
||
| 66 | 146 | $this->_electionContext = $electionContext; |
|
| 67 | 146 | $tagsFromString = null; |
|
| 68 | |||
| 69 | // Vote Weight |
||
| 70 | 146 | if (is_string($ranking)) : |
|
| 71 | 119 | $is_voteWeight = mb_strpos($ranking, '^'); |
|
| 72 | 119 | if ($is_voteWeight !== false) : |
|
| 73 | 3 | $weight = trim( substr($ranking, $is_voteWeight + 1) ); |
|
| 74 | |||
| 75 | // Errors |
||
| 76 | 3 | if ( !is_numeric($weight) ) : |
|
| 77 | 1 | throw new CondorcetException(13); |
|
| 78 | endif; |
||
| 79 | |||
| 80 | 3 | $weight = intval($weight); |
|
| 81 | |||
| 82 | 3 | $ranking = substr($ranking, 0,$is_voteWeight); |
|
| 83 | |||
| 84 | endif; |
||
| 85 | |||
| 86 | 119 | $is_voteTags = mb_strpos($ranking, '||'); |
|
| 87 | 119 | if ($is_voteTags !== false) : |
|
| 88 | 4 | $tagsFromString = explode(',', trim( substr($ranking, 0, $is_voteTags) )); |
|
| 89 | 4 | $ranking = substr($ranking, $is_voteTags + 2); |
|
| 90 | endif; |
||
| 91 | endif; |
||
| 92 | |||
| 93 | 146 | $this->setRanking($ranking, $ownTimestamp); |
|
| 94 | 146 | $this->addTags($tags); |
|
| 95 | 146 | $this->addTags($tagsFromString); |
|
| 96 | |||
| 97 | 146 | if (isset($weight)) : |
|
| 98 | 3 | $this->setWeight($weight); |
|
| 99 | endif; |
||
| 100 | |||
| 101 | 146 | $this->_electionContext = null; |
|
| 102 | 146 | } |
|
| 103 | |||
| 104 | 2 | public function __sleep () : array |
|
| 105 | { |
||
| 106 | 2 | $this->position = 1; |
|
| 107 | |||
| 108 | 2 | return array_keys(get_object_vars($this)); |
|
| 109 | } |
||
| 110 | |||
| 111 | 76 | public function __clone () |
|
| 112 | { |
||
| 113 | 76 | $this->destroyAllLink(); |
|
| 114 | 76 | $this->setHashCode(); |
|
| 115 | 76 | } |
|
| 116 | |||
| 117 | 146 | public function __toString () : string { |
|
| 118 | |||
| 119 | 146 | if (empty($this->getTags())) : |
|
| 120 | 146 | return $this->getSimpleRanking(); |
|
| 121 | else : |
||
| 122 | 17 | return $this->getTagsAsString().' || '.$this->getSimpleRanking(); |
|
| 123 | endif; |
||
| 124 | } |
||
| 125 | |||
| 126 | 1 | public function getHashCode () : string { |
|
| 127 | 1 | return $this->_hashCode; |
|
| 128 | } |
||
| 129 | |||
| 130 | /// |
||
| 131 | |||
| 132 | // GETTERS |
||
| 133 | |||
| 134 | 146 | public function getRanking () : array |
|
| 135 | { |
||
| 136 | 146 | return $this->_ranking; |
|
| 137 | } |
||
| 138 | |||
| 139 | 4 | public function getHistory () : array |
|
| 140 | { |
||
| 141 | 4 | return $this->_ranking_history; |
|
| 142 | } |
||
| 143 | |||
| 144 | |||
| 145 | 146 | public function getTags () : array |
|
| 146 | { |
||
| 147 | 146 | return $this->_tags; |
|
| 148 | } |
||
| 149 | |||
| 150 | 17 | public function getTagsAsString () : string |
|
| 151 | { |
||
| 152 | 17 | return implode(',',$this->getTags()); |
|
| 153 | } |
||
| 154 | |||
| 155 | 2 | public function getCreateTimestamp () : float |
|
| 156 | { |
||
| 157 | 2 | return $this->_ranking_history[0]['timestamp']; |
|
| 158 | } |
||
| 159 | |||
| 160 | 19 | public function getTimestamp () : float |
|
| 161 | { |
||
| 162 | 19 | return $this->_lastTimestamp; |
|
| 163 | } |
||
| 164 | |||
| 165 | 1 | public function countRankingCandidates () : int |
|
| 166 | { |
||
| 167 | 1 | return $this->_counter; |
|
| 168 | } |
||
| 169 | |||
| 170 | 127 | public function getAllCandidates () : array |
|
| 171 | { |
||
| 172 | 127 | $list = []; |
|
| 173 | |||
| 174 | 127 | foreach ($this->getRanking() as $rank) : |
|
| 175 | 127 | foreach ($rank as $oneCandidate) : |
|
| 176 | 127 | $list[] = $oneCandidate; |
|
| 177 | endforeach; |
||
| 178 | endforeach; |
||
| 179 | |||
| 180 | 127 | return $list; |
|
| 181 | } |
||
| 182 | |||
| 183 | 102 | public function getContextualRanking (Election $election) : array |
|
| 184 | { |
||
| 185 | 102 | if (!$this->haveLink($election)) : |
|
| 186 | throw new CondorcetException(22); |
||
| 187 | endif; |
||
| 188 | |||
| 189 | 102 | $countContextualCandidate = 0; |
|
| 190 | |||
| 191 | 102 | $present = $this->getAllCandidates(); |
|
| 192 | 102 | $candidates_list = $election->getCandidatesList(); |
|
| 193 | |||
| 194 | 102 | $newRanking = $this->computeContextualRankingWithoutImplicit($this->getRanking(), $election, $countContextualCandidate); |
|
| 195 | |||
| 196 | 102 | if ($election->getImplicitRankingRule() && $countContextualCandidate < $election->countCandidates()) : |
|
| 197 | 46 | $last_rank = []; |
|
| 198 | 46 | foreach ($candidates_list as $oneCandidate) : |
|
| 199 | 46 | if (!in_array($oneCandidate, $present, true)) : |
|
| 200 | 46 | $last_rank[] = $oneCandidate; |
|
| 201 | endif; |
||
| 202 | endforeach; |
||
| 203 | |||
| 204 | 46 | $newRanking[] = $last_rank; |
|
| 205 | endif; |
||
| 206 | |||
| 207 | 102 | return $newRanking; |
|
| 208 | } |
||
| 209 | |||
| 210 | 102 | protected function computeContextualRankingWithoutImplicit (array $ranking, Election $election, int &$countContextualCandidate = 0) : array |
|
| 211 | { |
||
| 212 | 102 | $newRanking = []; |
|
| 213 | 102 | $nextRank = 1; |
|
| 214 | 102 | $rankChange = false; |
|
| 215 | |||
| 216 | 102 | foreach ($ranking as $CandidatesInRanks) : |
|
| 217 | 102 | foreach ($CandidatesInRanks as $candidate) : |
|
| 218 | 102 | if ( $election->isRegisteredCandidate($candidate, true) ) : |
|
| 219 | 102 | $newRanking[$nextRank][] = $candidate; |
|
| 220 | 102 | $countContextualCandidate++; |
|
| 221 | 102 | $rankChange = true; |
|
| 222 | endif; |
||
| 223 | endforeach; |
||
| 224 | |||
| 225 | 102 | if ($rankChange) : |
|
| 226 | 102 | $nextRank++; |
|
| 227 | 102 | $rankChange = false; |
|
| 228 | endif; |
||
| 229 | endforeach; |
||
| 230 | |||
| 231 | 102 | return $newRanking; |
|
| 232 | } |
||
| 233 | |||
| 234 | 1 | public function getContextualRankingAsString (Election $election) : array |
|
| 235 | { |
||
| 236 | 1 | return CondorcetUtil::format($this->getContextualRanking($election),true); |
|
| 237 | } |
||
| 238 | |||
| 239 | 146 | public function getSimpleRanking (?Election $context = null) : string |
|
| 240 | { |
||
| 241 | 146 | $ranking = $context ? $this->getContextualRanking($context) : $this->getRanking(); |
|
| 242 | |||
| 243 | 146 | $simpleRanking = VoteUtil::getRankingAsString($ranking); |
|
| 244 | |||
| 245 | 146 | if ($this->_weight > 1 && ( ($context && $context->isVoteWeightAllowed()) || $context === null ) ) : |
|
| 246 | 8 | $simpleRanking .= " ^".$this->getWeight(); |
|
| 247 | endif; |
||
| 248 | |||
| 249 | 146 | return $simpleRanking; |
|
| 250 | } |
||
| 251 | |||
| 252 | |||
| 253 | // SETTERS |
||
| 254 | |||
| 255 | 146 | public function setRanking ($ranking, ?float $ownTimestamp = null) : bool |
|
| 256 | { |
||
| 257 | // Timestamp |
||
| 258 | 146 | if ($ownTimestamp !== null) : |
|
| 259 | 1 | if (!empty($this->_ranking_history) && $this->getTimestamp() >= $ownTimestamp) : |
|
| 260 | throw new CondorcetException(21); |
||
| 261 | endif; |
||
| 262 | endif; |
||
| 263 | |||
| 264 | // Ranking |
||
| 265 | 146 | $candidateCounter = $this->formatRanking($ranking); |
|
| 266 | |||
| 267 | 146 | if ($this->_electionContext !== null) : |
|
| 268 | 102 | $this->_electionContext->convertRankingCandidates($ranking); |
|
|
0 ignored issues
–
show
|
|||
| 269 | endif; |
||
| 270 | |||
| 271 | 146 | foreach ($this->_link as $link) : |
|
| 272 | 7 | $link->prepareUpdateVote($this); |
|
| 273 | endforeach; |
||
| 274 | |||
| 275 | 146 | $this->_ranking = $ranking; |
|
| 276 | 146 | $this->_lastTimestamp = $ownTimestamp ?? microtime(true); |
|
| 277 | 146 | $this->_counter = $candidateCounter; |
|
| 278 | |||
| 279 | 146 | $this->archiveRanking(); |
|
| 280 | |||
| 281 | 146 | if (!empty($this->_link)) : |
|
| 282 | |||
| 283 | try { |
||
| 284 | 7 | foreach ($this->_link as $link) : |
|
| 285 | 7 | if (!$link->checkVoteCandidate($this)) : |
|
| 286 | 7 | throw new CondorcetException(18); |
|
| 287 | endif; |
||
| 288 | endforeach; |
||
| 289 | } catch (CondorcetException $e) { |
||
| 290 | foreach ($this->_link as $link) : |
||
| 291 | $link->setStateToVote(); |
||
| 292 | endforeach; |
||
| 293 | |||
| 294 | throw $e; |
||
| 295 | } |
||
| 296 | |||
| 297 | 7 | foreach ($this->_link as $link) : |
|
| 298 | 7 | $link->finishUpdateVote($this); |
|
| 299 | endforeach; |
||
| 300 | endif; |
||
| 301 | |||
| 302 | 146 | $this->setHashCode(); |
|
| 303 | 146 | return true; |
|
| 304 | } |
||
| 305 | |||
| 306 | 146 | private function formatRanking (&$ranking) : int |
|
| 307 | { |
||
| 308 | 146 | if (is_string($ranking)) : |
|
| 309 | 120 | $ranking = VoteUtil::convertVoteInput($ranking); |
|
| 310 | endif; |
||
| 311 | |||
| 312 | 146 | if (!is_array($ranking)) : |
|
| 313 | throw new CondorcetException(5); |
||
| 314 | endif; |
||
| 315 | |||
| 316 | $ranking = array_filter($ranking, function ($key) { |
||
| 317 | 143 | return is_numeric($key); |
|
| 318 | 146 | }, ARRAY_FILTER_USE_KEY); |
|
| 319 | |||
| 320 | 146 | ksort($ranking); |
|
| 321 | |||
| 322 | 146 | $i = 1; $vote_r = []; |
|
| 323 | 146 | foreach ($ranking as &$value) : |
|
| 324 | 143 | if ( !is_array($value) ) : |
|
| 325 | 38 | $vote_r[$i] = [$value]; |
|
| 326 | else : |
||
| 327 | 122 | $vote_r[$i] = $value; |
|
| 328 | endif; |
||
| 329 | |||
| 330 | 143 | $i++; |
|
| 331 | endforeach; |
||
| 332 | |||
| 333 | 146 | $ranking = $vote_r; |
|
| 334 | |||
| 335 | 146 | $counter = 0; |
|
| 336 | 146 | $list_candidate = []; |
|
| 337 | 146 | foreach ($ranking as &$line) : |
|
| 338 | 143 | foreach ($line as &$Candidate) : |
|
| 339 | 143 | if ( !($Candidate instanceof Candidate) ) : |
|
| 340 | 122 | $Candidate = new Candidate ($Candidate); |
|
| 341 | 122 | $Candidate->setProvisionalState(true); |
|
| 342 | endif; |
||
| 343 | |||
| 344 | 143 | $counter++; |
|
| 345 | |||
| 346 | // Check Duplicate |
||
| 347 | |||
| 348 | // Check objet reference AND check candidates name |
||
| 349 | 143 | if (!in_array($Candidate, $list_candidate)) : |
|
| 350 | 143 | $list_candidate[] = $Candidate; |
|
| 351 | else : |
||
| 352 | 143 | throw new CondorcetException(5); |
|
| 353 | endif; |
||
| 354 | |||
| 355 | endforeach; |
||
| 356 | endforeach; |
||
| 357 | |||
| 358 | 146 | return $counter; |
|
| 359 | } |
||
| 360 | |||
| 361 | |||
| 362 | 3 | public function removeCandidate ($candidate) : bool |
|
| 363 | { |
||
| 364 | 3 | if ($candidate instanceof Candidate) : |
|
| 365 | 1 | $strict = true; |
|
| 366 | 3 | elseif (is_string($candidate)) : |
|
| 367 | 2 | $strict = false; |
|
| 368 | else : |
||
| 369 | 1 | throw new CondorcetException (32); |
|
| 370 | endif; |
||
| 371 | |||
| 372 | 2 | $ranking = $this->getRanking(); |
|
| 373 | |||
| 374 | 2 | $rankingCandidate = $this->getAllCandidates(); |
|
| 375 | |||
| 376 | 2 | if (!in_array($candidate, $rankingCandidate, $strict)) : |
|
| 377 | 1 | throw new CondorcetException (32); |
|
| 378 | endif; |
||
| 379 | |||
| 380 | 2 | foreach ($ranking as $rankingKey => &$rank) : |
|
| 381 | 2 | foreach ($rank as $oneRankKey => $oneRankValue) : |
|
| 382 | 2 | if ( $strict ? $oneRankValue === $candidate : $oneRankValue == $candidate ) : |
|
| 383 | 2 | unset($rank[$oneRankKey]); |
|
| 384 | endif; |
||
| 385 | endforeach; |
||
| 386 | |||
| 387 | 2 | if (empty($rank)) : |
|
| 388 | 2 | unset($ranking[$rankingKey]); |
|
| 389 | endif; |
||
| 390 | endforeach; |
||
| 391 | |||
| 392 | 2 | $this->setRanking($ranking); |
|
| 393 | |||
| 394 | 2 | return true; |
|
| 395 | } |
||
| 396 | |||
| 397 | |||
| 398 | 146 | public function addTags ($tags) : bool |
|
| 399 | { |
||
| 400 | 146 | $tags = VoteUtil::tagsConvert($tags); |
|
| 401 | |||
| 402 | 146 | if (empty($tags)) : |
|
| 403 | 146 | return false; |
|
| 404 | endif; |
||
| 405 | |||
| 406 | 17 | foreach ($tags as $key => $tag) : |
|
| 407 | 17 | if (is_numeric($tag)) : |
|
| 408 | throw new CondorcetException(17); |
||
| 409 | 17 | elseif (in_array($tag, $this->_tags, true)) : |
|
| 410 | 17 | unset($tags[$key]); |
|
| 411 | endif; |
||
| 412 | endforeach; |
||
| 413 | |||
| 414 | 17 | foreach ($tags as $tag) : |
|
| 415 | 17 | $this->_tags[] = $tag; |
|
| 416 | endforeach; |
||
| 417 | |||
| 418 | 17 | $this->setHashCode(); |
|
| 419 | |||
| 420 | 17 | return true; |
|
| 421 | } |
||
| 422 | |||
| 423 | 2 | public function removeTags ($tags) : array |
|
| 424 | { |
||
| 425 | 2 | $tags = VoteUtil::tagsConvert($tags); |
|
| 426 | |||
| 427 | 2 | if (empty($tags)) : |
|
| 428 | 2 | return []; |
|
| 429 | endif; |
||
| 430 | |||
| 431 | 2 | $rm = []; |
|
| 432 | 2 | foreach ($tags as $key => $tag) : |
|
| 433 | 2 | $tagK = array_search($tag, $this->_tags, true); |
|
| 434 | |||
| 435 | 2 | if ($tagK === false) : |
|
| 436 | 1 | unset($tags[$key]); |
|
| 437 | else : |
||
| 438 | 2 | $rm[] = $this->_tags[$tagK]; |
|
| 439 | 2 | unset($this->_tags[$tagK]); |
|
| 440 | endif; |
||
| 441 | endforeach; |
||
| 442 | |||
| 443 | 2 | $this->setHashCode(); |
|
| 444 | 2 | return $rm; |
|
| 445 | } |
||
| 446 | |||
| 447 | 2 | public function removeAllTags () : bool |
|
| 448 | { |
||
| 449 | 2 | $this->removeTags($this->getTags()); |
|
| 450 | 2 | return true; |
|
| 451 | } |
||
| 452 | |||
| 453 | 93 | public function getWeight () : int |
|
| 454 | { |
||
| 455 | 93 | return $this->_weight; |
|
| 456 | } |
||
| 457 | |||
| 458 | 94 | public function setWeight (int $newWeight) : int |
|
| 459 | { |
||
| 460 | 94 | if ($newWeight < 1) : |
|
| 461 | 1 | throw new CondorcetException(26); |
|
| 462 | endif; |
||
| 463 | |||
| 464 | 93 | if ($newWeight !== $this->_weight) : |
|
| 465 | |||
| 466 | 8 | $this->_weight = $newWeight; |
|
| 467 | |||
| 468 | 8 | if (!empty($this->_link)) : |
|
| 469 | 1 | foreach ($this->_link as &$link) : |
|
| 470 | 1 | $link->setStateToVote(); |
|
| 471 | endforeach; |
||
| 472 | endif; |
||
| 473 | endif; |
||
| 474 | |||
| 475 | 93 | $this->setHashCode(); |
|
| 476 | |||
| 477 | 93 | return $this->getWeight(); |
|
| 478 | } |
||
| 479 | |||
| 480 | /////////// INTERNAL /////////// |
||
| 481 | |||
| 482 | 146 | private function archiveRanking () : void |
|
| 483 | { |
||
| 484 | 146 | $this->_ranking_history[] = [ 'ranking' => $this->_ranking, |
|
| 485 | 146 | 'timestamp' => $this->_lastTimestamp, |
|
| 486 | 146 | 'counter' => $this->_counter ]; |
|
| 487 | |||
| 488 | 146 | $this->rewind(); |
|
| 489 | 146 | } |
|
| 490 | |||
| 491 | 146 | private function setHashCode () : string |
|
| 492 | { |
||
| 493 | 146 | return $this->_hashCode = hash('sha224', ((string) $this) . microtime(false)); |
|
| 494 | } |
||
| 495 | } |
||
| 496 |
Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.