Completed
Push — master ( 944e8c...873a6a )
by mw
32:14
created

SMWTimeValue::getISO8601Date()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2
Metric Value
dl 0
loc 3
ccs 0
cts 0
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 2
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 27
	/// The year before which we do not accept anything but year numbers and largely discourage calendar models.
136 27
	const PREHISTORY = -10000;
137 27
138 27
	protected function parseUserValue( $value ) {
139 27
		$value = trim( $value ); // ignore whitespace
140 27
		$this->m_wikivalue = $value;
141 27
		if ( $this->m_caption === false ) { // Store the caption now.
142
			$this->m_caption = $value;
143
		}
144 27
		$this->m_dataitem = null;
145 27
146
		/// TODO Direct JD input currently cannot cope with decimal numbers
147
		$datecomponents = array();
148 27
		$calendarmodel = $era = $hours = $minutes = $seconds = $timeoffset = false;
149 2
150 2
		// Check if it's parseable by wfTimestamp when it's not a year (which is wrongly interpreted).
151 27
		if ( strlen( $value ) != 4 && wfTimestamp( TS_MW, $value ) !== false ) {
152 25
			$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...
153
		}
154
		elseif ( $this->parseDateString( $value, $datecomponents, $calendarmodel, $era, $hours, $minutes, $seconds, $timeoffset ) ) {
155
			if ( ( $calendarmodel === false ) && ( $era === false ) && ( count( $datecomponents ) == 1 ) && ( intval( end( $datecomponents ) ) >= 100000 ) ) {
156 25
				$calendarmodel = 'JD'; // default to JD input if a single number was given as the date
157
			}
158
159
			if ( ( $calendarmodel == 'JD' ) || ( $calendarmodel == 'MJD' ) ) {
160
				if ( ( $era === false ) && ( $hours === false ) && ( $timeoffset == 0 ) ) {
161
					try {
162
						$jd = floatval( reset( $datecomponents ) );
163
						if ( $calendarmodel == 'MJD' ) {
164
							$jd += self::MJD_EPOCH;
165
						}
166
						$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...
167
					} catch ( SMWDataItemException $e ) {
168
						$this->addError( wfMessage( 'smw_nodatetime', $this->m_wikivalue )->inContentLanguage()->text() );
169
					}
170
				} else {
171 25
					$this->addError( wfMessage( 'smw_nodatetime', $this->m_wikivalue )->inContentLanguage()->text() );
172
				}
173 25
			} else {
174
				$this->setDateFromParsedValues( $datecomponents, $calendarmodel, $era, $hours, $minutes, $seconds, $timeoffset );
175 27
			}
176 3
		}
177 3
178 27
		if ( is_null( $this->m_dataitem ) ) { // make sure that m_dataitem is set in any case
179
			$this->m_dataitem = new SMWDITime( SMWDITime::CM_GREGORIAN, 32202 );
180
		}
181
	}
182
183
	/**
184
	 * Parse the given string to check if it a date/time value.
185
	 * The function sets the provided call-by-ref values to the respective
186
	 * values. If errors are encountered, they are added to the objects
187
	 * error list and false is returned. Otherwise, true is returned.
188
	 * @param $string string input time representation, e.g. "12 May 2007 13:45:23-3:30"
189
	 * @param $datecomponents array of strings that might belong to the specification of a date
190
	 * @param $calendarmodesl string if model was set in input, otherwise false
191
	 * @param $era string '+' or '-' if provided, otherwise false
192
	 * @param $hours integer set to a value between 0 and 24
193
	 * @param $minutes integer set to a value between 0 and 59
194
	 * @param $seconds integer set to a value between 0 and 59, or false if not given
195
	 * @param $timeoffset double set to a value for time offset (e.g. 3.5), or false if not given
196 27
	 * @return boolean stating if the parsing succeeded
197
	 * @todo This method in principle allows date parsing to be internationalized further. Should be done.
198
	 */
