PK       ! ¾g	  g	    TagsDef.phpnu Iw        <?php

namespace MediaWiki\ParamValidator\TypeDef;

use ChangeTags;
use MediaWiki\ChangeTags\ChangeTagsStore;
use MediaWiki\Message\Converter as MessageConverter;
use Wikimedia\Message\DataMessageValue;
use Wikimedia\ParamValidator\Callbacks;
use Wikimedia\ParamValidator\TypeDef\EnumDef;
use Wikimedia\ParamValidator\ValidationException;

/**
 * Type definition for tags type
 *
 * A tags type is an enum type for selecting MediaWiki change tags.
 *
 * Failure codes:
 *  - 'badtags': The value was not a valid set of tags. Data:
 *    - 'disallowedtags': The tags that were disallowed.
 *
 * @since 1.35
 */
class TagsDef extends EnumDef {

	private ChangeTagsStore $changeTagsStore;

	/** @var MessageConverter */
	private $messageConverter;

	public function __construct( Callbacks $callbacks, ChangeTagsStore $changeTagsStore ) {
		parent::__construct( $callbacks );
		$this->changeTagsStore = $changeTagsStore;
		$this->messageConverter = new MessageConverter();
	}

	public function validate( $name, $value, array $settings, array $options ) {
		$this->failIfNotString( $name, $value, $settings, $options );

		// Validate the full list of tags at once, because the caller will
		// *probably* stop at the first exception thrown.
		if ( isset( $options['values-list'] ) ) {
			$ret = $value;
			$tagsStatus = ChangeTags::canAddTagsAccompanyingChange( $options['values-list'] );
		} else {
			// The 'tags' type always returns an array.
			$ret = [ $value ];
			$tagsStatus = ChangeTags::canAddTagsAccompanyingChange( $ret );
		}

		if ( !$tagsStatus->isGood() ) {
			$msg = $this->messageConverter->convertMessage( $tagsStatus->getMessage() );
			$data = [];
			if ( $tagsStatus->value ) {
				// Specific tags are not allowed.
				$data['disallowedtags'] = $tagsStatus->value;
			// @codeCoverageIgnoreStart
			} else {
				// All are disallowed, I guess
				$data['disallowedtags'] = $settings['values-list'] ?? $ret;
			}
			// @codeCoverageIgnoreEnd

			// Only throw if $value is among the disallowed tags
			if ( in_array( $value, $data['disallowedtags'], true ) ) {
				throw new ValidationException(
					DataMessageValue::new( $msg->getKey(), $msg->getParams(), 'badtags', $data ),
					$name, $value, $settings
				);
			}
		}

		return $ret;
	}

	public function getEnumValues( $name, array $settings, array $options ) {
		return $this->changeTagsStore->listExplicitlyDefinedTags();
	}

}
PK       ! ׀S@?-  ?-    UserDef.phpnu Iw        <?php

namespace MediaWiki\ParamValidator\TypeDef;

use MediaWiki\Title\MalformedTitleException;
use MediaWiki\Title\TitleParser;
use MediaWiki\User\ExternalUserNames;
use MediaWiki\User\UserIdentity;
use MediaWiki\User\UserIdentityLookup;
use MediaWiki\User\UserIdentityValue;
use MediaWiki\User\UserNameUtils;
use MediaWiki\User\UserRigorOptions;
use Wikimedia\IPUtils;
use Wikimedia\Message\MessageValue;
use Wikimedia\ParamValidator\Callbacks;
use Wikimedia\ParamValidator\ParamValidator;
use Wikimedia\ParamValidator\TypeDef;

/**
 * Type definition for user types
 *
 * Failure codes:
 *  - 'baduser': The value was not a valid MediaWiki user. No data.
 *
 * @since 1.35
 */
class UserDef extends TypeDef {

	/**
	 * (string[]) Allowed types of user.
	 *
	 * One or more of the following values:
	 * - 'name': User names are allowed.
	 * - 'ip': IP ("anon") usernames are allowed.
	 * - 'temp': Temporary users are allowed.
	 * - 'cidr': IP ranges are allowed.
	 * - 'interwiki': Interwiki usernames are allowed.
	 * - 'id': Allow specifying user IDs, formatted like "#123".
	 *
	 * Default is `[ 'name', 'ip', 'temp', 'cidr', 'interwiki' ]`.
	 *
	 * Avoid combining 'id' with PARAM_ISMULTI, as it may result in excessive
	 * DB lookups. If you do combine them, consider setting low values for
	 * PARAM_ISMULTI_LIMIT1 and PARAM_ISMULTI_LIMIT2 to mitigate it.
	 */
	public const PARAM_ALLOWED_USER_TYPES = 'param-allowed-user-types';

