Completed
Branch dev-1.7.x (184d88)
by Boudry
05:25
created

Vote::__sleep()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 0
crap 1
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 121
        public function rewind() : void {
26 121
            $this->position = 1;
27 121
        }
28
29 110
        public function current() {
30 110
            return $this->getRanking()[$this->position];
31
        }
32
33 110
        public function key() : int {
34 110
            return $this->position;
35
        }
36
37 110
        public function next() : void {
38 110
            ++$this->position;
39 110
        }
40
41 110
        public function valid() : bool {
42 110
            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 121
    public function __construct ($ranking, $tags = null, ?float $ownTimestamp = null)
58
    {
59 121
        $tagsFromString = null;
60
        // Vote Weight
61 121
        if (is_string($ranking)) :
62 104
            $is_voteWeight = mb_strpos($ranking, '^');
63 104
            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 104
            $is_voteTags = mb_strpos($ranking, '||');
74 104
            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 121
        $this->setRanking($ranking, $ownTimestamp);
81 121
        $this->addTags($tags);
82 121
        $this->addTags($tagsFromString);
83
84 121
        if (isset($weight)) :
85 1
            $this->setWeight($weight);
86
        endif;
87 121
    }
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 121
    public function __toString () : string {
103
        
104 121
        if (empty($this->getTags())) :
105 121
            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 121
    public function getRanking () : ?array
120
    {
121 121
        if (!empty($this->_ranking)) :
122 121
            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 121
    public function getTags () : array
135
    {
136 121
        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 103
    public function getTimestamp () : float
150
    {
151 103
        return end($this->_ranking)['timestamp'];
152
    }
153
154 1
    public function countRankingCandidates () : int
155
    {
156 1
        return end($this->_ranking)['counter'];
157
    }
158
159 95
    public function getAllCandidates () : array
160
    {
161 95
        $list = [];
162
163 95
        foreach ($this->getRanking() as $rank) :
164 95
            foreach ($rank as $oneCandidate) :
165 95
                $list[] = $oneCandidate;
166
            endforeach;
167
        endforeach;
168
169 95
        return $list;
170
    }
171
172 94
    public function getContextualRanking (Election $election, bool $string = false) : array
173
    {
174 94
        if (!$this->haveLink($election)) :
175
            throw new CondorcetException(22);
176
        endif;
177
178 94
        $ranking = $this->getRanking();
179 94
        $present = $this->getAllCandidates();
180 94
        $newRanking = [];
181 94
        $candidates_list = $election->getCandidatesList(false);
182
183 94
        $nextRank = 1;
184 94
        $countContextualCandidate = 0;
185
186 94
        foreach ($ranking as $CandidatesInRanks) :
187 94
            foreach ($CandidatesInRanks as $candidate) :
188 94
                if ( $election->existCandidateId($candidate, true) ) :
189 94
                    $newRanking[$nextRank][] = $candidate;
190 94
                    $countContextualCandidate++;
191
                endif;
192
            endforeach;
193
194 94
            if (isset($newRanking[$nextRank])) :
195 94
                $nextRank++;
196
            endif;
197
        endforeach;
198
199 94
        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 94
        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 94
        return $newRanking;
219
    }
220
221 121
    public function getSimpleRanking (?Election $context = null) : string
222
    {
223 121
        $ranking = ($context) ? $this->getContextualRanking($context) : $this->getRanking();
224
225 121
        $simpleRanking = VoteUtil::getRankingAsString($ranking);
226
227 121
        if ($this->_weight > 1 && ( ($context && $context->isVoteWeightIsAllowed()) || $context === null )  ) :
228 4
            $simpleRanking .= " ^".$this->getWeight();
229
        endif;
230
231 121
        return $simpleRanking;
232
    }
233
234
235
    // SETTERS
236
237 121
    public function setRanking ($rankingCandidate, ?float $ownTimestamp = null) : bool
238
    {
239
        // Timestamp
240 121
        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 121
        $candidateCounter = $this->formatRanking($rankingCandidate);
248
249 121
        $this->archiveRanking($rankingCandidate, $candidateCounter, $ownTimestamp);
250
251 121
        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 121
        $this->setHashCode();
266 121
        return true;
267
    }
268
269 121
        private function formatRanking (&$ranking) : int
270
        {
271 121
            if (is_string($ranking)) :
272 105
                $ranking = VoteUtil::convertVoteInput($ranking);
273
            endif;
274
275 121
            if (!is_array($ranking)) :
276
                throw new CondorcetException(5);
277
            endif;
278
279
            $ranking = array_filter($ranking, function ($key) {
280 118
                return is_numeric($key);
281 121
            }, ARRAY_FILTER_USE_KEY);
282
283 121
            ksort($ranking);
284
            
285 121
            $i = 1; $vote_r = [];
286 121
            foreach ($ranking as &$value) :
287 118
                if ( !is_array($value) ) :
288 26
                    $vote_r[$i] = [$value];
289
                else :
290 108
                    $vote_r[$i] = $value;
291
                endif;
292
293 118
                $i++;
294
            endforeach;
295
296 121
            $ranking = $vote_r;
297
298 121
            $counter = 0;
299 121
            $list_candidate = [];
300 121
            foreach ($ranking as &$line) :
301 118
                foreach ($line as &$Candidate) :
302 118
                    if ( !($Candidate instanceof Candidate) ) :
303 107
                        $Candidate = new Candidate ($Candidate);
304 107
                        $Candidate->setProvisionalState(true);
305
                    endif;
306
307 118
                    $counter++;
308
309
                // Check Duplicate
310
311
                    // Check objet reference AND check candidates name
312 118
                    if (!in_array($Candidate, $list_candidate)) :
313 118
                        $list_candidate[] = $Candidate;
314
                    else : 
315 118
                        throw new CondorcetException(5);
316
                    endif;
317
318
                endforeach;
319
            endforeach;
320
321 121
            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 121
    public function addTags ($tags) : bool
366
    {
367 121
        if (is_object($tags) || is_bool($tags)) :
368
            throw new CondorcetException(17);
369
        endif;
370
371 121
        $tags = VoteUtil::tagsConvert($tags);
372
373 121
        if (empty($tags)) :
374 121
            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 82
    public function getWeight () : int
431
    {
432 82
        return $this->_weight;
433
    }
434
435 82
    public function setWeight (int $newWeight) : int
436
    {
437 82
        if ($newWeight < 1) :
438
            throw new CondorcetException(26);
439
        endif;
440
441 82
        $this->_weight = $newWeight;
442
443 82
        if (!empty($this->_link)) :
444 81
            foreach ($this->_link as &$link) :
445 81
                $link->setStateToVote();
446
            endforeach;
447
        endif;
448
449 82
        $this->setHashCode();
450
451 82
        return $this->getWeight();
452
    }
453
454
/////////// INTERNAL ///////////
455
456 121
    private function archiveRanking ($ranking, int $counter, ?float $ownTimestamp) : void
457
    {
458 121
        $this->_ranking[] = [   'ranking' => $ranking,
459 121
                                'timestamp' => ($ownTimestamp !== null) ? $ownTimestamp : microtime(true),
460 121
                                'counter' => $counter   ];
461
462 121
        $this->rewind();
463 121
    }
464
465 121
    private function setHashCode () : string
466
    {
467 121
        return $this->_hashCode = hash('sha224', ((string) $this) . microtime(false));
468
    }
469
}
470