199
	protected function parseDateString( $string, &$datecomponents, &$calendarmodel, &$era, &$hours, &$minutes, &$seconds, &$timeoffset ) {
200
		// crude preprocessing for supporting different date separation characters;
201 27
		// * this does not allow localized time notations such as "10.34 pm"
202
		// * this creates problems with keywords that contain "." such as "p.m."
203 27
		// * yet "." is an essential date separation character in languages such as German
204 27
		$parsevalue = str_replace( array( '/', '.', '&nbsp;', ',' ), array( '-', ' ', ' ', ' ' ), $string );
205 27
206 27
		$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 );
207 27
		$datecomponents = array();
208 27
		$calendarmodel = $timezoneoffset = $era = $ampm = false;
209 27
		$hours = $minutes = $seconds = $timeoffset = false;
210
		$unclearparts = array();
211 27
		$matchisnumber = false; // used for looking back; numbers are days/months/years by default but may be re-interpreted if certain further symbols are found
212 27
		$matchisdate = false; // used for ensuring that date parts are in one block
213 27
214 27
		foreach ( $matches as $match ) {
215
			$prevmatchwasnumber = $matchisnumber;
216 27
			$prevmatchwasdate   = $matchisdate;
217 19
			$matchisnumber = $matchisdate = false;
218 27
219 3
			if ( $match == ' ' ) {
220 3
				$matchisdate = $prevmatchwasdate; // spaces in dates do not end the date
221 27
			} elseif ( $match == '-' ) { // can only occur separately between date components
222 27
				$datecomponents[] = $match; // we check later if this makes sense
223 26
				$matchisdate = true;
224 26
			} elseif ( is_numeric( $match ) &&
225 26
			           ( $prevmatchwasdate || count( $datecomponents ) == 0 ) ) {
226 27
				$datecomponents[] = $match;
227
				$matchisnumber = true;
228 20
				$matchisdate = true;
229 2
			} elseif ( $era === false && in_array( $match, array( 'AD', 'CE' ) ) ) {
230 20
				$era = '+';
231 1
			} elseif ( $era === false && in_array( $match, array( 'BC', 'BCE' ) ) ) {
232 20
				$era = '-';
233 6
			} elseif ( $calendarmodel === false && in_array( $match, array( 'Gr', 'He', 'Jl', 'MJD', 'JD', 'OS' ) ) ) {
234 20
				$calendarmodel = $match;
235
			} elseif ( $ampm === false && ( strtolower( $match ) === 'am' || strtolower( $match ) === 'pm' ) ) {
236 20
				$ampm = strtolower( $match );
237 20
			} 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...
238
				// nothing to do
239 1
			} elseif ( $hours !== false && $timezoneoffset === false &&
240 20
			           array_key_exists( $match, self::$m_tz ) ) {
241 20
				// only accept timezone if time has already been set
242 20
				$timezoneoffset = self::$m_tz[$match];
243
			} elseif ( $prevmatchwasnumber && $hours === false && $timezoneoffset === false &&
244
			           array_key_exists( $match, self::$m_miltz ) &&
245
				   self::parseMilTimeString( end( $datecomponents ), $hours, $minutes, $seconds ) ) {
246 20
					// military timezone notation is found after a number -> re-interpret the number as military time
247 20
					array_pop( $datecomponents );
248 19
					$timezoneoffset = self::$m_miltz[$match];
249 19
			} elseif ( ( $prevmatchwasdate || count( $datecomponents ) == 0 ) &&
250 20
				   $this->parseMonthString( $match, $monthname ) ) {
251
				$datecomponents[] = $monthname;
252
				$matchisdate = true;
253
			} elseif ( $prevmatchwasnumber && $prevmatchwasdate && in_array( $match, array( 'st', 'nd', 'rd', 'th' ) ) ) {
254 3
				$datecomponents[] = 'd' . strval( array_pop( $datecomponents ) ); // must be a day; add standard marker
255
				$matchisdate = true;
256 27
			} else {
257
				$unclearparts[] = $match;
258
			}
259
		}
260
		// Useful for debugging:
261
		// 		print "\n\n Results \n\n";
262
		// 		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...
263
		// 		print "\ncalendarmodel: $calendarmodel   \ntimezoneoffset: $timezoneoffset  \nera: $era  \nampm: $ampm  \nh: $hours  \nm: $minutes  \ns:$seconds  \ntimeoffset: $timeoffset  \n";
264 27
		// 		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...
265 27
266 3
		// Abort if we found unclear or over-specific information:
267 3
		if ( count( $unclearparts ) != 0 ||
268
		     ( $timezoneoffset !== false && $timeoffset !== false ) ) {
269
			$this->addError( wfMessage( 'smw_nodatetime', $this->m_wikivalue )->inContentLanguage()->text() );
270 25
			return false;
271
		}
272
273 25
		$timeoffset = $timeoffset + $timezoneoffset;
274
		// Check if the a.m. and p.m. information is meaningful
275
276 25
		if ( $ampm !== false && ( $hours > 12 || $hours == 0 ) ) { // Note: the == 0 check subsumes $hours===false
277
			$this->addError( wfMessage( 'smw_nodatetime', $this->m_wikivalue )->inContentLanguage()->text() );
278 25
			return false;
279 6
		} elseif ( $ampm == 'am' && $hours == 12 ) {
280 6
			$hours = 0;
281
		} elseif ( $ampm == 'pm' && $hours < 12 ) {
282 25
			$hours += 12;
283
		}
284
285
		return true;
286
	}
287
288
	/**
289
	 * Parse the given string to check if it encodes an international time.
290
	 * If successful, the function sets the provided call-by-ref values to
291
	 * the respective numbers and returns true. Otherwise, it returns
292
	 * false and does not set any values.
293
	 * @note This method is only temporarily public for enabling SMWCompatibilityHelpers. Do not use it directly in your code.
294
	 *
295
	 * @param $string string input time representation, e.g. "13:45:23-3:30"
296
	 * @param $hours integer between 0 and 24
297
	 * @param $minutes integer between 0 and 59
298
	 * @param $seconds integer between 0 and 59, or false if not given
299 20
	 * @param $timeoffset double for time offset (e.g. 3.5), or false if not given
300 20
	 * @return boolean stating if the parsing succeeded
301 20
	 */
302
	public static function parseTimeString( $string, &$hours, &$minutes, &$seconds, &$timeoffset ) {
303 7
		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 ) ) {
304 7
			return false;
305 7
		} else {
306 1
			$nhours = intval( $match[1] );
307 1
			$nminutes = $match[2] ? intval( $match[2] ) : false;
308 7
			if ( ( count( $match ) > 3 ) && ( $match[3] !== '' ) ) {
309
				$nseconds = intval( substr( $match[3], 1 ) );
310 7
			} else {
311 7
				$nseconds = false;
312 7
			}
313 7
			if ( ( $nhours < 25 ) && ( ( $nhours < 24 ) || ( $nminutes + $nseconds == 0 ) ) ) {
314 7
				$hours = $nhours;
315 1
				$minutes = $nminutes;
316 1
				$seconds = $nseconds;
317
				if ( ( count( $match ) > 5 ) && ( $match[5] !== '' ) ) {
318
					$timeoffset = intval( $match[5] );
319 1
					if ( ( count( $match ) > 7 ) && ( $match[7] == '30' ) ) {
320 7
						$timeoffset += 0.5;
321
					}
322 7
				} else {
323
					$timeoffset = false;
324
				}
325
				return true;
326
			} else {
327
				return false;
328
			}
329
		}
330
	}
331
332
	/**
333
	 * Parse the given string to check if it encodes a "military time".
334
	 * If successful, the function sets the provided call-by-ref values to
335
	 * the respective numbers and returns true. Otherwise, it returns
336
	 * false and does not set any values.
337
	 * @param $string string input time representation, e.g. "134523"
338
	 * @param $hours integer between 0 and 24
339
	 * @param $minutes integer between 0 and 59
340
	 * @param $seconds integer between 0 and 59, or false if not given
341
	 * @return boolean stating if the parsing succeeded
342
	 */