	/**
	 * (bool) Whether to return a UserIdentity object.
	 *
	 * If false, the validated user name is returned as a string. Default is false.
	 *
	 * Avoid setting true with PARAM_ISMULTI, as it may result in excessive DB
	 * lookups. If you do combine them, consider setting low values for
	 * PARAM_ISMULTI_LIMIT1 and PARAM_ISMULTI_LIMIT2 to mitigate it.
	 */
	public const PARAM_RETURN_OBJECT = 'param-return-object';

	/** @var UserIdentityLookup */
	private $userIdentityLookup;

	/** @var TitleParser */
	private $titleParser;

	/** @var UserNameUtils */
	private $userNameUtils;

	/**
	 * @param Callbacks $callbacks
	 * @param UserIdentityLookup $userIdentityLookup
	 * @param TitleParser $titleParser
	 * @param UserNameUtils $userNameUtils
	 */
	public function __construct(
		Callbacks $callbacks,
		UserIdentityLookup $userIdentityLookup,
		TitleParser $titleParser,
		UserNameUtils $userNameUtils
	) {
		parent::__construct( $callbacks );
		$this->userIdentityLookup = $userIdentityLookup;
		$this->titleParser = $titleParser;
		$this->userNameUtils = $userNameUtils;
	}

	public function validate( $name, $value, array $settings, array $options ) {
		$this->failIfNotString( $name, $value, $settings, $options );

		[ $type, $user ] = $this->processUser( $value );

		if ( !$user || !in_array( $type, $settings[self::PARAM_ALLOWED_USER_TYPES], true ) ) {
			// Message used: paramvalidator-baduser
			$this->failure( 'baduser', $name, $value, $settings, $options );
		}

		return empty( $settings[self::PARAM_RETURN_OBJECT] ) ? $user->getName() : $user;
	}

	public function normalizeSettings( array $settings ) {
		if ( isset( $settings[self::PARAM_ALLOWED_USER_TYPES] ) ) {
			$settings[self::PARAM_ALLOWED_USER_TYPES] = array_values( array_intersect(
				[ 'name', 'ip', 'temp', 'cidr', 'interwiki', 'id' ],
				$settings[self::PARAM_ALLOWED_USER_TYPES]
			) );
		}
		if ( empty( $settings[self::PARAM_ALLOWED_USER_TYPES] ) ) {
			$settings[self::PARAM_ALLOWED_USER_TYPES] = [ 'name', 'ip', 'temp', 'cidr', 'interwiki' ];
		}

		return parent::normalizeSettings( $settings );
	}

	public function checkSettings( string $name, $settings, array $options, array $ret ): array {
		$ret = parent::checkSettings( $name, $settings, $options, $ret );

		$ret['allowedKeys'] = array_merge( $ret['allowedKeys'], [
			self::PARAM_ALLOWED_USER_TYPES, self::PARAM_RETURN_OBJECT,
		] );

		if ( !is_bool( $settings[self::PARAM_RETURN_OBJECT] ?? false ) ) {
			$ret['issues'][self::PARAM_RETURN_OBJECT] = 'PARAM_RETURN_OBJECT must be boolean, got '
				. gettype( $settings[self::PARAM_RETURN_OBJECT] );
		}

		$hasId = false;
		if ( isset( $settings[self::PARAM_ALLOWED_USER_TYPES] ) ) {
			if ( !is_array( $settings[self::PARAM_ALLOWED_USER_TYPES] ) ) {
				$ret['issues'][self::PARAM_ALLOWED_USER_TYPES] = 'PARAM_ALLOWED_USER_TYPES must be an array, '
					. 'got ' . gettype( $settings[self::PARAM_ALLOWED_USER_TYPES] );
			} elseif ( $settings[self::PARAM_ALLOWED_USER_TYPES] === [] ) {
				$ret['issues'][self::PARAM_ALLOWED_USER_TYPES] = 'PARAM_ALLOWED_USER_TYPES cannot be empty';
			} else {
				$bad = array_diff(
					$settings[self::PARAM_ALLOWED_USER_TYPES],
					[ 'name', 'ip', 'temp', 'cidr', 'interwiki', 'id' ]
				);
				if ( $bad ) {
					$ret['issues'][self::PARAM_ALLOWED_USER_TYPES] =
						'PARAM_ALLOWED_USER_TYPES contains invalid values: ' . implode( ', ', $bad );
				}

				$hasId = in_array( 'id', $settings[self::PARAM_ALLOWED_USER_TYPES], true );
			}
		}

		if ( !empty( $settings[ParamValidator::PARAM_ISMULTI] ) &&
			( $hasId || !empty( $settings[self::PARAM_RETURN_OBJECT] ) ) &&
			(
				( $settings[ParamValidator::PARAM_ISMULTI_LIMIT1] ?? 100 ) > 10 ||
				( $settings[ParamValidator::PARAM_ISMULTI_LIMIT2] ?? 100 ) > 10
			)
		) {
			$ret['issues'][] = 'Multi-valued user-type parameters with PARAM_RETURN_OBJECT or allowing IDs '
				. 'should set low values (<= 10) for PARAM_ISMULTI_LIMIT1 and PARAM_ISMULTI_LIMIT2.'
				. ' (Note that "<= 10" is arbitrary. If something hits this, we can investigate a real limit '
				. 'once we have a real use case to look at.)';
		}

		return $ret;
	}

