Completed
Push — dev-1.6.x ( f43552...3412fc )
by Boudry
16:50
created

Vote::countRankingCandidates()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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