Extension:CommunityConfiguration/Technical documentation

Getting started

edit

Registering a new provider

edit

Community configuration providers are a formal description of how a set of configuration options should be handled by the extension. They consist of three main component declarations: store, validator and an optional form. The following example shows how to add a provider using extension.json attributes:

"attributes": {
		"CommunityConfiguration": {
			"Providers": {
				"MyProvider": {
					"store": {
						"type": "wikipage",
						"args": [
							"MediaWiki:ExampleConfig.json"
						]
					},
					"validator": {
						"type": "jsonschema",
						"args": [
							"CommunityConfigurationExample\\Schemas\\ExampleSchema"
						]
					},
					"type": "mw-config"
				}
			}
		}
	},

There are other mechanisms for registering a new configuration provider, through MW Configuration setting $wgCommunityConfigurationProviders or using the CommunityConfigurationProvider_initListHook hook, CommunityConfigurationProvider_initListHook.


Creating a JSON schema with PHP

edit

The extensions provides a JsonSchema interface to facilitate creating JSON schema document representations with a PHP class. This also restricts the JSON schema features available to those Community Configuration can handle. Below a minimal example of an schema class:

<?php

namespace CommunityConfigurationExample\Schemas;

use MediaWiki\Extension\CommunityConfiguration\Schema\JsonSchema;

class ExampleSchema implements JsonSchema {
	public const ExampleConfigOption = [
		self::TYPE => self::TYPE_STRING
	];
}

Public class properties are collected as top level keys of an object. At this stage, Community Configuration can only work with type object for the top level data structure. Hence there's no support for top level type array. The ExampleSchema class would be expanded to the following JSON schema in json format:

{
	"$schema": "https://json-schema.org/draft-04/schema#"
	"$id": "CommunityConfigurationExample/Schemas/ExampleSchema/1.0.0",
	"type": "object",
	"properties": {
		"ExampleConfigOption": {
			"type": "string"
		}
	},
	"additionalProperties": false
}

Creating a new config page

edit

The configuration data for a given set of options under a provider is stored as wiki json pages in the MediaWiki namespace. Whenever you create a new provider using the "type": "wikipage" for its store, you'll need to create the page on your target wiki and add a valid configuration for the first time. This action requires some privileged rights, currently, any of 'administrator', 'interface administrator' (see more in docs/CC-user-rights-tbd).

This process is tedious and not ideal, further improvements are being discussed in task T351517

Configuration providers

edit

Community configuration providers are a formal description of how a set of configuration options should be handled by the extension. Each provider forms a group of configuration options that are treated equally: loaded from the same place, presented on the same configuration form and similar. For each provider, several aspects need to be declared:

  • type, which defines what access pattern should be used to read the configuration from the provider,
  • store, which defines where the configuration data is stored and which MediaWiki users should have the permissions to modify it
  • validator, which defines the requirements configuration data needs to meet to be considered valid
  • options (optional), to modify certain aspects of what providers normally does (for example, the excludeFromUI option causes the provider to be not included in the dashboard at Special:CommunityConfiguration)

This is the full description of what a provider can have configured:

"attributes": {
	"CommunityConfiguration": {
		"Providers": {
			"providerId": {
				"type": "...",
				"store": {
                	"type": "...",
                    "args": [
                    	// ...
                    ]
				},
				"validator": {
                	"type": "...",
                    "args": [
                    	// ...
                    ]
                },
                "options": {
                	// ...
				}
            }
      	}
    }
}

Registering a provider

edit

Providers can be registered in three ways:

We recommend using the first solution if possible. If a provider needs to be registered conditionally, it can be unset from the CommunityConfigurationProvider_initListHook hook.

Provider types

edit

Provider types define the class used for communication among the store, the validator and the caller. Most importantly, the exact pattern to use for reading/storing configuration data from the provider depends on the type of the provider.

The following provider types are supported by CommunityConfiguration:

  • data: configuration data are treated as a blob; with this type, CommunityConfiguration only supports fetch/store operations at the blob level (ie. fetching/storing the configuration data in full)
  • mw-config: configuration data are interpreted as representing MediaWiki configuration options (with each option having its own top-level key in the JSON); with this type, CommunityConfiguration allows individual configuration options to be fetched individually

