1
|
|
|
<?php |
|
|
|
|
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\CondorcetUtil; |
17
|
|
|
use Condorcet\Vote; |
18
|
|
|
use Condorcet\DataManager\VotesManager; |
19
|
|
|
use Condorcet\DataManager\DataHandlerDrivers\DataHandlerDriverInterface; |
20
|
|
|
use Condorcet\ElectionProcess\ResultManager; |
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
|
1 |
|
public static function setMaxParseIteration (?int $value) : ?int |
40
|
|
|
{ |
41
|
1 |
|
self::$_maxParseIteration = $value; |
42
|
1 |
|
return self::$_maxParseIteration; |
43
|
|
|
} |
44
|
|
|
|
45
|
|
|
// Change max vote number |
46
|
1 |
|
public static function setMaxVoteNumber (?int $value) : ?int |
47
|
|
|
{ |
48
|
1 |
|
self::$_maxVoteNumber = $value; |
49
|
1 |
|
return self::$_maxVoteNumber; |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
|
53
|
|
|
/////////// CONSTRUCTOR /////////// |
54
|
|
|
|
55
|
|
|
use CondorcetVersion; |
56
|
|
|
|
57
|
|
|
// Data and global options |
58
|
|
|
protected $_Candidates = []; // Candidate list |
59
|
|
|
protected $_Votes; // Votes list |
60
|
|
|
|
61
|
|
|
// Mechanics |
62
|
|
|
protected $_i_CandidateId = 'A'; |
63
|
|
|
protected $_State = 1; // 1 = Add Candidates / 2 = Voting / 3 = Some result have been computing |
64
|
|
|
protected $_timer; |
65
|
|
|
protected $_nextVoteTag = 0; |
66
|
|
|
protected $_ignoreStaticMaxVote = false; |
67
|
|
|
|
68
|
|
|
// Params |
69
|
|
|
protected $_ImplicitRanking = true; |
70
|
|
|
protected $_VoteWeightRule = false; |
71
|
|
|
|
72
|
|
|
// Result |
73
|
|
|
protected $_ResultManager; |
74
|
|
|
|
75
|
|
|
////// |
76
|
|
|
|
77
|
111 |
|
public function __construct () |
78
|
|
|
{ |
79
|
111 |
|
$this->_Candidates = []; |
80
|
111 |
|
$this->_Votes = new VotesManager ($this); |
81
|
111 |
|
$this->_timer = new Timer_Manager; |
82
|
111 |
|
} |
83
|
|
|
|
84
|
1 |
|
public function __destruct () |
85
|
|
|
{ |
86
|
1 |
|
$this->destroyAllLink(); |
87
|
1 |
|
} |
88
|
|
|
|
89
|
2 |
|
public function __sleep () : array |
90
|
|
|
{ |
91
|
|
|
// Don't include others data |
92
|
|
|
$include = [ |
93
|
2 |
|
'_Candidates', |
94
|
|
|
'_Votes', |
95
|
|
|
|
96
|
|
|
'_i_CandidateId', |
97
|
|
|
'_State', |
98
|
|
|
'_nextVoteTag', |
99
|
|
|
'_objectVersion', |
100
|
|
|
'_ignoreStaticMaxVote', |
101
|
|
|
|
102
|
|
|
'_ImplicitRanking', |
103
|
|
|
'_VoteWeightRule', |
104
|
|
|
|
105
|
|
|
'_ResultManager' |
106
|
|
|
]; |
107
|
|
|
|
108
|
2 |
|
!self::$_checksumMode && array_push($include, '_timer'); |
109
|
|
|
|
110
|
2 |
|
return $include; |
111
|
|
|
} |
112
|
|
|
|
113
|
1 |
|
public function __wakeup () |
114
|
|
|
{ |
115
|
1 |
|
if ( version_compare($this->getObjectVersion('MAJOR'),Condorcet::getVersion('MAJOR'),'!=') ) : |
116
|
|
|
throw new CondorcetException(11, 'Your object version is '.$this->getObjectVersion().' but the class engine version is '.Condorcet::getVersion('ENV')); |
117
|
|
|
endif; |
118
|
1 |
|
} |
119
|
|
|
|
120
|
1 |
|
public function __clone () |
121
|
|
|
{ |
122
|
1 |
|
$this->_Votes = clone $this->_Votes; |
123
|
1 |
|
$this->_Votes->setElection($this); |
124
|
1 |
|
$this->registerAllLinks(); |
125
|
|
|
|
126
|
1 |
|
$this->_timer = clone $this->_timer; |
127
|
|
|
|
128
|
1 |
|
if ($this->_ResultManager !== null) : |
129
|
1 |
|
$this->_ResultManager = clone $this->_ResultManager; |
130
|
1 |
|
$this->_ResultManager->setElection($this); |
131
|
|
|
endif; |
132
|
1 |
|
} |
133
|
|
|
|
134
|
|
|
|
135
|
|
|
/////////// INTERNAL GENERIC REGULATION /////////// |
136
|
|
|
|
137
|
|
|
|
138
|
1 |
|
protected function registerAllLinks () : void |
139
|
|
|
{ |
140
|
1 |
|
foreach ($this->_Candidates as $value) : |
141
|
1 |
|
$value->registerLink($this); |
142
|
|
|
endforeach; |
143
|
|
|
|
144
|
1 |
|
if ($this->_State > 1) : |
145
|
1 |
|
foreach ($this->_Votes as $value) : |
146
|
1 |
|
$value->registerLink($this); |
147
|
|
|
endforeach; |
148
|
|
|
endif; |
149
|
1 |
|
} |
150
|
|
|
|
151
|
1 |
|
protected function destroyAllLink () : void |
152
|
|
|
{ |
153
|
1 |
|
foreach ($this->_Candidates as $value) : |
154
|
1 |
|
$value->destroyLink($this); |
155
|
|
|
endforeach; |
156
|
|
|
|
157
|
1 |
|
if ($this->_State > 1) : |
158
|
1 |
|
foreach ($this->_Votes as $value) : |
159
|
1 |
|
$value->destroyLink($this); |
160
|
|
|
endforeach; |
161
|
|
|
endif; |
162
|
1 |
|
} |
163
|
|
|
|
164
|
|
|
////// |
165
|
|
|
|
166
|
|
|
|
167
|
2 |
|
public function getGlobalTimer (bool $float = false) { |
168
|
2 |
|
return $this->_timer->getGlobalTimer($float); |
169
|
|
|
} |
170
|
|
|
|
171
|
2 |
|
public function getLastTimer (bool $float = false) { |
172
|
2 |
|
return $this->_timer->getLastTimer($float); |
173
|
|
|
} |
174
|
|
|
|
175
|
80 |
|
public function getTimerManager () : Timer_Manager { |
176
|
80 |
|
return $this->_timer; |
177
|
|
|
} |
178
|
|
|
|
179
|
1 |
|
public function getChecksum () : string |
180
|
|
|
{ |
181
|
1 |
|
self::$_checksumMode = true; |
182
|
|
|
|
183
|
1 |
|
$r = hash_init('sha256'); |
184
|
|
|
|
185
|
1 |
|
foreach ($this->_Candidates as $value) : |
186
|
1 |
|
hash_update($r, (string) $value); |
187
|
|
|
endforeach; |
188
|
|
|
|
189
|
1 |
|
foreach ($this->_Votes as $value) : |
190
|
1 |
|
hash_update($r, (string) $value); |
191
|
|
|
endforeach; |
192
|
|
|
|
193
|
1 |
|
$this->_ResultManager !== null |
194
|
1 |
|
&& hash_update($r,serialize($this->_ResultManager->getPairwise()->getExplicitPairwise())); |
195
|
|
|
|
196
|
1 |
|
hash_update($r, $this->getObjectVersion('major')); |
197
|
|
|
|
198
|
1 |
|
self::$_checksumMode = false; |
199
|
|
|
|
200
|
1 |
|
return hash_final($r); |
201
|
|
|
} |
202
|
|
|
|
203
|
1 |
|
public function ignoreMaxVote (bool $state = true) : bool |
204
|
|
|
{ |
205
|
1 |
|
return $this->_ignoreStaticMaxVote = $state; |
206
|
|
|
} |
207
|
|
|
|
208
|
86 |
|
public function getImplicitRankingRule () : bool |
209
|
|
|
{ |
210
|
86 |
|
return $this->_ImplicitRanking; |
211
|
|
|
} |
212
|
|
|
|
213
|
3 |
|
public function setImplicitRanking (bool $rule = true) : bool |
214
|
|
|
{ |
215
|
3 |
|
$this->_ImplicitRanking = $rule; |
216
|
3 |
|
$this->cleanupResult(); |
217
|
3 |
|
return $this->getImplicitRankingRule(); |
218
|
|
|
} |
219
|
|
|
|
220
|
82 |
|
public function isVoteWeightIsAllowed () : bool |
221
|
|
|
{ |
222
|
82 |
|
return $this->_VoteWeightRule; |
223
|
|
|
} |
224
|
|
|
|
225
|
1 |
|
public function allowVoteWeight (bool $rule = true) : bool |
226
|
|
|
{ |
227
|
1 |
|
$this->_VoteWeightRule = $rule; |
228
|
1 |
|
$this->cleanupResult(); |
229
|
1 |
|
return $this->isVoteWeightIsAllowed(); |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
|
233
|
|
|
/////////// CANDIDATES /////////// |
234
|
|
|
|
235
|
|
|
|
236
|
|
|
// Add a vote candidate before voting |
237
|
106 |
|
public function addCandidate ($candidate_id = null) : Candidate |
238
|
|
|
{ |
239
|
|
|
// only if the vote has not started |
240
|
106 |
|
if ( $this->_State > 1 ) : |
241
|
|
|
throw new CondorcetException(2); |
242
|
|
|
endif; |
243
|
|
|
|
244
|
|
|
// Filter |
245
|
106 |
|
if ( is_bool($candidate_id) || is_array($candidate_id) || (is_object($candidate_id) && !($candidate_id instanceof Candidate)) ) : |
246
|
|
|
throw new CondorcetException(1, $candidate_id); |
247
|
|
|
endif; |
248
|
|
|
|
249
|
|
|
|
250
|
|
|
// Process |
251
|
106 |
|
if ( empty($candidate_id) ) : |
252
|
5 |
|
while ( !$this->canAddCandidate($this->_i_CandidateId) ) : |
253
|
5 |
|
$this->_i_CandidateId++; |
254
|
|
|
endwhile; |
255
|
|
|
|
256
|
5 |
|
$newCandidate = new Candidate($this->_i_CandidateId); |
257
|
|
|
else : // Try to add the candidate_id |
258
|
103 |
|
$newCandidate = ($candidate_id instanceof Candidate) ? $candidate_id : new Candidate ((string) $candidate_id); |
259
|
|
|
|
260
|
103 |
|
if ( !$this->canAddCandidate($newCandidate) ) : |
261
|
|
|
throw new CondorcetException(3,$candidate_id); |
262
|
|
|
endif; |
263
|
|
|
endif; |
264
|
|
|
|
265
|
|
|
// Register it |
266
|
106 |
|
$this->_Candidates[] = $newCandidate; |
267
|
|
|
|
268
|
|
|
// Linking |
269
|
106 |
|
$newCandidate->registerLink($this); |
270
|
|
|
|
271
|
|
|
// Disallow other candidate object name matching |
272
|
106 |
|
$newCandidate->setProvisionalState(false); |
273
|
|
|
|
274
|
106 |
|
return $newCandidate; |
275
|
|
|
} |
276
|
|
|
|
277
|
106 |
|
public function canAddCandidate ($candidate_id) : bool |
278
|
|
|
{ |
279
|
106 |
|
return !$this->existCandidateId($candidate_id, false); |
280
|
|
|
} |
281
|
|
|
|
282
|
|
|
|
283
|
|
|
// Destroy a register vote candidate before voting |
284
|
3 |
|
public function removeCandidate ($list) : array |
285
|
|
|
{ |
286
|
|
|
// only if the vote has not started |
287
|
3 |
|
if ( $this->_State > 1 ) : |
288
|
|
|
throw new CondorcetException(2); |
289
|
|
|
endif; |
290
|
|
|
|
291
|
3 |
|
if ( !is_array($list) ) : |
292
|
3 |
|
$list = [$list]; |
293
|
|
|
endif; |
294
|
|
|
|
295
|
3 |
|
foreach ($list as &$candidate_id) : |
296
|
3 |
|
$candidate_key = $this->getCandidateKey($candidate_id); |
297
|
|
|
|
298
|
3 |
|
if ( $candidate_key === false ) : |
299
|
|
|
throw new CondorcetException(4,$candidate_id); |
300
|
|
|
endif; |
301
|
|
|
|
302
|
3 |
|
$candidate_id = $candidate_key; |
303
|
|
|
endforeach; |
304
|
|
|
|
305
|
3 |
|
$rem = []; |
306
|
3 |
|
foreach ($list as $candidate_key) : |
307
|
3 |
|
$this->_Candidates[$candidate_key]->destroyLink($this); |
308
|
|
|
|
309
|
3 |
|
$rem[] = $this->_Candidates[$candidate_key]; |
310
|
|
|
|
311
|
3 |
|
unset($this->_Candidates[$candidate_key]); |
312
|
|
|
endforeach; |
313
|
|
|
|
314
|
3 |
|
return $rem; |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
|
318
|
1 |
|
public function jsonCandidates (string $input) |
319
|
|
|
{ |
320
|
1 |
|
$input = CondorcetUtil::prepareJson($input); |
321
|
1 |
|
if ($input === false) : |
322
|
|
|
return $input; |
323
|
|
|
endif; |
324
|
|
|
|
325
|
|
|
////// |
326
|
|
|
|
327
|
1 |
|
$adding = []; |
328
|
1 |
|
foreach ($input as $candidate) : |
329
|
|
|
try { |
330
|
1 |
|
$adding[] = $this->addCandidate($candidate); |
331
|
|
|
} |
332
|
1 |
|
catch (CondorcetException $e) {} |
|
|
|
|
333
|
|
|
endforeach; |
334
|
|
|
|
335
|
1 |
|
return $adding; |
336
|
|
|
} |
337
|
|
|
|
338
|
7 |
|
public function parseCandidates (string $input, bool $allowFile = true) |
339
|
|
|
{ |
340
|
7 |
|
$input = CondorcetUtil::prepareParse($input, $allowFile); |
341
|
7 |
|
if ($input === false) : |
342
|
|
|
return $input; |
343
|
|
|
endif; |
344
|
|
|
|
345
|
7 |
|
$adding = []; |
346
|
7 |
|
foreach ($input as $line) : |
347
|
|
|
// Empty Line |
348
|
7 |
|
if (empty($line)) : |
349
|
1 |
|
continue; |
350
|
|
|
endif; |
351
|
|
|
|
352
|
|
|
// addCandidate |
353
|
|
|
try { |
354
|
7 |
|
if (self::$_maxParseIteration !== null && count($adding) >= self::$_maxParseIteration) : |
355
|
|
|
throw new CondorcetException(12, self::$_maxParseIteration); |
356
|
|
|
endif; |
357
|
|
|
|
358
|
7 |
|
$adding[] = $this->addCandidate($line); |
359
|
|
|
} catch (CondorcetException $e) { |
360
|
|
|
if ($e->getCode() === 12) |
361
|
7 |
|
{throw $e;} |
362
|
|
|
} |
363
|
|
|
endforeach; |
364
|
|
|
|
365
|
7 |
|
return $adding; |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
|
369
|
|
|
//:: CANDIDATES TOOLS ::// |
370
|
|
|
|
371
|
|
|
// Count registered candidates |
372
|
87 |
|
public function countCandidates () : int |
373
|
|
|
{ |
374
|
87 |
|
return count($this->_Candidates); |
375
|
|
|
} |
376
|
|
|
|
377
|
|
|
// Get the list of registered CANDIDATES |
378
|
88 |
|
public function getCandidatesList (bool $stringMode = false) : array |
379
|
|
|
{ |
380
|
88 |
|
if (!$stringMode) : |
381
|
86 |
|
return $this->_Candidates; |
382
|
|
|
else : |
383
|
4 |
|
$result = []; |
384
|
|
|
|
385
|
4 |
|
foreach ($this->_Candidates as $candidateKey => &$oneCandidate) : |
386
|
4 |
|
$result[$candidateKey] = $oneCandidate->getName(); |
387
|
|
|
endforeach; |
388
|
|
|
|
389
|
4 |
|
return $result; |
390
|
|
|
endif; |
391
|
|
|
} |
392
|
|
|
|
393
|
93 |
|
public function getCandidateKey ($candidate_id) |
394
|
|
|
{ |
395
|
93 |
|
if ($candidate_id instanceof Candidate) : |
396
|
80 |
|
return array_search($candidate_id, $this->_Candidates, true); |
397
|
|
|
else: |
398
|
91 |
|
return array_search(trim((string) $candidate_id), $this->_Candidates, false); |
399
|
|
|
endif; |
400
|
|
|
} |
401
|
|
|
|
402
|
77 |
|
public function getCandidateId (int $candidate_key, bool $onlyName = false) |
403
|
|
|
{ |
404
|
77 |
|
if (!array_key_exists($candidate_key, $this->_Candidates)) : |
405
|
|
|
return false; |
406
|
|
|
else : |
407
|
77 |
|
return ($onlyName) ? $this->_Candidates[$candidate_key]->getName() : $this->_Candidates[$candidate_key]; |
408
|
|
|
endif; |
409
|
|
|
} |
410
|
|
|
|
411
|
106 |
|
public function existCandidateId ($candidate_id, bool $strict = true) : bool |
412
|
|
|
{ |
413
|
106 |
|
return ($strict) ? in_array($candidate_id, $this->_Candidates, true) : in_array((string) $candidate_id, $this->_Candidates); |
414
|
|
|
} |
415
|
|
|
|
416
|
3 |
|
public function getCandidateObjectByName (string $s) |
417
|
|
|
{ |
418
|
3 |
|
foreach ($this->_Candidates as $oneCandidate) : |
419
|
|
|
|
420
|
3 |
|
if ($oneCandidate->getName() === $s) : |
421
|
3 |
|
return $oneCandidate; |
422
|
|
|
endif; |
423
|
|
|
endforeach; |
424
|
|
|
|
425
|
1 |
|
return false; |
426
|
|
|
} |
427
|
|
|
|
428
|
|
|
|
429
|
|
|
|
430
|
|
|
/////////// VOTING /////////// |
431
|
|
|
|
432
|
|
|
|
433
|
|
|
// Close the candidate config, be ready for voting (optional) |
434
|
98 |
|
public function setStateToVote () : bool |
435
|
|
|
{ |
436
|
98 |
|
if ( $this->_State === 1 ) : |
437
|
98 |
|
if (empty($this->_Candidates)) : |
438
|
|
|
throw new CondorcetException(20); |
439
|
|
|
endif; |
440
|
|
|
|
441
|
98 |
|
$this->_State = 2; |
442
|
|
|
|
443
|
|
|
// If voting continues after a first set of results |
444
|
86 |
|
elseif ( $this->_State > 2 ) : |
445
|
5 |
|
$this->cleanupResult(); |
446
|
|
|
endif; |
447
|
|
|
|
448
|
98 |
|
return true; |
449
|
|
|
} |
450
|
|
|
|
451
|
|
|
|
452
|
|
|
// 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. |
453
|
98 |
|
public function addVote ($vote, $tag = null) : Vote |
454
|
|
|
{ |
455
|
98 |
|
$this->prepareVoteInput($vote, $tag); |
456
|
|
|
|
457
|
|
|
// Check Max Vote Count |
458
|
98 |
|
if ( self::$_maxVoteNumber !== null && !$this->_ignoreStaticMaxVote && $this->countVotes() >= self::$_maxVoteNumber ) : |
459
|
1 |
|
throw new CondorcetException(16, self::$_maxVoteNumber); |
460
|
|
|
endif; |
461
|
|
|
|
462
|
|
|
|
463
|
|
|
// Register vote |
464
|
98 |
|
return $this->registerVote($vote, $tag); // Return the vote object |
465
|
|
|
} |
466
|
|
|
|
467
|
|
|
// return True or throw an Exception |
468
|
5 |
|
public function prepareModifyVote (Vote $existVote) |
469
|
|
|
{ |
470
|
|
|
try { |
471
|
5 |
|
$this->prepareVoteInput($existVote); |
472
|
5 |
|
$this->setStateToVote(); |
473
|
|
|
} |
474
|
|
|
catch (\Exception $e) { |
475
|
|
|
throw $e; |
476
|
|
|
} |
477
|
5 |
|
} |
478
|
|
|
|
479
|
|
|
// Return the well formated vote to use. |
480
|
98 |
|
protected function prepareVoteInput (&$vote, $tag = null) : void |
481
|
|
|
{ |
482
|
98 |
|
if (!($vote instanceof Vote)) : |
483
|
82 |
|
$vote = new Vote ($vote, $tag); |
484
|
|
|
endif; |
485
|
|
|
|
486
|
|
|
// Check array format && Make checkVoteCandidate |
487
|
98 |
|
if ( !$this->checkVoteCandidate($vote) ) : |
488
|
|
|
throw new CondorcetException(5); |
489
|
|
|
endif; |
490
|
98 |
|
} |
491
|
|
|
|
492
|
|
|
|
493
|
98 |
|
public function checkVoteCandidate (Vote $vote) : bool |
494
|
|
|
{ |
495
|
98 |
|
$linkCount = $vote->countLinks(); |
496
|
98 |
|
$links = $vote->getLinks(); |
497
|
|
|
|
498
|
98 |
|
$mirror = $vote->getRanking(); |
499
|
98 |
|
$change = false; |
500
|
98 |
|
foreach ($vote as $rank => $choice) : |
501
|
98 |
|
foreach ($choice as $choiceKey => $candidate) : |
502
|
98 |
|
if ( !$this->existCandidateId($candidate, true) ) : |
503
|
91 |
|
if ($candidate->getProvisionalState() && $this->existCandidateId($candidate, false)) : |
504
|
90 |
|
if ( $linkCount === 0 || ($linkCount === 1 && reset($links) === $this) ) : |
505
|
90 |
|
$mirror[$rank][$choiceKey] = $this->_Candidates[$this->getCandidateKey((string) $candidate)]; |
506
|
90 |
|
$change = true; |
507
|
|
|
else : |
508
|
98 |
|
return false; |
509
|
|
|
endif; |
510
|
|
|
endif; |
511
|
|
|
endif; |
512
|
|
|
endforeach; |
513
|
|
|
endforeach; |
514
|
|
|
|
515
|
98 |
|
if ($change) : |
516
|
90 |
|
$vote->setRanking( |
517
|
90 |
|
$mirror, |
518
|
90 |
|
( abs($vote->getTimestamp() - microtime(true)) > 0.5 ) ? ($vote->getTimestamp() + 0.001) : null |
519
|
|
|
); |
520
|
|
|
endif; |
521
|
|
|
|
522
|
98 |
|
return true; |
523
|
|
|
} |
524
|
|
|
|
525
|
|
|
// Write a new vote |
526
|
98 |
|
protected function registerVote (Vote $vote, $tag = null) : Vote |
527
|
|
|
{ |
528
|
|
|
// Vote identifiant |
529
|
98 |
|
$vote->addTags($tag); |
530
|
|
|
|
531
|
|
|
// Register |
532
|
|
|
try { |
533
|
98 |
|
$vote->registerLink($this); |
534
|
98 |
|
$this->_Votes[] = $vote; |
535
|
|
|
} catch (CondorcetException $e) { |
536
|
|
|
// Security : Check if vote object not already register |
537
|
|
|
throw new CondorcetException(6,'Vote object already registred'); |
538
|
|
|
} |
539
|
|
|
|
540
|
98 |
|
return $vote; |
541
|
|
|
} |
542
|
|
|
|
543
|
|
|
|
544
|
5 |
|
public function removeVote ($in, bool $with = true) : array |
545
|
|
|
{ |
546
|
5 |
|
$rem = []; |
547
|
|
|
|
548
|
5 |
|
if ($in instanceof Vote) : |
549
|
4 |
|
$key = $this->getVoteKey($in); |
550
|
4 |
|
if ($key !== false) : |
551
|
4 |
|
$this->_Votes[$key]->destroyLink($this); |
552
|
|
|
|
553
|
4 |
|
$rem[] = $this->_Votes[$key]; |
554
|
|
|
|
555
|
4 |
|
unset($this->_Votes[$key]); |
556
|
|
|
endif; |
557
|
|
|
else : |
558
|
|
|
// Prepare Tags |
559
|
3 |
|
$tag = Vote::tagsConvert($in); |
560
|
|
|
|
561
|
|
|
// Deleting |
562
|
3 |
|
foreach ($this->getVotesList($tag, $with) as $key => $value) : |
563
|
3 |
|
$this->_Votes[$key]->destroyLink($this); |
564
|
|
|
|
565
|
3 |
|
$rem[] = $this->_Votes[$key]; |
566
|
|
|
|
567
|
3 |
|
unset($this->_Votes[$key]); |
568
|
|
|
endforeach; |
569
|
|
|
|
570
|
|
|
endif; |
571
|
|
|
|
572
|
5 |
|
return $rem; |
573
|
|
|
} |
574
|
|
|
|
575
|
|
|
|
576
|
2 |
|
public function jsonVotes (string $input) |
577
|
|
|
{ |
578
|
2 |
|
$input = CondorcetUtil::prepareJson($input); |
579
|
1 |
|
if ($input === false) : |
580
|
|
|
return $input; |
581
|
|
|
endif; |
582
|
|
|
|
583
|
|
|
////// |
584
|
|
|
|
585
|
1 |
|
$adding = []; |
586
|
|
|
|
587
|
1 |
|
foreach ($input as $record) : |
588
|
1 |
|
if (empty($record['vote'])) : |
589
|
1 |
|
continue; |
590
|
|
|
endif; |
591
|
|
|
|
592
|
1 |
|
$tags = (!isset($record['tag'])) ? null : $record['tag']; |
593
|
1 |
|
$multi = (!isset($record['multi'])) ? 1 : $record['multi']; |
594
|
|
|
|
595
|
1 |
|
for ($i = 0; $i < $multi; $i++) : |
596
|
1 |
|
if (self::$_maxParseIteration !== null && $this->countVotes() >= self::$_maxParseIteration) : |
597
|
|
|
throw new CondorcetException(12, self::$_maxParseIteration); |
598
|
|
|
endif; |
599
|
|
|
|
600
|
|
|
try { |
601
|
1 |
|
$adding[] = $this->addVote($record['vote'], $tags); |
602
|
|
|
} catch (\Exception $e) {} |
|
|
|
|
603
|
|
|
endfor; |
604
|
|
|
endforeach; |
605
|
|
|
|
606
|
1 |
|
return $adding; |
607
|
|
|
} |
608
|
|
|
|
609
|
70 |
|
public function parseVotes (string $input, bool $allowFile = true) |
610
|
|
|
{ |
611
|
70 |
|
$input = CondorcetUtil::prepareParse($input, $allowFile); |
612
|
70 |
|
if ($input === false) : |
613
|
|
|
return $input; |
614
|
|
|
endif; |
615
|
|
|
|
616
|
|
|
// Check each lines |
617
|
70 |
|
$adding = []; |
618
|
70 |
|
foreach ($input as $line) : |
619
|
|
|
// Empty Line |
620
|
70 |
|
if (empty($line)) : |
621
|
63 |
|
continue; |
622
|
|
|
endif; |
623
|
|
|
|
624
|
|
|
// Multiples |
625
|
70 |
|
$is_multiple = mb_strpos($line, '*'); |
626
|
70 |
|
if ($is_multiple !== false) : |
627
|
63 |
|
$multiple = trim( substr($line, $is_multiple + 1) ); |
628
|
|
|
|
629
|
|
|
// Errors |
630
|
63 |
|
if ( !is_numeric($multiple) ) : |
631
|
|
|
throw new CondorcetException(13, null); |
632
|
|
|
endif; |
633
|
|
|
|
634
|
63 |
|
$multiple = intval($multiple); |
635
|
|
|
|
636
|
|
|
// Reformat line |
637
|
63 |
|
$line = substr($line, 0, $is_multiple); |
638
|
|
|
else : |
639
|
9 |
|
$multiple = 1; |
640
|
|
|
endif; |
641
|
|
|
|
642
|
|
|
// Vote Weight |
643
|
70 |
|
$is_voteWeight = mb_strpos($line, '^'); |
644
|
70 |
|
if ($is_voteWeight !== false) : |
645
|
2 |
|
$weight = trim( substr($line, $is_voteWeight + 1) ); |
646
|
|
|
|
647
|
|
|
// Errors |
648
|
2 |
|
if ( !is_numeric($weight) ) : |
649
|
|
|
throw new CondorcetException(13, null); |
650
|
|
|
endif; |
651
|
|
|
|
652
|
2 |
|
$weight = intval($weight); |
653
|
|
|
|
654
|
|
|
// Reformat line |
655
|
2 |
|
$line = substr($line, 0, $is_voteWeight); |
656
|
|
|
else : |
657
|
69 |
|
$weight = 1; |
658
|
|
|
endif; |
659
|
|
|
|
660
|
|
|
// Tags + vote |
661
|
70 |
|
if (mb_strpos($line, '||') !== false) : |
662
|
4 |
|
$data = explode('||', $line); |
663
|
|
|
|
664
|
4 |
|
$vote = $data[1]; |
665
|
4 |
|
$tags = $data[0]; |
666
|
|
|
// Vote without tags |
667
|
|
|
else : |
668
|
69 |
|
$vote = $line; |
669
|
69 |
|
$tags = null; |
670
|
|
|
endif; |
671
|
|
|
|
672
|
|
|
// addVote |
673
|
70 |
|
for ($i = 0; $i < $multiple; $i++) : |
674
|
70 |
|
if (self::$_maxParseIteration !== null && count($adding) >= self::$_maxParseIteration) : |
675
|
1 |
|
throw new CondorcetException(12, self::$_maxParseIteration); |
676
|
|
|
endif; |
677
|
|
|
|
678
|
|
|
try { |
679
|
70 |
|
$adding[] = ($newVote = $this->addVote($vote, $tags)); |
680
|
70 |
|
$newVote->setWeight($weight); |
681
|
1 |
|
} catch (CondorcetException $e) {} |
|
|
|
|
682
|
|
|
endfor; |
683
|
|
|
endforeach; |
684
|
|
|
|
685
|
70 |
|
return $adding; |
686
|
|
|
} |
687
|
|
|
|
688
|
|
|
|
689
|
|
|
//:: LARGE ELECTION MODE ::// |
690
|
|
|
|
691
|
3 |
|
public function setExternalDataHandler (DataHandlerDriverInterface $driver) : bool |
692
|
|
|
{ |
693
|
3 |
|
if (!$this->_Votes->isUsingHandler()) : |
694
|
3 |
|
$this->_Votes->importHandler($driver); |
695
|
3 |
|
return true; |
696
|
|
|
else : |
697
|
|
|
throw new CondorcetException(24); |
698
|
|
|
endif; |
699
|
|
|
} |
700
|
|
|
|
701
|
1 |
|
public function removeExternalDataHandler () : bool |
702
|
|
|
{ |
703
|
1 |
|
if ($this->_Votes->isUsingHandler()) : |
704
|
1 |
|
$this->_Votes->closeHandler(); |
705
|
1 |
|
return true; |
706
|
|
|
else : |
707
|
|
|
throw new CondorcetException(23); |
708
|
|
|
endif; |
709
|
|
|
} |
710
|
|
|
|
711
|
|
|
|
712
|
|
|
//:: VOTING TOOLS ::// |
713
|
|
|
|
714
|
|
|
// How many votes are registered ? |
715
|
10 |
|
public function countVotes ($tag = null, bool $with = true) : int |
716
|
|
|
{ |
717
|
10 |
|
return $this->_Votes->countVotes(Vote::tagsConvert($tag),$with); |
718
|
|
|
} |
719
|
|
|
|
720
|
|
|
// Get the votes registered list |
721
|
9 |
|
public function getVotesList ($tag = null, bool $with = true) : array |
722
|
|
|
{ |
723
|
9 |
|
return $this->_Votes->getVotesList(Vote::tagsConvert($tag), $with); |
724
|
|
|
} |
725
|
|
|
|
726
|
4 |
|
public function getVotesListAsString () : string |
727
|
|
|
{ |
728
|
4 |
|
return $this->_Votes->getVotesListAsString(); |
729
|
|
|
} |
730
|
|
|
|
731
|
81 |
|
public function getVotesManager () : VotesManager { |
732
|
81 |
|
return $this->_Votes; |
733
|
|
|
} |
734
|
|
|
|
735
|
4 |
|
public function getVoteKey (Vote $vote) { |
736
|
4 |
|
return $this->_Votes->getVoteKey($vote); |
737
|
|
|
} |
738
|
|
|
|
739
|
|
|
|
740
|
|
|
/////////// RESULTS /////////// |
741
|
|
|
|
742
|
|
|
//:: PUBLIC FUNCTIONS ::// |
743
|
|
|
|
744
|
|
|
// Generic function for default result with ability to change default object method |
745
|
48 |
|
public function getResult ($method = true, array $options = []) : Result |
746
|
|
|
{ |
747
|
48 |
|
$this->prepareResult(); |
748
|
|
|
|
749
|
48 |
|
return $this->_ResultManager->getResult($method,$options); |
750
|
|
|
} |
751
|
|
|
|
752
|
|
|
|
753
|
78 |
|
public function getWinner (?string $substitution = null) |
754
|
|
|
{ |
755
|
78 |
|
$this->prepareResult(); |
756
|
|
|
|
757
|
78 |
|
return $this->_ResultManager->getWinner($substitution); |
758
|
|
|
} |
759
|
|
|
|
760
|
|
|
|
761
|
75 |
|
public function getLoser (?string $substitution = null) |
762
|
|
|
{ |
763
|
75 |
|
$this->prepareResult(); |
764
|
|
|
|
765
|
75 |
|
return $this->_ResultManager->getLoser($substitution); |
766
|
|
|
} |
767
|
|
|
|
768
|
|
|
|
769
|
1 |
|
public function computeResult ($method = true) : void |
770
|
|
|
{ |
771
|
1 |
|
$this->getResult($method); |
772
|
1 |
|
} |
773
|
|
|
|
774
|
|
|
|
775
|
|
|
//:: TOOLS FOR RESULT PROCESS ::// |
776
|
|
|
|
777
|
|
|
|
778
|
|
|
// Prepare to compute results & caching system |
779
|
80 |
|
protected function prepareResult () : bool |
780
|
|
|
{ |
781
|
80 |
|
if ($this->_State > 2) : |
782
|
77 |
|
return false; |
783
|
80 |
|
elseif ($this->_State === 2) : |
784
|
80 |
|
$this->cleanupResult(); |
785
|
|
|
|
786
|
80 |
|
$this->_ResultManager = new ResultManager ($this); |
787
|
|
|
|
788
|
|
|
// Change state to result |
789
|
80 |
|
$this->_State = 3; |
790
|
|
|
|
791
|
|
|
// Return |
792
|
80 |
|
return true; |
793
|
|
|
else : |
794
|
|
|
throw new CondorcetException(6); |
795
|
|
|
endif; |
796
|
|
|
} |
797
|
|
|
|
798
|
|
|
|
799
|
|
|
// Cleanup results to compute again with new votes |
800
|
80 |
|
protected function cleanupResult () : void |
801
|
|
|
{ |
802
|
|
|
// Reset state |
803
|
80 |
|
if ($this->_State > 2) : |
804
|
7 |
|
$this->_State = 2; |
805
|
|
|
endif; |
806
|
|
|
|
807
|
|
|
////// |
808
|
|
|
|
809
|
80 |
|
$this->_ResultManager = null; |
810
|
80 |
|
} |
811
|
|
|
|
812
|
|
|
|
813
|
|
|
//:: GET RAW DATA ::// |
814
|
|
|
|
815
|
77 |
|
public function getPairwise (bool $explicit = true) |
816
|
|
|
{ |
817
|
77 |
|
$this->prepareResult(); |
818
|
|
|
|
819
|
77 |
|
$pairwise = $this->_ResultManager->getPairwise($explicit); |
820
|
|
|
|
821
|
77 |
|
return (!$explicit) ? $pairwise : $pairwise->getExplicitPairwise($explicit); |
822
|
|
|
} |
823
|
|
|
|
824
|
|
|
} |
825
|
|
|
|
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.