Passed
Branch dev-2.0 (7a718b)
by Boudry
03:40
created

Vote::setRanking()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 8.1426

Importance

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