Extensions can define additional provider types via the attributes/CommunityConfiguration/ProviderClasses key in extension.json, see the declaration below. Any class used for a provider type needs to implement the IConfigurationProvider interface.

"attributes": {
	"CommunityConfiguration": {
		"ProviderClasses": {
			"GrowthSuggestedEdits": {
				// ObjectFactory specification
			}
      	}
    }
}

Stores

edit

Store defines where the configuration data is stored. The following stores are supported:

  • wikipage: MediaWiki wikipages are used as the storage backend, with the standard MediaWiki permission system used to guard access; configuration is stored to the page specified as the first argument
  • static: read-only provider that always returns its first argument as the configuration (this is mostly useful for testing purposes)

Validators

edit

Validators are used to ensure configuration meets defined criteria before being returned. CommunityConfiguration guarantees the validator passes on all reads and writes that happen via the CommunityConfiguration-defined interfaces. If the store allows modifications to happen outside of CommunityConfiguration[1] the validator may or may not run and its result may or may not block the write. CommunityConfiguration attempts to enforce rules imposed by the validator for known external writes[2], but this is done on best-efforts basis.

The following validators are supported:

  • jsonschema: configuration is validated against a defined PHP schema; for more details, see #PHP schemas[3]
  • noop: special validator to disable all validation on this provider

New validators can be defined via the $wgCommunityConfigurationValidators configuration variable, or added directly to CommunityConfiguration. All validators need to implement the IValidator interface.

Options

edit

The extension supports the following options for every provider:

  • excludeFromUI (defaults to false): if set to true, the provider is not displayed in the dashboard at Special:CommunityConfiguration,
  • editorCapability (defaults to generic-editor): value defines the editor capability that is responsible for rendering the editor users should see when going to Special:CommunityConfiguration/PROVIDER_ID; the default editor capability renders an editing form based on the PHP schema associated with the provider

PHP schemas

edit

CommunityConfiguration is designed to work with PHP files as the source for defining json-schema compliant documents. This approach is also used in other MediaWiki software components such as MainConfigSchema. The support for PHP schemas is built ad-hoc in the extension and has limitations compared to the full json-schema specification.

Limitations

edit
  • Limited support for json-schema version draft-04
  • Root schema must be of "type": "object"
  • No support for multiple types in any schema definition
  • additionalProperties is set to false for root properties

JSON-schema vocabulary

edit

The following json-schema keywords are supported:

Extended vocabulary

edit
  • control

Building the schema

edit

Internally, the CommunityConfiguration extension compiles each of the PHP files into an actual JSON schema (represented as PHP arrays). By default, the compiled schema is used in several other places:

  • the JSON validation library[4] to validate the configuration against the schema
  • the frontend, to generate the default editing form

This is the default behaviour, which can be adjusted as-needed. Validation library can be changed by setting a different validator. Editing form can be changed by setting a different editor capability.

MediaWiki Core is ultimately responsible for building the schema. CommunityConfiguration triggers the build via JsonSchemaBuilder::getRootSchema, which internally calls ReflectionSchemaSource::loadAsSchema from Core.

References

edit

A schema might choose to reference a different schema using the $ref keyword. References are identified via a PHP associative array of the following format: [ 'class' => CLASSNAME, 'field' => 'Constant' ]. CLASSNAME is a fully-qualified name of a PHP class, which must meet the requirements for a PHP schema. Constant is a name of a PHP constant within that class, which is the referenced subschema. It is currently not possible to reference a full class.

During the schema building process, ReflectionSchemaSource inlines all references. The result is a flat JSON schema (with all references replaced with their actual content). Using the built schema, it is impossible to determine whether references were used or not.

Note the behaviour of CommunityConfiguration is different than the standard JSON schema references. Normally, JSON Schema's $ref keyword is based on JSON Pointer URIs, and users access the subschema via the pointer URI. CommunityConfiguration inlines all references before making use of the schema, which is done to make processing the schema a bit easier.

Example

edit

With the following declarations:

class ExampleSchema extends JsonSchema {

	public const ExamplePageTitle = [
		self::REF => [
			'class' => MediaWikiDefinitions::class, 'field' => 'PageTitle'
		]
	];
}

class MediaWikiDefinitions extends JsonSchema {
	public const PageTitle = [
		self::TYPE => self::TYPE_STRING,
		self::DEFAULT => '',
	];
}

parsing ExampleSchema results in the following result:

