Skip to content

Commit

Permalink
Add config
Browse files Browse the repository at this point in the history
  • Loading branch information
malberts committed Nov 30, 2024
1 parent 30d9a7c commit 35137df
Show file tree
Hide file tree
Showing 28 changed files with 910 additions and 43 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
],
"require": {
"php": ">=8.1",
"composer/installers": "^2|^1.0.1"
"composer/installers": "^2|^1.0.1",
"opis/json-schema": "^2.3.0"
},
"require-dev": {
"phpstan/phpstan": "^2.0.1",
Expand Down
3 changes: 3 additions & 0 deletions example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"linkTargetSitelinkSiteId": "enwiki"
}
17 changes: 17 additions & 0 deletions extension.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,28 @@
},

"Hooks": {
"AlternateEdit": "ProfessionalWiki\\WikibaseFacetedSearch\\EntryPoints\\WikibaseFacetedSearchHooks::onAlternateEdit",
"BeforePageDisplay": "ProfessionalWiki\\WikibaseFacetedSearch\\EntryPoints\\WikibaseFacetedSearchHooks::onBeforePageDisplay",
"ContentHandlerDefaultModelFor": "ProfessionalWiki\\WikibaseFacetedSearch\\EntryPoints\\WikibaseFacetedSearchHooks::onContentHandlerDefaultModelFor",
"EditFilter": "ProfessionalWiki\\WikibaseFacetedSearch\\EntryPoints\\WikibaseFacetedSearchHooks::onEditFilter",
"EditFormPreloadText": "ProfessionalWiki\\WikibaseFacetedSearch\\EntryPoints\\WikibaseFacetedSearchHooks::onEditFormPreloadText",
"ShowSearchHitTitle": "ProfessionalWiki\\WikibaseFacetedSearch\\EntryPoints\\WikibaseFacetedSearchHooks::onShowSearchHitTitle",
"SpecialSearchResultsAppend": "ProfessionalWiki\\WikibaseFacetedSearch\\EntryPoints\\WikibaseFacetedSearchHooks::onSpecialSearchResultsAppend"
},

"config": {
"WikibaseFacetedSearchEnableInWikiConfig": {
"description": "If it should be possible to define configuration via MediaWiki:WikibaseFacetedSearch",
"value": true
},
"WikibaseFacetedSearch": {
"description": "Config in JSON format, following the JSON Schema at schema.json. Gets combined with config defined on MediaWiki:WikibaseFacetedSearch",
"value": ""
}
},

"SpecialPages": {
"WikibaseFacetedSearchConfig": "ProfessionalWiki\\WikibaseFacetedSearch\\EntryPoints\\SpecialWikibaseFacetedSearchConfig"
},

"ResourceFileModulePaths": {
Expand Down
10 changes: 9 additions & 1 deletion i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,13 @@
"Morne Alberts"
]
},
"wikibasefacetedsearch-description": "Enhances [[Special:Search]] with faceted search capabilities. Filter results based on instance type or statement values."
"wikibasefacetedsearch-description": "Enhances [[Special:Search]] with faceted search capabilities. Filter results based on instance type or statement values.",

"special-wikibase-faceted-search-config": "Faceted search config",

"wikibase-faceted-search-config-invalid": "Your changes were not saved. They contain the following {{PLURAL:$1|error|errors}}:",

"wikibase-faceted-search-config-help-documentation": "You can [[#Documentation|view the configuration documentation]] below the edit area.",
"wikibase-faceted-search-config-help": "Configuration documentation",
"wikibase-faceted-search-config-help-example": "Full example"
}
7 changes: 6 additions & 1 deletion i18n/qqq.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,10 @@
"Morne Alberts"
]
},
"wikibasefacetedsearch-description": "{{Desc|name=WikibaseFacetedSearch|url=https://github.com/ProfessionalWiki/WikibaseFacetedSearch}}"
"wikibasefacetedsearch-description": "{{Desc|name=WikibaseFacetedSearch|url=https://github.com/ProfessionalWiki/WikibaseFacetedSearch}}",
"special-wikibase-faceted-search-config": "This is the label on \"Special:WikibaseFacetedSearchConfig\" linking to \"MediaWiki:WikibaseFacetedSearch\".",
"wikibase-faceted-search-config-invalid": "Error message shown when attempting to save invalid configuration on MediaWiki:WikibaseFacetedSearch",
"wikibase-faceted-search-config-help-documentation": "Link to documentation displayed on \"MediaWiki:WikibaseFacetedSearch\"",
"wikibase-faceted-search-config-help": "Help text heading displayed on \"MediaWiki:WikibaseFacetedSearch\"",
"wikibase-faceted-search-config-help-example": "Help text heading displayed on \"MediaWiki:WikibaseFacetedSearch\"",
}
48 changes: 9 additions & 39 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
@@ -1,55 +1,25 @@
parameters:
ignoreErrors:
-
message: '#^Call to method addHTML\(\) on an unknown class OutputPage\.$#'
identifier: class.notFound
count: 1
path: src/EntryPoints/WikibaseFacetedSearchHooks.php

