Passed
Branch master (73ca69)
by Boudry
03:33
created

Election::jsonVotes()   D

Complexity

Conditions 10
Paths 19

Size

Total Lines 32
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 110

Importance

Changes 0
Metric Value
dl 0
loc 32
ccs 0
cts 16
cp 0
rs 4.8196
c 0
b 0
f 0
cc 10
eloc 22
nc 19
nop 1
crap 110

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 25 and the first side effect is on line 58.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
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\Condorcet;
13
use Condorcet\CondorcetException;
14
use Condorcet\CondorcetVersion;
15
use Condorcet\Result;
16
use Condorcet\Vote;
17
use Condorcet\Algo\Pairwise;
18
use Condorcet\DataManager\VotesManager;
19
use Condorcet\DataManager\DataHandlerDrivers\DataHandlerDriverInterface;
20
use Condorcet\Timer\Chrono as Timer_Chrono;
21
use Condorcet\Timer\Manager as Timer_Manager;
22
23
24
// Base Condorcet class
25
class Election
26
{
27
28
/////////// PROPERTIES ///////////
29
30
    public const MAX_LENGTH_CANDIDATE_ID = 30; // Max length for candidate identifiant string
31
32
    protected static $_maxParseIteration = null;
33
    protected static $_maxVoteNumber = null;
34
    protected static $_checksumMode = false;
35
36
/////////// STATICS METHODS ///////////
37
38
    // Change max parse iteration
39
    public static function setMaxParseIteration (?int $value) : ?int
40
    {
41
        self::$_maxParseIteration = $value;
42
        return self::$_maxParseIteration;
43
    }
44
45
    // Change max vote number
46
    public static function setMaxVoteNumber (?int $value) : ?int
47
    {
48
        self::$_maxVoteNumber = $value;
49
        return self::$_maxVoteNumber;
50
    }
51
52
53
    // Check JSON format
54
    public static function isJson (string $string) : bool
55
    {
56
        if (is_numeric($string) || $string === 'true' || $string === 'false' || $string === 'null' || empty($string)) :
57
            return false;
58
        endif;
59
60
        // try to decode string
61
        json_decode($string);
1 ignored issue
show
Coding Style introduced by
The visibility should be declared for property $string.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
62
63
        // check if error occured
64
        return json_last_error() === JSON_ERROR_NONE;
65
    }
66
67
68
    // Generic action before parsing data from string input
69 2
    public static function prepareParse (string $input, bool $allowFile) : array
70
    {
71
        // Is string or is file ?
72 2
        if ($allowFile === true && is_file($input)) :
73
            $input = file_get_contents($input);
74
        endif;
75
76
        // Line
77 2
        $input = preg_replace("(\r\n|\n|\r)",';',$input);
78 2
        $input = explode(';', $input);
79
80
        // Delete comments
81 2
        foreach ($input as &$line) :
82
            // Delete comments
83 2
            $is_comment = mb_strpos($line, '#');
84 2
            if ($is_comment !== false) :
85 2
                $line = substr($line, 0, $is_comment);
86
            endif;
87
88
            // Trim
89 2
            $line = trim($line);
90
        endforeach;
91
92 2
        return $input;
93
    }
94
95
96
    public static function prepareJson (string $input)
97
    {
98
        if (!self::isJson($input)) :
99
            throw new CondorcetException(15);
100
        endif;
101
102
        return json_decode($input, true);
103
    }
104
105
106
/////////// CONSTRUCTOR ///////////
107
108
    use CondorcetVersion;
109
110
    // Data and global options
111
    protected $_Candidates = []; // Candidate list
112
    protected $_Votes; // Votes list
113
114
    // Mechanics
115
    protected $_i_CandidateId = 'A';
116
    protected $_State = 1; // 1 = Add Candidates / 2 = Voting / 3 = Some result have been computing
117
    protected $_timer;
118
    protected $_nextVoteTag = 0;
119
    protected $_ignoreStaticMaxVote = false;
120
121
    // Params
122
    protected $_ImplicitRanking = true;
123
    protected $_VoteWeightRule = false;
124
125
    // Result
126
    protected $_Pairwise;
127
    protected $_Calculator;
128
129
        //////
130
131 4
    public function __construct ()
132
    {
133 4
        $this->_Candidates = [];
134 4
        $this->_Votes = new VotesManager ($this);
135 4
        $this->_timer = new Timer_Manager;
136 4
    }
137
138
    public function __destruct ()
139
    {
140
        $this->destroyAllLink();
141
    }
142
143
    public function __sleep () : array
144
    {
145
        // Don't include others data
146
        $include = [
147
            '_Candidates',
148
            '_Votes',
149
150
            '_i_CandidateId',
151
            '_State',
152
            '_nextVoteTag',
153
            '_objectVersion',
154
            '_ignoreStaticMaxVote',
155
156
            '_ImplicitRanking',
157
            '_VoteWeightRule',
158
159
            '_Pairwise',
160
            '_Calculator',
161
        ];
162
163
        !self::$_checksumMode AND array_push($include, '_timer');
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
164
165
        return $include;
166
    }
167
168
    public function __wakeup ()
169
    {
170
        if ( version_compare($this->getObjectVersion('MAJOR'),Condorcet::getVersion('MAJOR'),'!=') ) :
171
            throw new CondorcetException(11, 'Your object version is '.$this->getObjectVersion().' but the class engine version is '.Condorcet::getVersion('ENV'));
172
        endif;
173
    }
174
175
    public function __clone ()
176
    {
177
        $this->registerAllLinks();
178
    }
179
180
181
/////////// INTERNAL GENERIC REGULATION ///////////
182
183
184
    protected function registerAllLinks () : void
185
    {
186
        foreach ($this->_Candidates as $value) :
187
            $value->registerLink($this);
188
        endforeach;
189
190
        if ($this->_State > 1) :
191
            foreach ($this->_Votes as $value) :
192
                $value->registerLink($this);
193
            endforeach;
194
        endif;
195
    }
196
197
    protected function destroyAllLink () : void
198
    {
199
        foreach ($this->_Candidates as $value) :
200
            $value->destroyLink($this);
201
        endforeach;
202
203
        if ($this->_State > 1) :
204
            foreach ($this->_Votes as $value) :
205
                $value->destroyLink($this);
206
            endforeach;
207
        endif;
208
    }
209
210
        //////
211
212
213 1
    public function getGlobalTimer (bool $float = false) {
214 1
        return $this->_timer->getGlobalTimer($float);
215
    }
216
217 2
    public function getLastTimer (bool $float = false) {
218 2
        return $this->_timer->getLastTimer($float);
219
    }
220
221 4
    public function getTimerManager () : Timer_Manager {
222 4
        return $this->_timer;
223
    }
224
225
    public function getChecksum () : string
226
    {
227
        self::$_checksumMode = true;
228
229
        $r = hash_init('sha256');
230
231
        foreach ($this->_Candidates as $value) :
232
            hash_update($r, (string) $value);
233
        endforeach;
234
235
        foreach ($this->_Votes as $value) :
236
            hash_update($r, (string) $value);
237
        endforeach;
238
239
        $this->_Pairwise !== null
240
            AND hash_update($r,serialize($this->_Pairwise->getExplicitPairwise()));
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
241
242
        hash_update($r, $this->getObjectVersion('major'));
243
244
        self::$_checksumMode = false;
245
246
        return hash_final($r);
247
    }
248
249
    public function ignoreMaxVote (bool $state = true) : bool
250
    {
251
        $this->_ignoreStaticMaxVote = $state;
252
        return $this->_ignoreStaticMaxVote;
253
    }
254
255 4
    public function getImplicitRankingRule () : bool
256
    {
257 4
        return $this->_ImplicitRanking;
258
    }
259
260
    public function setImplicitRanking (bool $rule = true) : bool
261
    {
262
        $this->_ImplicitRanking = $rule;
263
        $this->cleanupResult();
264
        return $this->getImplicitRankingRule();
265
    }
266
267 4
    public function isVoteWeightIsAllowed () : bool
268
    {
269 4
        return $this->_VoteWeightRule;
270
    }
271
272
    public function allowVoteWeight (bool $rule = true) : bool
273
    {
274
        $this->_VoteWeightRule = $rule;
275
        $this->cleanupResult();
276
        return $this->isVoteWeightIsAllowed();
277
    }
278
279
280
/////////// CANDIDATES ///////////
281
282
283
    // Add a vote candidate before voting
284 4
    public function addCandidate ($candidate_id = null) : Candidate
285
    {
286
        // only if the vote has not started
287 4
        if ( $this->_State > 1 ) :
288
            throw new CondorcetException(2);
289
        endif;
290
291
        // Filter
292 4
        if ( is_bool($candidate_id) || is_array($candidate_id) || (is_object($candidate_id) && !($candidate_id instanceof Candidate)) ) :
293
            throw new CondorcetException(1, $candidate_id);
1 ignored issue
show
Documentation introduced by
$candidate_id is of type boolean|array|object, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
294
        endif;
295
296
297
        // Process
298 4
        if ( empty($candidate_id) ) : // $candidate_id is empty ...
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
299 2
            while ( !$this->canAddCandidate($this->_i_CandidateId) ) :
300 2
                $this->_i_CandidateId++;
301
            endwhile;
302
303 2
            $newCandidate = new Candidate($this->_i_CandidateId);
304
        else : // Try to add the candidate_id
305 4
            $newCandidate = ($candidate_id instanceof Candidate) ? $candidate_id : new Candidate ((string) $candidate_id);
306
307 4
            if ( !$this->canAddCandidate($newCandidate) ) :
308
                throw new CondorcetException(3,$candidate_id);
309
            endif;
310
        endif;
311
312
        // Register it
313 4
        $this->_Candidates[] = $newCandidate;
314
315
        // Linking
316 4
        $newCandidate->registerLink($this);
317
318
        // Disallow other candidate object name matching 
319 4
        $newCandidate->setProvisionalState = false;
0 ignored issues
show
Bug introduced by
The property setProvisionalState does not seem to exist in Condorcet\Candidate.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
320
321 4
        return $newCandidate;
322
    }
323
324 4
        public function canAddCandidate ($candidate_id) : bool
325
        {
326 4
            return !$this->existCandidateId($candidate_id, false);
327
        }
328
329
330
    // Destroy a register vote candidate before voting
331 2
    public function removeCandidate ($list) : array
332
    {
333
        // only if the vote has not started
334 2
        if ( $this->_State > 1 ) :
335
            throw new CondorcetException(2);
336
        endif;
337
338
        
339 2
        if ( !is_array($list) ) :
340 2
            $list = [$list];
341
        endif;
342
343 2
        foreach ($list as &$candidate_id) :
344 2
            $candidate_key = $this->getCandidateKey($candidate_id);
345
346 2
            if ( $candidate_key === false ) :
347
                throw new CondorcetException(4,$candidate_id);
348
            endif;
349
350 2
            $candidate_id = $candidate_key;
351
        endforeach;
352
353 2
        $rem = [];
354 2
        foreach ($list as $candidate_key) :
355 2
            $this->_Candidates[$candidate_key]->destroyLink($this);
356
357 2
            $rem[] = $this->_Candidates[$candidate_key];
358
359 2
            unset($this->_Candidates[$candidate_key]);
360
        endforeach;
361
362 2
        return $rem;
363
    }
364
365
366
    public function jsonCandidates (string $input)
367
    {
368
        $input = self::prepareJson($input);
369
        if ($input === false) :
370
            return $input;
371
        endif;
372
373
            //////
374
375
        $adding = [];
376
        foreach ($input as $candidate) :
377
            try {
378
                $adding[] = $this->addCandidate($candidate);
379
            }
380
            catch (\Exception $e) {}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
381
        endforeach;
382
383
        return $adding;
384
    }
385
386
387
    public function parseCandidates (string $input, bool $allowFile = true)
388
    {
389
        $input = self::prepareParse($input, $allowFile);
390
        if ($input === false) :
391
            return $input;
392
        endif;
393
394
        $adding = [];
395
        foreach ($input as $line) :
396
            // Empty Line
397
            if (empty($line)) :
398
                continue;
399
            endif;
400
401
            // addCandidate
402
            try {
403
                if (self::$_maxParseIteration !== null && count($adding) >= self::$_maxParseIteration) :
404
                    throw new CondorcetException(12, self::$_maxParseIteration);
405
                endif;
406
407
                $adding[] = $this->addCandidate($line);
408
            } catch (CondorcetException $e) {
409
                if ($e->getCode() === 12)
410
                    {throw $e;}
411
            }
412
        endforeach;
413
414
        return $adding;
415
    }
416
417
418
        //:: CANDIDATES TOOLS :://
419
420
        // Count registered candidates
421 4
        public function countCandidates () : int
422
        {
423 4
            return count($this->_Candidates);
424
        }
425
426
        // Get the list of registered CANDIDATES
427 4
        public function getCandidatesList (bool $stringMode = false) : array
428
        {
429 4
            if (!$stringMode) :
430 4
                return $this->_Candidates;
431
            else :
432 2
                $result = [];
433
434 2
                foreach ($this->_Candidates as $candidateKey => &$oneCandidate) :
435 2
                    $result[$candidateKey] = $oneCandidate->getName();
436
                endforeach;
437
438 2
                return $result;
439
            endif;
440
        }
441
442 4
        public function getCandidateKey ($candidate_id)
443
        {
444 4
            if ($candidate_id instanceof Candidate) :
445 4
                return array_search($candidate_id, $this->_Candidates, true);
446
            else:
447 4
                return array_search(trim((string) $candidate_id), $this->_Candidates, false);
448
            endif;
449
        }
450
451 4
        public function getCandidateId (int $candidate_key, bool $onlyName = false)
452
        {
453 4
            if (!array_key_exists($candidate_key, $this->_Candidates)) :
454
                return false;
455
            else :
456 4
                return ($onlyName) ? $this->_Candidates[$candidate_key]->getName() : $this->_Candidates[$candidate_key];
457
            endif;
458
        }
459
460 4
        public function existCandidateId ($candidate_id, bool $strict = true) : bool
461
        {
462 4
            return ($strict) ? in_array($candidate_id, $this->_Candidates, true) : in_array((string) $candidate_id, $this->_Candidates);
463
        }
464
465 2
        public function getCandidateObjectByName (string $s)
466
        {
467 2
            foreach ($this->_Candidates as $oneCandidate) :
468
469 2
                if ($oneCandidate->getName() === $s) :
470 2
                    return $oneCandidate;
471
                endif;
472
            endforeach;
473
474
            return false;
475
        }
476
477
478
479
/////////// VOTING ///////////
480
481
482
    // Close the candidate config, be ready for voting (optional)
483 4
    public function setStateToVote () : bool
484
    {
485 4
        if ( $this->_State === 1 ) :
486 4
                if (empty($this->_Candidates)) :
487
                    throw new CondorcetException(20);
488
                endif;
489
490 4
                $this->_State = 2;
491
492
        // If voting continues after a first set of results
493 4
        elseif ( $this->_State > 2 ) :
494 2
                $this->cleanupResult();
495
        endif;
496
497 4
        return true;
498
    }
499
500
501
    // Add a single vote. Array key is the rank, each candidate in a rank are separate by ',' It is not necessary to register the last rank.
502 4
    public function addVote ($vote, $tag = null) : Vote
503
    {
504 4
        $this->prepareVoteInput($vote, $tag);
505
506
        // Check Max Vote Count
507 4
        if ( self::$_maxVoteNumber !== null && !$this->_ignoreStaticMaxVote && $this->countVotes() >= self::$_maxVoteNumber ) :
508
            throw new CondorcetException(16, self::$_maxVoteNumber);
509
        endif;
510
511
512
        // Register vote
513 4
        return $this->registerVote($vote, $tag); // Return the vote object
514
    }
515
516
        // return True or throw an Exception
517 2
        public function prepareModifyVote (Vote $existVote)
518
        {
519
            try {
520 2
                $this->prepareVoteInput($existVote);
521 2
                $this->setStateToVote();
522
            }
523
            catch (\Exception $e) {
524
                throw $e;
525
            }
526 2
        }
527
528
        // Return the well formated vote to use.
529 4
        protected function prepareVoteInput (&$vote, $tag = null) : void
530
        {
531 4
            if (!($vote instanceof Vote)) :
532 4
                $vote = new Vote ($vote, $tag);
533
            endif;
534
535
            // Check array format && Make checkVoteCandidate
536 4
            if ( !$this->checkVoteCandidate($vote) ) :
537
                throw new CondorcetException(5);
538
            endif;
539 4
        }
540
541
542 4
        public function checkVoteCandidate (Vote $vote) : bool
543
        {
544 4
            $linkCount = $vote->countLinks();
545 4
            $links = $vote->getLinks();
546
547 4
            $mirror = $vote->getRanking();
548 4
            $change = false;
549 4
            foreach ($vote as $rank => $choice) :
550 4
                foreach ($choice as $choiceKey => $candidate) :
551 4
                    if ( !$this->existCandidateId($candidate, true) ) :
552 4
                        if ($candidate->getProvisionalState() && $this->existCandidateId($candidate, false)) :
553 4
                            if ( $linkCount === 0 || ($linkCount === 1 && reset($links) === $this) ) :
554 4
                                $mirror[$rank][$choiceKey] = $this->_Candidates[$this->getCandidateKey((string) $candidate)];
555 4
                                $change = true;
556
                            else :
557 4
                                return false;
558
                            endif;
559
                        endif;
560
                    endif;
561
                endforeach;
562
            endforeach;
563
564 4
            if ($change) :
565 4
                $vote->setRanking(
566 4
                                    $mirror,
567 4
                                    ( abs($vote->getTimestamp() - microtime(true)) > 0.5 ) ? ($vote->getTimestamp() + 0.001) : false
0 ignored issues
show
Bug introduced by
It seems like abs($vote->getTimestamp(...stamp() + 0.001 : false can also be of type double; however, Condorcet\Vote::setRanking() does only seem to accept boolean, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
568
                );
569
            endif;
570
571 4
            return true;
572
        }
573
574
        // Write a new vote
575 4
        protected function registerVote (Vote $vote, $tag = null) : Vote
576
        {
577
            // Vote identifiant
578 4
            $vote->addTags($tag);
579
            
580
            // Register
581
            try {
582 4
                $vote->registerLink($this);
583 4
                $this->_Votes[] = $vote;
584
            } catch (CondorcetException $e) {
585
                // Security : Check if vote object not already register
586
                throw new CondorcetException(6,'Vote object already registred');
587
            }
588
589 4
            return $vote;
590
        }
591
592
593 3
    public function removeVote ($in, bool $with = true) : array
594
    {    
595 3
        $rem = [];
596
597 3
        if ($in instanceof Vote) :
598 2
            $key = $this->getVoteKey($in);
599 2
            if ($key !== false) :
600 2
                $this->_Votes[$key]->destroyLink($this);
601
602 2
                $rem[] = $this->_Votes[$key];
603
604 2
                unset($this->_Votes[$key]);
605
            endif;
606
        else :
607
            // Prepare Tags
608 3
            $tag = Vote::tagsConvert($in);
609
610
            // Deleting
611 3
            foreach ($this->getVotesList($tag, $with) as $key => $value) :
612 3
                $this->_Votes[$key]->destroyLink($this);
613
614 3
                $rem[] = $this->_Votes[$key];
615
616 3
                unset($this->_Votes[$key]);
617
            endforeach;
618
619
        endif;
620
621 3
        return $rem;
622
    }
623
624
625
    public function jsonVotes (string $input)
626
    {
627
        $input = self::prepareJson($input);
628
        if ($input === false) :
629
            return $input;
630
        endif;
631
632
            //////
633
634
        $adding = [];
635
636
        foreach ($input as $record) :
637
            if (empty($record['vote'])) :
638
                continue;
639
            endif;
640
641
            $tags = (!isset($record['tag'])) ? null : $record['tag'];
642
            $multi = (!isset($record['multi'])) ? 1 : $record['multi'];
643
644
            for ($i = 0; $i < $multi; $i++) :
645
                if (self::$_maxParseIteration !== null && $this->countVotes() >= self::$_maxParseIteration) :
646
                    throw new CondorcetException(12, self::$_maxParseIteration);
647
                endif;
648
649
                try {
650
                    $adding[] = $this->addVote($record['vote'], $tags);
651
                } catch (\Exception $e) {}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
652
            endfor;
653
        endforeach;
654
655
        return $adding;
656
    }
657
658 2
    public function parseVotes (string $input, bool $allowFile = true)
659
    {
660 2
        $input = self::prepareParse($input, $allowFile);
661 2
        if ($input === false) :
662
            return $input;
663
        endif;
664
665
        // Check each lines
666 2
        $adding = [];
667 2
        foreach ($input as $line) :
668
            // Empty Line
669 2
            if (empty($line)) :
670 2
                continue;
671
            endif;
672
673
            // Multiples
674 2
            $is_multiple = mb_strpos($line, '*');
675 2
            if ($is_multiple !== false) :
676 2
                $multiple = trim( substr($line, $is_multiple + 1) );
677
678
                // Errors
679 2
                if ( !is_numeric($multiple) ) :
680
                    throw new CondorcetException(13, null);
681
                endif;
682
683 2
                $multiple = intval($multiple);
684
685
                // Reformat line
686 2
                $line = substr($line, 0, $is_multiple);
687
            else :
688 2
                $multiple = 1;
689
            endif;
690
691
            // Vote Weight
692 2
            $is_voteWeight = mb_strpos($line, '^');
693 2
            if ($is_voteWeight !== false) :
694
                $weight = trim( substr($line, $is_voteWeight + 1) );
695
696
                // Errors
697
                if ( !is_numeric($weight) ) :
698
                    throw new CondorcetException(13, null);
699
                endif;
700
701
                $weight = intval($weight);
702
703
                // Reformat line
704
                $line = substr($line, 0, $is_voteWeight);
705
            else :
706 2
                $weight = 1;
707
            endif;
708
709
            // Tags + vote
710 2
            if (mb_strpos($line, '||') !== false) :
711 2
                $data = explode('||', $line);
712
713 2
                $vote = $data[1];
714 2
                $tags = $data[0];
715
            // Vote without tags
716
            else :
717 2
                $vote = $line;
718 2
                $tags = null;
719
            endif;
720
721
            // addVote
722 2
            for ($i = 0; $i < $multiple; $i++) :
723 2
                if (self::$_maxParseIteration !== null && count($adding) >= self::$_maxParseIteration) :
724
                    throw new CondorcetException(12, self::$_maxParseIteration);
725
                endif;
726
727
                try {
728 2
                    $adding[] = ($newVote = $this->addVote($vote, $tags));
729 2
                    $newVote->setWeight($weight);
730
                } catch (\Exception $e) {}
1 ignored issue
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
731
            endfor;
732
        endforeach;
733
734 2
        return $adding;
735
    }
736
737
738
    //:: LARGE ELECTION MODE :://
739
740
    public function setExternalDataHandler (DataHandlerDriverInterface $driver) : bool
741
    {
742
        if (!$this->_Votes->isUsingHandler()) :
743
            $this->_Votes->importHandler($driver);
744
            return true;
745
        else :
746
            throw new CondorcetException(24);
747
        endif;
748
    }
749
750
    public function removeExternalDataHandler () : bool
751
    {
752
        if ($this->_Votes->isUsingHandler()) :
753
            $this->_Votes->closeHandler();
754
            return true;
755
        else :
756
            throw new CondorcetException(23);
757
        endif;
758
    }
759
760
761
    //:: VOTING TOOLS :://
762
763
    // How many votes are registered ?
764 4
    public function countVotes ($tag = null, bool $with = true) : int
765
    {
766 4
        return $this->_Votes->countVotes(Vote::tagsConvert($tag),$with);
767
    }
768
769
    // Get the votes registered list
770 4
    public function getVotesList ($tag = null, bool $with = true) : array
771
    {
772 4
        return $this->_Votes->getVotesList(Vote::tagsConvert($tag), $with);
773
    }
774
775
    public function getVotesListAsString () : string
776
    {
777
        return $this->_Votes->getVotesListAsString();
778
    }
779
780 4
    public function getVotesManager () : VotesManager {
781 4
        return $this->_Votes;
782
    }
783
784 2
    public function getVoteKey (Vote $vote) {
785 2
        return $this->_Votes->getVoteKey($vote);
786
    }
787
788
    public function getVoteByKey (int $key) {
789
        if (!isset($this->_Votes[$key])) :
790
            return false;
791
        else :
792
            return $this->_Votes[$key];
793
        endif;
794
    }
795
796
797
/////////// RESULTS ///////////
798
799
800
    //:: PUBLIC FUNCTIONS :://
801
802
    // Generic function for default result with ability to change default object method
803 4
    public function getResult ($method = true, array $options = []) : Result
804
    {
805 4
        $options = $this->formatResultOptions($options);
806
807
        // Filter if tag is provided & return
808 4
        if ($options['%tagFilter']) :
809 1
            $chrono = new Timer_Chrono ($this->_timer, 'GetResult with filter');
810
811 1
            $filter = new self;
812
813 1
            foreach ($this->getCandidatesList() as $candidate) :
814 1
                $filter->addCandidate($candidate);
815
            endforeach;
816
817 1
            foreach ($this->getVotesList($options['tags'], $options['withTag']) as $vote) :
818 1
                $filter->addVote($vote);
819
            endforeach;
820
821 1
            unset($chrono);
822
823 1
            return $filter->getResult($method);
824
        endif;
825
826
            ////// Start //////
827
828
        // Prepare
829 4
        $this->prepareResult();
830
831
            //////
832
833 4
        $chrono = new Timer_Chrono ($this->_timer);
834
835 4
        if ($method === true) :
836 3
            $this->initResult(Condorcet::getDefaultMethod());
837 3
            $result = $this->_Calculator[Condorcet::getDefaultMethod()]->getResult();
838 4
        elseif ($method = Condorcet::isAuthMethod($method)) :
0 ignored issues
show
Documentation introduced by
$method is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
839 4
            $this->initResult($method);
840 4
            $result = $this->_Calculator[$method]->getResult();
841
        else :
842
            throw new CondorcetException(8,$method);
843
        endif;
844
845 4
        $chrono->setRole('GetResult for '.$method);
846
847 4
        return $result;
848
    }
849
850
851 4
    public function getWinner (?string $substitution = null)
852
    {
853 4
        $algo = $this->condorcetBasicSubstitution($substitution);
854
855
            //////
856
857 4
        if ($algo === Condorcet::CONDORCET_BASIC_CLASS) :
858 4
            $chrono = new Timer_Chrono ($this->_timer, 'GetWinner for CondorcetBasic');
0 ignored issues
show
Unused Code introduced by
$chrono is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
859 4
            $this->initResult($algo);
860 4
            $result = $this->_Calculator[$algo]->getWinner();
861
862 4
            return ($result === null) ? null : $this->getCandidateId($result);
863
        else :
864 2
            return $this->getResult($algo)->getWinner();
1 ignored issue
show
Documentation introduced by
$algo is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
865
        endif;
866
    }
867
868
869 4
    public function getLoser (?string $substitution = null)
870
    {
871 4
        $algo = $this->condorcetBasicSubstitution($substitution);
872
873
            //////
874
875 4
        if ($algo === Condorcet::CONDORCET_BASIC_CLASS) :
876 4
            $chrono = new Timer_Chrono ($this->_timer, 'GetLoser for CondorcetBasic');
0 ignored issues
show
Unused Code introduced by
$chrono is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
877 4
            $this->initResult($algo);
878 4
            $result = $this->_Calculator[$algo]->getLoser();
879
880 4
            return ($result === null) ? null : $this->getCandidateId($result);
881
        else :
882 2
            return $this->getResult($algo)->getLoser();
1 ignored issue
show
Documentation introduced by
$algo is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
883
        endif;
884
    }
885
886 4
        protected function condorcetBasicSubstitution ($substitution) : string {
887 4
            if ( $substitution ) :
888 2
                if ($substitution === true) :
889
                    $substitution = Condorcet::getDefaultMethod();
890
                endif;
891
                
892 2
                if ( Condorcet::isAuthMethod($substitution) ) :
893 2
                    $algo = $substitution;
894
                else :
895 2
                    throw new CondorcetException(9,$substitution);
896
                endif;
897
            else :
898 4
                $algo = Condorcet::CONDORCET_BASIC_CLASS;
899
            endif;
900
901 4
            return $algo;
902
        }
903
904
905
    public function computeResult ($method = true) : void
906
    {
907
        $this->getResult($method);
908
    }
909
910
911
    //:: TOOLS FOR RESULT PROCESS :://
912
913
914
    // Prepare to compute results & caching system
915 4
    protected function prepareResult () : bool
916
    {
917 4
        if ($this->_State > 2) :
918 4
            return false;
919 4
        elseif ($this->_State === 2) :
920 4
            $this->cleanupResult();
921
922
            // Do Pairewise
923 4
            $this->_Pairwise = new Pairwise ($this);
924
925
            // Change state to result
926 4
            $this->_State = 3;
927
928
            // Return
929 4
            return true;
930
        else :
931
            throw new CondorcetException(6);
932
        endif;
933
    }
934
935
936 4
    protected function initResult (string $class) : void
937
    {
938 4
        if ( !isset($this->_Calculator[$class]) ) :
939 4
            $this->_Calculator[$class] = new $class($this);
940
        endif;
941 4
    }
942
943
944
    // Cleanup results to compute again with new votes
945 4
    protected function cleanupResult () : void
946
    {
947
        // Reset state
948 4
        if ($this->_State > 2) : 
949 2
            $this->_State = 2;
950
        endif;
951
952
            //////
953
954
        // Clean pairwise
955 4
        $this->_Pairwise = null;
956
957
        // Algos
958 4
        $this->_Calculator = null;
959 4
    }
960
961
962 4
    protected function formatResultOptions (array $arg) : array
963
    {
964
        // About tag filter
965 4
        if (isset($arg['tags'])):
966 1
            $arg['%tagFilter'] = true;
967
968 1
            if ( !isset($arg['withTag']) || !is_bool($arg['withTag']) ) :
969 1
                $arg['withTag'] = true;
970
            endif;
971
        else:
972 4
            $arg['%tagFilter'] = false;
973
        endif;
974
975 4
        return $arg;
976
    }
977
978
979
    //:: GET RAW DATA :://
980
981 4
    public function getPairwise (bool $explicit = true)
982
    {
983 4
        $this->prepareResult();
984
985 4
        return (!$explicit) ? $this->_Pairwise : $this->_Pairwise->getExplicitPairwise();
986
    }
987
988
}
989