Completed
Push — master ( b59220...419e79 )
by mw
90:31 queued 54:46
created

SMWTimeValue::isInterpretableAsYearOnly()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20
Metric Value
dl 0
loc 3
ccs 0
cts 1
cp 0
rs 9.2
cc 4
eloc 2
nc 5
nop 1
crap 20
1
<?php
2
3
use SMW\DataValues\ValueFormatters\DataValueFormatter;
4
5
/**
6
 * @ingroup SMWDataValues
7
 */
8
9
/**
10
 * This datavalue captures values of dates and times, in many formats,
11
 * throughout history and pre-history. The implementation can handle dates
12
 * across history with full precision for storing, and substantial precision
13
 * for sorting and querying. The range of supported past dates should encompass
14
 * the Beginning of Time according to most of today's theories. The range of
15
 * supported future dates is limited more strictly, but it does also allow
16
 * year numbers in the order of 10^9.
17
 *
18
 * The implementation notices and stores whether parts of a date/time have been
19
 * omitted (as in "2008" or "May 2007"). For all exporting and sorting
20
 * purposes, incomplete dates are completed with defaults (usually using the
21
 * earliest possible time, i.e. interpreting "2008" as "Jan 1 2008 00:00:00").
22
 * The information on what was unspecified is kept internally for improving
23
 * behavior e.g. for outputs (defaults are not printed when querying for a
24
 * value). This largely uses the precision handling of SMWDITime.
25
 *
26
 *
27
 * Date formats
28
 *
29
 * Dates can be given in many formats, using numbers, month names, and
30
 * abbreviated month names. The preferred interpretation of ambiguous dates
31
 * ("1 2 2008" or even "1 2 3 BC") is controlled by the language file, as is
32
 * the local naming of months. English month names are always supported.
33
 *
34
 * Dates can be given in Gregorian or Julian calendar, set by the token "Jl"
35
 * or "Gr" in the input. If neither is set, a default is chosen: inputs after
36
 * October 15, 1582 (the time when the Gregorian calendar was first inaugurated
37
 * in some parts of the world) are considered Gr, earlier inputs are considered
38
 * Jl. In addition to Jl and Gr, we support "OS" (Old Style English dates that
39
 * refer to the use of Julian calendar with a displaced change of year on March
40
 * 24), JD (direct numerical input in Julian Day notation), and MJD (direct
41
 * numerical input in Modified Julian Day notation as used in aviation and
42
 * space flight).
43
 *
44
 * The class does not support the input of negative year numbers but uses the
45
 * markers "BC"/"BCE" and "AD"/"CE" instead. There is no year 0 in Gregorian or
46
 * Julian calendars, but the class graciously considers this input to mean year
47
 * 1 BC(E).
48
 *
49
 * For prehisoric dates before 9999 BC(E) only year numbers are allowed
50
 * (nothing else makes much sense). At this time, the years of Julian and
51
 * Gregorian calendar still overlap significantly, so the transition to a
52
 * purely solar annotation of prehistoric years is smooth. Technically, the
53
 * class will consider prehistoric dates as Gregorian but very ancient times
54
 * may be interpreted as desired (probably with reference to a physical notion
55
 * of time that is not dependent on revolutions of earth around the sun).
56
 *
57
 *
58
 * Time formats
59
 *
60
 * Times can be in formats like "23:12:45" and "12:30" possibly with additional
61
 * modifiers "am" or "pm". Timezones are supported: the class knows many
62
 * international timezone monikers (e.g. CET or GMT) and also allows time
63
 * offsets directly after a time (e.g. "10:30-3:30" or "14:45:23+2"). Such
64
 * offsets always refer to UTC. Timezones are only used on input and are not
65
 * stored as part of the value.
66
 *
67
 * Time offsets take leap years into account, e.g. the date
68
 * "Feb 28 2004 23:00+2:00" is equivalent to "29 February 2004 01:00:00", while
69
 * "Feb 28 1900 23:00+2:00" is equivalent to "1 March 1900 01:00:00".
70
 *
71
 * Military time format is supported. This consists of 4 or 6 numeric digits
72
 * followed by a one-letter timezone code (e.g. 1240Z is equivalent to 12:40
73
 * UTC).
74
 *
75
 *
76
 * I18N
77
 *
78
 * Currently, neither keywords like "BCE", "Jl", or "pm", nor timezone monikers
79
 * are internationalized. Timezone monikers may not require this, other than
80
 * possibly for Cyrillic (added when needed). Month names are fully
81
 * internationalized, but English names and abbreviations will also work in all
82
 * languages. The class also supports ordinal day-of-month annotations like
83
 * "st" and "rd", again only for English.
84
 *
85
 * I18N includes the preferred order of dates, e.g. to interpret "5 6 2010".
86
 *
87
 * @todo Theparsing process can encounter many kinds of well-defined problems
88
 * but uses only one error message. More detailed reporting should be done.
89
 * @todo Try to reuse more of MediaWiki's records, e.g. to obtain month names
90
 * or to format dates. The problem is that MW is based on SIO timestamps that
91
 * don't extend to very ancient or future dates, and that MW uses PHP functions
92
 * that are bound to UNIX time.
93
 *
94
 * @author Markus Krötzsch
95
 * @author Fabian Howahl
96
 * @author Terry A. Hurlbut
97
 * @ingroup SMWDataValues
98
 */
