Completed
Branch master (fa2fc5)
by Boudry
05:50 queued 02:29
created

Vote::getAllCandidates()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

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