343
	protected static function parseMilTimeString( $string, &$hours, &$minutes, &$seconds ) {
344
		if ( !preg_match( "/^([0-2][0-9])([0-5][0-9])([0-5][0-9])?$/u", $string, $match ) ) {
345
			return false;
346
		} else {
347
			$nhours = intval( $match[1] );
348
			$nminutes = $match[2] ? intval( $match[2] ) : false;
349
			$nseconds = ( ( count( $match ) > 3 ) && $match[3] ) ? intval( $match[3] ) : false;
350
			if ( ( $nhours < 25 ) && ( ( $nhours < 24 ) || ( $nminutes + $nseconds == 0 ) ) ) {
351
				$hours = $nhours;
352
				$minutes = $nminutes;
353
				$seconds = $nseconds;
354
				return true;
355
			} else {
356
				return false;
357
			}
358
		}
359
	}
360
361
	/**
362
	 * Parse the given string to check if it refers to the string name ot
363
	 * abbreviation of a month name. If yes, it is replaced by a normalized
364
	 * month name (placed in the call-by-ref parameter) and true is
365
	 * returned. Otherwise, false is returned and $monthname is not changed.
366
	 * @param $string string month name or abbreviation to parse
367 20
	 * @param $monthname string with standard 3-letter English month abbreviation
368
	 * @return boolean stating whether a month was found
369
	 */
370
	protected static function parseMonthString( $string, &$monthname ) {
371 20
		/**
372
		 * @var SMWLanguage $smwgContLang
373 20
		 */
374
		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...
375 20
376 19
		$monthnum = $smwgContLang->findMonth( $string ); // takes precedence over English month names!
377 19
378 3
		if ( $monthnum !== false ) {
379
			$monthnum -= 1;
380
		} else {
381 20
			$monthnum = array_search( $string, self::$m_months ); // check English names
382 19
		}
383 19
384 3
		if ( $monthnum !== false ) {
385
			$monthname = self::$m_monthsshort[$monthnum];
386
			return true;
387
		} elseif ( array_search( $string, self::$m_monthsshort ) !== false ) {
388 3
			$monthname = $string;
389
			return true;
390
		} else {
391
			return false;
392
		}
393
	}
394
395
	/**
396
	 * Validate and interpret the date components as retrieved when parsing
397
	 * a user input. The method takes care of guessing how a list of values
398
	 * such as "10 12 13" is to be interpreted using the current language
399
	 * settings. The result is stored in the call-by-ref parameter
400
	 * $date that uses keys 'y', 'm', 'd' and contains the respective
401
	 * numbers as values, or false if not specified. If errors occur, error
402
	 * messages are added to the objects list of errors, and false is
403
	 * returned. Otherwise, true is returned.
404
	 * @param $datecomponents array of strings that might belong to the specification of a date
405 25
	 * @param $date array set to result
406 25
	 * @return boolean stating if successful
407
	 */