99
class SMWTimeValue extends SMWDataValue {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
100
	protected $m_dataitem_greg = null;
101
	protected $m_dataitem_jul = null;
102
103
	protected $m_wikivalue; // a suitable wiki input value
104
105
	// The following are constant (array-valued constants are not supported, hence the declaration as private static variable):
106
	protected static $m_months = array( 'January', 'February', 'March', 'April' , 'May' , 'June' , 'July' , 'August' , 'September' , 'October' , 'November' , 'December' );
107
	protected static $m_monthsshort = array( 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' );
108
	protected static $m_formats = array( SMW_Y => array( 'y' ), SMW_YM => array( 'y', 'm' ), SMW_MY => array( 'm', 'y' ), SMW_YDM => array( 'y', 'd', 'm' ), SMW_YMD => array( 'y', 'm', 'd' ), SMW_DMY => array( 'd', 'm', 'y' ), SMW_MDY => array( 'm', 'd', 'y' ) );
109
110
	/// General purpose time zone monikers and their associated offsets in hours and fractions of hours
111
	protected static $m_tz = array( 'A' => 1, 'ACDT' => 10.5, 'ACST' => 9.5, 'ADT' => -3, 'AEDT' => 11,
112
		'AEST' => 10, 'AKDT' => -8, 'AKST' => -9, 'AST' => -4, 'AWDT' => 9, 'AWST' => 8,
113
		'B' => 2, 'BST' => 1, 'C' => 3, 'CDT' => - 5, 'CEDT' => 2, 'CEST' => 2,
114
		'CET' => 1, 'CST' => -6, 'CXT' => 7, 'D' => 4, 'E' => 5, 'EDT' => - 4,
115
		'EEDT' => 3, 'EEST' => 3, 'EET' => 2, 'EST' => - 5, 'F' => 6, 'G' => 7,
116
		'GMT' => 0, 'H' => 8, 'HAA' => - 3, 'HAC' => - 5, 'HADT' => - 9, 'HAE' => -4,
117
		'HAP' => -7, 'HAR' => -6, 'HAST' => -10, 'HAT' => -2.5, 'HAY' => -8,
118
		'HNA' => -4, 'HNC' => -6, 'HNE' => -5, 'HNP' => -8, 'HNR' => -7, 'HNT' => -3.5,
119
		'HNY' => -9, 'I' => 9, 'IST' => 1, 'K' => 10, 'L' => 11, 'M' => 12,
120
		'MDT' => -6, 'MESZ' => 2, 'MEZ' => 1, 'MSD' => 4, 'MSK' => 3, 'MST' => -7,
121
		'N' => -1, 'NDT' => -2.5, 'NFT' => 11.5, 'NST' => -3.5, 'O' => -2, 'P' => -3 ,
122
		'PDT' => -7, 'PST' => -8, 'Q' => - 4, 'R' => - 5, 'S' => -6, 'T' => -7,
123
		'U' => -8, 'UTC' => 0, 'V' => - 9, 'W' => -10, 'WDT' => 9, 'WEDT' => 1,
124
		'WEST' => 1, 'WET' => 0, 'WST' => 8, 'X' => -11, 'Y' => -12, 'Z' => 0 );
125
	/// Military time zone monikers and their associated offsets in hours
126
	protected static $m_miltz = array( 'A' => 1, 'B' => 2, 'C' => 3, 'D' => 4, 'E' => 5, 'F' => 6,
127
		'G' => 7, 'H' => 8, 'I' => 9, 'K' => 10, 'L' => 11, 'M' => 12, 'N' => -1, 'O' => -2,
128
		'P' => -3, 'Q' => -4, 'R' => -5, 'S' => -6, 'T' => -7, 'U' => -8, 'V' => -9,
129
		'W' => -10, 'X' => -11, 'Y' => -12, 'Z' => 0 );
130
131
	/// Moment of switchover to Gregorian calendar.
132
	const J1582 = 2299160.5;
133
	/// Offset of Julian Days for Modified JD inputs.
134
	const MJD_EPOCH = 2400000.5;
135
	/// The year before which we do not accept anything but year numbers and largely discourage calendar models.
136
	const PREHISTORY = -10000;
137
138 29
	protected function parseUserValue( $value ) {
139 29
		$value = trim( $value ); // ignore whitespace
140 29
		$this->m_wikivalue = $value;
141 29
		if ( $this->m_caption === false ) { // Store the caption now.
142 29
			$this->m_caption = $value;
143 29
		}
144 29
		$this->m_dataitem = null;
145
146
		/// TODO Direct JD input currently cannot cope with decimal numbers
147 29
		$datecomponents = array();
148 29
		$calendarmodel = $era = $hours = $minutes = $seconds = $timeoffset = false;
149
150
		if ( $this->isInterpretableAsYearOnly( $value ) ) {
151 29
			$this->m_dataitem = new SMWDITime( $this->getCalendarModel( null, $value, null, null ), $value );
152 3
		} elseif ( strlen( $value ) != 4 && wfTimestamp( TS_MW, $value ) !== false ) {
153 3
			$this->m_dataitem = SMWDITime::newFromTimestamp( $value );
0 ignored issues
show
Documentation Bug introduced by
It seems like \SMWDITime::newFromTimestamp($value) can also be of type false. However, the property $m_dataitem is declared as type object<SMWDataItem>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
154 29
		}
155 27
		elseif ( $this->parseDateString( $value, $datecomponents, $calendarmodel, $era, $hours, $minutes, $seconds, $timeoffset ) ) {
156
			if ( ( $calendarmodel === false ) && ( $era === false ) && ( count( $datecomponents ) == 1 ) && ( intval( end( $datecomponents ) ) >= 100000 ) ) {
157
				$calendarmodel = 'JD'; // default to JD input if a single number was given as the date
158
			}
159 27
160
			if ( ( $calendarmodel == 'JD' ) || ( $calendarmodel == 'MJD' ) ) {
161
				if ( ( $era === false ) && ( $hours === false ) && ( $timeoffset == 0 ) ) {
162
					try {
163
						$jd = floatval( reset( $datecomponents ) );
164
						if ( $calendarmodel == 'MJD' ) {
165
							$jd += self::MJD_EPOCH;
166
						}
167
						$this->m_dataitem = SMWDITime::newFromJD( $jd, SMWDITime::CM_GREGORIAN, SMWDITime::PREC_YMDT, $this->m_typeid );
0 ignored issues
show
Unused Code introduced by
The call to SMWDITime::newFromJD() has too many arguments starting with $this->m_typeid.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
168
					} catch ( SMWDataItemException $e ) {
169
						$this->addError( wfMessage( 'smw_nodatetime', $this->m_wikivalue )->inContentLanguage()->text() );
170
					}
171
				} else {
172
					$this->addError( wfMessage( 'smw_nodatetime', $this->m_wikivalue )->inContentLanguage()->text() );
173
				}
174 27
			} else {
175
				$this->setDateFromParsedValues( $datecomponents, $calendarmodel, $era, $hours, $minutes, $seconds, $timeoffset );
176 27
			}
177
		}
178 29
179 3
		if ( is_null( $this->m_dataitem ) ) { // make sure that m_dataitem is set in any case
180 3
			$this->m_dataitem = new SMWDITime( SMWDITime::CM_GREGORIAN, 32202 );
181 29
		}
182
	}
183
184
	/**
185
	 * Parse the given string to check if it a date/time value.
186
	 * The function sets the provided call-by-ref values to the respective
187
	 * values. If errors are encountered, they are added to the objects
188
	 * error list and false is returned. Otherwise, true is returned.
189
	 * @param $string string input time representation, e.g. "12 May 2007 13:45:23-3:30"
190
	 * @param $datecomponents array of strings that might belong to the specification of a date
191
	 * @param $calendarmodesl string if model was set in input, otherwise false
192
	 * @param $era string '+' or '-' if provided, otherwise false
193
	 * @param $hours integer set to a value between 0 and 24
194
	 * @param $minutes integer set to a value between 0 and 59
195
	 * @param $seconds integer set to a value between 0 and 59, or false if not given
196
	 * @param $timeoffset double set to a value for time offset (e.g. 3.5), or false if not given
197
	 * @return boolean stating if the parsing succeeded
198
	 * @todo This method in principle allows date parsing to be internationalized further. Should be done.
199 29
	 */
200
	protected function parseDateString( $string, &$datecomponents, &$calendarmodel, &$era, &$hours, &$minutes, &$seconds, &$timeoffset ) {
201
		// crude preprocessing for supporting different date separation characters;
202
		// * this does not allow localized time notations such as "10.34 pm"
203
		// * this creates problems with keywords that contain "." such as "p.m."
204 29
		// * yet "." is an essential date separation character in languages such as German
205
		$parsevalue = str_replace( array( '/', '.', '&nbsp;', ',' ), array( '-', ' ', ' ', ' ' ), $string );
206 29
207 29
		$matches = preg_split( "/([T]?[0-2]?[0-9]:[\:0-9]+[+\-]?[0-2]?[0-9\:]+|[\p{L}]+|[0-9]+|[ ])/u", $parsevalue, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY );
208 29
		$datecomponents = array();
209 29
		$calendarmodel = $timezoneoffset = $era = $ampm = false;
210 29
		$hours = $minutes = $seconds = $timeoffset = false;
211 29
		$unclearparts = array();
212 29
		$matchisnumber = false; // used for looking back; numbers are days/months/years by default but may be re-interpreted if certain further symbols are found
213
		$matchisdate = false; // used for ensuring that date parts are in one block
214 29
215 29
		foreach ( $matches as $match ) {
216 29
			$prevmatchwasnumber = $matchisnumber;
217 29
			$prevmatchwasdate   = $matchisdate;
218
			$matchisnumber = $matchisdate = false;
219 29
220 21
			if ( $match == ' ' ) {
221 29
				$matchisdate = $prevmatchwasdate; // spaces in dates do not end the date
222 4
			} elseif ( $match == '-' ) { // can only occur separately between date components
223 4
				$datecomponents[] = $match; // we check later if this makes sense
224 29
				$matchisdate = true;
225 29
			} elseif ( is_numeric( $match ) &&
226 28
			           ( $prevmatchwasdate || count( $datecomponents ) == 0 ) ) {
227 28
				$datecomponents[] = $match;
228 28
				$matchisnumber = true;
229 29
				$matchisdate = true;
230
			} elseif ( $era === false && in_array( $match, array( 'AD', 'CE' ) ) ) {
231 22
				$era = '+';
232 4
			} elseif ( $era === false && in_array( $match, array( 'BC', 'BCE' ) ) ) {
233 22
				$era = '-';
234 2
			} elseif ( $calendarmodel === false && in_array( $match, array( 'Gr', 'He', 'Jl', 'MJD', 'JD', 'OS' ) ) ) {
235 22
				$calendarmodel = $match;
236 7
			} elseif ( $ampm === false && ( strtolower( $match ) === 'am' || strtolower( $match ) === 'pm' ) ) {
237 22
				$ampm = strtolower( $match );
238
			} elseif ( $hours === false && self::parseTimeString( $match, $hours, $minutes, $seconds, $timeoffset ) ) {
0 ignored issues
show
Unused Code introduced by
This elseif statement is empty, and could be removed.

This check looks for the bodies of elseif statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These elseif bodies can be removed. If you have an empty elseif but statements in the else branch, consider inverting the condition.

Loading history...
239 22
				// nothing to do
240 22
			} elseif ( $hours !== false && $timezoneoffset === false &&
241
			           array_key_exists( $match, self::$m_tz ) ) {
242 2
				// only accept timezone if time has already been set
243 22
				$timezoneoffset = self::$m_tz[$match];
244 22
			} elseif ( $prevmatchwasnumber && $hours === false && $timezoneoffset === false &&
245 22
			           array_key_exists( $match, self::$m_miltz ) &&
246
				   self::parseMilTimeString( end( $datecomponents ), $hours, $minutes, $seconds ) ) {
247
					// military timezone notation is found after a number -> re-interpret the number as military time
248
					array_pop( $datecomponents );
249 22
					$timezoneoffset = self::$m_miltz[$match];
250 22
			} elseif ( ( $prevmatchwasdate || count( $datecomponents ) == 0 ) &&
251 21
				   $this->parseMonthString( $match, $monthname ) ) {
252 21
				$datecomponents[] = $monthname;
253 22
				$matchisdate = true;
254
			} elseif ( $prevmatchwasnumber && $prevmatchwasdate && in_array( $match, array( 'st', 'nd', 'rd', 'th' ) ) ) {
255
				$datecomponents[] = 'd' . strval( array_pop( $datecomponents ) ); // must be a day; add standard marker
256
				$matchisdate = true;
257 3
			} else {
258
				$unclearparts[] = $match;
259 29
			}
260
		}
261
		// Useful for debugging:
262
		// 		print "\n\n Results \n\n";
263
		// 		debug_zval_dump( $datecomponents );
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% 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...
264
		// 		print "\ncalendarmodel: $calendarmodel   \ntimezoneoffset: $timezoneoffset  \nera: $era  \nampm: $ampm  \nh: $hours  \nm: $minutes  \ns:$seconds  \ntimeoffset: $timeoffset  \n";
265
		// 		debug_zval_dump( $unclearparts );
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% 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...
266
267 29
		// Abort if we found unclear or over-specific information:
268 29
		if ( count( $unclearparts ) != 0 ||
269 3
		     ( $timezoneoffset !== false && $timeoffset !== false ) ) {
270 3
			$this->addError( wfMessage( 'smw_nodatetime', $this->m_wikivalue )->inContentLanguage()->text() );
271
			return false;
272
		}
273 27
274
		$timeoffset = $timeoffset + $timezoneoffset;
275
		// Check if the a.m. and p.m. information is meaningful
276 27
277
		if ( $ampm !== false && ( $hours > 12 || $hours == 0 ) ) { // Note: the == 0 check subsumes $hours===false
278
			$this->addError( wfMessage( 'smw_nodatetime', $this->m_wikivalue )->inContentLanguage()->text() );
279 27
			return false;
280
		} elseif ( $ampm == 'am' && $hours == 12 ) {
281 27
			$hours = 0;
282 7
		} elseif ( $ampm == 'pm' && $hours < 12 ) {
283 7
			$hours += 12;
284
		}
285 27
286
		return true;
287
	}
288
289
	/**
290
	 * Parse the given string to check if it encodes an international time.
291
	 * If successful, the function sets the provided call-by-ref values to
292
	 * the respective numbers and returns true. Otherwise, it returns
293
	 * false and does not set any values.
294
	 * @note This method is only temporarily public for enabling SMWCompatibilityHelpers. Do not use it directly in your code.
295
	 *
296
	 * @param $string string input time representation, e.g. "13:45:23-3:30"
297
	 * @param $hours integer between 0 and 24
298
	 * @param $minutes integer between 0 and 59
299
	 * @param $seconds integer between 0 and 59, or false if not given
300
	 * @param $timeoffset double for time offset (e.g. 3.5), or false if not given
301
	 * @return boolean stating if the parsing succeeded
302 22
	 */
303 22
	public static function parseTimeString( $string, &$hours, &$minutes, &$seconds, &$timeoffset ) {
304 22
		if ( !preg_match( "/^[T]?([0-2]?[0-9]):([0-5][0-9])(:[0-5][0-9])?(([+\-][0-2]?[0-9])(:(30|00))?)?$/u", $string, $match ) ) {
305
			return false;
306 9
		} else {
307 9
			$nhours = intval( $match[1] );
308 9
			$nminutes = $match[2] ? intval( $match[2] ) : false;
309 3
			if ( ( count( $match ) > 3 ) && ( $match[3] !== '' ) ) {
310 3
				$nseconds = intval( substr( $match[3], 1 ) );
311 8
			} else {
312
				$nseconds = false;
313 9
			}
314 9
			if ( ( $nhours < 25 ) && ( ( $nhours < 24 ) || ( $nminutes + $nseconds == 0 ) ) ) {
315 9
				$hours = $nhours;
316 9
				$minutes = $nminutes;
317 9
				$seconds = $nseconds;
318 2
				if ( ( count( $match ) > 5 ) && ( $match[5] !== '' ) ) {
319 2
					$timeoffset = intval( $match[5] );
320
					if ( ( count( $match ) > 7 ) && ( $match[7] == '30' ) ) {
321
						$timeoffset += 0.5;
322 2
					}
323 9
				} else {
324
					$timeoffset = false;
325 9
				}
326
				return true;
327
			} else {
328
				return false;
329
			}
330
		}
331
	}
332
333
	/**
334
	 * Parse the given string to check if it encodes a "military time".
335
	 * If successful, the function sets the provided call-by-ref values to
336
	 * the respective numbers and returns true. Otherwise, it returns
337
	 * false and does not set any values.
338
	 * @param $string string input time representation, e.g. "134523"
339
	 * @param $hours integer between 0 and 24
340
	 * @param $minutes integer between 0 and 59
341
	 * @param $seconds integer between 0 and 59, or false if not given
342
	 * @return boolean stating if the parsing succeeded
343
	 */
344
	protected static function parseMilTimeString( $string, &$hours, &$minutes, &$seconds ) {
345
		if ( !preg_match( "/^([0-2][0-9])([0-5][0-9])([0-5][0-9])?$/u", $string, $match ) ) {
346
			return false;
347
		} else {
348
			$nhours = intval( $match[1] );
349
			$nminutes = $match[2] ? intval( $match[2] ) : false;
350
			$nseconds = ( ( count( $match ) > 3 ) && $match[3] ) ? intval( $match[3] ) : false;
351
			if ( ( $nhours < 25 ) && ( ( $nhours < 24 ) || ( $nminutes + $nseconds == 0 ) ) ) {
352
				$hours = $nhours;
353
				$minutes = $nminutes;
354
				$seconds = $nseconds;
355
				return true;
356
			} else {
357
				return false;
358
			}
359
		}
360
	}
361
362
	/**
363
	 * Parse the given string to check if it refers to the string name ot
364
	 * abbreviation of a month name. If yes, it is replaced by a normalized
365
	 * month name (placed in the call-by-ref parameter) and true is
366
	 * returned. Otherwise, false is returned and $monthname is not changed.
367
	 * @param $string string month name or abbreviation to parse
368
	 * @param $monthname string with standard 3-letter English month abbreviation
369
	 * @return boolean stating whether a month was found
370 22
	 */
371
	protected static function parseMonthString( $string, &$monthname ) {
372
		/**
373
		 * @var SMWLanguage $smwgContLang
374 22
		 */
375
		global $smwgContLang;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
376 22
377
		$monthnum = $smwgContLang->findMonth( $string ); // takes precedence over English month names!
378 22
379 21
		if ( $monthnum !== false ) {
380 21
			$monthnum -= 1;
381 3
		} else {
382
			$monthnum = array_search( $string, self::$m_months ); // check English names
383
		}
384 22
385 21
		if ( $monthnum !== false ) {
386 21
			$monthname = self::$m_monthsshort[$monthnum];
387 3
			return true;
388
		} elseif ( array_search( $string, self::$m_monthsshort ) !== false ) {
389
			$monthname = $string;
390
			return true;
391 3
		} else {
392
			return false;
393
		}
394
	}
395
396
	/**
397
	 * Validate and interpret the date components as retrieved when parsing
398
	 * a user input. The method takes care of guessing how a list of values
399
	 * such as "10 12 13" is to be interpreted using the current language
400
	 * settings. The result is stored in the call-by-ref parameter
401
	 * $date that uses keys 'y', 'm', 'd' and contains the respective
402
	 * numbers as values, or false if not specified. If errors occur, error
403
	 * messages are added to the objects list of errors, and false is
404
	 * returned. Otherwise, true is returned.
405
	 * @param $datecomponents array of strings that might belong to the specification of a date
406
	 * @param $date array set to result
407
	 * @return boolean stating if successful
408 27
	 */
409 27
	protected function interpretDateComponents( $datecomponents, &$date ) {
410
		global $smwgContLang;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
411
		// The following code segment creates a bit vector to encode
412
		// which role each digit of the entered date can take (day,
413
		// year, month). The vector starts with 1 and contains three
414
		// bits per date component, set ot true whenever this component
415
		// could be a month, a day, or a year (this is the order).
416
		// Examples:
417
		//   100 component could only be a month
418
		//   010 component could only be a day
419
		//   001 component could only be a year
420
		//   011 component could be a day or a year but no month etc.
421 27
		// For three components, we thus get a 10 digit bit vector.
422 27
		$datevector = 1;
423 27
		$propercomponents = array();
424 27
		$justfounddash = true; // avoid two dashes in a row, or dashes at the end
425 27
		$error = false;
426 27
		$numvalue = 0;
427 27
		foreach ( $datecomponents as $component ) {
428 4
			if ( $component == "-" ) {
429
				if ( $justfounddash ) {
430
					$error = true;
431
					break;
432 4
				}
433 4
				$justfounddash = true;
434 27
			} else {
435 27
				$justfounddash = false;
436 27
				$datevector = ( $datevector << 3 ) | $this->checkDateComponent( $component, $numvalue );
437
				$propercomponents[] = $numvalue;
438 27
			}
439 27
		}
440
		if ( ( $error ) || ( $justfounddash ) || ( count( $propercomponents ) == 0 ) || ( count( $propercomponents ) > 3 ) ) {
441
			$this->addError( wfMessage( 'smw_nodatetime', $this->m_wikivalue )->inContentLanguage()->text() );
442
			return false;
443
		}
444 27
		// Now use the bitvector to find the preferred interpretation of the date components:
445 27
		$dateformats = $smwgContLang->getDateFormats();
446 27
		$date = array( 'y' => false, 'm' => false, 'd' => false );
447 27
		foreach ( $dateformats[count( $propercomponents ) - 1] as $formatvector ) {
448 27
			if ( !( ~$datevector & $formatvector ) ) { // check if $formatvector => $datevector ("the input supports the format")
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% 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...
449 27
				$i = 0;
450 27
				foreach ( self::$m_formats[$formatvector] as $fieldname ) {
451 27
					$date[$fieldname] = $propercomponents[$i];
452 27
					$i += 1;
453 27
				}
454
				break;
455 27
			}
456 27
		}
457
		if ( $date['y'] === false ) { // no band matches the entered date
458
			$this->addError( wfMessage( 'smw_nodatetime', $this->m_wikivalue )->inContentLanguage()->text() );
459
			return false;
460 27
		}
461
		return true;
462
	}
463
464
	/**
465
	 * Initialise data from the provided intermediate results after
466
	 * parsing, assuming that a conventional date notation is used.
467
	 * If errors occur, error messages are added to the objects list of
468
	 * errors, and false is returned. Otherwise, true is returned.
469
	 * @param $datecomponents array of strings that might belong to the specification of a date
470
	 * @param $calendarmodesl string if model was set in input, otherwise false
471
	 * @param $era string '+' or '-' if provided, otherwise false
472
	 * @param $hours integer value between 0 and 24
473
	 * @param $minutes integer value between 0 and 59
474
	 * @param $seconds integer value between 0 and 59, or false if not given
475
	 * @param $timeoffset double value for time offset (e.g. 3.5), or false if not given
476
	 * @return boolean stating if successful
477 27
	 */
478 27
	protected function setDateFromParsedValues( $datecomponents, $calendarmodel, $era, $hours, $minutes, $seconds, $timeoffset ) {
479 27
		$date = false;
480
		if ( !$this->interpretDateComponents( $datecomponents, $date ) ) {
481
			return false;
482
		}
483
484 27
		// Handle BC: the year is negative.
485 4
		if ( ( $era == '-' ) && ( $date['y'] > 0 ) ) { // see class documentation on BC, "year 0", and ISO conformance ...
486 4
			$date['y'] = -( $date['y'] );
487
		}
488 27
		// Old Style is a special case of Julian calendar model where the change of the year was 25 March:
489 27
		if ( ( $calendarmodel == 'OS' ) &&
490
		     ( ( $date['m'] < 3 ) || ( ( $date['m'] == 3 ) && ( $date['d'] < 25 ) ) ) ) {
491
			$date['y']++;
492
		}
493 27
494
		$calmod = $this->getCalendarModel( $calendarmodel, $date['y'], $date['m'], $date['d'] );
495 27
		try {
496 27
			$this->m_dataitem = new SMWDITime( $calmod, $date['y'], $date['m'], $date['d'], $hours, $minutes, $seconds, $this->m_typeid );
497
		} catch ( SMWDataItemException $e ) {
498
			$this->addError( wfMessage( 'smw_nodatetime', $this->m_wikivalue )->inContentLanguage()->text() );
499
			return false;
500
		}
501
502
		// Having more than years or specifying a calendar model does
503
		// not make sense for prehistoric dates, and our calendar
504 27
		// conversion would not be reliable if JD numbers get too huge:
505 27
		if ( ( $date['y'] <= self::PREHISTORY ) &&
506
		     ( ( $this->m_dataitem->getPrecision() > SMWDITime::PREC_Y ) || ( $calendarmodel !== false ) ) ) {
507
			$this->addError( wfMessage( 'smw_nodatetime', $this->m_wikivalue )->inContentLanguage()->text() );
508
			return false;
509 27
		}
510 2
		if ( $timeoffset != 0 ) {
511
			$newjd = $this->m_dataitem->getJD() - $timeoffset / 24;
512 2
			try {
513 2
				$this->m_dataitem = SMWDITime::newFromJD( $newjd, $calmod, $this->m_dataitem->getPrecision(), $this->m_typeid );
0 ignored issues
show
Unused Code introduced by
The call to SMWDITime::newFromJD() has too many arguments starting with $this->m_typeid.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
514
			} catch ( SMWDataItemException $e ) {
515
				$this->addError( wfMessage( 'smw_nodatetime', $this->m_wikivalue )->inContentLanguage()->text() );
516
				return false;
517 2
			}
518 27
		}
519
		return true;
520
	}
521
522
	/**
523
	 * Check which roles a string component might play in a date, and
524
	 * set the call-by-ref parameter to the proper numerical
525
	 * representation. The component string has already been normalized to
526
	 * be either a plain number, a month name, or a plain number with "d"
527
	 * pre-pended. The result is a bit vector to indicate the possible
528
	 * interpretations.
529
	 * @param $component string
530
	 * @param $numvalue integer representing the components value
531
	 * @return integer that encodes a three-digit bit vector
532 27
	 */
533 27
	protected static function checkDateComponent( $component, &$numvalue ) {
534
		if ( $component === '' ) { // should not happen
535
			$numvalue = 0;
536 27
			return 0;
537 27
		} elseif ( is_numeric( $component ) ) {
538 27
			$numvalue = intval( $component );
539 18
			if ( ( $numvalue >= 1 ) && ( $numvalue <= 12 ) ) {
540 27
				return SMW_DAY_MONTH_YEAR; // can be a month, day or year
541 17
			} elseif ( ( $numvalue >= 1 ) && ( $numvalue <= 31 ) ) {
542
				return SMW_DAY_YEAR; // can be day or year
543 27
			} else { // number can just be a year
544
				return SMW_YEAR;
545 20
			}
546
		} elseif ( $component { 0 } == 'd' ) { // already marked as day
547
			if ( is_numeric( substr( $component, 1 ) ) ) {
548
				$numvalue = intval( substr( $component, 1 ) );
549
				return ( ( $numvalue >= 1 ) && ( $numvalue <= 31 ) ) ? SMW_DAY : 0;
550
			} else {
551
				return 0;
552
			}
553 20
		} else {
554 20
			$monthnum = array_search( $component, self::$m_monthsshort );
555 20
			if ( $monthnum !== false ) {
556 20
				$numvalue = $monthnum + 1;
557
				return SMW_MONTH;
558
			} else {
559
				return 0;
560
			}
561
		}
562
	}
563
564
	/**
565
	 * Determine the calender model under which an input should be
566
	 * interpreted based on the given input data.
567
	 * @param $presetmodel mixed string related to a user input calendar model (OS, Jl, Gr) or false
568
	 * @param $year integer of the given year (adjusted for BC(E), i.e. possibly negative)
569
	 * @param $month mixed integer of the month or false
570
	 * @param $day mixed integer of the day or false
571
	 * @return integer either SMWDITime::CM_GREGORIAN or SMWDITime::CM_JULIAN
572 27
	 */
573 27
	protected function getCalendarModel( $presetmodel, $year, $month, $day ) {
574
		if ( $presetmodel == 'OS' ) { // Old Style is a notational convention of Julian dates only
575
			$presetmodel = 'Jl';
576 27
		}
577 2
		if ( $presetmodel == 'Gr' ) {
578 27
			return SMWDITime::CM_GREGORIAN;
579 2
		} elseif ( $presetmodel == 'Jl' ) {
580
			return SMWDITime::CM_JULIAN;
581 27
		}
582 10
		if ( ( $year > 1582 ) ||
583 27
		     ( ( $year == 1582 ) && ( $month > 10 ) ) ||
584 26
		     ( ( $year == 1582 ) && ( $month == 10 ) && ( $day > 4 ) ) ) {
585 10
			return SMWDITime::CM_GREGORIAN;
586 10
		} elseif ( $year > self::PREHISTORY ) {
587
			return SMWDITime::CM_JULIAN;
588
		} else {
589
			// proleptic Julian years at some point deviate from the count of complete revolutions of the earth around the sun
590
			// hence assume that earlier date years are Gregorian (where this effect is very weak only)
591 3
			// This is mostly for internal use since we will not allow users to specify calendar models at this scale
592
			return SMWDITime::CM_GREGORIAN;
593
		}
594
	}
595
596
	/**
597
	 * @see SMWDataValue::loadDataItem
598
	 *
599
	 * {@inheritDoc}
600 34
	 */
601
	protected function loadDataItem( SMWDataItem $dataItem ) {
602 34
603
		if ( $dataItem->getDIType() !== SMWDataItem::TYPE_TIME ) {
604
			return false;
605
		}
606 34
607 34
		$this->m_dataitem = $dataItem;
608 34
		$this->m_caption = false;
0 ignored issues
show
Documentation Bug introduced by
The property $m_caption was declared of type string, but false is of type false. 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...
609
		$this->m_wikivalue = false;
610 34
611
		return true;
612
	}
613
614
	/**
615
	 * @see SMWDataValue::getShortWikiText
616
	 *
617
	 * {@inheritDoc}
618 19
	 */
619 19
	public function getShortWikiText( $linker = null ) {
620
		return $this->getDataValueFormatter()->format( DataValueFormatter::WIKI_SHORT, $linker );
621
	}
622
623
	/**
624
	 * @see SMWDataValue::getShortHTMLText
625
	 *
626
	 * {@inheritDoc}
627
	 */
628
	public function getShortHTMLText( $linker = null ) {
629
		return $this->getDataValueFormatter()->format( DataValueFormatter::HTML_SHORT, $linker );
630
	}
631
632
	/**
633
	 * @see SMWDataValue::getLongWikiText
634
	 *
635
	 * {@inheritDoc}
636 6
	 */
637 6
	public function getLongWikiText( $linker = null ) {
638
		return $this->getDataValueFormatter()->format( DataValueFormatter::WIKI_LONG, $linker );
639
	}
640
641
	/**
642
	 * @see SMWDataValue::getLongHTMLText
643
	 *
644
	 * {@inheritDoc}
645
	 */
646
	public function getLongHTMLText( $linker = null ) {
647
		return $this->getDataValueFormatter()->format( DataValueFormatter::HTML_LONG, $linker );
648
	}
649
650
	/**
651
	 * @todo The preferred caption may not be suitable as a wiki value (i.e. not parsable).
652
	 * @see SMWDataValue::getLongHTMLText
653
	 *
654
	 * {@inheritDoc}
655 3
	 */
656 3
	public function getWikiValue() {
657
		return $this->m_wikivalue ? $this->m_wikivalue : $this->getLongWikiText();
658
	}
659
660
	/**
661
	 * @see SMWDataValue::isNumeric
662
	 *
663
	 * {@inheritDoc}
664
	 */
665
	public function isNumeric() {
666
		return true;
667
	}
668
669
	/**
670
	 * Return the year number in the given calendar model, or false if
671
	 * this number is not available (typically when attempting to get
672
	 * prehistoric Julian calendar dates). As everywhere in this class,
673
	 * there is no year 0.
674
	 * @param $calendarmodel integer either SMWDITime::CM_GREGORIAN or SMWDITime::CM_JULIAN
675
	 * @return mixed typically a number but possibly false
676
	 */
677
	public function getYear( $calendarmodel = SMWDITime::CM_GREGORIAN ) {
678
		$di = $this->getDataItemForCalendarModel( $calendarmodel );
679
		if ( !is_null( $di ) ) {
680
			return $di->getYear();
681
		} else {
682
			return false;
683
		}
684
	}
685
686
	/**
687
	 * Return the month number in the given calendar model, or false if
688
	 * this number is not available (typically when attempting to get
689
	 * prehistoric Julian calendar dates).
690
	 * @param $calendarmodel integer either SMWDITime::CM_GREGORIAN or SMWDITime::CM_JULIAN
691
	 * @param $default value to return if month is not set at our level of precision
692
	 * @return mixed typically a number but possibly anything given as $default
693
	 */
694
	public function getMonth( $calendarmodel = SMWDITime::CM_GREGORIAN, $default = 1 ) {
695
		$di = $this->getDataItemForCalendarModel( $calendarmodel );
696
		if ( !is_null( $di ) ) {
697
			return ( $di->getPrecision() >= SMWDITime::PREC_YM ) ? $di->getMonth() : $default;
698
		} else {
699
			return false;
700
		}
701
	}
702
703
	/**
704
	 * Return the day number in the given calendar model, or false if this
705
	 * number is not available (typically when attempting to get
706
	 * prehistoric Julian calendar dates).
707
	 * @param $calendarmodel integer either SMWDITime::CM_GREGORIAN or SMWDITime::CM_JULIAN
708
	 * @param $default value to return if day is not set at our level of precision
709
	 * @return mixed typically a number but possibly anything given as $default
710
	 */
711
	public function getDay( $calendarmodel = SMWDITime::CM_GREGORIAN, $default = 1 ) {
712
		$di = $this->getDataItemForCalendarModel( $calendarmodel );
713
		if ( !is_null( $di ) ) {
714
			return ( $di->getPrecision() >= SMWDITime::PREC_YMD ) ? $di->getDay() : $default;
715
		} else {
716
			return false;
717
		}
718
	}
719
720
	/**
721
	 * @see TimeValueFormatter::getTimeStringFromDataItem
722
	 *
723
	 * @return
724
	 */
725
	public function getTimeString( $default = '00:00:00' ) {
726
		return $this->getDataValueFormatter()->getTimeString( $default );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class SMW\DataValues\ValueFormatters\DataValueFormatter as the method getTimeString() does only exist in the following sub-classes of SMW\DataValues\ValueFormatters\DataValueFormatter: SMW\DataValues\ValueFormatters\TimeValueFormatter. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
727
	}
728
729
	/**
730
	 * @deprecated This method is now called getISO8601Date(). It will vanish before SMW 1.7.
731
	 */
732
	public function getXMLSchemaDate( $mindefault = true ) {
733
		return $this->getISO8601Date( $mindefault );
734
	}
735
736
	/**
737
	 * @see TimeValueFormatter::getISO8601DateFromDataItem
738
	 *
739
	 * @param $mindefault boolean determining whether values below the
740
	 * precision of our input should be completed with minimal or maximal
741
	 * conceivable values
742
	 *
743
	 * @return string
744 27
	 */
745 27
	public function getISO8601Date( $mindefault = true ) {
746
		return $this->getDataValueFormatter()->getISO8601Date( $mindefault );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class SMW\DataValues\ValueFormatters\DataValueFormatter as the method getISO8601Date() does only exist in the following sub-classes of SMW\DataValues\ValueFormatters\DataValueFormatter: SMW\DataValues\ValueFormatters\TimeValueFormatter. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
747
	}
748
749
	/**
750
	 * @see TimeValueFormatter::getMediaWikiDateFromDataItem
751
	 *
752
	 * @return string
753 1
	 */
754 1
	public function getMediaWikiDate() {
755
		return $this->getDataValueFormatter()->getMediaWikiDate();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class SMW\DataValues\ValueFormatters\DataValueFormatter as the method getMediaWikiDate() does only exist in the following sub-classes of SMW\DataValues\ValueFormatters\DataValueFormatter: SMW\DataValues\ValueFormatters\TimeValueFormatter. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
756
	}
757
758
	/**
759
	 * Get the current data in the specified calendar model. Conversion is
760
	 * not done for prehistoric dates (where it might lead to precision
761
	 * errors and produce results that are not meaningful). In this case,
762
	 * null might be returned if no data in the specified format is
763
	 * available.
764
	 * @param $calendarmodel integer one of SMWDITime::CM_GREGORIAN or SMWDITime::CM_JULIAN
765
	 * @return SMWDITime
766 34
	 */
767 34
	public function getDataItemForCalendarModel( $calendarmodel ) {
768 3
		if ( $this->m_dataitem->getYear() <= self::PREHISTORY ) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class SMWDataItem as the method getYear() does only exist in the following sub-classes of SMWDataItem: SMWDITime. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
769 34
			return ( $this->m_dataitem->getCalendarModel() == $calendarmodel ) ? $this->m_dataitem : null;
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class SMWDataItem as the method getCalendarModel() does only exist in the following sub-classes of SMWDataItem: SMWDITime. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
770 34
		} elseif ( $calendarmodel == SMWDITime::CM_GREGORIAN ) {
771 34
			if ( is_null( $this->m_dataitem_greg ) ) {
772 34
				$this->m_dataitem_greg = $this->m_dataitem->getForCalendarModel( SMWDITime::CM_GREGORIAN );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class SMWDataItem as the method getForCalendarModel() does only exist in the following sub-classes of SMWDataItem: SMWDITime. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
773 34
			}
774
			return $this->m_dataitem_greg;
775 3
		} else {
776 3
			if ( is_null( $this->m_dataitem_jul ) ) {
777 3
				$this->m_dataitem_jul = $this->m_dataitem->getForCalendarModel( SMWDITime::CM_JULIAN );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class SMWDataItem as the method getForCalendarModel() does only exist in the following sub-classes of SMWDataItem: SMWDITime. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
778 3
			}
779
			return $this->m_dataitem_jul;
780
		}
781
	}
782
783
	private function isInterpretableAsYearOnly( $value ) {
784
		return strpos( $value, ' ' ) === false && is_numeric( strval( $value ) ) && ( strval( $value ) < 0 || strlen( $value ) < 6 );
785
	}
786
787
}
788