Passed
Branch dev-2.0 (9d1c14)
by Boudry
02:42
created

Vote::getRanking()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.0625

Importance

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