-
message: '#^Call to method addModuleStyles\(\) on an unknown class OutputPage\.$#'
identifier: class.notFound
count: 1
path: src/EntryPoints/WikibaseFacetedSearchHooks.php

-
message: '#^Call to method getNamespace\(\) on an unknown class Title\.$#'
identifier: class.notFound
count: 1
path: src/EntryPoints/WikibaseFacetedSearchHooks.php

-
message: '#^Call to static method element\(\) on an unknown class Html\.$#'
identifier: class.notFound
count: 1
path: src/EntryPoints/WikibaseFacetedSearchHooks.php

-
message: '#^Constant WB_NS_ITEM not found\.$#'
identifier: constant.notFound
count: 1
path: src/EntryPoints/WikibaseFacetedSearchHooks.php

-
message: '#^Parameter \$output of method ProfessionalWiki\\WikibaseFacetedSearch\\EntryPoints\\WikibaseFacetedSearchHooks\:\:onSpecialSearchResultsAppend\(\) has invalid type OutputPage\.$#'
identifier: class.notFound
message: '#^Parameter \#1 \$configArray of method ProfessionalWiki\\WikibaseFacetedSearch\\Persistence\\ConfigDeserializer\:\:newConfig\(\) expects array\<string, mixed\>, array\<mixed, mixed\> given\.$#'
identifier: argument.type
count: 1
path: src/EntryPoints/WikibaseFacetedSearchHooks.php
path: src/Persistence/ConfigDeserializer.php

-
message: '#^Parameter \$specialSearch of method ProfessionalWiki\\WikibaseFacetedSearch\\EntryPoints\\WikibaseFacetedSearchHooks\:\:onShowSearchHitTitle\(\) has invalid type SpecialSearch\.$#'
identifier: class.notFound
message: '#^Parameter \$linkTargetSitelinkSiteId of class ProfessionalWiki\\WikibaseFacetedSearch\\Application\\Config constructor expects string\|null, mixed given\.$#'
identifier: argument.type
count: 1
path: src/EntryPoints/WikibaseFacetedSearchHooks.php
path: src/Persistence/ConfigDeserializer.php

-
message: '#^Parameter \$specialSearch of method ProfessionalWiki\\WikibaseFacetedSearch\\EntryPoints\\WikibaseFacetedSearchHooks\:\:onSpecialSearchResultsAppend\(\) has invalid type SpecialSearch\.$#'
identifier: class.notFound
message: '#^Cannot cast mixed to string\.$#'
identifier: cast.string
count: 1
path: src/EntryPoints/WikibaseFacetedSearchHooks.php

-
message: '#^Parameter \$title of method ProfessionalWiki\\WikibaseFacetedSearch\\EntryPoints\\WikibaseFacetedSearchHooks\:\:onShowSearchHitTitle\(\) has invalid type Title\.$#'
identifier: class.notFound
count: 1
path: src/EntryPoints/WikibaseFacetedSearchHooks.php
path: src/WikibaseFacetedSearchExtension.php
2 changes: 2 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ parameters:
- ../../tests/phpunit
- ../../vendor
- ../../extensions/Wikibase
bootstrapFiles:
- ../../includes/AutoLoader.php
12 changes: 12 additions & 0 deletions schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"type": "object",
"additionalProperties": false,
"properties": {
"linkTargetSitelinkSiteId": {
"type": [
"string",
"null"
]
}
}
}
20 changes: 20 additions & 0 deletions src/Application/Config.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare( strict_types = 1 );

namespace ProfessionalWiki\WikibaseFacetedSearch\Application;

class Config {

public function __construct(
public readonly ?string $linkTargetSitelinkSiteId = null
) {
}

public function combine( Config $config ): self {
return new Config(
$config->linkTargetSitelinkSiteId ?? $this->linkTargetSitelinkSiteId
);
}

}
36 changes: 36 additions & 0 deletions src/EntryPoints/SpecialWikibaseFacetedSearchConfig.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare( strict_types = 1 );

namespace ProfessionalWiki\WikibaseFacetedSearch\EntryPoints;

use Message;
use ProfessionalWiki\WikibaseFacetedSearch\WikibaseFacetedSearchExtension;
use SpecialPage;
use Title;

class SpecialWikibaseFacetedSearchConfig extends SpecialPage {

public function __construct() {
parent::__construct( 'WikibaseFacetedSearchConfig' );
}

public function execute( $subPage ): void {
parent::execute( $subPage );

$title = Title::newFromText( WikibaseFacetedSearchExtension::CONFIG_PAGE_TITLE, NS_MEDIAWIKI );

if ( $title instanceof Title ) {
$this->getOutput()->redirect( $title->getFullURL() );
}
}

public function getGroupName(): string {
return 'wikibase';
}

public function getDescription(): Message {
return $this->msg( 'special-wikibase-faceted-search-config' );
}

}
73 changes: 72 additions & 1 deletion src/EntryPoints/WikibaseFacetedSearchHooks.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,16 @@

namespace ProfessionalWiki\WikibaseFacetedSearch\EntryPoints;