[
    "$schema" => "https://json-schema.org/draft-04/schema#",
    "$id" => "CommunityConfigurationExample/Schemas/ExampleSchema",
    "additionalProperties" => false,
    "type" => "object",
    "properties" => [
      "ExamplePageTitle" => [
        "type" => "string",
        "default" => "",
      ],
    ],
]

If needed, the parsing can be triggered via a shell.php session using \MediaWiki\MediaWikiServices::getInstance()->get('CommunityConfiguration.ProviderFactory')->newProvider('ProviderName')->getValidator()->getSchemaBuilder()->getRootSchema() (replacing ProviderName with the name of the respective provider).

Versioning

edit

Schema definitions need to change occasionally due to new product or technical requirements. CommunityConfiguration provides a way for developers to migrate between schema versions. All schemas extending the JsonSchema class have a public constVERSION = '1.0.0' that can be override for bumping the schema version, eg: public constVERSION = '1.0.1'. Learn more about migrations in the section below.

Migrations

edit

In order for CommunityConfiguration to ease with a schema version migration a convenience maintenance script CommunityConfiguration/maintenance/migrateConfig.php is provided. Find the steps to prepare for a migration below:


Backup the current schema

edit

In order to access older schema versions, create a copy of the current schema version in some directory in your extension. The schema backup file name should contain its version number suffixed with underscore separators and the class name should also be updated to avoid duplicating the already declared. For example:

Schemas
├── Migrations
│   └── MySchema_1_0_0.php
└── MySchema.php

Link the older schema to the new one using: public const SCHEMA_NEXT_VERSION = '2.0.0';

Bump the schema version

edit

Once the new schema version definitions have been added, removed or changed, set a higher version number in the schema file, MySchema.php. Link the new schema version to the older one using SCHEMA_PREVIOUS_VERSION. For example:

class MySchema extends JsonSchema {

	public const VERSION = '2.0.0';
	public const SCHEMA_PREVIOUS_VERSION = '1.0.0';
	public const SCHEMA_CONVERTER = MySchemaConverter_2_0_0::class;
    
    // Add, remove or change definitions
    
    public const NewDefinition = [
        self::TYPE => self::TYPE_STRING,
		self::DEFAULT => '',
    ]
}

Create a converter

edit

In order to be able to transform some configuration data format from one schema version to another, the extension provides conventional methods in the ISchemaConverter interface. Create a PHP class implementing such interface and suffixing the new version number on its file name, for example:

Schemas
├── Converters
│   └── MySchemaConverter_2_0_0.php
├── Migrations
│   └── MySchema_1_0_0.php
└── MySchema.php

Implement interface methods upgradeFromOlder and downgradeFromNewer, for example:

class MySchemaConverter_2_0_0 implements ISchemaConverter {

	public function upgradeFromOlder( stdClass $data ): stdClass {
		$data->NewDefinition = '';
		return $data;
	}

	public function downgradeFromNewer( stdClass $data ): stdClass {
		unset( $data->NewDefinition );
		return $data;
	}
}

Run the migration

edit

In order to apply the migration to the existing config use migrateConfig maintenance script in the extension pointing to the provider using the target schema.

> php extensions/CommunityConfiguration/maintenance/migrateConfig.php MyProvider

Glossary

edit
  • schema: polysemic, it can refer to a full configuration specification in a PHP file, eg: MySchema.php or to the specification of a property at any level in the schema, eg: { "type": "string" }
  • subschema: used to disambiguate schema, refers to the specification of a property at any level in the schema as opposed to the full schema.
  • root schema: used to disambiguate schema, refers to the top level specification of a full schema. In CommunityConfiguration this is restricted to be "type": "object", see [Extension:CommunityConfiguration/Technical_documentation#tbd].
  • property: refers to the schema defined under the key "properties" in any schema.
  • root property: refers to the schema defined under the key "properties" in the root schema.

References

edit
  1. For example, this affects the wikipage store, where admins can go to the underlying wikipage and edit it directly.
  2. For the wikipage backend, CommunityConfiguration implements the JsonValidateSave hook to prevent invalid saves.
  3. In case you are wondering about the "JSON schema" vs "PHP schema" inconsistency, CommunityConfiguration internally compiles PHP schemas into a JSON schema, which is then used for validation.
  4. As of July 2024, jsonrainbow/json-schema is used.