408
	protected function interpretDateComponents( $datecomponents, &$date ) {
409
		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...
410
		// The following code segment creates a bit vector to encode
411
		// which role each digit of the entered date can take (day,
412
		// year, month). The vector starts with 1 and contains three
413
		// bits per date component, set ot true whenever this component
414
		// could be a month, a day, or a year (this is the order).
415
		// Examples:
416
		//   100 component could only be a month
417
		//   010 component could only be a day
418 25
		//   001 component could only be a year
419 25
		//   011 component could be a day or a year but no month etc.
420 25
		// For three components, we thus get a 10 digit bit vector.
421 25
		$datevector = 1;
422 25
		$propercomponents = array();
423 25
		$justfounddash = true; // avoid two dashes in a row, or dashes at the end
424 25
		$error = false;
425 3
		$numvalue = 0;
426
		foreach ( $datecomponents as $component ) {
427
			if ( $component == "-" ) {
428
				if ( $justfounddash ) {
429 3
					$error = true;
430 3
					break;
431 25
				}
432 25
				$justfounddash = true;
433 25
			} else {
434
				$justfounddash = false;
435 25
				$datevector = ( $datevector << 3 ) | $this->checkDateComponent( $component, $numvalue );
436 25
				$propercomponents[] = $numvalue;
437
			}
438
		}
439
		if ( ( $error ) || ( $justfounddash ) || ( count( $propercomponents ) == 0 ) || ( count( $propercomponents ) > 3 ) ) {
440
			$this->addError( wfMessage( 'smw_nodatetime', $this->m_wikivalue )->inContentLanguage()->text() );
441 25
			return false;
442 25
		}
443 25
		// Now use the bitvector to find the preferred interpretation of the date components:
444 25
		$dateformats = $smwgContLang->getDateFormats();
445 25
		$date = array( 'y' => false, 'm' => false, 'd' => false );
446 25
		foreach ( $dateformats[count( $propercomponents ) - 1] as $formatvector ) {
447 25
			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...
448 25
				$i = 0;
449 25
				foreach ( self::$m_formats[$formatvector] as $fieldname ) {
450 25
					$date[$fieldname] = $propercomponents[$i];
451
					$i += 1;
452 25
				}
453 25
				break;
454
			}
455
		}
456
		if ( $date['y'] === false ) { // no band matches the entered date
457 25
			$this->addError( wfMessage( 'smw_nodatetime', $this->m_wikivalue )->inContentLanguage()->text() );
458
			return false;
459
		}
460
		return true;
461
	}
462
463
	/**
464
	 * Initialise data from the provided intermediate results after
465
	 * parsing, assuming that a conventional date notation is used.
466
	 * If errors occur, error messages are added to the objects list of
467
	 * errors, and false is returned. Otherwise, true is returned.
468
	 * @param $datecomponents array of strings that might belong to the specification of a date
469
	 * @param $calendarmodesl string if model was set in input, otherwise false
470
	 * @param $era string '+' or '-' if provided, otherwise false
471
	 * @param $hours integer value between 0 and 24
472
	 * @param $minutes integer value between 0 and 59
473
	 * @param $seconds integer value between 0 and 59, or false if not given
474 25
	 * @param $timeoffset double value for time offset (e.g. 3.5), or false if not given
475 25
	 * @return boolean stating if successful
476 25
	 */
477
	protected function setDateFromParsedValues( $datecomponents, $calendarmodel, $era, $hours, $minutes, $seconds, $timeoffset ) {
478
		$date = false;
479
		if ( !$this->interpretDateComponents( $datecomponents, $date ) ) {
480
			return false;
481 25
		}
482 2
483 2
		// Handle BC: the year is negative.
484
		if ( ( $era == '-' ) && ( $date['y'] > 0 ) ) { // see class documentation on BC, "year 0", and ISO conformance ...
485 25
			$date['y'] = -( $date['y'] );
486 25
		}
487
		// Old Style is a special case of Julian calendar model where the change of the year was 25 March:
488
		if ( ( $calendarmodel == 'OS' ) &&
489
		     ( ( $date['m'] < 3 ) || ( ( $date['m'] == 3 ) && ( $date['d'] < 25 ) ) ) ) {
490 25
			$date['y']++;
491
		}
492 25
493 25
		$calmod = $this->getCalendarModel( $calendarmodel, $date['y'], $date['m'], $date['d'] );
494
		try {
495
			$this->m_dataitem = new SMWDITime( $calmod, $date['y'], $date['m'], $date['d'], $hours, $minutes, $seconds, $this->m_typeid );
0 ignored issues
show
Unused Code introduced by
The call to SMWDITime::__construct() 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...
496
		} catch ( SMWDataItemException $e ) {
497
			$this->addError( wfMessage( 'smw_nodatetime', $this->m_wikivalue )->inContentLanguage()->text() );
498
			return false;
499
		}
500
501 25
		// Having more than years or specifying a calendar model does
502 25
		// not make sense for prehistoric dates, and our calendar
503
		// conversion would not be reliable if JD numbers get too huge:
504
		if ( ( $date['y'] <= self::PREHISTORY ) &&
505
		     ( ( $this->m_dataitem->getPrecision() > SMWDITime::PREC_Y ) || ( $calendarmodel !== false ) ) ) {
506 25
			$this->addError( wfMessage( 'smw_nodatetime', $this->m_wikivalue )->inContentLanguage()->text() );
507 1
			return false;
508
		}
509 1
		if ( $timeoffset != 0 ) {
510 1
			$newjd = $this->m_dataitem->getJD() - $timeoffset / 24;
511
			try {
512
				$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...
513
			} catch ( SMWDataItemException $e ) {
514 1
				$this->addError( wfMessage( 'smw_nodatetime', $this->m_wikivalue )->inContentLanguage()->text() );
515 25
				return false;
516
			}
517
		}
518
		return true;
519
	}