	/**
	 * Process $value to a UserIdentity, if possible
	 * @param string $value
	 * @return array [ string $type, UserIdentity|null $user ]
	 * @phan-return array{0:string,1:UserIdentity|null}
	 */
	private function processUser( string $value ): array {
		// A user ID?
		if ( preg_match( '/^#(\d+)$/D', $value, $m ) ) {
			// This used to use the IP address of the current request if the
			// id was 0, to match the behavior of User objects, but was switched
			// to "Unknown user" because the former behavior is likely unexpected.
			// If the id corresponds to a user in the database, use that user, otherwise
			// return a UserIdentityValue with id 0 (regardless of the input id) and
			// the name "Unknown user"
			$userId = (int)$m[1];
			if ( $userId !== 0 ) {
				// Check the database.
				$userIdentity = $this->userIdentityLookup->getUserIdentityByUserId( $userId );
				if ( $userIdentity ) {
					return [ 'id', $userIdentity ];
				}
			}
			// Fall back to "Unknown user"
			return [
				'id',
				new UserIdentityValue( 0, "Unknown user" )
			];
		}

		// An interwiki username?
		if ( ExternalUserNames::isExternal( $value ) ) {
			$name = $this->userNameUtils->getCanonical( $value, UserRigorOptions::RIGOR_NONE );
			// UserIdentityValue has the username which includes the > separating the external
			// wiki database and the actual name, but is created for the *local* wiki, like
			// for User objects (local is the default, but we specify it anyway to show
			// that its intentional even though the username is for a different wiki)
			// NOTE: We deliberately use the raw $value instead of the canonical $name
			// to avoid converting the first character of the interwiki prefix to uppercase
			$user = $name !== false ? new UserIdentityValue( 0, $value, UserIdentityValue::LOCAL ) : null;
			return [ 'interwiki', $user ];
		}

		// A temp user?
		if ( $this->userNameUtils->isTemp( $value ) ) {
			$userIdentity = $this->userIdentityLookup->getUserIdentityByName( $value );
			return [ 'temp', $userIdentity ];
		}

		// A valid user name?
		// Match behavior of UserFactory::newFromName with RIGOR_VALID and User::getId()
		// we know that if there is a canonical form from UserNameUtils then this can't
		// look like an IP, and since we checked for external user names above it isn't
		// that either, so if this is a valid user name then we check the database for
		// the id, and if there is no user with this name the id is 0
		$canonicalName = $this->userNameUtils->getCanonical( $value, UserRigorOptions::RIGOR_VALID );
		if ( $canonicalName !== false ) {
			$userIdentity = $this->userIdentityLookup->getUserIdentityByName( $canonicalName );
			if ( $userIdentity ) {
				return [ 'name', $userIdentity ];
			}
			// Fall back to id 0
			return [
				'name',
				new UserIdentityValue( 0, $canonicalName )
			];
		}

		// (T232672) Reproduce the normalization applied in UserNameUtils::getCanonical() when
		// performing the checks below.
		if ( strpos( $value, '#' ) !== false ) {
			return [ '', null ];
		}

		try {
			$t = $this->titleParser->parseTitle( $value );
		} catch ( MalformedTitleException $_ ) {
			$t = null;
		}
		if ( !$t || $t->getNamespace() !== NS_USER || $t->isExternal() ) { // likely
			try {
				$t = $this->titleParser->parseTitle( "User:$value" );
			} catch ( MalformedTitleException $_ ) {
				$t = null;
			}
		}
		if ( !$t || $t->getNamespace() !== NS_USER || $t->isExternal() ) {
			// If it wasn't a valid User-namespace title, fail.
			return [ '', null ];
		}
		$value = $t->getText();

		// An IP?
		$b = IPUtils::RE_IP_BYTE;
		if ( IPUtils::isValid( $value ) ||
			// See comment for UserNameUtils::isIP. We don't just call that function
			// here because it also returns true for things like
			// 300.300.300.300 that are neither valid usernames nor valid IP
			// addresses.
			preg_match( "/^$b\.$b\.$b\.xxx$/D", $value )
		) {
			$name = IPUtils::sanitizeIP( $value );
			// We don't really need to use UserNameUtils::getCanonical() because for anonymous
			// users the only validation is that there is no `#` (which is already the case if its
			// a valid IP or matches the regex) and the only normalization is making the first
			// character uppercase (doesn't matter for numbers) and replacing underscores with
			// spaces (doesn't apply to IPs). But, better safe than sorry?
			$name = $this->userNameUtils->getCanonical( $name, UserRigorOptions::RIGOR_NONE );
			return [ 'ip', UserIdentityValue::newAnonymous( $name ) ];
		}

		// A range?
		if ( IPUtils::isValidRange( $value ) ) {
			$name = IPUtils::sanitizeIP( $value );
			// Per above, the UserNameUtils call isn't strictly needed, but doesn't hurt
			$name = $this->userNameUtils->getCanonical( $name, UserRigorOptions::RIGOR_NONE );
			return [ 'cidr', UserIdentityValue::newAnonymous( $name ) ];
		}

		// Fail.
		return [ '', null ];
	}

