41 protected $validators = [];
43 private static $ignorePatterns;
45 public function __construct(
46 private readonly
string $groupId,
48 if ( self::$ignorePatterns ===
null ) {
50 self::reloadIgnorePatterns();
55 protected static function foldValue(
string $value ): string {
56 return str_replace(
' ',
'_', strtolower( $value ) );
68 $this->validators = [];
69 foreach ( $validatorConfigs as $config ) {
70 $this->addValidator( $config );
79 $validatorId = $validatorConfig[
'id'] ?? null;
80 $className = $validatorConfig[
'class'] ??
null;
82 if ( $validatorId !==
null ) {
83 $validator = ValidatorFactory::get(
85 $validatorConfig[
'params'] ??
null
87 } elseif ( $className !==
null ) {
88 $validator = ValidatorFactory::loadInstance(
90 $validatorConfig[
'params'] ??
null
93 throw new InvalidArgumentException(
94 'Validator configuration does not specify the \'class\' or \'id\'.'
98 $isInsertable = $validatorConfig[
'insertable'] ??
false;
99 if ( $isInsertable && !$validator instanceof InsertablesSuggester ) {
100 $actualClassName = get_class( $validator );
101 throw new InvalidArgumentException(
102 "Insertable validator $actualClassName does not implement InsertablesSuggester interface."
106 $this->validators[] = [
107 'instance' => $validator,
108 'insertable' => $isInsertable,
109 'enforce' => $validatorConfig[
'enforce'] ??
false,
110 'include' => $validatorConfig[
'keymatch'] ?? $validatorConfig[
'include'] ??
false,
111 'exclude' => $validatorConfig[
'exclude'] ?? false
121 return array_column( $this->validators,
'instance' );
131 $insertableValidators = [];
132 foreach ( $this->validators as $validator ) {
133 if ( $validator[
'insertable'] ===
true ) {
134 $insertableValidators[] = $validator[
'instance'];
138 return $insertableValidators;
149 bool $ignoreWarnings =
false
154 foreach ( $this->validators as $validator ) {
155 $this->runValidation(
165 $errors = $this->filterValidations( $message->
key(), $errors, $code );
166 $warnings = $this->filterValidations( $message->
key(), $warnings, $code );
175 bool $ignoreWarnings =
false
180 foreach ( $this->validators as $validator ) {
181 $this->runValidation(
190 $errors = $this->filterValidations( $message->
key(), $errors, $code );
191 $warnings = $this->filterValidations( $message->
key(), $warnings, $code );
193 if ( $warnings->hasIssues() || $errors->hasIssues() ) {
198 return new ValidationResult( $errors, $warnings );
202 public static function reloadIgnorePatterns(): void {
203 $validationExclusionFile = Services::getInstance()->getConfigHelper()->getValidationExclusionFile();
205 if ( $validationExclusionFile ===
false ) {
206 self::$ignorePatterns = [];
210 $list = PHPVariableLoader::loadVariableFromPHPFile(
211 $validationExclusionFile,
212 'validationExclusionList'
214 $keys = [
'group',
'check',
'subcheck',
'code',
'message' ];
216 if ( $list && !is_array( $list ) ) {
217 throw new InvalidArgumentException(
218 "validationExclusionList defined in $validationExclusionFile must be an array"
222 foreach ( $list as $key => $pattern ) {
223 foreach ( $keys as $checkKey ) {
224 if ( !isset( $pattern[$checkKey] ) ) {
225 $list[$key][$checkKey] =
'#';
226 } elseif ( is_array( $pattern[$checkKey] ) ) {
227 $list[$key][$checkKey] =
229 [ self::class,
'foldValue' ],
233 $list[$key][$checkKey] = self::foldValue( $pattern[$checkKey] );
238 self::$ignorePatterns = $list;
242 private function filterValidations(
244 ValidationIssues $issues,
245 string $targetLanguage
246 ): ValidationIssues {
247 $filteredIssues = new ValidationIssues();
249 foreach ( $issues as $issue ) {
250 foreach ( self::$ignorePatterns as $pattern ) {
251 if ( $this->shouldIgnore( $messageKey, $issue, $this->groupId, $targetLanguage, $pattern ) ) {
255 $filteredIssues->add( $issue );
258 return $filteredIssues;
261 private function shouldIgnore(
263 ValidationIssue $issue,
264 string $messageGroupId,
265 string $targetLanguage,
268 return $this->matchesIgnorePattern( $pattern[
'group'], $messageGroupId )
269 && $this->matchesIgnorePattern( $pattern[
'check'], $issue->type() )
270 && $this->matchesIgnorePattern( $pattern[
'subcheck'], $issue->subType() )
271 && $this->matchesIgnorePattern( $pattern[
'message'], $messageKey )
272 && $this->matchesIgnorePattern( $pattern[
'code'], $targetLanguage );
282 private function matchesIgnorePattern( $pattern,
string $value ): bool {
283 if ( $pattern ===
'#' ) {
285 } elseif ( is_array( $pattern ) ) {
286 return in_array( strtolower( $value ), $pattern,
true );
288 return strtolower( $value ) === $pattern;
300 protected function doesKeyMatch(
string $key, array $keyMatches ): bool {
301 $normalizedKey = lcfirst( $key );
302 foreach ( $keyMatches as $match ) {
303 if ( is_string( $match ) ) {
304 if ( lcfirst( $match ) === $normalizedKey ) {
311 if ( !is_array( $match ) ) {
312 throw new InvalidArgumentException(
313 "Invalid key matcher configuration passed. Expected type: array or string. " .
314 "Received: " . get_debug_type( $match ) .
". match value: " . FormatJson::encode( $match )
318 $matcherType = $match[
'type'];
319 $pattern = $match[
'pattern'];
323 ( $matcherType ===
'regex' && preg_match( $pattern, $normalizedKey ) === 1 ) ||
324 ( $matcherType ===
'wildcard' && fnmatch( $pattern, $normalizedKey ) )
338 private function runValidation(
339 array $validatorData,
341 string $targetLanguage,
342 ValidationIssues $errors,
343 ValidationIssues $warnings,
348 $validator = $validatorData[
'instance'];
350 $definition = $message->definition();
353 $includedKeys = $validatorData[
'include'];
354 if ( $includedKeys !==
false && !$this->doesKeyMatch( $message->key(), $includedKeys ) ) {
358 $excludedKeys = $validatorData[
'exclude'];
359 if ( $excludedKeys !==
false && $this->doesKeyMatch( $message->key(), $excludedKeys ) ) {
363 if ( $validatorData[
'enforce'] ===
true ) {
364 $errors->merge( $validator->getIssues( $message, $targetLanguage ) );
365 } elseif ( !$ignoreWarnings ) {
366 $warnings->merge( $validator->getIssues( $message, $targetLanguage ) );
369 }
catch ( Exception $e ) {
370 throw new RuntimeException(
371 'An error occurred while validating message: ' . $message->key() .
'; group: ' .
372 $this->groupId .
"; validator: " . get_class( $validator ) .
"\n. Exception: $e"
return[ 'Translate:AggregateGroupManager'=> static function(MediaWikiServices $services):AggregateGroupManager { return new AggregateGroupManager($services->getTitleFactory(), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:AggregateGroupMessageGroupFactory'=> static function(MediaWikiServices $services):AggregateGroupMessageGroupFactory { return new AggregateGroupMessageGroupFactory($services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:ConfigHelper'=> static function():ConfigHelper { return new ConfigHelper();}, 'Translate:CsvTranslationImporter'=> static function(MediaWikiServices $services):CsvTranslationImporter { return new CsvTranslationImporter( $services->getWikiPageFactory());}, 'Translate:EntitySearch'=> static function(MediaWikiServices $services):EntitySearch { return new EntitySearch($services->getMainWANObjectCache(), $services->getCollationFactory() ->makeCollation( 'uca-default-u-kn'), MessageGroups::singleton(), $services->getNamespaceInfo(), $services->get( 'Translate:MessageIndex'), $services->getTitleParser(), $services->getTitleFormatter());}, 'Translate:ExternalMessageSourceStateComparator'=> static function(MediaWikiServices $services):ExternalMessageSourceStateComparator { return new ExternalMessageSourceStateComparator(new SimpleStringComparator(), $services->getRevisionLookup(), $services->getPageStore());}, 'Translate:ExternalMessageSourceStateImporter'=> static function(MediaWikiServices $services):ExternalMessageSourceStateImporter { return new ExternalMessageSourceStateImporter($services->get( 'Translate:GroupSynchronizationCache'), $services->getJobQueueGroup(), LoggerFactory::getInstance(LogNames::GROUP_SYNCHRONIZATION), $services->get( 'Translate:MessageIndex'), $services->getTitleFactory(), $services->get( 'Translate:MessageGroupSubscription'), new ServiceOptions(ExternalMessageSourceStateImporter::CONSTRUCTOR_OPTIONS, $services->getMainConfig()));}, 'Translate:FileBasedMessageGroupFactory'=> static function(MediaWikiServices $services):FileBasedMessageGroupFactory { return new FileBasedMessageGroupFactory(new MessageGroupConfigurationParser(), $services->getContentLanguageCode() ->toString(), new ServiceOptions(FileBasedMessageGroupFactory::SERVICE_OPTIONS, $services->getMainConfig()),);}, 'Translate:FileFormatFactory'=> static function(MediaWikiServices $services):FileFormatFactory { return new FileFormatFactory( $services->getObjectFactory());}, 'Translate:GroupSynchronizationCache'=> static function(MediaWikiServices $services):GroupSynchronizationCache { return new GroupSynchronizationCache( $services->get( 'Translate:PersistentCache'));}, 'Translate:HookDefinedMessageGroupFactory'=> static function(MediaWikiServices $services):HookDefinedMessageGroupFactory { return new HookDefinedMessageGroupFactory( $services->get( 'Translate:HookRunner'));}, 'Translate:HookRunner'=> static function(MediaWikiServices $services):HookRunner { return new HookRunner( $services->getHookContainer());}, 'Translate:MessageBundleDependencyPurger'=> static function(MediaWikiServices $services):MessageBundleDependencyPurger { return new MessageBundleDependencyPurger( $services->get( 'Translate:TranslatableBundleFactory'));}, 'Translate:MessageBundleMessageGroupFactory'=> static function(MediaWikiServices $services):MessageBundleMessageGroupFactory { return new MessageBundleMessageGroupFactory($services->get( 'Translate:MessageGroupMetadata'), new ServiceOptions(MessageBundleMessageGroupFactory::SERVICE_OPTIONS, $services->getMainConfig()),);}, 'Translate:MessageBundleStore'=> static function(MediaWikiServices $services):MessageBundleStore { return new MessageBundleStore($services->get( 'Translate:RevTagStore'), $services->getJobQueueGroup(), $services->getLanguageNameUtils(), $services->get( 'Translate:MessageIndex'), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:MessageBundleTranslationLoader'=> static function(MediaWikiServices $services):MessageBundleTranslationLoader { return new MessageBundleTranslationLoader( $services->getLanguageFallback());}, 'Translate:MessageGroupMetadata'=> static function(MediaWikiServices $services):MessageGroupMetadata { return new MessageGroupMetadata( $services->getConnectionProvider());}, 'Translate:MessageGroupReviewStore'=> static function(MediaWikiServices $services):MessageGroupReviewStore { return new MessageGroupReviewStore($services->getConnectionProvider(), $services->get( 'Translate:HookRunner'));}, 'Translate:MessageGroupStatsTableFactory'=> static function(MediaWikiServices $services):MessageGroupStatsTableFactory { return new MessageGroupStatsTableFactory($services->get( 'Translate:ProgressStatsTableFactory'), $services->getLinkRenderer(), $services->get( 'Translate:MessageGroupReviewStore'), $services->get( 'Translate:MessageGroupMetadata'), $services->getMainConfig() ->get( 'TranslateWorkflowStates') !==false);}, 'Translate:MessageGroupSubscription'=> static function(MediaWikiServices $services):MessageGroupSubscription { return new MessageGroupSubscription($services->get( 'Translate:MessageGroupSubscriptionStore'), $services->getJobQueueGroup(), $services->getUserIdentityLookup(), LoggerFactory::getInstance(LogNames::GROUP_SUBSCRIPTION), new ServiceOptions(MessageGroupSubscription::CONSTRUCTOR_OPTIONS, $services->getMainConfig()));}, 'Translate:MessageGroupSubscriptionHookHandler'=> static function(MediaWikiServices $services):?MessageGroupSubscriptionHookHandler { if(! $services->getExtensionRegistry() ->isLoaded( 'Echo')) { return null;} return new MessageGroupSubscriptionHookHandler($services->get( 'Translate:MessageGroupSubscription'), $services->getUserFactory());}, 'Translate:MessageGroupSubscriptionStore'=> static function(MediaWikiServices $services):MessageGroupSubscriptionStore { return new MessageGroupSubscriptionStore( $services->getConnectionProvider());}, 'Translate:MessageIndex'=> static function(MediaWikiServices $services):MessageIndex { $params=(array) $services->getMainConfig() ->get( 'TranslateMessageIndex');$class=array_shift( $params);$implementationMap=['HashMessageIndex'=> HashMessageIndex::class, 'CDBMessageIndex'=> CDBMessageIndex::class, 'DatabaseMessageIndex'=> DatabaseMessageIndex::class, 'hash'=> HashMessageIndex::class, 'cdb'=> CDBMessageIndex::class, 'database'=> DatabaseMessageIndex::class,];$messageIndexStoreClass=$implementationMap[$class] ?? $implementationMap['database'];return new MessageIndex(new $messageIndexStoreClass, $services->getMainWANObjectCache(), $services->getJobQueueGroup(), $services->get( 'Translate:HookRunner'), LoggerFactory::getInstance(LogNames::MAIN), $services->getMainObjectStash(), $services->getConnectionProvider(), new ServiceOptions(MessageIndex::SERVICE_OPTIONS, $services->getMainConfig()),);}, 'Translate:MessagePrefixStats'=> static function(MediaWikiServices $services):MessagePrefixStats { return new MessagePrefixStats( $services->getTitleParser());}, 'Translate:ParsingPlaceholderFactory'=> static function():ParsingPlaceholderFactory { return new ParsingPlaceholderFactory();}, 'Translate:PersistentCache'=> static function(MediaWikiServices $services):PersistentCache { return new PersistentDatabaseCache($services->getConnectionProvider(), $services->getJsonCodec());}, 'Translate:ProgressStatsTableFactory'=> static function(MediaWikiServices $services):ProgressStatsTableFactory { return new ProgressStatsTableFactory($services->getLinkRenderer(), $services->get( 'Translate:ConfigHelper'), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:RevTagStore'=> static function(MediaWikiServices $services):RevTagStore { return new RevTagStore( $services->getConnectionProvider());}, 'Translate:SubpageListBuilder'=> static function(MediaWikiServices $services):SubpageListBuilder { return new SubpageListBuilder($services->get( 'Translate:TranslatableBundleFactory'), $services->getLinkBatchFactory());}, 'Translate:TranslatableBundleDeleter'=> static function(MediaWikiServices $services):TranslatableBundleDeleter { return new TranslatableBundleDeleter($services->getMainObjectStash(), $services->getJobQueueGroup(), $services->get( 'Translate:SubpageListBuilder'), $services->get( 'Translate:TranslatableBundleFactory'));}, 'Translate:TranslatableBundleExporter'=> static function(MediaWikiServices $services):TranslatableBundleExporter { return new TranslatableBundleExporter($services->get( 'Translate:SubpageListBuilder'), $services->getWikiExporterFactory(), $services->getConnectionProvider());}, 'Translate:TranslatableBundleFactory'=> static function(MediaWikiServices $services):TranslatableBundleFactory { return new TranslatableBundleFactory($services->get( 'Translate:TranslatablePageStore'), $services->get( 'Translate:MessageBundleStore'));}, 'Translate:TranslatableBundleImporter'=> static function(MediaWikiServices $services):TranslatableBundleImporter { return new TranslatableBundleImporter($services->getWikiImporterFactory(), $services->get( 'Translate:TranslatablePageParser'), $services->getRevisionLookup(), $services->getNamespaceInfo(), $services->getTitleFactory(), $services->getFormatterFactory());}, 'Translate:TranslatableBundleMover'=> static function(MediaWikiServices $services):TranslatableBundleMover { return new TranslatableBundleMover($services->getMovePageFactory(), $services->getJobQueueGroup(), $services->getLinkBatchFactory(), $services->get( 'Translate:TranslatableBundleFactory'), $services->get( 'Translate:SubpageListBuilder'), $services->getConnectionProvider(), $services->getObjectCacheFactory(), $services->getMainConfig() ->get( 'TranslatePageMoveLimit'));}, 'Translate:TranslatableBundleStatusStore'=> static function(MediaWikiServices $services):TranslatableBundleStatusStore { return new TranslatableBundleStatusStore($services->getConnectionProvider() ->getPrimaryDatabase(), $services->getCollationFactory() ->makeCollation( 'uca-default-u-kn'), $services->getDBLoadBalancer() ->getMaintenanceConnectionRef(DB_PRIMARY));}, 'Translate:TranslatablePageMarker'=> static function(MediaWikiServices $services):TranslatablePageMarker { return new TranslatablePageMarker($services->getConnectionProvider(), $services->getJobQueueGroup(), $services->getLinkRenderer(), MessageGroups::singleton(), $services->get( 'Translate:MessageIndex'), $services->getTitleFormatter(), $services->getTitleParser(), $services->get( 'Translate:TranslatablePageParser'), $services->get( 'Translate:TranslatablePageStore'), $services->get( 'Translate:TranslatablePageStateStore'), $services->get( 'Translate:TranslationUnitStoreFactory'), $services->get( 'Translate:MessageGroupMetadata'), $services->getWikiPageFactory(), $services->get( 'Translate:TranslatablePageView'), $services->get( 'Translate:MessageGroupSubscription'), $services->getFormatterFactory(), $services->get( 'Translate:HookRunner'),);}, 'Translate:TranslatablePageMessageGroupFactory'=> static function(MediaWikiServices $services):TranslatablePageMessageGroupFactory { return new TranslatablePageMessageGroupFactory(new ServiceOptions(TranslatablePageMessageGroupFactory::SERVICE_OPTIONS, $services->getMainConfig()),);}, 'Translate:TranslatablePageParser'=> static function(MediaWikiServices $services):TranslatablePageParser { return new TranslatablePageParser($services->get( 'Translate:ParsingPlaceholderFactory'));}, 'Translate:TranslatablePageStateStore'=> static function(MediaWikiServices $services):TranslatablePageStateStore { return new TranslatablePageStateStore($services->get( 'Translate:PersistentCache'), $services->getPageStore());}, 'Translate:TranslatablePageStore'=> static function(MediaWikiServices $services):TranslatablePageStore { return new TranslatablePageStore($services->get( 'Translate:MessageIndex'), $services->getJobQueueGroup(), $services->get( 'Translate:RevTagStore'), $services->getConnectionProvider(), $services->get( 'Translate:TranslatableBundleStatusStore'), $services->get( 'Translate:TranslatablePageParser'), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:TranslatablePageView'=> static function(MediaWikiServices $services):TranslatablePageView { return new TranslatablePageView($services->getConnectionProvider(), $services->get( 'Translate:TranslatablePageStateStore'), new ServiceOptions(TranslatablePageView::SERVICE_OPTIONS, $services->getMainConfig()));}, 'Translate:TranslateSandbox'=> static function(MediaWikiServices $services):TranslateSandbox { return new TranslateSandbox($services->getUserFactory(), $services->getConnectionProvider(), $services->getPermissionManager(), $services->getAuthManager(), $services->getUserGroupManager(), $services->getActorStore(), $services->getUserOptionsManager(), $services->getJobQueueGroup(), $services->get( 'Translate:HookRunner'), new ServiceOptions(TranslateSandbox::CONSTRUCTOR_OPTIONS, $services->getMainConfig()));}, 'Translate:TranslationStashReader'=> static function(MediaWikiServices $services):TranslationStashReader { return new TranslationStashStorage( $services->getConnectionProvider() ->getPrimaryDatabase());}, 'Translate:TranslationStatsDataProvider'=> static function(MediaWikiServices $services):TranslationStatsDataProvider { return new TranslationStatsDataProvider(new ServiceOptions(TranslationStatsDataProvider::CONSTRUCTOR_OPTIONS, $services->getMainConfig()), $services->getObjectFactory(), $services->getConnectionProvider());}, 'Translate:TranslationUnitStoreFactory'=> static function(MediaWikiServices $services):TranslationUnitStoreFactory { return new TranslationUnitStoreFactory( $services->getDBLoadBalancer());}, 'Translate:TranslatorActivity'=> static function(MediaWikiServices $services):TranslatorActivity { $query=new TranslatorActivityQuery($services->getMainConfig(), $services->getConnectionProvider());return new TranslatorActivity($services->getMainObjectStash(), $query, $services->getJobQueueGroup());}, 'Translate:TtmServerFactory'=> static function(MediaWikiServices $services):TtmServerFactory { $config=$services->getMainConfig();$default=$config->get( 'TranslateTranslationDefaultService');if( $default===false) { $default=null;} return new TtmServerFactory( $config->get( 'TranslateTranslationServices'), $default);}, 'Translate:WorkflowStatesMessageGroupLoader'=> static function(MediaWikiServices $services):WorkflowStatesMessageGroupLoader { return new WorkflowStatesMessageGroupLoader(new ServiceOptions(WorkflowStatesMessageGroupLoader::CONSTRUCTOR_OPTIONS, $services->getMainConfig()),);},]
@phpcs-require-sorted-array