failure( 'badexpiry', $name, $value, $settings, $options ); } if ( $expiry < self::timestamp() ) { $this->failure( 'badexpiry-past', $name, $value, $settings, $options ); } $max = $settings[self::PARAM_MAX] ?? null; if ( self::expiryExceedsMax( $expiry, $max ) ) { $dontUseMax = empty( $settings[self::PARAM_USE_MAX] ); // Show warning that expiry exceeds the max, and that the max is being used instead. $msg = DataMessageValue::new( $dontUseMax ? 'paramvalidator-badexpiry-duration' : 'paramvalidator-badexpiry-duration-max', [ $max ] ); $this->failure( $msg, $name, $value, $settings, $options, $dontUseMax ); return self::normalizeExpiry( $max ); } return $expiry; } public function getHelpInfo( $name, array $settings, array $options ) { $info = parent::getHelpInfo( $name, $settings, $options ); $info[ParamValidator::PARAM_TYPE] = MessageValue::new( 'paramvalidator-help-type-expiry' ) ->params( empty( $settings[ParamValidator::PARAM_ISMULTI] ) ? 1 : 2 ) ->textListParams( // Should be quoted or monospace for presentation purposes, // but textListParams() doesn't do this. array_map( function ( $val ) { return "\"$val\""; }, self::INFINITY_VALS ) ); return $info; } /** * Normalize a user-inputted expiry. * @param string|null $expiry * @return string|false|null Timestamp as TS_ISO_8601, 'infinity', false if invalid, * or null when given null. */ public static function normalizeExpiry( ?string $expiry ) { if ( $expiry === null ) { return null; } if ( in_array( $expiry, self::INFINITY_VALS, true ) ) { return 'infinity'; } // ConvertibleTimestamp::time() used so we can fake the current time in ExpiryDefTest. $unix = strtotime( $expiry, ConvertibleTimestamp::time() ); if ( $unix === false ) { // Invalid expiry. return false; } return self::timestamp( $unix ); } /** * Convert to or get the current time in TS_ISO_8601. * @param bool|int $unix * @return string|false */ protected static function timestamp( $unix = false ) { // Don't pass 0, since ConvertibleTimestamp interprets that to mean the current timestamp. // '00' does the right thing. Without this check, calling normalizeExpiry() // with 1970-01-01T00:00:00Z incorrectly returns the current time. return ConvertibleTimestamp::convert( TS_ISO_8601, $unix === 0 ? '00' : $unix ); } /** * Given an expiry, test if the normalized value exceeds the given maximum. * * @param string|null $expiry * @param string|null $max Relative maximum duration acceptable by strtotime() (i.e. '6 months') * @return bool */ public static function expiryExceedsMax( ?string $expiry, ?string $max = null ): bool { // Normalize the max and given expiries to TS_ISO_8601 format. $expiry = self::normalizeExpiry( $expiry ); $max = self::normalizeExpiry( $max ); if ( !$max || !$expiry || $expiry === 'infinity' ) { // Either there is no max or given expiry was invalid. return false; } return $expiry > $max; } }