	public function getParamInfo( $name, array $settings, array $options ) {
		$info = parent::getParamInfo( $name, $settings, $options );

		$info['subtypes'] = $settings[self::PARAM_ALLOWED_USER_TYPES];

		return $info;
	}

	public function getHelpInfo( $name, array $settings, array $options ) {
		$info = parent::getParamInfo( $name, $settings, $options );

		$isMulti = !empty( $settings[ParamValidator::PARAM_ISMULTI] );

		$subtypes = [];
		foreach ( $settings[self::PARAM_ALLOWED_USER_TYPES] as $st ) {
			// Messages: paramvalidator-help-type-user-subtype-name,
			// paramvalidator-help-type-user-subtype-ip, paramvalidator-help-type-user-subtype-cidr,
			// paramvalidator-help-type-user-subtype-interwiki, paramvalidator-help-type-user-subtype-id,
			// paramvalidator-help-type-user-subtype-temp
			$subtypes[] = MessageValue::new( "paramvalidator-help-type-user-subtype-$st" );
		}
		$info[ParamValidator::PARAM_TYPE] = MessageValue::new( 'paramvalidator-help-type-user' )
			->params( $isMulti ? 2 : 1 )
			->textListParams( $subtypes )
			->numParams( count( $subtypes ) );

		return $info;
	}

}
PK       !  Ł      ArrayDef.phpnu Iw        <?php

namespace MediaWiki\ParamValidator\TypeDef;

use InvalidArgumentException;
use JsonSchema\Constraints\Constraint;
use JsonSchema\Validator;
use LogicException;
use Wikimedia\Message\DataMessageValue;
use Wikimedia\ParamValidator\TypeDef;

/**
 * Type definition for array structures, typically
 * used for validating JSON request bodies.
 *
 * Failure codes:
 *  - 'notarray': The value is not an array.
 *
 * @since 1.42
 */
class ArrayDef extends TypeDef {

	/**
	 * (object) Schema settings.
	 *
	 */
	public const PARAM_SCHEMA = 'param-schema';

	public function supportsArrays() {
		return true;
	}

