1<?php 2 3namespace Wikimedia\ParamValidator\TypeDef; 4 5use InvalidArgumentException; 6use Wikimedia\Message\MessageValue; 7use Wikimedia\ParamValidator\Callbacks; 8use Wikimedia\ParamValidator\ParamValidator; 9use Wikimedia\ParamValidator\TypeDef; 10use Wikimedia\ParamValidator\ValidationException; 11use Wikimedia\Timestamp\ConvertibleTimestamp; 12use Wikimedia\Timestamp\TimestampException; 13 14/** 15 * Type definition for timestamp types 16 * 17 * This uses the wikimedia/timestamp library for parsing and formatting the 18 * timestamps. 19 * 20 * The result from validate() is a ConvertibleTimestamp by default, but this 21 * may be changed by both a constructor option and a PARAM constant. 22 * 23 * Failure codes: 24 * - 'badtimestamp': The timestamp is not valid. No data, but the 25 * TimestampException is available via Exception::getPrevious(). 26 * - 'unclearnowtimestamp': Non-fatal. The value is the empty string or "0". 27 * Use 'now' instead if you really want the current timestamp. No data. 28 * 29 * @since 1.34 30 * @unstable 31 */ 32class TimestampDef extends TypeDef { 33 34 /** 35 * (string|int) Timestamp format to return from validate() 36 * 37 * Values include: 38 * - 'ConvertibleTimestamp': A ConvertibleTimestamp object. 39 * - 'DateTime': A PHP DateTime object 40 * - One of ConvertibleTimestamp's TS_* constants. 41 * 42 * This does not affect the format returned by stringifyValue(). 43 */ 44 public const PARAM_TIMESTAMP_FORMAT = 'param-timestamp-format'; 45 46 /** @var string|int */ 47 protected $defaultFormat; 48 49 /** @var int */ 50 protected $stringifyFormat; 51 52 /** 53 * @param Callbacks $callbacks 54 * @param array $options Options: 55 * - defaultFormat: (string|int) Default for PARAM_TIMESTAMP_FORMAT. 56 * Default if not specified is 'ConvertibleTimestamp'. 57 * - stringifyFormat: (int) Format to use for stringifyValue(). 58 * Default is TS_ISO_8601. 59 */ 60 public function __construct( Callbacks $callbacks, array $options = [] ) { 61 parent::__construct( $callbacks ); 62 63 $this->defaultFormat = $options['defaultFormat'] ?? 'ConvertibleTimestamp'; 64 $this->stringifyFormat = $options['stringifyFormat'] ?? TS_ISO_8601; 65 66 // Check values by trying to convert 0 67 if ( $this->defaultFormat !== 'ConvertibleTimestamp' && $this->defaultFormat !== 'DateTime' && 68 ConvertibleTimestamp::convert( $this->defaultFormat, 0 ) === false 69 ) { 70 throw new InvalidArgumentException( 'Invalid value for $options[\'defaultFormat\']' ); 71 } 72 if ( ConvertibleTimestamp::convert( $this->stringifyFormat, 0 ) === false ) { 73 throw new InvalidArgumentException( 'Invalid value for $options[\'stringifyFormat\']' ); 74 } 75 } 76 77 public function validate( $name, $value, array $settings, array $options ) { 78 // Confusing synonyms for the current time accepted by ConvertibleTimestamp 79 if ( !$value ) { 80 $this->failure( 'unclearnowtimestamp', $name, $value, $settings, $options, false ); 81 $value = 'now'; 82 } 83 84 $format = $settings[self::PARAM_TIMESTAMP_FORMAT] ?? $this->defaultFormat; 85 86 try { 87 $timestampObj = new ConvertibleTimestamp( $value === 'now' ? false : $value ); 88 89 $timestamp = ( $format !== 'ConvertibleTimestamp' && $format !== 'DateTime' ) 90 ? $timestampObj->getTimestamp( $format ) 91 : null; 92 } catch ( TimestampException $ex ) { 93 // $this->failure() doesn't handle passing a previous exception 94 throw new ValidationException( 95 $this->failureMessage( 'badtimestamp' )->plaintextParams( $name, $value ), 96 $name, $value, $settings, $ex 97 ); 98 } 99 100 switch ( $format ) { 101 case 'ConvertibleTimestamp': 102 return $timestampObj; 103 104 case 'DateTime': 105 // Eew, no getter. 106 return $timestampObj->timestamp; 107 108 default: 109 return $timestamp; 110 } 111 } 112 113 public function checkSettings( string $name, $settings, array $options, array $ret ): array { 114 $ret = parent::checkSettings( $name, $settings, $options, $ret ); 115 116 $ret['allowedKeys'] = array_merge( $ret['allowedKeys'], [ 117 self::PARAM_TIMESTAMP_FORMAT, 118 ] ); 119 120 $f = $settings[self::PARAM_TIMESTAMP_FORMAT] ?? $this->defaultFormat; 121 if ( $f !== 'ConvertibleTimestamp' && $f !== 'DateTime' && 122 ConvertibleTimestamp::convert( $f, 0 ) === false 123 ) { 124 $ret['issues'][self::PARAM_TIMESTAMP_FORMAT] = 'Value for PARAM_TIMESTAMP_FORMAT is not valid'; 125 } 126 127 return $ret; 128 } 129 130 public function stringifyValue( $name, $value, array $settings, array $options ) { 131 if ( !$value instanceof ConvertibleTimestamp ) { 132 $value = new ConvertibleTimestamp( $value ); 133 } 134 return $value->getTimestamp( $this->stringifyFormat ); 135 } 136 137 public function getHelpInfo( $name, array $settings, array $options ) { 138 $info = parent::getHelpInfo( $name, $settings, $options ); 139 140 $info[ParamValidator::PARAM_TYPE] = MessageValue::new( 'paramvalidator-help-type-timestamp' ) 141 ->params( empty( $settings[ParamValidator::PARAM_ISMULTI] ) ? 1 : 2 ); 142 143 return $info; 144 } 145 146} 147