520
521
	/**
522
	 * Check which roles a string component might play in a date, and
523
	 * set the call-by-ref parameter to the proper numerical
524
	 * representation. The component string has already been normalized to
525
	 * be either a plain number, a month name, or a plain number with "d"
526
	 * pre-pended. The result is a bit vector to indicate the possible
527
	 * interpretations.
528
	 * @param $component string
529 25
	 * @param $numvalue integer representing the components value
530 25
	 * @return integer that encodes a three-digit bit vector
531
	 */
532
	protected static function checkDateComponent( $component, &$numvalue ) {
533 25
		if ( $component === '' ) { // should not happen
534 25
			$numvalue = 0;
535 25
			return 0;
536 16
		} elseif ( is_numeric( $component ) ) {
537 25
			$numvalue = intval( $component );
538 16
			if ( ( $numvalue >= 1 ) && ( $numvalue <= 12 ) ) {
539
				return SMW_DAY_MONTH_YEAR; // can be a month, day or year
540 25
			} elseif ( ( $numvalue >= 1 ) && ( $numvalue <= 31 ) ) {
541
				return SMW_DAY_YEAR; // can be day or year
542 18
			} else { // number can just be a year
543
				return SMW_YEAR;
544
			}
545
		} elseif ( $component { 0 } == 'd' ) { // already marked as day
546
			if ( is_numeric( substr( $component, 1 ) ) ) {
547
				$numvalue = intval( substr( $component, 1 ) );
548
				return ( ( $numvalue >= 1 ) && ( $numvalue <= 31 ) ) ? SMW_DAY : 0;
549
			} else {
550 18
				return 0;
551 18
			}
552 18
		} else {
553 18
			$monthnum = array_search( $component, self::$m_monthsshort );
554
			if ( $monthnum !== false ) {
555
				$numvalue = $monthnum + 1;
556
				return SMW_MONTH;
557
			} else {
558
				return 0;
559
			}
560
		}
561
	}
562
563
	/**
564
	 * Determine the calender model under which an input should be
565
	 * interpreted based on the given input data.
566
	 * @param $presetmodel mixed string related to a user input calendar model (OS, Jl, Gr) or false
567
	 * @param $year integer of the given year (adjusted for BC(E), i.e. possibly negative)
568
	 * @param $month mixed integer of the month or false
569 25
	 * @param $day mixed integer of the day or false
570 25
	 * @return integer either SMWDITime::CM_GREGORIAN or SMWDITime::CM_JULIAN
571
	 */