	public function validate( $name, $value, array $settings, array $options ) {
		if ( !is_array( $value ) ) {
			// Message used: paramvalidator-notarray
			$this->failure( 'notarray', $name, $value, $settings, $options );
		}

		if ( isset( $settings[ self::PARAM_SCHEMA ] ) ) {
			$schema = $settings[ self::PARAM_SCHEMA ];

			if ( !isset( $schema[ 'type' ] ) ) {
				throw new InvalidArgumentException( "Schema type not set " );
			}

			$types = (array)$schema['type'];
			foreach ( $types as $type ) {
				// @todo:  start using JsonSchemaTrait::normalizeJsonSchema
				// so we can also support the "list" and "map" types
				if ( ( $type !== 'object' ) && ( $type !== 'array' ) ) {
					throw new LogicException( 'Invalid data type' );
				}
			}

			$validator = new Validator();
			$validator->validate(
				$value, $schema,
				Constraint::CHECK_MODE_TYPE_CAST | Constraint::CHECK_MODE_APPLY_DEFAULTS );
			if ( !$validator->isValid() ) {
				$errorCode = 'schema-validation-failed';
				foreach ( $validator->getErrors() as $error ) {
					$message = DataMessageValue::new(
						"paramvalidator-$errorCode",
						[ $error[ 'message' ] ],
						$errorCode,
						[ 'schema-validation-error' => $error ]
					);
					$this->failure( $message, $name, $value, $settings, $options );
				}
			}
		}
		return $value;
	}

	public function stringifyValue( $name, $value, array $settings, array $options ) {
		if ( !is_array( $value ) ) {
			return parent::stringifyValue( $name, $value, $settings, $options );
		}

		return json_encode( $value );
	}

	/**
	 * Returns a JSON Schema of type array, with the input schema for each array item.
	 *
	 * If $itemSchema is a string, it must be a valid JSON type, and all list entries will be
	 * validated to be of that type.
	 *
	 * If $itemSchema is an array, it must represent a valid schema, and all list entries will
	 * be validated to be of that schema. Nested lists are supported, as are lists of maps and
	 * more complicated schemas.
	 *
	 * Examples:
	 *  A list of integers, like [ 1, 2, 3 ]
	 *   ArrayDef::makeListSchema( "integer" )
	 *  A list of strings, where each value must be either "a" or "b", like [ "a", "a", "b", "b" ]
	 * *   ArrayDef::makeListSchema( [ 'enum' => [ 'a', 'b' ] ] )
	 *  A list of lists of strings, like [ [ "foo", 'bar" ], [ "baz", "qux" ] ]
	 *   ArrayDef::makeListSchema( ArrayDef::makeListSchema( "string" ) )
	 *
	 * @since 1.43
	 *
	 * @param array|string $itemSchema
	 *
	 * @return array
	 */
	public static function makeListSchema( $itemSchema ): array {
		return [
			'type' => 'array',
			'items' => static::normalizeSchema( $itemSchema )
		];
	}

	/**
	 * Returns a JSON Schema of type object, with the input schema for each array item.
	 *
	 * If $entrySchema is a string, it must be a valid JSON type, and all map entries will be
	 * validated to be of that type.
	 *
	 * If $entrySchema is an array, it must represent a valid schema, and all map entries will
	 * be validated to be of that schema. Nested maps are supported, as are maps of lists and
	 * more complicated schemas
	 *
	 * Examples:
	 *  A map of integers, like [ 'key1' => 1, 'key2' => 2, 'key3' => 3 ]
	 *   ArrayDef::makeMapSchema( "integer" )
	 *  A map of where each value must be 0 or 1, like [ 'key1' => 1, 'key2' => 1, 'key3' => 0 ]
	 *   ArrayDef::makeMapSchema( [ 'enum' => [ 0, 1 ] ] )
	 *  A map of maps, like [ 'k1' => [ 'k2' => 'a' ], 'k3' => [ 'k4' => 'b', 'k5' => 'c' ] ]
	 *   ArrayDef::makeMapSchema( ArrayDef::makeMapSchema( "string" ) )
	 *
	 * @since 1.43
	 *
	 * @param array|string $entrySchema
	 *
	 * @return array
	 */
	public static function makeMapSchema( $entrySchema ): array {
		return [
			'type' => 'object',
			'additionalProperties' => static::normalizeSchema( $entrySchema )
		];
	}

