PageRiconferma::getQuorum()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php declare( strict_types=1 );
2
3
namespace BotRiconferme\Wiki\Page;
4
5
use BadMethodCallException;
6
use BotRiconferme\Message\Message;
7
use LogicException;
8
9
/**
10
 * Represents a single riconferma page
11
 */
12
class PageRiconferma extends Page {
13
	// Sections of the page, value = section number. Loaded in self::defineSections
14
	/** @var int|null */
15
	private $supportSection;
16
	/** @var int|null */
17
	private $opposeSection;
18
	/** @var int[] Counts of votes for each section */
19
	private $sectionCounts = [];
20
21
	// Possible outcomes of a vote
22
	public const OUTCOME_OK = 0;
23
	public const OUTCOME_FAIL_VOTES = 1;
24
	public const OUTCOME_NO_QUOR = 2;
25
	public const OUTCOME_FAIL = self::OUTCOME_FAIL_VOTES | self::OUTCOME_NO_QUOR;
26
27
	// Values depending on bureaucracy
28
	public const REQUIRED_OPPOSE_MAX = 15;
29
	public const REQUIRED_OPPOSE_QUORUM_RATIO = 1 / 4;
30
	public const SIMPLE_DURATION = 7;
31
	public const VOTE_DURATION = 14;
32
	public const SUCCESS_RATIO = 2 / 3;
33
34
	/**
35
	 * Define the numbers of the support and oppose sections. These are lazy-loaded
36
	 * because they can vary depending on whether the page is a vote, which is relatively
37
	 * expensive to know since it requires parsing the content of the page.
38
	 */
39
	private function defineSections(): void {
40
		$this->supportSection = $this->isVote() ? 3 : 0;
41
		$this->opposeSection = $this->isVote() ? 4 : 3;
42
	}
43
44
	/**
45
	 * Get the name of the user from the title
46
	 *
47
	 * @return string
48
	 */
49
	public function getUserName(): string {
50
		return explode( '/', $this->title )[2];
51
	}
52
53
	/**
54
	 * Returns the progressive number in the title
55
	 *
56
	 * @return int
57
	 */
58
	public function getNum(): int {
59
		$bits = explode( '/', $this->getTitle() );
60
		return (int)end( $bits );
61
	}
62
63
	/**
64
	 * Get the last part of the title as Username/Num
65
	 *
66
	 * @return string
67
	 */
68
	public function getUserNum(): string {
69
		return explode( '/', $this->getTitle(), 3 )[2];
70
	}
71
72
	/**
73
	 * Get the amount of opposing votes
74
	 *
75
	 * @return int
76
	 */
77
	public function getOpposingCount(): int {
78
		$this->defineSections();
79
		return $this->getCountForSection( $this->opposeSection );
80
	}
81
82
	/**
83
	 * Get the amount support votes
84
	 *
85
	 * @return int
86
	 * @throws BadMethodCallException
87
	 */
88
	public function getSupportCount(): int {
89
		if ( !$this->isVote() ) {
90
			throw new BadMethodCallException( 'Cannot get support for a non-vote page.' );
91
		}
92
		$this->defineSections();
93
		return $this->getCountForSection( $this->supportSection );
94
	}
95
96
	/**
97
	 * Count the votes in the given section
98
	 *
99
	 * @param int $secNum
100
	 * @return int
101
	 */
102
	protected function getCountForSection( int $secNum ): int {
103
		if ( !isset( $this->sectionCounts[ $secNum ] ) ) {
104
			$content = $this->wiki->getPageContent( $this->title, $secNum );
105
			// Let's hope that this is good enough...
106
			$this->sectionCounts[$secNum] = preg_match_all( "/^# *(?![# *:]|\.\.\.$)/m", $content );
107
		}
108
		return $this->sectionCounts[$secNum];
109
	}
110
111
	/**
112
	 * Gets the quorum used for the current page
113
	 *
114
	 * @return int
115
	 */
116
	protected function getQuorum(): int {
117
		$reg = "!soddisfare il \[\[[^|\]]+\|quorum]] di '''(\d+) voti'''!";
118
		return (int)$this->getMatch( $reg )[1];
119
	}
120
121
	/**
122
	 * Whether this page has enough opposing votes
123
	 *
124
	 * @return bool
125
	 */
126
	public function hasOpposition(): bool {
127
		$req = min(
128
			self::REQUIRED_OPPOSE_MAX,
129
			ceil( $this->getQuorum() * self::REQUIRED_OPPOSE_QUORUM_RATIO )
130
		);
131
		return $this->getOpposingCount() >= $req;
132
	}
133
134
	/**
135
	 * Gets the outcome for the vote
136
	 *
137
	 * @return int One of the OUTCOME_* constants
138
	 */
139
	public function getOutcome(): int {
140
		if ( !$this->isVote() ) {
141
			return self::OUTCOME_OK;
142
		}
143
		$totalVotes = $this->getOpposingCount() + $this->getSupportCount();
144
145
		if ( $this->getSupportCount() < $this->getQuorum() ) {
146
			$ret = self::OUTCOME_NO_QUOR;
147
		} elseif ( $this->getSupportCount() < self::SUCCESS_RATIO * $totalVotes ) {
148
			$ret = self::OUTCOME_FAIL_VOTES;
149
		} else {
150
			$ret = self::OUTCOME_OK;
151
		}
152
		return $ret;
153
	}
154
155
	/**
156
	 * Get the result text for the page itself
157
	 *
158
	 * @return string
159
	 * @throws BadMethodCallException
160
	 * @throws LogicException
161
	 */
162
	public function getOutcomeText(): string {
163
		if ( !$this->isVote() ) {
164
			throw new BadMethodCallException( 'No need for an outcome text.' );
165
		}
166
167
		$text = sprintf(
168
			' Con %d voti a favore e %d contrari',
169
			$this->getSupportCount(),
170
			$this->getOpposingCount()
171
		);
172
		$user = $this->getUserName();
173
174
		switch ( $this->getOutcome() ) {
175
			case self::OUTCOME_OK:
176
				$text .= " $user viene riconfermato amministratore.";
177
				break;
178
			/** @noinspection PhpMissingBreakStatementInspection */
179
			case self::OUTCOME_NO_QUOR:
180
				$text .= ', non raggiungendo il quorum,';
181
				// Fall-through intended
182
			case self::OUTCOME_FAIL:
183
				$text .= " $user non viene riconfermato amministratore.";
184
				break;
185
			default:
186
				throw new LogicException( 'Invalid outcome: ' . $this->getOutcome() );
187
		}
188
		return $text;
189
	}
190
191
	/**
192
	 * Whether this page is a vote
193
	 *
194
	 * @return bool
195
	 */
196
	public function isVote(): bool {
197
		$sectionReg = '/<!-- SEZIONE DA UTILIZZARE PER/';
198
		return !$this->matches( $sectionReg );
199
	}
200
201
	/**
202
	 * Get the timestamp of the creation of the page
203
	 *
204
	 * @return int
205
	 */
206
	public function getCreationTimestamp(): int {
207
		return $this->wiki->getPageCreationTS( $this->title );
208
	}
209
210
	/**
211
	 * Get the end time
212
	 *
213
	 * @return int
214
	 */
215
	public function getEndTimestamp(): int {
216
		if ( $this->isVote() ) {
217
			$reg = "!La votazione ha inizio il.+ e ha termine il (\d+ \w+ \d+) alle ([\d:]+)!";
218
			[ , $day, $hours ] = $this->getMatch( $reg );
219
			$day = preg_replace( '![^ \w]!', '', $day );
220
			return Message::getTimestampFromLocalTime( "$day $hours" );
221
		}
222
		return $this->getCreationTimestamp() + 60 * 60 * 24 * self::SIMPLE_DURATION;
223
	}
224
}
225