1<?php
2
3namespace MediaWiki\ParamValidator\TypeDef;
4
5use MediaWiki\Linker\LinkTarget;
6use TitleFactory;
7use Wikimedia\Message\MessageValue;
8use Wikimedia\ParamValidator\Callbacks;
9use Wikimedia\ParamValidator\ParamValidator;
10use Wikimedia\ParamValidator\TypeDef;
11
12/**
13 * Type definition for page titles.
14 *
15 * Failure codes:
16 * - 'badtitle': invalid title (e.g. containing disallowed characters). No data.
17 * - 'missingtitle': the page with this title does not exist (when PARAM_MUST_EXIST
18 *   was specified). No data.
19 *
20 * @since 1.36
21 */
22class TitleDef extends TypeDef {
23
24	/**
25	 * (bool) Whether the page with the given title needs to exist.
26	 *
27	 * Defaults to false.
28	 */
29	public const PARAM_MUST_EXIST = 'param-must-exist';
30
31	/**
32	 * (bool) Whether to return a LinkTarget.
33	 *
34	 * If false, the validated title is returned as a string (in getPrefixedText() format).
35	 * Default is false.
36	 *
37	 * Avoid setting true with PARAM_ISMULTI, as it may result in excessive DB
38	 * lookups. If you do combine them, consider setting low values for
39	 * PARAM_ISMULTI_LIMIT1 and PARAM_ISMULTI_LIMIT2 to mitigate it.
40	 */
41	public const PARAM_RETURN_OBJECT = 'param-return-object';
42
43	/** @var TitleFactory */
44	private $titleFactory;
45
46	/**
47	 * @param Callbacks $callbacks
48	 * @param TitleFactory $titleFactory
49	 */
50	public function __construct( Callbacks $callbacks, TitleFactory $titleFactory ) {
51		parent::__construct( $callbacks );
52		$this->titleFactory = $titleFactory;
53	}
54
55	/**
56	 * @inheritDoc
57	 * @return string|LinkTarget Depending on the PARAM_RETURN_OBJECT setting.
58	 */
59	public function validate( $name, $value, array $settings, array $options ) {
60		$mustExist = !empty( $settings[self::PARAM_MUST_EXIST] );
61		$returnObject = !empty( $settings[self::PARAM_RETURN_OBJECT] );
62
63		$title = $this->titleFactory->newFromText( $value );
64
65		if ( !$title ) {
66			$this->failure( 'badtitle', $name, $value, $settings, $options );
67		} elseif ( $mustExist && !$title->exists() ) {
68			$this->failure( 'missingtitle', $name, $value, $settings, $options );
69		}
70
71		if ( $returnObject ) {
72			return $title->getTitleValue();
73		} else {
74			return $title->getPrefixedText();
75		}
76	}
77
78	/** @inheritDoc */
79	public function stringifyValue( $name, $value, array $settings, array $options ) {
80		if ( $value instanceof LinkTarget ) {
81			return $this->titleFactory->newFromLinkTarget( $value )->getPrefixedText();
82		}
83		return parent::stringifyValue( $name, $value, $settings, $options );
84	}
85
86	/** @inheritDoc */
87	public function checkSettings( string $name, $settings, array $options, array $ret ): array {
88		$ret = parent::checkSettings( $name, $settings, $options, $ret );
89
90		$ret['allowedKeys'] = array_merge( $ret['allowedKeys'], [
91			self::PARAM_MUST_EXIST, self::PARAM_RETURN_OBJECT,
92		] );
93
94		if ( !is_bool( $settings[self::PARAM_MUST_EXIST] ?? false ) ) {
95			$ret['issues'][self::PARAM_MUST_EXIST] = 'PARAM_MUST_EXIST must be boolean, got '
96				. gettype( $settings[self::PARAM_MUST_EXIST] );
97		}
98
99		if ( !is_bool( $settings[self::PARAM_RETURN_OBJECT] ?? false ) ) {
100			$ret['issues'][self::PARAM_RETURN_OBJECT] = 'PARAM_RETURN_OBJECT must be boolean, got '
101				. gettype( $settings[self::PARAM_RETURN_OBJECT] );
102		}
103
104		if ( !empty( $settings[ParamValidator::PARAM_ISMULTI] ) &&
105			 !empty( $settings[self::PARAM_RETURN_OBJECT] ) &&
106			 (
107				 ( $settings[ParamValidator::PARAM_ISMULTI_LIMIT1] ?? 100 ) > 10 ||
108				 ( $settings[ParamValidator::PARAM_ISMULTI_LIMIT2] ?? 100 ) > 10
109			 )
110		) {
111			$ret['issues'][] = 'Multi-valued title-type parameters with PARAM_RETURN_OBJECT '
112				. 'should set low values (<= 10) for PARAM_ISMULTI_LIMIT1 and PARAM_ISMULTI_LIMIT2.'
113				. ' (Note that "<= 10" is arbitrary. If something hits this, we can investigate a real limit '
114				. 'once we have a real use case to look at.)';
115		}
116
117		return $ret;
118	}
119
120	/** @inheritDoc */
121	public function getParamInfo( $name, array $settings, array $options ) {
122		$info = parent::getParamInfo( $name, $settings, $options );
123
124		$info['mustExist'] = !empty( $settings[self::PARAM_MUST_EXIST] );
125
126		return $info;
127	}
128
129	/** @inheritDoc */
130	public function getHelpInfo( $name, array $settings, array $options ) {
131		$info = parent::getParamInfo( $name, $settings, $options );
132
133		$info[ParamValidator::PARAM_TYPE] = MessageValue::new( 'paramvalidator-help-type-title' );
134
135		$mustExist = !empty( $settings[self::PARAM_MUST_EXIST] );
136		$info[self::PARAM_MUST_EXIST] = $mustExist
137			? MessageValue::new( 'paramvalidator-help-type-title-must-exist' )
138			: MessageValue::new( 'paramvalidator-help-type-title-no-must-exist' );
139
140		return $info;
141	}
142
143}
144