	/**
	 * Returns a JSON Schema of type object, with properties defined by the function params.
	 *
	 * Any input schemas must either be a string corresponding to valid JSON types, or valid
	 * schemas. Nested schemas are supported.
	 *
	 * Examples:
	 *  An object with required parameters "a" and "b", where "a" must be an integer and "b" can
	 *  have one of the values "x", "y", or "z", no optional parameters, and additional parameters
	 *  are disallowed:
	 *   ArrayDef::makeObjectSchema( [ 'a' => 'integer', 'b' => [ 'enum' => [ 'x', 'y', 'z' ] ] ] )
	 *  The same object, but parameter "b" is optional:
	 *   ArrayDef::makeObjectSchema( [ 'a' => 'integer' ], [ 'b' => [ 'enum' => [ 'x', 'y', 'z' ] ] ] )
	 *  An object with no required properties, an optional property "a" of type string, with
	 *  arbitrary additional properties allowed (effectively, an arbitrary object, but if "a"
	 *  is present, it must be a string):
	 *   ArrayDef::makeObjectSchema( [ ], [ 'a' => 'string' ], true )
	 * @since 1.43
	 *
	 * @param array $required properties that are required to be present, as name/schema pairs
	 * @param array $optional properties that may or may not be present, as name/schema pairs
	 * @param array|bool|string $additional schema additional properties must match, or false
	 * 	to disallow additional properties, or true to allow arbitrary additional properties
	 *
	 * @return array
	 */
	public static function makeObjectSchema(
		array $required = [], array $optional = [], $additional = false
	): array {
		$schema = [ 'type' => 'object' ];

		if ( $required ) {
			foreach ( $required as $propertyName => $propertySchema ) {
				$schema['required'][] = $propertyName;
				$schema['properties'][$propertyName] = static::normalizeSchema( $propertySchema );
			}
		}

		if ( $optional ) {
			foreach ( $optional as $propertyName => $propertySchema ) {
				if ( isset( $schema['properties'][$propertyName] ) ) {
					throw new InvalidArgumentException(
						"Property {$propertyName} defined as both required and optional"
					);
				}
				$schema['properties'][$propertyName] = static::normalizeSchema( $propertySchema );
			}
		}

		// The easiest way to allow all extra properties is to not specify additionalProperties
		if ( $additional !== true ) {
			// A value of false disallows additional properties
			if ( $additional === false ) {
				$schema['additionalProperties'] = false;
			} else {
				$schema['additionalProperties'] = static::normalizeSchema( $additional );
			}
		}

		return $schema;
	}

	/**
	 * Returns a representation of the input schema
	 *
	 * If $schema is a string, it must be a valid JSON type.
	 * If $schema is an array, it must represent a valid schema.
	 *
	 * @param array|string $schema
	 *
	 * @return array
	 */
	private static function normalizeSchema( $schema ): array {
		if ( is_array( $schema ) ) {
			return $schema;
		} else {
			return [ 'type' => $schema ];
		}
	}
}
PK       ! ㌼      TitleDef.phpnu Iw        <?php

namespace MediaWiki\ParamValidator\TypeDef;

use MediaWiki\Linker\LinkTarget;
use MediaWiki\Title\TitleFactory;
use Wikimedia\Message\MessageValue;
use Wikimedia\ParamValidator\Callbacks;
use Wikimedia\ParamValidator\ParamValidator;
use Wikimedia\ParamValidator\TypeDef;

/**
 * Type definition for page titles.
 *
 * Failure codes:
 * - 'badtitle': invalid title (e.g. containing disallowed characters). No data.
 * - 'missingtitle': the page with this title does not exist (when PARAM_MUST_EXIST
 *   was specified). No data.
 *
 * @since 1.36
 */
class TitleDef extends TypeDef {

	/**
	 * (bool) Whether the page with the given title needs to exist.
	 *
	 * Defaults to false.
	 */
	public const PARAM_MUST_EXIST = 'param-must-exist';

	/**
	 * (bool) Whether to return a LinkTarget.
	 *
	 * If false, the validated title is returned as a string (in getPrefixedText() format).
	 * Default is false.
	 *
	 * Avoid setting true with PARAM_ISMULTI, as it may result in excessive DB
	 * lookups. If you do combine them, consider setting low values for
	 * PARAM_ISMULTI_LIMIT1 and PARAM_ISMULTI_LIMIT2 to mitigate it.
	 */
	public const PARAM_RETURN_OBJECT = 'param-return-object';

	/** @var TitleFactory */
	private $titleFactory;