use EditPage;
use Html;
use HtmlArmor;
use OutputPage;
use ProfessionalWiki\WikibaseFacetedSearch\Persistence\ConfigJsonValidator;
use ProfessionalWiki\WikibaseFacetedSearch\Presentation\ConfigJsonErrorFormatter;
use ProfessionalWiki\WikibaseFacetedSearch\Presentation\ExportConfigEditPageTextBuilder;
use ProfessionalWiki\WikibaseFacetedSearch\WikibaseFacetedSearchExtension;
use SearchResult;
use Skin;
use SpecialSearch;
use Title;

Expand Down Expand Up @@ -41,8 +48,72 @@ public static function onSpecialSearchResultsAppend(
// TODO: generate facets from search term
$output->addModuleStyles( 'ext.wikibase.facetedsearch.styles' );
$output->addHTML(
\Html::element( 'div', [ 'class' => 'wikibase-faceted-search__facets' ] )
Html::element( 'div', [ 'class' => 'wikibase-faceted-search__facets' ] )
);
}

public static function onContentHandlerDefaultModelFor( Title $title, ?string &$model ): void {
if ( WikibaseFacetedSearchExtension::getInstance()->isConfigTitle( $title ) ) {
$model = CONTENT_MODEL_JSON;
}
}

public static function onEditFilter( EditPage $editPage, ?string $text, ?string $section, string &$error ): void {
$validator = ConfigJsonValidator::newInstance();

if ( is_string( $text )
&& WikibaseFacetedSearchExtension::getInstance()->isConfigTitle( $editPage->getTitle() )
&& !$validator->validate( $text )
) {
$errors = $validator->getErrors();
$error = Html::errorBox(
wfMessage( 'wikibase-faceted-search-config-invalid', count( $errors ) )->escaped() .
ConfigJsonErrorFormatter::format( $errors )
);
}
}

public static function onAlternateEdit( EditPage $editPage ): void {
if ( WikibaseFacetedSearchExtension::getInstance()->isConfigTitle( $editPage->getTitle() ) ) {
$editPage->suppressIntro = true;

$textBuilder = new ExportConfigEditPageTextBuilder( $editPage->getContext() );
$editPage->editFormTextTop = $textBuilder->createTopHtml();
$editPage->editFormTextBottom = $textBuilder->createBottomHtml();
}
}

public static function onEditFormPreloadText( string &$text, Title &$title ): void {
if ( WikibaseFacetedSearchExtension::getInstance()->isConfigTitle( $title ) ) {
$text = trim( '
{
"linkTargetSitelinkSiteId": null
}' );
}
}

public static function onBeforePageDisplay( OutputPage $out, Skin $skin ): void {
$title = $out->getTitle();

if ( $title === null ) {
return;
}

if ( WikibaseFacetedSearchExtension::getInstance()->isConfigTitle( $title ) ) {
$html = $out->getHTML();
$out->clearHTML();
$out->addHTML( self::getConfigPageHtml( $html ) );
}
}

private static function getConfigPageHtml( string $html ): string {
$jsonTablePosition = strpos( $html, '<table class="mw-json">' );

if ( !$jsonTablePosition ) {
return $html;
}

return substr( $html, $jsonTablePosition );
}

}
40 changes: 40 additions & 0 deletions src/Persistence/CombiningConfigLookup.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

declare( strict_types = 1 );

namespace ProfessionalWiki\WikibaseFacetedSearch\Persistence;

use ProfessionalWiki\WikibaseFacetedSearch\Application\Config;

/**
* Combines these config sources, with the latter overriding the former:
* * Defaults
* * $baseConfig (LocalSettings.php)
* * ConfigLookup (MediaWiki:WikibaseFacetedSearch)
*/
class CombiningConfigLookup implements ConfigLookup {

public function __construct(
private readonly string $baseConfig,
private readonly ConfigDeserializer $deserializer,
private readonly ConfigLookup $configLookup,
private readonly bool $enableWikiConfig
) {
}

public function getConfig(): Config {
$config = $this->createDefaultConfig()->combine(
$this->deserializer->deserialize( $this->baseConfig )
);

if ( !$this->enableWikiConfig ) {
return $config;
}

return $config->combine( $this->configLookup->getConfig() );
}

private function createDefaultConfig(): Config {
return new Config();
}
}
37 changes: 37 additions & 0 deletions src/Persistence/ConfigDeserializer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare( strict_types = 1 );

namespace ProfessionalWiki\WikibaseFacetedSearch\Persistence;

use ProfessionalWiki\WikibaseFacetedSearch\Application\Config;

class ConfigDeserializer {

public function __construct(
private readonly ConfigJsonValidator $validator
) {
}

public function deserialize( string $configJson ): Config {
if ( $this->validator->validate( $configJson ) ) {
$configArray = json_decode( $configJson, true );

if ( is_array( $configArray ) ) {
return $this->newConfig( $configArray );
}
}

return new Config();
}

/**
* @param array<string, mixed> $configArray
*/
private function newConfig( array $configArray ): Config {
return new Config(
linkTargetSitelinkSiteId: $configArray['linkTargetSitelinkSiteId'] ?? null,
);
}

}
Loading

0 comments on commit 35137df

Please sign in to comment.