Completed
Branch dev-2.0 (210bc7)
by Boudry
06:33
created

Vote::key()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
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);
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
        if (!empty($this->_link)) :
251 7
            foreach ($this->_link as $link) :
252 7
                $link->prepareUpdateVote($this);
253
            endforeach;
254
        endif;
255
256 143
        $this->archiveRanking($rankingCandidate, $candidateCounter, $ownTimestamp);
257
258 143
        if (!empty($this->_link)) :
259
            
260
            try {
261 7
                foreach ($this->_link as $link) :
262 7
                    if (!$link->checkVoteCandidate($this)) :
263 7
                        throw new CondorcetException(18);
264
                    endif;
265
                endforeach;
266
            } catch (CondorcetException $e) {
267
                foreach ($this->_link as $link) :
268
                    $link->setStateToVote();
269
                endforeach;
270
271
                throw $e;
272
            }
273
274 7
            foreach ($this->_link as $link) :
275 7
                $link->finishUpdateVote($this);
276
            endforeach;
277
        endif;
278
279 143
        $this->setHashCode();
280 143
        return true;
281
    }
282
283 143
        private function formatRanking (&$ranking) : int
284
        {
285 143
            if (is_string($ranking)) :
286 118
                $ranking = VoteUtil::convertVoteInput($ranking);
287
            endif;
288
289 143
            if (!is_array($ranking)) :
290
                throw new CondorcetException(5);
291
            endif;
292
293
            $ranking = array_filter($ranking, function ($key) {
294 140
                return is_numeric($key);
295 143
            }, ARRAY_FILTER_USE_KEY);
296
297 143
            ksort($ranking);
298
            
299 143
            $i = 1; $vote_r = [];
300 143
            foreach ($ranking as &$value) :
301 140
                if ( !is_array($value) ) :
302 35
                    $vote_r[$i] = [$value];
303
                else :
304 121
                    $vote_r[$i] = $value;
305
                endif;
306
307 140
                $i++;
308
            endforeach;
309
310 143
            $ranking = $vote_r;
311
312 143
            $counter = 0;
313 143
            $list_candidate = [];
314 143
            foreach ($ranking as &$line) :
315 140
                foreach ($line as &$Candidate) :
316 140
                    if ( !($Candidate instanceof Candidate) ) :
317 120
                        $Candidate = new Candidate ($Candidate);
318 120
                        $Candidate->setProvisionalState(true);
319
                    endif;
320
321 140
                    $counter++;
322
323
                // Check Duplicate
324
325
                    // Check objet reference AND check candidates name
326 140
                    if (!in_array($Candidate, $list_candidate)) :
327 140
                        $list_candidate[] = $Candidate;
328
                    else : 
329 140
                        throw new CondorcetException(5);
330
                    endif;
331
332
                endforeach;
333
            endforeach;
334
335 143
            return $counter;
336
        }
337
338
339 3
    public function removeCandidate ($candidate) : bool
340
    {
341 3
        if ($candidate instanceof Candidate) :
342 1
            $strict = true;
343 3
        elseif (is_string($candidate)) :
344 2
            $strict = false;
345
        else :
346 1
            throw new CondorcetException (32);
347
        endif;
348
349 2
        $ranking = $this->getRanking();
350
351 2
        $rankingCandidate = $this->getAllCandidates();
352
353 2
        if (!in_array($candidate, $rankingCandidate, $strict)) :
354 1
            throw new CondorcetException (32);
355
        endif;
356
357 2
        foreach ($ranking as $rankingKey => &$rank) :
358 2
            foreach ($rank as $oneRankKey => $oneRankValue) :
359 2
                if ( $strict ? $oneRankValue === $candidate : $oneRankValue == $candidate ) :
360 2
                    unset($rank[$oneRankKey]);
361
                endif;
362
            endforeach;
363
364 2
            if (empty($rank)) :
365 2
                unset($ranking[$rankingKey]);
366
            endif;
367
        endforeach;
368
369 2
        $this->setRanking($ranking);
370
371 2
        return true;
372
    }
373
374
375 143
    public function addTags ($tags) : bool
376
    {
377 143
        $tags = VoteUtil::tagsConvert($tags);
378
379 143
        if (empty($tags)) :
380 143
            return false;
381
        endif;
382
383 15
        foreach ($tags as $key => $tag) :
384 15
            if (is_numeric($tag)) :
385
                throw new CondorcetException(17);
386 15
            elseif (in_array($tag, $this->_tags, true)) :
387 15
                unset($tags[$key]);
388
            endif;
389
        endforeach;
390
391 15
        foreach ($tags as $tag) :
392 15
            $this->_tags[] = $tag;
393
        endforeach;
394
395 15
        $this->setHashCode();
396
397 15
        return true;
398
    }
399
400 2
    public function removeTags ($tags) : array
401
    {
402 2
        $tags = VoteUtil::tagsConvert($tags);
403
404 2
        if (empty($tags)) :
405 2
            return [];
406
        endif;
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 91
    public function getWeight () : int
431
    {
432 91
        return $this->_weight;
433
    }
434
435 92
    public function setWeight (int $newWeight) : int
436
    {
437 92
        if ($newWeight < 1) :
438 1
            throw new CondorcetException(26);
439
        endif;
440
441 91
        $this->_weight = $newWeight;
442
443 91
        if (!empty($this->_link)) :
444 1
            foreach ($this->_link as &$link) :
445 1
                $link->setStateToVote();
446
            endforeach;
447
        endif;
448
449 91
        $this->setHashCode();
450
451 91
        return $this->getWeight();
452
    }
453
454
/////////// INTERNAL ///////////
455
456 143
    private function archiveRanking ($ranking, int $counter, ?float $ownTimestamp) : void
457
    {
458 143
        $this->_ranking[] = [   'ranking' => $ranking,
459 143
                                'timestamp' => $ownTimestamp ?? microtime(true),
460 143
                                'counter' => $counter   ];
461
462 143
        $this->rewind();
463 143
    }
464
465 143
    private function setHashCode () : string
466
    {
467 143
        return $this->_hashCode = hash('sha224', ((string) $this) . microtime(false));
468
    }
469
}
470