	/**
	 * @param Callbacks $callbacks
	 * @param TitleFactory $titleFactory
	 */
	public function __construct( Callbacks $callbacks, TitleFactory $titleFactory ) {
		parent::__construct( $callbacks );
		$this->titleFactory = $titleFactory;
	}

	/**
	 * @inheritDoc
	 * @return string|LinkTarget Depending on the PARAM_RETURN_OBJECT setting.
	 */
	public function validate( $name, $value, array $settings, array $options ) {
		$mustExist = !empty( $settings[self::PARAM_MUST_EXIST] );
		$returnObject = !empty( $settings[self::PARAM_RETURN_OBJECT] );

		$this->failIfNotString( $name, $value, $settings, $options );

		$title = $this->titleFactory->newFromText( $value );

		if ( !$title ) {
			// Message used: paramvalidator-badtitle
			$this->failure( 'badtitle', $name, $value, $settings, $options );
		} elseif ( $mustExist && !$title->exists() ) {
			// Message used: paramvalidator-missingtitle
			$this->failure( 'missingtitle', $name, $value, $settings, $options );
		}

		if ( $returnObject ) {
			return $title->getTitleValue();
		} else {
			return $title->getPrefixedText();
		}
	}

	/** @inheritDoc */
	public function stringifyValue( $name, $value, array $settings, array $options ) {
		if ( $value instanceof LinkTarget ) {
			return $this->titleFactory->newFromLinkTarget( $value )->getPrefixedText();
		}
		return parent::stringifyValue( $name, $value, $settings, $options );
	}

	/** @inheritDoc */
	public function checkSettings( string $name, $settings, array $options, array $ret ): array {
		$ret = parent::checkSettings( $name, $settings, $options, $ret );

		$ret['allowedKeys'] = array_merge( $ret['allowedKeys'], [
			self::PARAM_MUST_EXIST, self::PARAM_RETURN_OBJECT,
		] );

		if ( !is_bool( $settings[self::PARAM_MUST_EXIST] ?? false ) ) {
			$ret['issues'][self::PARAM_MUST_EXIST] = 'PARAM_MUST_EXIST must be boolean, got '
				. gettype( $settings[self::PARAM_MUST_EXIST] );
		}

		if ( !is_bool( $settings[self::PARAM_RETURN_OBJECT] ?? false ) ) {
			$ret['issues'][self::PARAM_RETURN_OBJECT] = 'PARAM_RETURN_OBJECT must be boolean, got '
				. gettype( $settings[self::PARAM_RETURN_OBJECT] );
		}

		if ( !empty( $settings[ParamValidator::PARAM_ISMULTI] ) &&
			!empty( $settings[self::PARAM_RETURN_OBJECT] ) &&
			(
				( $settings[ParamValidator::PARAM_ISMULTI_LIMIT1] ?? 100 ) > 10 ||
				( $settings[ParamValidator::PARAM_ISMULTI_LIMIT2] ?? 100 ) > 10
			)
		) {
			$ret['issues'][] = 'Multi-valued title-type parameters with PARAM_RETURN_OBJECT '
				. 'should set low values (<= 10) for PARAM_ISMULTI_LIMIT1 and PARAM_ISMULTI_LIMIT2.'
				. ' (Note that "<= 10" is arbitrary. If something hits this, we can investigate a real limit '
				. 'once we have a real use case to look at.)';
		}

		return $ret;
	}

	/** @inheritDoc */
	public function getParamInfo( $name, array $settings, array $options ) {
		$info = parent::getParamInfo( $name, $settings, $options );

		$info['mustExist'] = !empty( $settings[self::PARAM_MUST_EXIST] );

		return $info;
	}

	/** @inheritDoc */
	public function getHelpInfo( $name, array $settings, array $options ) {
		$info = parent::getParamInfo( $name, $settings, $options );

		$info[ParamValidator::PARAM_TYPE] = MessageValue::new( 'paramvalidator-help-type-title' );

		$mustExist = !empty( $settings[self::PARAM_MUST_EXIST] );
		$info[self::PARAM_MUST_EXIST] = $mustExist
			? MessageValue::new( 'paramvalidator-help-type-title-must-exist' )
			: MessageValue::new( 'paramvalidator-help-type-title-no-must-exist' );

		return $info;
	}

}
PK       ! 1Y      NamespaceDef.phpnu Iw        <?php

namespace MediaWiki\ParamValidator\TypeDef;

