RFA::__construct()   F
last analyzed

Complexity

Conditions 25
Paths 11988

Size

Total Lines 99
Code Lines 63

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 650

Importance

Changes 0
Metric Value
cc 25
eloc 63
nc 11988
nop 3
dl 0
loc 99
rs 2
c 0
b 0
f 0
ccs 0
cts 80
cp 0
crap 650

How to fix   Long Method    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
2
/**
3
 * RfA Analysis Library, modified for use with Peachy
4
 * Copyright (C) 2006 Tangotango (tangotango.wp _at_ gmail _dot_ com)
5
 *
6
 * This program is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU General Public License
8
 * as published by the Free Software Foundation; either version 2
9
 * of the License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19
 */
20
21
/**
22
 * An RFA object contains the parsed information for an RFA
23
 */
24
class RFA {
25
26
	protected $pgUsername = false;
27
	protected $enddate = false;
28
	protected $support = array();
29
	protected $oppose = array();
30
	protected $neutral = array();
31
	protected $duplicates = array();
32
	protected $lasterror = '';
33
34
	/**
35
	 * Analyzes an RFA. Returns TRUE on success, FALSE on failure
36
	 * @param Wiki $wiki
37
	 * @param $page
38
	 * @param null $rawwikitext
39
	 */
40
	public function __construct( Wiki $wiki, $page, $rawwikitext = null ) {
41
		if( is_null( $rawwikitext ) ) $rawwikitext = $wiki->initPage( $page )->get_text();
42
43
		$split = preg_split(
44
			"/^(?:(?:'''|(?:<includeonly><noin<\/includeonly><includeonly>clude><\/includeonly>)?={4,5}(?:<includeonly><\/noin<\/includeonly><includeonly>clude><\/includeonly>''')?)"
45
			. "\s*?(Support|Oppose|Neutral|Comments)\s*?(?:'''|(?:'''<includeonly><noin<\/includeonly><includeonly>clude><\/includeonly>)?={4,5}(?:<includeonly><\/noin<\/includeonly><includeonly>clude><\/includeonly>)?)|;\s*(Support|Oppose|Neutral|Comments))\s*(?:<br>|<br \/>)?\s*$/im"
46
			, $rawwikitext, -1, PREG_SPLIT_DELIM_CAPTURE
47
		);
48
49
		$header = array_shift( $split );
50
51
		//=== Deal with the header ===//
52
		$header = str_ireplace( array( '<nowiki>', '</nowiki>' ), '', $header );
53
54
		if( preg_match( "/===\s*\[\[User:(.*?)\|.*?\]\]\s*===/", $header, $matches ) ) {
55
			$this->username = $matches[1];
0 ignored issues
show
Bug introduced by
The property username does not seem to exist. Did you mean pgUsername?

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...
56
		} elseif( preg_match( "/===\s*\[\[.*?\|(.*?)\]\]\s*===/", $header, $matches ) ) {
57
			$this->username = $matches[1];
0 ignored issues
show
Bug introduced by
The property username does not seem to exist. Did you mean pgUsername?

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...
58
		}
59
60
		$header = str_replace( array( '[[', ']]' ), '', $header );
61
62
		if( preg_match( "/end(?:ing|ed)?(?: no earlier than)? (.*?) \(UTC\)/i", $header, $matches ) ) {
63
			$this->enddate = $matches[1];
0 ignored issues
show
Documentation Bug introduced by
The property $enddate was declared of type boolean, but $matches[1] is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
64
		}
65
		//=== End header stuff ===//
66
67
		//Now parse through each non-header section, figuring out what they are
68
		//Nothing expected = 0, Support = 1, Oppose = 2, Neutral = 3
69
		$nextsection = 0;
70
71
		foreach( $split as $splut ){
72
			$splut = trim( $splut );
73
			if( empty( $splut ) ) {
74
				continue;
75
			}
76
77
			if( strcasecmp( $splut, 'Support' ) == 0 ) {
78
				$nextsection = 1;
79
			} elseif( strcasecmp( $splut, 'Oppose' ) == 0 ) {
80
				$nextsection = 2;
81
			} elseif( strcasecmp( $splut, 'Neutral' ) == 0 ) {
82
				$nextsection = 3;
83
			} else {
84
				switch( $nextsection ){
85
					case 1:
86
						$support = $splut;
87
						break;
88
					case 2:
89
						$oppose = $splut;
90
						break;
91
					case 3:
92
						$neutral = $splut;
93
						break;
94
				}
95
				$nextsection = 0;
96
			}
97
		}
98
99
		if( !isset( $support ) ) {
100
			$this->lasterror = "Support section not found";
101
			return false;
0 ignored issues
show
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
102
		}
103
		if( !isset( $oppose ) ) {
104
			$this->lasterror = "Oppose section not found";
105
			return false;
0 ignored issues
show
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
106
		}
107
		if( !isset( $neutral ) ) {
108
			$this->lasterror = "Neutral section not found";
109
			return false;
0 ignored issues
show
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
110
		}
111
112
		$this->support = $this->analyzeSection( $support );
113
		$this->oppose = $this->analyzeSection( $oppose );
114
		$this->neutral = $this->analyzeSection( $neutral );
115
116
		//Merge all votes in one array and sort:
117
		$m = array();
118
		foreach( $this->support as $s ){
119
			if( isset( $s['name'] ) ) $m[] = $s['name'];
120
		}
121
		foreach( $this->oppose as $o ){
122
			if( isset( $o['name'] ) ) $m[] = $o['name'];
123
		}
124
		foreach( $this->neutral as $n ){
125
			if( isset( $n['name'] ) ) $m[] = $n['name'];
126
		}
127
		sort( $m );
128
		//Find duplicates:
129
		for( $i = 0; $i < count( $m ); $i++ ){
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
Performance Best Practice introduced by
Consider avoiding function calls on each iteration of the for loop.

If you have a function call in the test part of a for loop, this function is executed on each iteration. Often such a function, can be moved to the initialization part and be cached.

// count() is called on each iteration
for ($i=0; $i < count($collection); $i++) { }

// count() is only called once
for ($i=0, $c=count($collection); $i<$c; $i++) { }
Loading history...
130
			if( $i != count( $m ) - 1 ) {
131
				if( $m[$i] == $m[$i + 1] ) {
132
					$this->duplicates[] = $m[$i];
133
				}
134
			}
135
		}
136
137
		return true;
0 ignored issues
show
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
138
	}
139
140
	public function get_username() {
141
		return $this->username;
0 ignored issues
show
Bug introduced by
The property username does not seem to exist. Did you mean pgUsername?

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...
142
	}
143
144
	public function get_enddate() {
145
		return $this->enddate;
146
	}
147
148
	public function get_support() {
149
		return $this->support;
150
	}
151
152
	public function get_oppose() {
153
		return $this->oppose;
154
	}
155
156
	public function get_neutral() {
157
		return $this->neutral;
158
	}
159
160
	public function get_duplicates() {
161
		return $this->duplicates;
162
	}
163
164
	public function get_lasterror() {
165
		return $this->lasterror;
166
	}
167
168
	/**
169
	 * Attempts to find a signature in $input using the default regex. Returns matches.
170
	 * @param $input
171
	 * @param $matches
172
	 *
173
	 * @return int
174
	 */
175
	protected function findSig( $input, &$matches ) {
176
		//Supports User: and User talk: wikilinks, {{fullurl}}, unsubsted {{unsigned}}, unsubsted {{unsigned2}}, anything that looks like a custom sig template
177
		return preg_match_all(
178
			"/\[\[[Uu]ser(?:[\s_][Tt]alk)?\:([^\]\|\/]*)(?:\|[^\]]*)?\]\]" //1: Normal [[User:XX]] and [[User talk:XX]]
179
			. "|\{\{(?:[Ff]ullurl\:[Uu]ser(?:[\s_][Tt]alk)?\:|[Uu]nsigned\|)([^\}\|]*)(?:|[\|\}]*)?\}\}" //2: {{fullurl}} and {{unsigned}} templates
180
			. "|(?:\{\{)[Uu]ser(?:[\s_][Tt]alk)?\:([^\}\/\|]*)" //3: {{User:XX/sig}} templates
181
			. "|\{\{[Uu]nsigned2\|[^\|]*\|([^\}]*)\}\}" //4: {{unsigned2|Date|XX}} templates
182
			. "|(?:\[\[)[Uu]ser\:([^\]\/\|]*)\/[Ss]ig[\|\]]/" //5: [[User:XX/sig]] links (compromise measure)
183
			, $input, $matches, PREG_OFFSET_CAPTURE
184
		);
185
	}
186
187
	/**
188
	 * Attempts to find a signature in $input using a different regex. Returns matches.
189
	 * @param $input
190
	 * @param $matches
191
	 *
192
	 * @return int
193
	 */
194
	protected function findSigAlt( $input, &$matches ) {
195
		return preg_match_all(
196
			"/\[\[[Uu]ser(?:[\s_][Tt]alk)?\:([^\]\/\|]*)" //5: "[[User:XX/PageAboutMe" links (notice no end tag)
197
			. "|\[\[[Ss]pecial\:[Cc]ontributions\/([^\|\]]*)/"
198
			, $input, $matches, PREG_OFFSET_CAPTURE
199
		);
200
	}
201
202
	/**
203
	 * Attempts to find a signature in $input. Returns the name of the user, false on failure.
204
	 * @param $input
205
	 * @param $iffy
206
	 *
207
	 * @return bool|string false if not found Signature, or the Signature if it is found
208
	 */
209
	protected function findSigInLine( $input, &$iffy ) {
210
		$iffy = 0;
211
212
		$parsee_array = explode( "\n", $input );
213
		for( $n = 0; $n < count( $parsee_array ); $n++ ){ //This for will terminate when a sig is found.
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
Performance Best Practice introduced by
Consider avoiding function calls on each iteration of the for loop.

If you have a function call in the test part of a for loop, this function is executed on each iteration. Often such a function, can be moved to the initialization part and be cached.

// count() is called on each iteration
for ($i=0; $i < count($collection); $i++) { }

// count() is only called once
for ($i=0, $c=count($collection); $i<$c; $i++) { }
Loading history...
214
			$parsee = $parsee_array[$n];
215
			//Okay, let's try and remove "copied from above" messages. If the line has more than one timestamp, we'll disregard anything after the first.
216
			//Note: we're ignoring people who use custom timestamps - if these peoples' votes are moved, the mover's name will show up as having voted.
217
218
			//If more than one timestamp is found in the first portion of the vote:
219
			$tsmatches = array();
220
			$dummymatches = array();
221
			if( preg_match_all( '/' . "[0-2][0-9]\:[0-5][0-9], [1-3]?[0-9] (?:January|February|March|April|May|June|July|August|September|October|November|December) \d{4} \(UTC\)" . '/', $parsee, $tsmatches, PREG_OFFSET_CAPTURE ) > 1 ) {
222
				//Go through each timestamp-section, looking for a signature
223
				foreach( $tsmatches[0] as $minisection ){
224
					$temp = substr( $parsee, 0, $minisection[1] );
225
					//If a signature is found, stop and use it as voter
226
					if( $this->findSig( $temp, $dummymatches ) != 0 ) { //KNOWN ISSUE: Write description later
227
						$parsee = $temp;
228
						break;
229
					}
230
				}
231
			}
232
233
			//Start the main signature-finding:
234
			$matches = array();
235
			if( $this->findSig( $parsee, $matches ) == 0 ) {
236
				//Okay, signature not found. Let's try the backup regex
237
				if( $this->findSigAlt( $parsee, $matches ) == 0 ) {
238
					//Signature was not found in this iteration of the main loop :(
239
					continue; //Go on to next newline (may be iffy)
240
				} else {
241
					$merged = array_merge( $matches[1], $matches[2] );
242
				}
243
			} else {
244
				//Merge the match arrays:
245
				$merged = array_merge( $matches[5], $matches[1], $matches[3], $matches[2], $matches[4] );
246
			}
247
			//Remove blank values and arrays of the form ('',-1):
248
			foreach( $merged as $key => $value ){
249
				if( is_array( $value ) && ( $value[0] == '' ) && ( $value[1] == -1 ) ) {
250
					unset( $merged[$key] );
251
				} elseif( $value == "" ) {
252
					unset( $merged[$key] );
253
				}
254
			}
255
256
			//Let's find out the real signature
257
			$keys = array();
258
			$values = array();
259
			foreach( $merged as $mergee ){
260
				$keys[] = $mergee[0];
261
				$values[] = $mergee[1];
262
			}
263
			//Now sort:
264
			array_multisort( $values, SORT_DESC, SORT_NUMERIC, $keys );
265
			//Now we should have the most relevant match (i.e., the sig) at the top of $keys
266
			$i = 0;
267
			$foundsig = '';
268
			while( $foundsig == '' ){
269
				$foundsig = trim( $keys[$i++] );
270
				if( $i == count( $keys ) ) break; //If we can only find blank usernames in the sig, catch overflow
271
				//Also fires when the first sig is also the last sig, so not an error
272
			}
273
274
			//Set iffy flag (level 1) if went beyond first line
275
			if( $n > 0 ) {
276
				$iffy = 1;
277
			}
278
			return $foundsig;
279
		}
280
281
		return false;
282
	}
283
284
	/**
285
	 * Analyzes an RFA section. Returns an array of parsed signatures on success. Undefined behaviour on failure.
286
	 * @param string $input
287
	 *
288
	 * @return array
289
	 */
290
	private function analyzeSection( $input ) {
291
		//Remove trailing sharp, if any
292
		$input = preg_replace( '/#\s*$/', '', $input );
293
294
		//Old preg_split regex: "/(^|\n)\s*\#[^\#\:\*]/"
295
		$parsed = preg_split( "/(^|\n)\#/", $input );
296
		//Shift off first empty element:
297
		array_shift( $parsed );
298
299
		foreach( $parsed as &$parsee ){ //Foreach line
300
			//If the line is empty for some reason, ignore
301
			$parsee = trim( $parsee );
302
			if( empty( $parsee ) ) continue;
303
304
			//If the line has been indented (disabled), or is a comment, ignore
305
			if( ( $parsee[0] == ':' ) || ( $parsee[0] == '*' ) || ( $parsee[0] == '#' ) ) {
306
				$parsee = '///###///';
307
				continue;
308
			}; //struck-out vote or comment
309
310
			$parsedsig = $this->findSigInLine( $parsee, $iffy ); //Find signature
311
			$orgsig = $parsee;
312
			$parsee = array();
313
			$parsee['context'] = $orgsig;
314
			if( $parsedsig === false ) {
315
				$parsee['error'] = 'Signature not found';
316
			} else {
317
				$parsee['name'] = $parsedsig;
318
			}
319
			if( @$iffy == 1 ) {
320
				$parsee['iffy'] = '1';
321
			}
322
		} //Foreach line
323
324
		if( ( count( $parsed ) == 1 ) && ( @trim( $parsed[0]['name'] ) == '' ) ) { //filters out placeholder sharp sign used in empty sections
325
			$parsed = array();
326
		}
327
328
		//Delete struck-out keys "continued" in foreach
329
		foreach( $parsed as $key => $value ){
330
			if( $value == '///###///' ) {
331
				unset( $parsed[$key] );
332
			}
333
		}
334
335
		return $parsed;
336
	}
337
338
}
339