572
	protected function getCalendarModel( $presetmodel, $year, $month, $day ) {
573 25
		if ( $presetmodel == 'OS' ) { // Old Style is a notational convention of Julian dates only
574 1
			$presetmodel = 'Jl';
575 25
		}
576 1
		if ( $presetmodel == 'Gr' ) {
577
			return SMWDITime::CM_GREGORIAN;
578 25
		} elseif ( $presetmodel == 'Jl' ) {
579 8
			return SMWDITime::CM_JULIAN;
580 25
		}
581 25
		if ( ( $year > 1582 ) ||
582 8
		     ( ( $year == 1582 ) && ( $month > 10 ) ) ||
583 8
		     ( ( $year == 1582 ) && ( $month == 10 ) && ( $day > 4 ) ) ) {
584
			return SMWDITime::CM_GREGORIAN;
585
		} elseif ( $year > self::PREHISTORY ) {
586
			return SMWDITime::CM_JULIAN;
587
		} else {
588 1
			// proleptic Julian years at some point deviate from the count of complete revolutions of the earth around the sun
589
			// hence assume that earlier date years are Gregorian (where this effect is very weak only)
590
			// This is mostly for internal use since we will not allow users to specify calendar models at this scale
591
			return SMWDITime::CM_GREGORIAN;
592
		}
593
	}
594
595
	/**
596
	 * @see SMWDataValue::loadDataItem
597 32
	 *
598 32
	 * {@inheritDoc}
599 32
	 */
600 32
	protected function loadDataItem( SMWDataItem $dataItem ) {
601 32
602
		if ( $dataItem->getDIType() !== SMWDataItem::TYPE_TIME ) {
603
			return false;
604
		}
605
606
		$this->m_dataitem = $dataItem;
607 17
		$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...
608 17
		$this->m_wikivalue = false;
609 16
610
		return true;
611
	}
612
613 1
	/**
614
	 * @see SMWDataValue::getShortWikiText
615
	 *
616
	 * {@inheritDoc}
617
	 */
618
	public function getShortWikiText( $linker = null ) {
619
		return $this->getDataValueFormatter()->format( DataValueFormatter::WIKI_SHORT, $linker );
620 3
	}
621 3
622
	/**
623
	 * @see SMWDataValue::getShortHTMLText
624
	 *
625
	 * {@inheritDoc}
626
	 */
627
	public function getShortHTMLText( $linker = null ) {
628
		return $this->getDataValueFormatter()->format( DataValueFormatter::HTML_SHORT, $linker );
629 3
	}
630 3
631
	/**
632
	 * @see SMWDataValue::getLongWikiText
633
	 *
634
	 * {@inheritDoc}
635
	 */
636
	public function getLongWikiText( $linker = null ) {
637
		return $this->getDataValueFormatter()->format( DataValueFormatter::WIKI_LONG, $linker );
638
	}
639
640
	/**
641
	 * @see SMWDataValue::getLongHTMLText
642
	 *
643
	 * {@inheritDoc}
644
	 */
645 25
	public function getLongHTMLText( $linker = null ) {
646 25
		return $this->getDataValueFormatter()->format( DataValueFormatter::HTML_LONG, $linker );
647 25
	}
648 25
649
	/**
650
	 * @todo The preferred caption may not be suitable as a wiki value (i.e. not parsable).
651
	 * @see SMWDataValue::getLongHTMLText
652
	 *
653
	 * {@inheritDoc}
654
	 */
655
	public function getWikiValue() {
656
		return $this->m_wikivalue ? $this->m_wikivalue : $this->getLongWikiText();
657
	}
658
659
	/**
660
	 * @see SMWDataValue::isNumeric
661
	 *
662 25
	 * {@inheritDoc}
663 25
	 */
664 25
	public function isNumeric() {
665 25
		return true;
666
	}
667
668
	/**
669
	 * Return the year number in the given calendar model, or false if
670
	 * this number is not available (typically when attempting to get
671
	 * prehistoric Julian calendar dates). As everywhere in this class,
672
	 * there is no year 0.
673
	 * @param $calendarmodel integer either SMWDITime::CM_GREGORIAN or SMWDITime::CM_JULIAN
674
	 * @return mixed typically a number but possibly false
675
	 */
676
	public function getYear( $calendarmodel = SMWDITime::CM_GREGORIAN ) {
677
		$di = $this->getDataItemForCalendarModel( $calendarmodel );
678
		if ( !is_null( $di ) ) {
679 25
			return $di->getYear();
680 25
		} else {
681 25
			return false;
682 25
		}
683
	}