use MediaWiki\Api\ApiResult;
use MediaWiki\Title\NamespaceInfo;
use Wikimedia\ParamValidator\Callbacks;
use Wikimedia\ParamValidator\ParamValidator;
use Wikimedia\ParamValidator\TypeDef\EnumDef;

/**
 * Type definition for namespace types
 *
 * A namespace type is an enum type that accepts MediaWiki namespace IDs.
 *
 * @since 1.35
 */
class NamespaceDef extends EnumDef {

	/**
	 * (int[]) Additional namespace IDs to recognize.
	 *
	 * Generally this will be used to include NS_SPECIAL and/or NS_MEDIA.
	 */
	public const PARAM_EXTRA_NAMESPACES = 'param-extra-namespaces';

	/** @var NamespaceInfo */
	private $nsInfo;

	public function __construct( Callbacks $callbacks, NamespaceInfo $nsInfo ) {
		parent::__construct( $callbacks );
		$this->nsInfo = $nsInfo;
	}

	public function validate( $name, $value, array $settings, array $options ) {
		if ( !is_int( $value ) && preg_match( '/^[+-]?\d+$/D', $value ) ) {
			// Convert to int since that's what getEnumValues() returns.
			$value = (int)$value;
		}

		return parent::validate( $name, $value, $settings, $options );
	}

	public function getEnumValues( $name, array $settings, array $options ) {
		$namespaces = $this->nsInfo->getValidNamespaces();
		$extra = $settings[self::PARAM_EXTRA_NAMESPACES] ?? [];
		if ( is_array( $extra ) && $extra !== [] ) {
			$namespaces = array_merge( $namespaces, $extra );
		}
		sort( $namespaces );
		return $namespaces;
	}

	public function normalizeSettings( array $settings ) {
		// Force PARAM_ALL
		if ( !empty( $settings[ParamValidator::PARAM_ISMULTI] ) ) {
			$settings[ParamValidator::PARAM_ALL] = true;
		}
		return parent::normalizeSettings( $settings );
	}

	public function checkSettings( string $name, $settings, array $options, array $ret ): array {
		$ret = parent::checkSettings( $name, $settings, $options, $ret );

		$ret['allowedKeys'] = array_merge( $ret['allowedKeys'], [
			self::PARAM_EXTRA_NAMESPACES,
		] );

		if ( !empty( $settings[ParamValidator::PARAM_ISMULTI] ) &&
			( $settings[ParamValidator::PARAM_ALL] ?? true ) !== true &&
			!isset( $ret['issues'][ParamValidator::PARAM_ALL] )
		) {
			$ret['issues'][ParamValidator::PARAM_ALL] =
				'PARAM_ALL cannot be false or a string for namespace-type parameters';
		}

		$ns = $settings[self::PARAM_EXTRA_NAMESPACES] ?? [];
		if ( !is_array( $ns ) ) {
			$type = gettype( $ns );
		} elseif ( $ns === [] ) {
			$type = 'integer[]';
		} else {
			$types = array_unique( array_map( 'gettype', $ns ) );
			$type = implode( '|', $types );
			$type = count( $types ) > 1 ? "($type)[]" : "{$type}[]";
		}
		if ( $type !== 'integer[]' ) {
			$ret['issues'][self::PARAM_EXTRA_NAMESPACES] =
				"PARAM_EXTRA_NAMESPACES must be an integer[], got $type";
		}

		return $ret;
	}

	public function getParamInfo( $name, array $settings, array $options ) {
		$info = parent::getParamInfo( $name, $settings, $options );

		$info['type'] = 'namespace';
		$extra = $settings[self::PARAM_EXTRA_NAMESPACES] ?? [];
		if ( is_array( $extra ) && $extra !== [] ) {
			$info['extranamespaces'] = array_values( $extra );
			if ( isset( $options['module'] ) ) {
				// ApiResult metadata when used with the Action API.
				ApiResult::setIndexedTagName( $info['extranamespaces'], 'ns' );
			}
		}

		return $info;
	}

}
PK         ! ¾g	  g	                  TagsDef.phpnu Iw        PK         ! ׀S@?-  ?-              	  UserDef.phpnu Iw        PK         !  Ł                7  ArrayDef.phpnu Iw        PK         ! ㌼                T  TitleDef.phpnu Iw        PK         ! 1Y                f  NamespaceDef.phpnu Iw        PK      ~  t    