684
685
	/**
686
	 * Return the month number in the given calendar model, or false if
687
	 * this number is not available (typically when attempting to get
688
	 * prehistoric Julian calendar dates).
689
	 * @param $calendarmodel integer either SMWDITime::CM_GREGORIAN or SMWDITime::CM_JULIAN
690
	 * @param $default value to return if month is not set at our level of precision
691
	 * @return mixed typically a number but possibly anything given as $default
692
	 */
693
	public function getMonth( $calendarmodel = SMWDITime::CM_GREGORIAN, $default = 1 ) {
694
		$di = $this->getDataItemForCalendarModel( $calendarmodel );
695
		if ( !is_null( $di ) ) {
696 30
			return ( $di->getPrecision() >= SMWDITime::PREC_YM ) ? $di->getMonth() : $default;
697 30
		} else {
698
			return false;
699
		}
700 30
	}
701 30
702 30
	/**
703
	 * Return the day number in the given calendar model, or false if this
704
	 * number is not available (typically when attempting to get
705
	 * prehistoric Julian calendar dates).
706
	 * @param $calendarmodel integer either SMWDITime::CM_GREGORIAN or SMWDITime::CM_JULIAN
707
	 * @param $default value to return if day is not set at our level of precision
708
	 * @return mixed typically a number but possibly anything given as $default
709
	 */
710
	public function getDay( $calendarmodel = SMWDITime::CM_GREGORIAN, $default = 1 ) {
711
		$di = $this->getDataItemForCalendarModel( $calendarmodel );
712
		if ( !is_null( $di ) ) {
713
			return ( $di->getPrecision() >= SMWDITime::PREC_YMD ) ? $di->getDay() : $default;
714
		} else {
715
			return false;
716
		}
717
	}
718
719
	/**
720
	 * @see TimeValueFormatter::getTimeStringFromDataItem
721
	 *
722
	 * @return
723
	 */
724
	public function getTimeString( $default = '00:00:00' ) {
725
		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...
726
	}
727
728 25
	/**
729 25
	 * @deprecated This method is now called getISO8601Date(). It will vanish before SMW 1.7.
730 25
	 */
731 25
	public function getXMLSchemaDate( $mindefault = true ) {
732 25
		return $this->getISO8601Date( $mindefault );
733 25
	}
734
735
	/**
736
	 * @see TimeValueFormatter::getISO8601DateFromDataItem
737 25
	 *
738
	 * @param $mindefault boolean determining whether values below the
739 25
	 * precision of our input should be completed with minimal or maximal
740 25
	 * conceivable values
741 25
	 *
742 25
	 * @return string
743
	 */
744
	public function getISO8601Date( $mindefault = true ) {
745
		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...
746
	}
747
748
	/**
749
	 * @see TimeValueFormatter::getMediaWikiDateFromDataItem
750
	 *
751 1
	 * @return string
752 1
	 */
753
	public function getMediaWikiDate() {
754 1
		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...
755 1
	}
756
757 1
	/**
758 1
	 * Get the current data in the specified calendar model. Conversion is
759
	 * not done for prehistoric dates (where it might lead to precision
760
	 * errors and produce results that are not meaningful). In this case,
761 1
	 * null might be returned if no data in the specified format is
762
	 * available.
763 1
	 * @param $calendarmodel integer one of SMWDITime::CM_GREGORIAN or SMWDITime::CM_JULIAN
764
	 * @return SMWDITime
765
	 */
766
	public function getDataItemForCalendarModel( $calendarmodel ) {
767 1
		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...
768 1
			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...
769
		} elseif ( $calendarmodel == SMWDITime::CM_GREGORIAN ) {
770 1
			if ( is_null( $this->m_dataitem_greg ) ) {
771 1
				$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...
772
			}
773
			return $this->m_dataitem_greg;
774 1
		} else {
775 1
			if ( is_null( $this->m_dataitem_jul ) ) {
776
				$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...
777
			}
778
			return $this->m_dataitem_jul;
779
		}
780
	}
781
782
}
783