From f32bcf673db3935010df9cae3969e9bc15dff73d Mon Sep 17 00:00:00 2001 From: Tobias Oetterer Date: Sat, 13 Jul 2024 08:37:40 +0200 Subject: [PATCH] Squashed commit of the following: commit e0d8f1288bec46d6bfd1468b0fa040f8eb363790 Author: Tobias Oetterer Date: Sat Jul 13 08:33:57 2024 +0200 finished overhaul * switched to suggested mw hook handling system * moved skin and module handling in new class BootstrapComponentsService * moved to MediaWikiServices for three "main classes" * ParserOutputHelper still crude construct. stays in for now commit 39d171692b8d7e7517e3a2ddad252d026a276e78 Author: Tobias Oetterer Date: Tue Jul 9 18:09:51 2024 +0200 starting refactoring * moved extension classes to proper MediaWiki namespace schema * started switching to ServiceWiring * started moving hook registration to mw default using extension.json * abandoning registration via onExtensionFunction * new HooksHandler class taking over (and incorporating DefaultHooksHandler) * obsoleting class HookRegisty * augmenting method signatures with type declarations --- BootstrapComponents.magic.php | 5 +- composer.json | 5 +- docs/release-notes.md | 18 + extension.json | 42 +- phpunit.xml.dist | 2 +- src/AbstractComponent.php | 7 +- src/ApplicationFactory.php | 53 +- src/AttributeManager.php | 2 +- src/BootstrapComponents.php | 78 +-- src/BootstrapComponentsService.php | 88 ++++ src/CarouselGallery.php | 24 +- src/ComponentLibrary.php | 185 ++++--- src/Components/Accordion.php | 4 +- src/Components/Alert.php | 4 +- src/Components/Badge.php | 4 +- src/Components/Button.php | 4 +- src/Components/Card.php | 10 +- src/Components/Carousel.php | 6 +- src/Components/Collapse.php | 8 +- src/Components/Jumbotron.php | 4 +- src/Components/Modal.php | 8 +- src/Components/Popover.php | 4 +- src/Components/Tooltip.php | 4 +- src/ComponentsDefinition.json | 22 +- src/HookRegistry.php | 364 ------------- src/Hooks/DefaultHooksHandler.php | 2 +- src/Hooks/OutputPageParserOutput.php | 90 ++-- src/Hooks/ParserFirstCallInit.php | 21 +- src/HooksHandler.php | 287 ++++++++++ src/ImageModal.php | 180 ++++--- src/ImageModalTrigger.php | 2 +- src/LuaLibrary.php | 53 +- src/ModalBuilder.php | 10 +- src/NestableInterface.php | 4 +- src/NestingController.php | 28 +- src/ParserOutputHelper.php | 112 +--- src/ParserRequest.php | 62 ++- src/ServiceWiring.php | 32 ++ tests/PhpUnitEnvironment.php | 2 +- tests/TestInfoScreen.php | 2 +- tests/autoloader.php | 4 +- tests/bootstrap.php | 4 +- tests/phpunit/ExecutionTimeTestListener.php | 2 +- tests/phpunit/Fixtures/TestConfig.php | 72 +++ .../Integration/I18nJsonFileIntegrityTest.php | 2 +- ...ComponentsJsonTestCaseScriptRunnerTest.php | 21 +- .../JSONScript/readmeContentsBuilder.php | 2 +- tests/phpunit/Unit/AbstractComponentTest.php | 13 +- tests/phpunit/Unit/ApplicationFactoryTest.php | 46 +- tests/phpunit/Unit/AttributeManagerTest.php | 12 +- .../Unit/BootstrapComponentServiceTest.php | 88 ++++ tests/phpunit/Unit/CarouselGalleryTest.php | 16 +- tests/phpunit/Unit/ComponentLibraryTest.php | 144 ++--- .../phpunit/Unit/Components/AccordionTest.php | 10 +- tests/phpunit/Unit/Components/AlertTest.php | 10 +- tests/phpunit/Unit/Components/BadgeTest.php | 10 +- tests/phpunit/Unit/Components/ButtonTest.php | 10 +- tests/phpunit/Unit/Components/CardTest.php | 12 +- .../phpunit/Unit/Components/CarouselTest.php | 10 +- .../phpunit/Unit/Components/CollapseTest.php | 10 +- .../phpunit/Unit/Components/JumbotronTest.php | 10 +- tests/phpunit/Unit/Components/ModalTest.php | 12 +- tests/phpunit/Unit/Components/PopoverTest.php | 10 +- tests/phpunit/Unit/Components/TooltipTest.php | 13 +- tests/phpunit/Unit/ComponentsTestBase.php | 40 +- tests/phpunit/Unit/HookRegistryTest.php | 496 ------------------ .../Unit/Hooks/DefaultHooksHandlerTest.php | 47 -- .../Unit/Hooks/OutputPageParserOutputTest.php | 60 +-- .../Unit/Hooks/ParserFirstCallInitTest.php | 26 +- tests/phpunit/Unit/HooksHandlerTest.php | 75 +++ tests/phpunit/Unit/ImageModalTest.php | 222 +++----- tests/phpunit/Unit/ImageModalTriggerTest.php | 14 +- tests/phpunit/Unit/LuaLibraryGetSkinTest.php | 6 +- tests/phpunit/Unit/LuaLibraryParseTest.php | 4 +- tests/phpunit/Unit/LuaLibraryTest.php | 10 +- tests/phpunit/Unit/LuaLibraryTestBase.php | 4 +- tests/phpunit/Unit/ModalBuilderTest.php | 16 +- tests/phpunit/Unit/NestingControllerTest.php | 20 +- tests/phpunit/Unit/ParserOutputHelperTest.php | 116 +--- tests/phpunit/Unit/ParserRequestTest.php | 26 +- tests/phpunit/Unit/ServiceWiringTest.php | 45 ++ 81 files changed, 1573 insertions(+), 2039 deletions(-) create mode 100644 src/BootstrapComponentsService.php delete mode 100644 src/HookRegistry.php create mode 100644 src/HooksHandler.php create mode 100644 src/ServiceWiring.php create mode 100644 tests/phpunit/Fixtures/TestConfig.php create mode 100644 tests/phpunit/Unit/BootstrapComponentServiceTest.php delete mode 100644 tests/phpunit/Unit/HookRegistryTest.php delete mode 100644 tests/phpunit/Unit/Hooks/DefaultHooksHandlerTest.php create mode 100644 tests/phpunit/Unit/HooksHandlerTest.php create mode 100644 tests/phpunit/Unit/ServiceWiringTest.php diff --git a/BootstrapComponents.magic.php b/BootstrapComponents.magic.php index 8e7a905..186112f 100644 --- a/BootstrapComponents.magic.php +++ b/BootstrapComponents.magic.php @@ -24,9 +24,12 @@ * @author Tobias Oetterer */ +use MediaWiki\MediaWikiServices; + $magicWords = []; -$componentLibrary = \BootstrapComponents\ApplicationFactory::getInstance()->getComponentLibrary(); +/** @var \MediaWiki\Extension\BootstrapComponents\ComponentLibrary $componentLibrary */ +$componentLibrary = MediaWikiServices::getInstance()->getService( 'BootstrapComponents.ComponentLibrary' ); // English $magicWords['en'] = $componentLibrary->compileMagicWordsArray(); diff --git a/composer.json b/composer.json index bdce4aa..e01ee5f 100644 --- a/composer.json +++ b/composer.json @@ -26,9 +26,8 @@ }, "require": { "php": ">=7.4", - "composer/installers": "^2|^1.0.1", - "mediawiki/mw-extension-registry-helper": "^1.0", - "mediawiki/bootstrap": "^5.0|^4.0" + "composer/installers": "^2", + "mediawiki/bootstrap": "^5.0" }, "require-dev": { "mediawiki/mediawiki-codesniffer": "43.0.0", diff --git a/docs/release-notes.md b/docs/release-notes.md index d47722c..0b776d5 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -4,6 +4,24 @@ Released on _not yet_ +Changes: +* dropping support of mediawiki < 1.39 +* removed auto-loading of Extension:Bootstrap. +* add translations via translatewiki +* +Fixes: +* fixed php 8 deprecation notice indicating missing logic encapsulation + in class AbstractComponent + +Refactoring: +* modified extension namespace to be compatible with mediawiki + extension namespace schema + + +### BootstrapComponents 5.1.2 + +Released on 12-May-2024 + Changes: * add css classes to restore the usual infobox layout * add more style control to card component diff --git a/extension.json b/extension.json index 027bc65..d31a64c 100644 --- a/extension.json +++ b/extension.json @@ -1,37 +1,60 @@ { "name": "BootstrapComponents", - "version": "5.1.2-dev", + "version": "5.2.0-dev", "author": [ "Tobias Oetterer" ], "url": "https://www.mediawiki.org/wiki/Extension:BootstrapComponents", "descriptionmsg": "bootstrap-components-desc", "license-name": "GPL-3.0-or-later", "type": "parserhook", "requires": { - "MediaWiki": ">= 1.35.0" + "MediaWiki": ">= 1.39.0" }, "ConfigRegistry": { "BootstrapComponents": "GlobalVarConfig::newInstance" }, "AutoloadNamespaces": { - "BootstrapComponents\\": "src/" + "MediaWiki\\Extension\\BootstrapComponents\\": "src/" }, "TestAutoloadNamespaces": { - "BootstrapComponents\\Tests\\": "tests/phpunit/" + "MediaWiki\\Extension\\BootstrapComponents\\Tests\\": "tests/phpunit/" }, + "ServiceWiringFiles": [ + "src/ServiceWiring.php" + ], "@note": "the extension's main hooks are registered in BootstrapComponents\\HookRegistry", "HookHandlers": { "BootStrapHooks": { - "class": "BootstrapComponents\\Hooks\\DefaultHooksHandler" + "class": "MediaWiki\\Extension\\BootstrapComponents\\HooksHandler", + "services": [ + "BootstrapComponentsService", + "BootstrapComponents.ComponentLibrary", + "BootstrapComponents.NestingController" + ] } }, "Hooks": { - "SetupAfterCache": { + "GalleryGetModes": { + "handler": "BootStrapHooks" + }, + "ImageBeforeProduceHTML": { + "handler": "BootStrapHooks" + }, + "InternalParseBeforeLinks": { + "handler": "BootStrapHooks" + }, + "OutputPageParserOutput": { "handler": "BootStrapHooks" }, "ParserAfterParse": { "handler": "BootStrapHooks" }, - "ScribuntoExternalLibraries": "BootstrapComponents\\Hooks\\DefaultHooksHandler::onScribuntoExternalLibraries" + "ParserFirstCallInit": { + "handler": "BootStrapHooks" + }, + "SetupAfterCache": { + "handler": "BootStrapHooks" + }, + "ScribuntoExternalLibraries": "MediaWiki\\Extension\\BootstrapComponents\\HooksHandler::onScribuntoExternalLibraries" }, "config": { "BootstrapComponentsDisableSourceLinkOnImageModal": { @@ -55,10 +78,7 @@ "public": true } }, - "callback": "BootstrapComponents\\BootstrapComponents::init", - "ExtensionFunctions": [ - "BootstrapComponents\\BootstrapComponents::onExtensionFunction" - ], + "callback": "MediaWiki\\Extension\\BootstrapComponents\\BootstrapComponents::init", "MessagesDirs": { "BootstrapComponents": [ "i18n" diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 0801e61..545ca25 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -14,7 +14,7 @@ verbose="true"> + class="Mediawiki\Extension\BootstrapComponents\Tests\ExecutionTimeTestListener"> true 10 diff --git a/src/AbstractComponent.php b/src/AbstractComponent.php index 717e3ea..e3ae555 100644 --- a/src/AbstractComponent.php +++ b/src/AbstractComponent.php @@ -24,7 +24,7 @@ * @author Tobias Oetterer */ -namespace BootstrapComponents; +namespace MediaWiki\Extension\BootstrapComponents; use \MWException; @@ -283,7 +283,7 @@ protected function prepareInput( $parserRequest, $fullParse = false ) { $parserRequest->getFrame() ); } - if ( $input && preg_match( '/\n\n/', $input ) || preg_match( '/

getParserOutputHelper()->addTrackingCategory(); - $this->getParserOutputHelper()->loadBootstrapModules(); $modules = $this->getComponentLibrary()->getModulesFor( $this->getComponentName(), - $this->getParserOutputHelper()->getNameOfActiveSkin() + 'vector' ); $this->getParserOutputHelper()->addModules( $modules ); } diff --git a/src/ApplicationFactory.php b/src/ApplicationFactory.php index 55b40ca..2048a69 100644 --- a/src/ApplicationFactory.php +++ b/src/ApplicationFactory.php @@ -24,7 +24,7 @@ * @author Tobias Oetterer */ -namespace BootstrapComponents; +namespace MediaWiki\Extension\BootstrapComponents; use MediaWiki\Logger\LoggerFactory; use MediaWiki\MediaWikiServices; @@ -88,27 +88,7 @@ public static function getInstance() { public function __construct() { $this->applicationStore = []; $this->applicationClassRegister = $this->getApplicationClassRegister(); - $this->getLogger()->info( 'ApplicationFactory was build!' ); - } - - /** - * @param null|bool|array $componentWhiteList - * - * @throws MWException cascading {@see \BootstrapComponents\ApplicationFactory::getApplication} - * - * @return ComponentLibrary - */ - public function getComponentLibrary( $componentWhiteList = null ) { - return $this->getApplication( 'ComponentLibrary', $componentWhiteList ); - } - - /** - * @throws MWException cascading {@see \BootstrapComponents\ApplicationFactory::getApplication} - * - * @return NestingController - */ - public function getNestingController() { - return $this->getApplication( 'NestingController' ); + $this->getLogger()->debug( 'ApplicationFactory was build!' ); } /** @@ -119,7 +99,7 @@ public function getNestingController() { * * @return AttributeManager */ - public function getNewAttributeManager( $validAttributes, $aliases ) { + public function getNewAttributeManager( array $validAttributes, array $aliases ): AttributeManager { return new AttributeManager( $validAttributes, $aliases ); } @@ -133,7 +113,9 @@ public function getNewAttributeManager( $validAttributes, $aliases ) { * * @return ModalBuilder */ - public function getNewModalBuilder( $id, $trigger, $content, $parserOutputHelper ) { + public function getNewModalBuilder( + string $id, string $trigger, string $content, ParserOutputHelper $parserOutputHelper + ): ModalBuilder { return new ModalBuilder( $id, $trigger, $content, $parserOutputHelper ); } @@ -148,7 +130,9 @@ public function getNewModalBuilder( $id, $trigger, $content, $parserOutputHelper * * @return ParserRequest */ - public function getNewParserRequest( $argumentsPassedByParser, $isParserFunction, $componentName = 'unknown' ) { + public function getNewParserRequest( + array $argumentsPassedByParser, bool $isParserFunction, string $componentName = 'unknown' + ): ParserRequest { return new ParserRequest( $argumentsPassedByParser, $isParserFunction, $componentName ); } @@ -178,7 +162,7 @@ public function getParserOutputHelper( $parser = null ) { * * @return bool */ - public function registerApplication( $name, $class ) { + public function registerApplication( string $name, string $class ): bool { $application = trim( $name ); $applicationClass = trim( $class ); if ( $application != '' && class_exists( $applicationClass ) ) { @@ -199,7 +183,7 @@ public function registerApplication( $name, $class ) { * * @return bool */ - public function resetLookup( $application = null ) { + public function resetLookup( ?string $application = null ): bool { if ( is_null( $application ) ) { $this->applicationStore = []; return true; @@ -218,9 +202,9 @@ public function resetLookup( $application = null ) { * * @throws MWException when no class is registered for the requested application or the creation of the object fails. * - * @return mixed|object + * @return object */ - protected function getApplication( $name ) { + protected function getApplication( $name ): object { if ( isset( $this->applicationStore[$name] ) ) { return $this->applicationStore[$name]; } @@ -233,9 +217,11 @@ protected function getApplication( $name ) { try { $objectReflection = new ReflectionClass( $this->applicationClassRegister[$name] ); } catch ( \ReflectionException $e ) { - throw new MWException( 'Error while trying to build application "' . $name . '" with class ' . $this->applicationClassRegister[$name] ); + throw new MWException( + 'Error while trying to build application "' . $name . '" with class ' . $this->applicationClassRegister[$name] + ); } - $this->getLogger()->info( 'ApplicationFactory successfully build application ' . $name ); + $this->getLogger()->debug( 'ApplicationFactory successfully build application ' . $name ); return $this->applicationStore[$name] = $objectReflection->newInstanceArgs( $args ); } @@ -244,9 +230,8 @@ protected function getApplication( $name ) { */ protected function getApplicationClassRegister() { return [ - 'ComponentLibrary' => 'BootstrapComponents\\ComponentLibrary', - 'NestingController' => 'BootstrapComponents\\NestingController', - 'ParserOutputHelper' => 'BootstrapComponents\\ParserOutputHelper', + 'NestingController' => 'MediaWiki\\Extension\\BootstrapComponents\\NestingController', + 'ParserOutputHelper' => 'MediaWiki\\Extension\\BootstrapComponents\\ParserOutputHelper', ]; } diff --git a/src/AttributeManager.php b/src/AttributeManager.php index 0fa95e7..72a3b0d 100644 --- a/src/AttributeManager.php +++ b/src/AttributeManager.php @@ -24,7 +24,7 @@ * @author Tobias Oetterer */ -namespace BootstrapComponents; +namespace MediaWiki\Extension\BootstrapComponents; /** * Class AttributeManager diff --git a/src/BootstrapComponents.php b/src/BootstrapComponents.php index 0b33bd8..628c5d8 100644 --- a/src/BootstrapComponents.php +++ b/src/BootstrapComponents.php @@ -41,11 +41,10 @@ * @ingroup BootstrapComponents */ -namespace BootstrapComponents; +namespace MediaWiki\Extension\BootstrapComponents; use ConfigException; use Exception; -use ExtensionRegistryHelper\ExtensionRegistryHelper; use MWException; /** @@ -57,15 +56,15 @@ */ class BootstrapComponents { - /** - * @var bool $hooksRegistered - */ - private static $hooksRegistered = false; + const EXTENSION_DATA_DEFERRED_CONTENT_KEY = 'bsc_deferredContent'; + + const EXTENSION_DATA_NO_IMAGE_MODAL = 'bsc_no_image_modal'; + /** * @var string $version */ - private static $version; + private static string $version; /** * Add this to extension.json's 'callable' entry. @@ -85,11 +84,16 @@ public static function init( array $info ) { self::$version = $info['version'] ?? 'UNKNOWN'; - # @todo remove emergency load of Extension "Bootstrap" on next mayor update! Keep the error handling! - # why? leaving this in forces users, who install BSC via git to augment their composer.local.json - # note: if this is to be removed, "mediawiki/mw-extension-registry-helper": "^1.0" can be removed from clj - // should be loaded manually in LocalSettings.php. If not, we give it a try! - ExtensionRegistryHelper::singleton()->loadExtensionRecursive( 'Bootstrap' ); + if ( !defined( 'MEDIAWIKI' ) ) { + echo 'This file is part of the Mediawiki extension BootstrapComponents, it is not a valid entry point.' . PHP_EOL; + throw new MWException( 'This file is part of a Mediawiki Extension, it is not a valid entry point.' ); + } + + if ( version_compare( $GLOBALS[ 'wgVersion' ], '1.39', 'lt' ) ) { + echo 'Error: Bootstrap Components ' + . 'is only compatible with MediaWiki 1.39 or above. You need to upgrade MediaWiki first.' . PHP_EOL; + throw new MWException( 'BootstrapComponents detected an incompatible MediaWiki version. Exiting.' ); + } // Using the constant as indicator to avoid class_exists if ( !\ExtensionRegistry::getInstance()->isLoaded('Bootstrap') ) { @@ -106,48 +110,6 @@ public static function init( array $info ) { } } - /** - * Since 1.37 extension.json's "callback" is too early for HookRegistry, since all the MW-Services and the - * ConfigFactory is not ready, yet. - * - * @throws ConfigException cascading {@see HookRegistry::__construct} and {@see HookRegistry::run} - * @throws MWException cascading {@see HookRegistry::__construct} - * - * @return void - */ - public static function onExtensionFunction() { - if ( self::doCheckRequirements() ) { - $hookRegistry = new HookRegistry(); - $hookRegistry->run(); - self::$hooksRegistered = true; - } - } - - /** - * @return bool - * @throws MWException - * - */ - public static function doCheckRequirements(): bool - { - if ( !defined( 'MEDIAWIKI' ) ) { - echo 'This file is part of the Mediawiki extension BootstrapComponents, it is not a valid entry point.' . PHP_EOL; - throw new MWException( 'This file is part of a Mediawiki Extension, it is not a valid entry point.' ); - } - - if ( version_compare( $GLOBALS[ 'wgVersion' ], '1.35', 'lt' ) ) { - echo 'Error: Bootstrap Components ' - . 'is only compatible with MediaWiki 1.35 or above. You need to upgrade MediaWiki first.' . PHP_EOL; - throw new MWException( 'BootstrapComponents detected an incompatible MediaWiki version. Exiting.' ); - } - - if ( self::hooksRegistrationDone() ) { - // Do not initialize more than once. - return false; - } - return true; - } - /** * Returns version number of Extension BootstrapComponents * @@ -157,12 +119,4 @@ public static function getVersion(): string { return self::$version ?: 'UNDEFINED'; } - - /** - * @return bool - */ - public static function hooksRegistrationDone(): bool - { - return self::$hooksRegistered; - } } diff --git a/src/BootstrapComponentsService.php b/src/BootstrapComponentsService.php new file mode 100644 index 0000000..069ca32 --- /dev/null +++ b/src/BootstrapComponentsService.php @@ -0,0 +1,88 @@ +mainConfig = $mainConfig; + $this->activeComponents = []; + } + + /** + * @return string + */ + public function getNameOfActiveSkin(): string { + if ( empty( $this->nameOfActiveSkin ) ) { + $this->nameOfActiveSkin = $this->detectSkinInUse( + defined( 'MW_NO_SESSION' ) + ); + } + return $this->nameOfActiveSkin; + } + + public function getActiveComponents(): array { + return array_keys( $this->activeComponents ); + } + + /** + * Registers a component type as active on the current page. Will be used to calculate the + * required modules for the page later on. + * + * @param string $componentName + * + * @return void + */ + public function registerComponentAsActive( string $componentName ): void { + $this->activeComponents[$componentName] = true; + } + + /** + * Returns true, if active skin is vector + * + * @return bool + */ + public function vectorSkinInUse(): bool { + return in_array( strtolower( $this->getNameOfActiveSkin() ), [ 'vector', 'vector-2022' ] ) ; + } + + /** + * @param bool $useConfig set this to true, if we can't rely on {@see \RequestContext::getSkin} + * + * @return string + */ + protected function detectSkinInUse( bool $useConfig = false ): string { + if ( !$useConfig ) { + $skin = RequestContext::getMain()->getSkin(); + if ( !empty( $skin ) && is_a( $skin, 'Skin' ) ) { + return $skin->getSkinName(); + } + } + return $this->mainConfig->has( 'DefaultSkin' ) + ? strtolower( $this->mainConfig->get( 'DefaultSkin' ) ) : 'unknown'; + } +} diff --git a/src/CarouselGallery.php b/src/CarouselGallery.php index e0d588b..dd05855 100644 --- a/src/CarouselGallery.php +++ b/src/CarouselGallery.php @@ -24,10 +24,10 @@ * @author Tobias Oetterer */ -namespace BootstrapComponents; +namespace MediaWiki\Extension\BootstrapComponents; -use BootstrapComponents\Components\Carousel; -use \ImageGalleryBase; +use MediaWiki\Extension\BootstrapComponents\Components\Carousel; +use ImageGalleryBase; use MediaWiki\MediaWikiServices; use Title; @@ -57,9 +57,9 @@ public function toHTML( $parserOutputHelper = null ) { } $carousel = new Carousel( - ApplicationFactory::getInstance()->getComponentLibrary(), + MediaWikiServices::getInstance()->getService( 'BootstrapComponents.ComponentLibrary' ), $parserOutputHelper, - ApplicationFactory::getInstance()->getNestingController() + MediaWikiServices::getInstance()->getService( 'BootstrapComponents.NestingController' ), ); $carouselParserRequest = $this->constructCarouselParserRequest(); @@ -125,9 +125,6 @@ private function buildImageStringFromData( $imageData ) { /** * Extracts the gallery images and builds image tags for every valid image. * - * TODO 1.34+ When this extension will support only MW 1.34+, the condition can be simplified - * since \MediaWiki\MediaWikiServices::getBadFileLookup() does exist in this case. - * * @param $imageList * @param \Parser $parser * @param bool $hideBadImages @@ -147,14 +144,9 @@ private function convertImages( $imageList, $parser = null, $hideBadImages = tru } continue; } elseif ( - $hideBadImages && ( - ( method_exists( '\MediaWiki\MediaWikiServices', 'getBadFileLookup' ) && - MediaWikiServices::getInstance() - ->getBadFileLookup() - ->isBadFile( $imageTitle->getDBkey(), $contextTitle ) - ) || - ( function_exists( 'wfIsBadImage' ) && wfIsBadImage( $imageTitle->getDBkey(), $contextTitle ) ) - ) ) { + $hideBadImages && MediaWikiServices::getInstance()->getBadFileLookup() + ->isBadFile( $imageTitle->getDBkey(), $contextTitle ) + ) { continue; } diff --git a/src/ComponentLibrary.php b/src/ComponentLibrary.php index 2472e0e..382326c 100644 --- a/src/ComponentLibrary.php +++ b/src/ComponentLibrary.php @@ -24,9 +24,9 @@ * @author Tobias Oetterer */ -namespace BootstrapComponents; +namespace MediaWiki\Extension\BootstrapComponents; -use \MediaWiki\MediaWikiServices; +use MediaWiki\MediaWikiServices; use \MWException; /** @@ -84,46 +84,35 @@ class ComponentLibrary { * * @var array $componentDataStore */ - private $componentDataStore; + private array $componentDataStore; /** * The list of registered/allowed bootstrap components, name or alias * * @var string[] $registeredComponents */ - private $registeredComponents; + private array $registeredComponents; /** * @param string $componentName * * @return string */ - public static function compileParserHookStringFor( $componentName ) { + public static function compileParserHookStringFor( string $componentName ): string { return self::PARSER_HOOK_PREFIX . strtolower( $componentName ); } /** * ComponentLibrary constructor. * - * Do not instantiate directly, but use {@see ApplicationFactory::getComponentLibrary} instead. - * - * @param bool|array $componentWhiteList (see {@see \BootstrapComponents\ComponentLibrary::$componentWhiteList}) - * - * @throws \ConfigException cascading {@see \ConfigFactory::makeConfig} and - * @see ApplicationFactory::getComponentLibrary + * Do not instantiate directly, but use + * MediaWikiService::getInstance->get('BootstrapComponents.ComponentLibrary') instead. * + * @param bool|array $componentWhiteList (see {@see ComponentLibrary::$componentWhiteList}) */ - public function __construct( $componentWhiteList = null ) { - - if ( is_null( $componentWhiteList ) ) { - $myConfig = MediaWikiServices::getInstance()->getConfigFactory()->makeConfig( 'BootstrapComponents' ); + public function __construct( bool|array $componentWhiteList = true ) { - $componentWhiteList = $myConfig->has( 'BootstrapComponentsWhitelist' ) - ? $myConfig->get( 'BootstrapComponentsWhitelist' ) - : true; - } - $componentWhiteList = $this->mangle( $componentWhiteList ); - $this->registeredComponents = $this->registerComponents( $componentWhiteList ); + $this->registeredComponents = $this->processWhitelist( $componentWhiteList ); } /** @@ -131,7 +120,7 @@ public function __construct( $componentWhiteList = null ) { * * @return array */ - public function compileMagicWordsArray() { + public function compileMagicWordsArray(): array { $magicWords = []; foreach ( $this->getRegisteredComponents() as $componentName ) { if ( $this->isParserFunction( $componentName ) ) { @@ -142,17 +131,6 @@ public function compileMagicWordsArray() { return $magicWords; } - /** - * Checks, if component $componentIdentifier is registered - * - * @param string $componentIdentifier - * - * @return bool - */ - public function isRegistered( $componentIdentifier ) { - return in_array( $componentIdentifier, $this->registeredComponents, true ); - } - /** * Returns the defined/allowed attribute aliases for component/alias $componentIdentifier. * @@ -161,7 +139,7 @@ public function isRegistered( $componentIdentifier ) { * @return array * @throws MWException provided component is not known */ - public function getAliasesFor( $componentIdentifier ) { + public function getAliasesFor( string $componentIdentifier ): array { return $this->accessComponentDataStore( $componentIdentifier, 'aliases' ); } @@ -173,7 +151,7 @@ public function getAliasesFor( $componentIdentifier ) { * @return array * @throws MWException provided component is not known */ - public function getAttributesFor( $componentIdentifier ) { + public function getAttributesFor( string $componentIdentifier ): array { return $this->accessComponentDataStore( $componentIdentifier, 'attributes' ); } @@ -183,8 +161,9 @@ public function getAttributesFor( $componentIdentifier ) { * @param string $componentIdentifier * * @return string + * @throws MWException provided component is not known */ - public function getClassFor( $componentIdentifier ) { + public function getClassFor( string $componentIdentifier ): string { return $this->accessComponentDataStore( $componentIdentifier, 'class' ); } @@ -194,11 +173,12 @@ public function getClassFor( $componentIdentifier ) { * @param string $componentIdentifier * * @return string - * @see \BootstrapComponents\ComponentLibrary::HANDLER_TYPE_PARSER_FUNCTION, - * \BootstrapComponents\ComponentLibrary::HANDLER_TYPE_TAG_EXTENSION + * + * @see ComponentLibrary::HANDLER_TYPE_PARSER_FUNCTION + * @see ComponentLibrary::HANDLER_TYPE_TAG_EXTENSION * */ - public function getHandlerTypeFor( $componentIdentifier ) { + public function getHandlerTypeFor( string $componentIdentifier ): string { try { return $this->accessComponentDataStore( $componentIdentifier, 'handlerType' ); } catch ( MWException $e ) { @@ -225,6 +205,10 @@ public function getKnownComponents(): array { * @return array */ public function getModulesFor( string $componentIdentifier, ?string $skin = null ): array { + if ( !$this->isKnown( $componentIdentifier ) ) { + // this prevents us from running into a MWException in the next call. + return []; + } $allModules = $this->accessComponentDataStore( $componentIdentifier, 'modules' ); $modules = isset( $allModules['default'] ) @@ -248,13 +232,12 @@ public function getModulesFor( string $componentIdentifier, ?string $skin = null * * @return string */ - public function getNameFor( $componentClass ): string { + public function getNameFor( string $componentClass ): string { $component = null; - // if $componentClass is not in values in $this->registeredComponentClasses, this has to fail foreach ( $this->getComponentDataStore() as $componentIdentifier => $componentData ) { - if ( isset( $componentData['class'] ) && ( $componentData['class'] == $componentClass ) && isset( $componentData['name'] ) ) { - $component = $componentData['name']; + if ( isset( $componentData['class'] ) && ( $componentData['class'] == $componentClass ) ) { + $component = $componentIdentifier; break; } } @@ -273,15 +256,35 @@ public function getRegisteredComponents(): array { return $this->registeredComponents; } + /** + * @param string $componentIdentifier + * + * @return bool + */ + public function isKnown( string $componentIdentifier ): bool { + return in_array( $componentIdentifier, $this->getKnownComponents() ); + } + /** * True, if referenced component is registered as parser function. * - * @param string $componentName + * @param string $componentIdentifier + * + * @return bool + */ + public function isParserFunction( string $componentIdentifier ): string { + return $this->getHandlerTypeFor( $componentIdentifier ) == self::HANDLER_TYPE_PARSER_FUNCTION; + } + + /** + * Checks, if component $componentIdentifier is registered + * + * @param string $componentIdentifier * * @return bool */ - public function isParserFunction( $componentName ) { - return $this->getHandlerTypeFor( $componentName ) == self::HANDLER_TYPE_PARSER_FUNCTION; + public function isRegistered( string $componentIdentifier ): bool { + return in_array( $componentIdentifier, $this->registeredComponents, true ); } /** @@ -291,7 +294,7 @@ public function isParserFunction( $componentName ) { * * @return bool */ - public function isTagExtension( $componentName ) { + public function isTagExtension( string $componentName ): bool { return $this->getHandlerTypeFor( $componentName ) == self::HANDLER_TYPE_TAG_EXTENSION; } @@ -299,10 +302,11 @@ public function isTagExtension( $componentName ) { * @param string $componentIdentifier * @param string $field * - * @throws MWException on non existing $componentIdentifier or $field + * @throws MWException on non-existing $componentIdentifier or $field + * * @return mixed */ - protected function accessComponentDataStore( $componentIdentifier, $field ) { + protected function accessComponentDataStore( string $componentIdentifier, string $field ): mixed { if ( !isset( $this->getComponentDataStore()[$componentIdentifier][$field] ) ) { throw new MWException( 'Trying to access undefined field \'' . $field . '\' of component \'' . $componentIdentifier . '\'. Aborting' @@ -311,22 +315,9 @@ protected function accessComponentDataStore( $componentIdentifier, $field ) { return $this->getComponentDataStore()[$componentIdentifier][$field]; } - /** - * Sees to it, that the whitelist (if it is an array) contains only lowercase strings. - * - * @param bool|array $componentWhiteList - * - * @return bool|array - */ - private function mangle( $componentWhiteList ) { - if ( !is_array( $componentWhiteList ) ) { - return $componentWhiteList; - } - $newWhiteList = []; - foreach ( $componentWhiteList as $element ) { - $newWhiteList[] = strtolower( trim( $element ) ); - } - return $newWhiteList; + protected function hasFieldInDataStore( string $componentIdentifier, string $field ): bool { + return $this->isRegistered( $componentIdentifier ) + && isset( $this->getComponentDataStore()[$componentIdentifier][$field] ); } /** @@ -336,35 +327,13 @@ private function mangle( $componentWhiteList ) { * * @return array */ - private function normalizeAttributes( $componentAttributes ) { - $componentAttributes = (array) $componentAttributes; - $componentAttributes = array_unique( + private function normalizeAttributes( array $componentAttributes ): array { + return array_unique( array_merge( $componentAttributes, self::DEFAULT_ATTRIBUTES ) ); - return $componentAttributes; - } - - /** - * Generates the array for registered components containing all whitelisted component identifiers - * - * @param bool|array $componentWhiteList - * - * @return string[] list of registered component identifiers - */ - private function registerComponents( $componentWhiteList ) { - $registeredComponents = []; - foreach ( $this->getKnownComponents() as $componentIdentifier ) { - - if ( !$componentWhiteList || (is_array( $componentWhiteList ) && !in_array( $componentIdentifier, $componentWhiteList )) ) { - // if $componentWhiteList is false, or and array and does not contain the $componentIdentifier, we will not register it - continue; - } - $registeredComponents[] = $componentIdentifier; - } - return $registeredComponents; } /** @@ -372,7 +341,7 @@ private function registerComponents( $componentWhiteList ) { * * @return array */ - private function getComponentDataStore() { + private function getComponentDataStore(): array { if ( !empty( $this->componentDataStore ) ) { return $this->componentDataStore; } @@ -388,11 +357,9 @@ private function getComponentDataStore() { } $componentData['name'] = $componentName; - $componentData['attributes'] = $this->normalizeAttributes( - (isset( $componentData['attributes'] ) ? $componentData['attributes'] : []) - ); - $componentData['aliases'] = isset( $componentData['aliases'] ) ? $componentData['aliases'] : []; - $componentData['modules'] = isset( $componentData['modules'] ) ? $componentData['modules'] : []; + $componentData['attributes'] = $this->normalizeAttributes( ($componentData['attributes'] ?? []) ); + $componentData['aliases'] = $componentData['aliases'] ?? []; + $componentData['modules'] = $componentData['modules'] ?? []; $componentDataStore[$componentName] = $componentData; } @@ -404,4 +371,32 @@ private function getComponentDataStore() { return $this->componentDataStore = $componentDataStore; } + + /** + * If whitelist is a bool, this returns either an empty array or an array, containing all + * identifiers from the componentDataStore. + * + * If the whileList is a non-empty array, this trims and lowercases its values. + * + * @param bool|array $componentWhiteList + * + * @return array + */ + private function processWhitelist( null|bool|array $componentWhiteList ): array { + if ( !is_array( $componentWhiteList ) ) { + if ( !$componentWhiteList ) { + return []; + } + $componentWhiteList = $this->getKnownComponents(); + sort( $componentWhiteList ); + return $componentWhiteList; + } + $newWhiteList = []; + foreach ( $componentWhiteList as $element ) { + $newWhiteList[] = strtolower( trim( $element ) ); + } + $newWhiteList = array_intersect( $newWhiteList, $this->getKnownComponents() ); + sort( $newWhiteList ); + return $newWhiteList; + } } diff --git a/src/Components/Accordion.php b/src/Components/Accordion.php index fdb660f..0bb5ce3 100644 --- a/src/Components/Accordion.php +++ b/src/Components/Accordion.php @@ -24,9 +24,9 @@ * @author Tobias Oetterer */ -namespace BootstrapComponents\Components; +namespace MediaWiki\Extension\BootstrapComponents\Components; -use BootstrapComponents\AbstractComponent; +use MediaWiki\Extension\BootstrapComponents\AbstractComponent; use \Html; /** diff --git a/src/Components/Alert.php b/src/Components/Alert.php index 0a7cc5d..aa55c00 100644 --- a/src/Components/Alert.php +++ b/src/Components/Alert.php @@ -24,9 +24,9 @@ * @author Tobias Oetterer */ -namespace BootstrapComponents\Components; +namespace MediaWiki\Extension\BootstrapComponents\Components; -use BootstrapComponents\AbstractComponent; +use MediaWiki\Extension\BootstrapComponents\AbstractComponent; use \Html; /** diff --git a/src/Components/Badge.php b/src/Components/Badge.php index 3263d9d..1267cf2 100644 --- a/src/Components/Badge.php +++ b/src/Components/Badge.php @@ -24,9 +24,9 @@ * @author Tobias Oetterer */ -namespace BootstrapComponents\Components; +namespace MediaWiki\Extension\BootstrapComponents\Components; -use BootstrapComponents\AbstractComponent; +use MediaWiki\Extension\BootstrapComponents\AbstractComponent; use \Html; /** diff --git a/src/Components/Button.php b/src/Components/Button.php index 30755cc..e4e0597 100644 --- a/src/Components/Button.php +++ b/src/Components/Button.php @@ -24,9 +24,9 @@ * @author Tobias Oetterer */ -namespace BootstrapComponents\Components; +namespace MediaWiki\Extension\BootstrapComponents\Components; -use BootstrapComponents\AbstractComponent; +use MediaWiki\Extension\BootstrapComponents\AbstractComponent; use \Html; use \Title; diff --git a/src/Components/Card.php b/src/Components/Card.php index 2dfd0f5..9d41e9a 100644 --- a/src/Components/Card.php +++ b/src/Components/Card.php @@ -24,12 +24,12 @@ * @author Tobias Oetterer */ -namespace BootstrapComponents\Components; +namespace MediaWiki\Extension\BootstrapComponents\Components; -use BootstrapComponents\ComponentLibrary; -use BootstrapComponents\AbstractComponent; -use BootstrapComponents\NestingController; -use BootstrapComponents\ParserOutputHelper; +use MediaWiki\Extension\BootstrapComponents\ComponentLibrary; +use MediaWiki\Extension\BootstrapComponents\AbstractComponent; +use MediaWiki\Extension\BootstrapComponents\NestingController; +use MediaWiki\Extension\BootstrapComponents\ParserOutputHelper; use \Html; use \MWException; diff --git a/src/Components/Carousel.php b/src/Components/Carousel.php index d43c0da..936ec79 100644 --- a/src/Components/Carousel.php +++ b/src/Components/Carousel.php @@ -24,10 +24,10 @@ * @author Tobias Oetterer */ -namespace BootstrapComponents\Components; +namespace MediaWiki\Extension\BootstrapComponents\Components; -use BootstrapComponents\AbstractComponent; -use BootstrapComponents\ParserRequest; +use MediaWiki\Extension\BootstrapComponents\AbstractComponent; +use MediaWiki\Extension\BootstrapComponents\ParserRequest; use \Html; /** diff --git a/src/Components/Collapse.php b/src/Components/Collapse.php index 123ca43..838393c 100644 --- a/src/Components/Collapse.php +++ b/src/Components/Collapse.php @@ -24,11 +24,11 @@ * @author Tobias Oetterer */ -namespace BootstrapComponents\Components; +namespace MediaWiki\Extension\BootstrapComponents\Components; -use BootstrapComponents\ApplicationFactory; -use BootstrapComponents\AbstractComponent; -use BootstrapComponents\ParserRequest; +use MediaWiki\Extension\BootstrapComponents\ApplicationFactory; +use MediaWiki\Extension\BootstrapComponents\AbstractComponent; +use MediaWiki\Extension\BootstrapComponents\ParserRequest; use \Html; use \MWException; diff --git a/src/Components/Jumbotron.php b/src/Components/Jumbotron.php index 097cff4..fc75de7 100644 --- a/src/Components/Jumbotron.php +++ b/src/Components/Jumbotron.php @@ -24,9 +24,9 @@ * @author Tobias Oetterer */ -namespace BootstrapComponents\Components; +namespace MediaWiki\Extension\BootstrapComponents\Components; -use BootstrapComponents\AbstractComponent; +use MediaWiki\Extension\BootstrapComponents\AbstractComponent; use \Html; /** diff --git a/src/Components/Modal.php b/src/Components/Modal.php index 5f01138..935bc61 100644 --- a/src/Components/Modal.php +++ b/src/Components/Modal.php @@ -24,11 +24,11 @@ * @author Tobias Oetterer */ -namespace BootstrapComponents\Components; +namespace MediaWiki\Extension\BootstrapComponents\Components; -use BootstrapComponents\AbstractComponent; -use BootstrapComponents\ApplicationFactory; -use BootstrapComponents\ModalBuilder; +use MediaWiki\Extension\BootstrapComponents\AbstractComponent; +use MediaWiki\Extension\BootstrapComponents\ApplicationFactory; +use MediaWiki\Extension\BootstrapComponents\ModalBuilder; use \Html; /** diff --git a/src/Components/Popover.php b/src/Components/Popover.php index b5fc6d9..15b4195 100644 --- a/src/Components/Popover.php +++ b/src/Components/Popover.php @@ -24,9 +24,9 @@ * @author Tobias Oetterer */ -namespace BootstrapComponents\Components; +namespace MediaWiki\Extension\BootstrapComponents\Components; -use BootstrapComponents\AbstractComponent; +use MediaWiki\Extension\BootstrapComponents\AbstractComponent; use \Html; /** diff --git a/src/Components/Tooltip.php b/src/Components/Tooltip.php index f63cf75..a73ba0e 100644 --- a/src/Components/Tooltip.php +++ b/src/Components/Tooltip.php @@ -24,9 +24,9 @@ * @author Tobias Oetterer */ -namespace BootstrapComponents\Components; +namespace MediaWiki\Extension\BootstrapComponents\Components; -use BootstrapComponents\AbstractComponent; +use MediaWiki\Extension\BootstrapComponents\AbstractComponent; use \Html; /** diff --git a/src/ComponentsDefinition.json b/src/ComponentsDefinition.json index 7e3838e..38362a1 100644 --- a/src/ComponentsDefinition.json +++ b/src/ComponentsDefinition.json @@ -1,13 +1,13 @@ { "accordion": { - "class": "BootstrapComponents\\Components\\Accordion", + "class": "MediaWiki\\Extension\\BootstrapComponents\\Components\\Accordion", "handlerType": "tag", "modules": { "default": "ext.bootstrapComponents.accordion.fix" } }, "alert": { - "class": "BootstrapComponents\\Components\\Alert", + "class": "MediaWiki\\Extension\\BootstrapComponents\\Components\\Alert", "handlerType": "tag", "attributes": [ "color", @@ -18,7 +18,7 @@ } }, "badge": { - "class": "BootstrapComponents\\Components\\Badge", + "class": "MediaWiki\\Extension\\BootstrapComponents\\Components\\Badge", "handlerType": "function", "attributes": [ "color", @@ -26,7 +26,7 @@ ] }, "button": { - "class": "BootstrapComponents\\Components\\Button", + "class": "MediaWiki\\Extension\\BootstrapComponents\\Components\\Button", "handlerType": "function", "attributes": [ "active", @@ -41,7 +41,7 @@ } }, "card": { - "class": "BootstrapComponents\\Components\\Card", + "class": "MediaWiki\\Extension\\BootstrapComponents\\Components\\Card", "handlerType": "tag", "attributes": [ "active", @@ -68,7 +68,7 @@ } }, "carousel": { - "class": "BootstrapComponents\\Components\\Carousel", + "class": "MediaWiki\\Extension\\BootstrapComponents\\Components\\Carousel", "handlerType": "function", "attributes": [ "fade" @@ -78,7 +78,7 @@ } }, "collapse": { - "class": "BootstrapComponents\\Components\\Collapse", + "class": "MediaWiki\\Extension\\BootstrapComponents\\Components\\Collapse", "handlerType": "tag", "attributes": [ "active", @@ -93,12 +93,12 @@ } }, "jumbotron": { - "class": "BootstrapComponents\\Components\\Jumbotron", + "class": "MediaWiki\\Extension\\BootstrapComponents\\Components\\Jumbotron", "handlerType": "tag" }, "label": "badge", "modal": { - "class": "BootstrapComponents\\Components\\Modal", + "class": "MediaWiki\\Extension\\BootstrapComponents\\Components\\Modal", "handlerType": "tag", "attributes": [ "color", @@ -127,7 +127,7 @@ }, "panel": "card", "popover": { - "class": "BootstrapComponents\\Components\\Popover", + "class": "MediaWiki\\Extension\\BootstrapComponents\\Components\\Popover", "handlerType": "tag", "attributes": [ "color", @@ -155,7 +155,7 @@ } }, "tooltip": { - "class": "BootstrapComponents\\Components\\Tooltip", + "class": "MediaWiki\\Extension\\BootstrapComponents\\Components\\Tooltip", "handlerType": "function", "attributes": [ "placement", diff --git a/src/HookRegistry.php b/src/HookRegistry.php deleted file mode 100644 index 73e642c..0000000 --- a/src/HookRegistry.php +++ /dev/null @@ -1,364 +0,0 @@ -. - * - * @file - * @ingroup BootstrapComponents - * @author Tobias Oetterer - */ - -namespace BootstrapComponents; - -use BootstrapComponents\Hooks\OutputPageParserOutput; -use BootstrapComponents\Hooks\ParserFirstCallInit; -use Closure; -use Config; -use ConfigException; -use Hooks; -use MediaWiki\MediaWikiServices; -use MWException; -use OutputPage; -use Parser; -use ParserOutput; - -/** - * Class HookRegistry - * - * Registers all hooks and components for Extension BootstrapComponents. - * - * Information on how to add a new hook - * 1. add it to {@see HookRegistry::AVAILABLE_HOOKS}. - * 2. add an appropriate entry in the array inside {@see HookRegistry::getCompleteHookDefinitionList} - * with the hook as array key and the callback as value. - * 3. have {@see HookRegistry::compileRequestedHooksListFor} add the hook to its result array. Based on - * a certain condition, if necessary. - * 4. add appropriate tests to {@see \BootstrapComponents\Tests\Unit\HookRegistryTest}. - * - * @since 1.0 - */ -class HookRegistry { - - /** - * @var array - */ - const AVAILABLE_HOOKS = [ - 'GalleryGetModes', - 'ImageBeforeProduceHTML', - 'InternalParseBeforeLinks', - 'OutputPageParserOutput', - 'ParserFirstCallInit', - ]; - // dev note: for modals, please see \BootstrapComponents\ModalBuilder for a list of tested hooks - - /** - * @var ComponentLibrary $componentLibrary - */ - private $componentLibrary; - - /** - * @var Config $myConfig - */ - private $myConfig; - - /** - * @var NestingController $nestingController - */ - private $nestingController; - - /** - * HookRegistry constructor. - * - * @throws ConfigException cascading {@see HookRegistry::getHooksToRegister} - * @throws MWException cascading {@see HookRegistry::getHooksToRegister} - * - */ - public function __construct() { - - $this->myConfig = $this->registerMyConfiguration(); - - list ( $this->componentLibrary, $this->nestingController ) = - $this->initializeApplications( $this->myConfig ); - } - - /** - * @param array $hooksToRegister - * - * @return array - */ - public function buildHookCallbackListFor( array $hooksToRegister ): array - { - $hookCallbackList = []; - $completeHookDefinitionList = - $this->getCompleteHookDefinitionList( $this->myConfig, $this->componentLibrary, - $this->nestingController ); - foreach ( $hooksToRegister as $requestedHook ) { - if ( isset( $completeHookDefinitionList[$requestedHook] ) ) { - $hookCallbackList[$requestedHook] = $completeHookDefinitionList[$requestedHook]; - } - } - - return $hookCallbackList; - } - - /** - * Used to clear registered hooks for integration tests - * - * @deprecated the use of Hooks::clear() is deprecated, see there - * @throws MWException cascading {@see Hooks::clear} - */ - public function clear() { - foreach ( self::AVAILABLE_HOOKS as $name ) { - Hooks::clear( $name ); - } - } - - /** - * @param Config $myConfig - * - * @return string[] - * @throws ConfigException cascading {@see Config::get} - * - */ - public function compileRequestedHooksListFor( Config $myConfig ): array - { - $requestedHookList = [ - 'OutputPageParserOutput', - 'ParserFirstCallInit', - ]; - if ( $myConfig->has( 'BootstrapComponentsEnableCarouselGalleryMode' ) && - $myConfig->get( 'BootstrapComponentsEnableCarouselGalleryMode' ) ) { - $requestedHookList[] = 'GalleryGetModes'; - } - if ( $myConfig->has( 'BootstrapComponentsModalReplaceImageTag' ) && - $myConfig->get( 'BootstrapComponentsModalReplaceImageTag' ) ) { - $requestedHookList[] = 'ImageBeforeProduceHTML'; - $requestedHookList[] = 'InternalParseBeforeLinks'; - } - - return $requestedHookList; - } - - /** - * @param Config $myConfig - * @param ComponentLibrary $componentLibrary - * @param NestingController $nestingController - * - * @return Closure[] - */ - public function getCompleteHookDefinitionList( - Config $myConfig, ComponentLibrary $componentLibrary, NestingController $nestingController - ): array - { - return [ - /** - * Hook: GalleryGetModes - * - * Allows extensions to add classes that can render different modes of a gallery. - * - * @see https://www.mediawiki.org/wiki/Manual:Hooks/GalleryGetModes - */ - 'GalleryGetModes' => function ( &$modeArray ) { - $modeArray['carousel'] = 'BootstrapComponents\\CarouselGallery'; - - return true; - }, - - /** - * Hook: ImageBeforeProduceHTML - * - * Called before producing the HTML created by a wiki image insertion - * - * @see https://www.mediawiki.org/wiki/Manual:Hooks/ImageBeforeProduceHTML - */ - 'ImageBeforeProduceHTML' => $this->createImageBeforeProduceHTMLCallback( $nestingController, - $myConfig ), - - /** - * Hook: InternalParseBeforeLinks - * - * Used to process the expanded wiki code after , HTML-comments, and templates have been treated. - * - * @see https://www.mediawiki.org/wiki/Manual:Hooks/InternalParseBeforeLinks - */ - 'InternalParseBeforeLinks' => $this->createInternalParseBeforeLinksCallback(), - - /** - * Hook: OutputPageParserOutput - * - * Called after parse, before the HTML is added to the output. - * - * @see https://www.mediawiki.org/wiki/Manual:Hooks/OutputPageParserOutput - */ - 'OutputPageParserOutput' => function ( - OutputPage &$outputPage, ParserOutput $parserOutput, - ParserOutputHelper &$parserOutputHelper = null - ) { - // @todo check, if we need to omit execution on actions edit, submit, or history - // $action = $outputPage->parserOptions()->getUser()->getRequest()->getVal( "action" ); - $hook = - new OutputPageParserOutput( $outputPage, $parserOutput, $parserOutputHelper ); - - return $hook->process(); - }, - - /** - * Hook: ParserFirstCallInit - * - * Called when the parser initializes for the first time. - * - * @see https://www.mediawiki.org/wiki/Manual:Hooks/ParserFirstCallInit - */ - 'ParserFirstCallInit' => function ( Parser $parser ) use ( - $componentLibrary, $nestingController - ) { - $hook = new ParserFirstCallInit( $parser, $componentLibrary, $nestingController ); - - return $hook->process(); - }, - ]; - } - - /** - * @param Config $myConfig - * - * @return array - * @throws ConfigException cascading {@see Config::get} - * - * @throws MWException cascading {@see ApplicationFactory} calls - */ - public function initializeApplications(Config $myConfig ): array - { - $applicationFactory = ApplicationFactory::getInstance(); - $componentLibrary = - $applicationFactory->getComponentLibrary( $myConfig->get( 'BootstrapComponentsWhitelist' ) ); - $nestingController = $applicationFactory->getNestingController(); - - return [ $componentLibrary, $nestingController ]; - } - - /** - * @param string $hook - * - * @return boolean - */ - public function isRegistered(string $hook ): bool - { - return MediaWikiServices::getInstance()->getHookContainer()->isRegistered( $hook ); - } - - /** - * Registers all supplied hooks. - * - * @param array $hookList $hook => $callback - * - * @return int number of registered hooks - */ - public function register(array $hookList ): int - { - foreach ( $hookList as $hook => $callback ) { - MediaWikiServices::getInstance()->getHookContainer()->register( $hook, $callback ); - } - - return count( $hookList ); - } - - /** - * Executes the setup process. - * - * @return int - * @throws ConfigException - * - */ - public function run(): int - { - $requestedHooks = $this->compileRequestedHooksListFor( $this->myConfig ); - $hookCallbackList = $this->buildHookCallbackListFor( $requestedHooks ); - - return $this->register( $hookCallbackList ); - } - - /** - * Callback for Hook: ImageBeforeProduceHTML - * - * Called before producing the HTML created by a wiki image insertion - * - * @param NestingController $nestingController - * @param Config $myConfig - * - * @return Closure - * @see https://www.mediawiki.org/wiki/Manual:Hooks/ImageBeforeProduceHTML - * - */ - private function createImageBeforeProduceHTMLCallback( - NestingController $nestingController, Config $myConfig - ): Closure - { - return function ( - &$dummy, &$title, &$file, &$frameParams, &$handlerParams, &$time, &$res - ) use ( $nestingController, $myConfig ) { - - $imageModal = new ImageModal( $dummy, $title, $file, $nestingController ); - - if ( $myConfig->has( 'BootstrapComponentsDisableSourceLinkOnImageModal' ) && - $myConfig->get( 'BootstrapComponentsDisableSourceLinkOnImageModal' ) ) { - $imageModal->disableSourceLink(); - } - - return $imageModal->parse( $frameParams, $handlerParams, $time, $res ); - }; - } - - /** - * Callback for Hook: InternalParseBeforeLinks - * - * Used to process the expanded wiki code after , HTML-comments, and templates have been treated. - * - * @see https://www.mediawiki.org/wiki/Manual:Hooks/InternalParseBeforeLinks - * - * @return Closure - */ - private function createInternalParseBeforeLinksCallback(): Closure - { - return function ( Parser &$parser, &$text ) { - $mw = MediaWikiServices::getInstance()->getMagicWordFactory()->get( 'BSC_NO_IMAGE_MODAL' ); - // we do not use our ParserOutputHelper class here, for we would need to reset it in integration tests. - // resetting our factory build classes is unfortunately a little skittish - $parser->getOutput() - ->setExtensionData( 'bsc_no_image_modal', $mw->matchAndRemove( $text ) ); - - return true; - }; - } - - /** - * Registers and returns my own configuration, so that it is present during pre-init onExtensionLoad(). See phabricator issue T184837 - * - * @see https://phabricator.wikimedia.org/T184837 - * - * @return Config - */ - private function registerMyConfiguration(): Config - { - $configFactory = MediaWikiServices::getInstance()->getConfigFactory(); - $configFactory->register( 'BootstrapComponents', 'GlobalVarConfig::newInstance' ); - - return $configFactory->makeConfig( 'BootstrapComponents' ); - } -} diff --git a/src/Hooks/DefaultHooksHandler.php b/src/Hooks/DefaultHooksHandler.php index 1aa0ea4..673afdb 100644 --- a/src/Hooks/DefaultHooksHandler.php +++ b/src/Hooks/DefaultHooksHandler.php @@ -1,6 +1,6 @@ '; /** - * @var ParserOutput $parserOutput + * @var string + */ + const INJECTION_SUFFIX = ''; + + /** + * @var BootstrapComponentsService */ - private $parserOutput; + private BootstrapComponentsService $bootstrapComponentService; /** - * @var ParserOutputHelper $parserOutputHelper + * @var OutputPage $outputPage */ - private $parserOutputHelper; + private OutputPage $outputPage; + + /** + * @var ParserOutput $parserOutput + */ + private ParserOutput $parserOutput; /** * OutputPageParserOutput constructor. * * @param OutputPage $outputPage * @param ParserOutput $parserOutput - * @param ParserOutputHelper|null $parserOutputHelper - * - * @throws MWException + * @param BootstrapComponentsService $service */ public function __construct( - OutputPage &$outputPage, ParserOutput $parserOutput, ?ParserOutputHelper &$parserOutputHelper = null + OutputPage &$outputPage, ParserOutput $parserOutput, BootstrapComponentsService $service ) { $this->outputPage = $outputPage; $this->parserOutput = $parserOutput; - if ( is_null( $parserOutputHelper ) ) { - $parserOutputHelper = ApplicationFactory::getInstance()->getParserOutputHelper(); - } - - $this->parserOutputHelper = $parserOutputHelper; + $this->bootstrapComponentService = $service; } /** - * @return bool + * @return void */ - public function process(): bool - { - $deferredText = $this->getParserOutputHelper()->getContentForLaterInjection( - $this->getParserOutput() - ); + public function process(): void { + $deferredText = $this->getContentForLaterInjection( $this->getParserOutput() ); if ( !empty( $deferredText ) ) { $this->getOutputPage()->addHTML( $deferredText ); } - if ( $this->getParserOutputHelper()->vectorSkinInUse() ) { + if ( $this->getBootstrapComponentsService()->vectorSkinInUse() ) { $this->getOutputPage()->addModules( [ 'ext.bootstrapComponents.vector-fix' ] ); } + } + + /** + * Returns the raw html that is to be inserted at the end of the page. + * + * @param ParserOutput $parserOutput + * + * @return string + */ + protected function getContentForLaterInjection( ParserOutput $parserOutput ): string { + $deferredContent = $parserOutput + ->getExtensionData(BootstrapComponents::EXTENSION_DATA_DEFERRED_CONTENT_KEY ); + + if (empty($deferredContent) || !is_array($deferredContent)) { + return ''; + } - return true; + // clearing extension data for unit and integration tests to work + $parserOutput->setExtensionData( BootstrapComponents::EXTENSION_DATA_DEFERRED_CONTENT_KEY, null ); + return self::INJECTION_PREFIX . implode( array_values( $deferredContent ) ) . self::INJECTION_SUFFIX; + } + + protected function getBootstrapComponentsService(): BootstrapComponentsService { + return $this->bootstrapComponentService; } /** - * @return \OutputPage + * @return OutputPage */ protected function getOutputPage(): OutputPage { @@ -110,18 +132,10 @@ protected function getOutputPage(): OutputPage } /** - * @return \ParserOutput + * @return ParserOutput */ protected function getParserOutput(): ParserOutput { return $this->parserOutput; } - - /** - * @return ParserOutputHelper - */ - protected function getParserOutputHelper(): ?ParserOutputHelper - { - return $this->parserOutputHelper; - } } diff --git a/src/Hooks/ParserFirstCallInit.php b/src/Hooks/ParserFirstCallInit.php index 80709d5..0adbe14 100644 --- a/src/Hooks/ParserFirstCallInit.php +++ b/src/Hooks/ParserFirstCallInit.php @@ -24,14 +24,15 @@ * @author Tobias Oetterer */ -namespace BootstrapComponents\Hooks; +namespace MediaWiki\Extension\BootstrapComponents\Hooks; -use \BootstrapComponents\ApplicationFactory; -use \BootstrapComponents\ComponentLibrary; -use \BootstrapComponents\NestingController; -use \BootstrapComponents\ParserOutputHelper; -use \Parser; -use \ReflectionClass; +use Closure; +use MediaWiki\Extension\BootstrapComponents\ApplicationFactory; +use MediaWiki\Extension\BootstrapComponents\ComponentLibrary; +use MediaWiki\Extension\BootstrapComponents\NestingController; +use MediaWiki\Extension\BootstrapComponents\ParserOutputHelper; +use Parser; +use ReflectionClass; /** * Class ParserFirstCallInit @@ -140,9 +141,9 @@ protected function getParserOutputHelper() { * * @param string $componentName * - * @return \Closure + * @return Closure */ - private function createParserHookCallbackFor( $componentName ) { + private function createParserHookCallbackFor( string $componentName ): Closure { $componentLibrary = $this->getComponentLibrary(); $nestingController = $this->getNestingController(); @@ -152,6 +153,7 @@ private function createParserHookCallbackFor( $componentName ) { $componentClass = $componentLibrary->getClassFor( $componentName ); $objectReflection = new ReflectionClass( $componentClass ); + /** @var \MediaWiki\Extension\BootstrapComponents\AbstractComponent $object */ $object = $objectReflection->newInstanceArgs( [ $componentLibrary, $parserOutputHelper, $nestingController ] ); $parserRequest = ApplicationFactory::getInstance()->getNewParserRequest( @@ -159,7 +161,6 @@ private function createParserHookCallbackFor( $componentName ) { $componentLibrary->isParserFunction( $componentName ), $componentName ); - /** @var \BootstrapComponents\AbstractComponent $object */ return $object->parseComponent( $parserRequest ); }; } diff --git a/src/HooksHandler.php b/src/HooksHandler.php new file mode 100644 index 0000000..0b07332 --- /dev/null +++ b/src/HooksHandler.php @@ -0,0 +1,287 @@ +bootstrapComponentsService = $bootstrapComponentsService; + $this->componentLibrary = $componentLibrary; + $this->nestingController = $nestingController; + } + + /** + * Hook: ScribuntoExternalLibraries + * + * Allow extensions to add Scribunto libraries + * + * @see https://www.mediawiki.org/wiki/Manual:Hooks/ScribuntoExternalLibraries + */ + public static function onScribuntoExternalLibraries( $engine, array &$extraLibraries ): bool + { + if ( $engine == 'lua' ) { + $extraLibraries['mw.bootstrap'] = LuaLibrary::class; + } + + return true; + } + + /** + * Hook: GalleryGetModes + * + * Allows extensions to add classes that can render different modes of a gallery. + * + * @see https://www.mediawiki.org/wiki/Manual:Hooks/GalleryGetModes + * + * @param array $modeArray + * @return bool + */ + + public function onGalleryGetModes( &$modeArray ): bool + { + if ( + $this->getConfig()->has( 'BootstrapComponentsEnableCarouselGalleryMode' ) + && $this->getConfig()->get( 'BootstrapComponentsEnableCarouselGalleryMode' ) + ) { + $modeArray['carousel'] = CarouselGallery::class; + } + return true; + } + + /** + * Hook: ImageBeforeProduceHTML + * + * Called before producing the HTML created by a wiki image insertion + * + * @see https://www.mediawiki.org/wiki/Manual:Hooks/ImageBeforeProduceHTML + * + * @codeCoverageIgnore trivial + * + * @param \DummyLinker $linker + * @param \Title $title + * @param File|\LocalFile $file + * @param array $frameParams + * @param array $handlerParams + * @param bool|string $time + * @param null|string $res + * @param Parser $parser + * @param string $query + * @param null|int $widthOption + * @throws \MWException + */ + public function onImageBeforeProduceHTML( + $linker, &$title, &$file, &$frameParams, &$handlerParams, &$time, &$res, $parser, &$query, &$widthOption + ): bool { + if ( $this->getConfig()->has( 'BootstrapComponentsModalReplaceImageTag' ) && + $this->getConfig()->get( 'BootstrapComponentsModalReplaceImageTag' ) ) + { + $imageModal = new ImageModal( $linker, $title, $file, + $this->getNestingController(), $this->getBootstrapComponentsService() + ); + + if ( $this->getConfig()->has( 'BootstrapComponentsDisableSourceLinkOnImageModal' ) && + $this->getConfig()->get( 'BootstrapComponentsDisableSourceLinkOnImageModal' ) ) { + $imageModal->disableSourceLink(); + } + + return $imageModal->parse( $frameParams, $handlerParams, $time, $res ); + } + return true; + } + + /** + * Hook: InternalParseBeforeLinks + * + * Used to process the expanded wiki code after , HTML-comments, and templates have been treated. + * + * @see https://www.mediawiki.org/wiki/Manual:Hooks/InternalParseBeforeLinks + * + * @codeCoverageIgnore trivial + * + * @param Parser $parser + * @param string $text + * @param StripState $stripState + * @return bool + */ + public function onInternalParseBeforeLinks( $parser, &$text, $stripState ): bool { + // we do not use our ParserOutputHelper class here, for we would need to reset it in integration tests. + // resetting our factory build classes is unfortunately a little skittish + $parser->getOutput()->setExtensionData( + BootstrapComponents::EXTENSION_DATA_NO_IMAGE_MODAL, + MediaWikiServices::getInstance()->getMagicWordFactory()->get( 'BSC_NO_IMAGE_MODAL' ) + ->matchAndRemove( $text ) + ); + + return true; + } + + public function onOutputPageParserOutput( $outputPage, $parserOutput ): void { + // @todo check, if we need to omit execution on actions edit, submit, or history + // $action = $outputPage->parserOptions()->getUser()->getRequest()->getVal( "action" ); + $hook = + new OutputPageParserOutput( $outputPage, $parserOutput, $this->getBootstrapComponentsService() ); + + $hook->process(); + } + + /** + * Hook: ParserAfterParse + * + * Called from Parser::parse() just after the call to Parser::internalParse() returns. + * + * @see https://www.mediawiki.org/wiki/Manual:Hooks/ParserAfterParse + * + * @codeCoverageIgnore trivial + * + * @param Parser $parser + * @param string $text + * @param StripState $stripState + * @return bool + */ + public function onParserAfterParse( $parser, &$text, $stripState ): bool { + + // once, this was only loaded, when a component was paced on the page. now, we load it always + // to keep the layout of all the wiki pages consistent. + $parser->getOutput()->addModuleStyles( ['ext.bootstrapComponents.bootstrap.fix'] ); + $parser->getOutput()->addModuleStyles( ['ext.bootstrap.styles'] ); + $parser->getOutput()->addModules( ['ext.bootstrap.scripts'] ); + $skin = $this->getBootstrapComponentsService()->getNameOfActiveSkin(); + foreach ( $this->getBootstrapComponentsService()->getActiveComponents() as $activeComponent ) { + if ( !$this->getComponentLibrary()->isRegistered( $activeComponent ) ) { + continue; + } + foreach ( $this->getComponentLibrary()->getModulesFor( $activeComponent ) as $module ) { + $parser->getOutput()->addModuleStyles( $module, $skin ); + } + } + return true; + } + + /** + * Hook: ParserFirstCallInit + * + * Called when the parser initializes for the first time. + * + * @see https://www.mediawiki.org/wiki/Manual:Hooks/ParserFirstCallInit + * + * @param Parser $parser + * + * @return bool + * @throws \MWException + */ + public function onParserFirstCallInit( $parser ): bool { + $hook = new ParserFirstCallInit( $parser, $this->getComponentLibrary(), $this->getNestingController() ); + + return $hook->process(); + } + + /** + * Hook: SetupAfterCache + * + * Called in Setup.php, after cache objects are set + * + * @see https://www.mediawiki.org/wiki/Manual:Hooks/SetupAfterCache + * + * @codeCoverageIgnore trivial + */ + public function onSetupAfterCache(): bool { + // think about only adding modules for whitelisted components instead of all + BootstrapManager::getInstance()->addAllBootstrapModules(); + return true; + } + + /** + * @return BootstrapComponentsService + */ + protected function getBootstrapComponentsService(): BootstrapComponentsService { + return $this->bootstrapComponentsService; + } + + /** + * @return ComponentLibrary + */ + protected function getComponentLibrary(): ComponentLibrary { + return $this->componentLibrary; + } + + /** + * @return Config + */ + protected function getConfig(): Config { + if ( !isset( $this->config ) ) { + $this->config = MediaWikiServices::getInstance()->getConfigFactory()->makeConfig('BootstrapComponents'); + } + return $this->config; + } + + /** + * @return NestingController + */ + protected function getNestingController(): NestingController { + return $this->nestingController; + } +} diff --git a/src/ImageModal.php b/src/ImageModal.php index c734ca7..e4e5b1d 100644 --- a/src/ImageModal.php +++ b/src/ImageModal.php @@ -24,13 +24,15 @@ * @author Tobias Oetterer */ -namespace BootstrapComponents; +namespace MediaWiki\Extension\BootstrapComponents; -use \Linker; -use \Html; -use \MediaWiki\MediaWikiServices; -use \RequestContext; -use \Title; +use DummyLinker; +use File; +use Html; +use MediaTransformOutput; +use MediaWiki\MediaWikiServices; +use MWException; +use Title; /** * Class ImageModal @@ -48,67 +50,75 @@ class ImageModal implements NestableInterface { const PARENTS_PREVENTING_MODAL = [ 'button', 'collapse ', 'image_modal', 'modal', 'popover', 'tooltip' ]; /** - * @var \DummyLinker $dummyLinker + * @var BootstrapComponentsService */ - private $dummyLinker; + private BootstrapComponentsService $bootstrapComponentService; /** - * @var \File $file + * @var DummyLinker $dummyLinker */ - private $file; + private DummyLinker $dummyLinker; /** - * @var string $id + * @var File $file */ - private $id; + private File $file; + + /** + * @var null|string $id + */ + private null|string $id; /** * @var NestingController $nestingController */ - private $nestingController; + private NestingController $nestingController; /** - * @var NestableInterface|false $parentComponent + * @var null|bool|NestableInterface $parentComponent */ - private $parentComponent; + private null|bool|NestableInterface $parentComponent; /** * @var ParserOutputHelper $parserOutputHelper */ - private $parserOutputHelper; + private ParserOutputHelper $parserOutputHelper; /** * @var bool $disableSourceLink */ - private $disableSourceLink; + private bool $disableSourceLink; /** * @var Title $title */ - private $title; + private Title $title; /** * ImageModal constructor. * - * @param \DummyLinker $dummyLinker - * @param \Title $title - * @param \File $file - * @param NestingController $nestingController - * @param ParserOutputHelper $parserOutputHelper DI for unit testing + * @param DummyLinker $dummyLinker + * @param Title $title + * @param File $file + * @param NestingController $nestingController + * @param BootstrapComponentsService $bootstrapComponentService + * @param ParserOutputHelper|null $parserOutputHelper DI for unit testing * - * @throws \MWException cascading {@see \BootstrapComponents\ApplicationFactory} methods + * @throws MWException cascading {@see ApplicationFactory} methods */ - public function __construct( $dummyLinker, $title, $file, $nestingController = null, $parserOutputHelper = null ) { + public function __construct( + DummyLinker $dummyLinker, Title $title, File $file, + NestingController $nestingController, BootstrapComponentsService $bootstrapComponentService, + ParserOutputHelper $parserOutputHelper = null + ) { $this->file = $file; $this->dummyLinker = $dummyLinker; $this->title = $title; - $this->nestingController = is_null( $nestingController ) - ? ApplicationFactory::getInstance()->getNestingController() - : $nestingController; - $this->parserOutputHelper = is_null( $parserOutputHelper ) - ? ApplicationFactory::getInstance()->getParserOutputHelper() - : $parserOutputHelper ; + $this->nestingController = $nestingController; + $this->bootstrapComponentService = $bootstrapComponentService; + $this->parserOutputHelper = $parserOutputHelper + ?? ApplicationFactory::getInstance()->getParserOutputHelper(); $this->parentComponent = $this->getNestingController()->getCurrentElement(); $this->id = $this->getNestingController()->generateUniqueId( @@ -120,8 +130,10 @@ public function __construct( $dummyLinker, $title, $file, $nestingController = n /** * @inheritdoc */ - public function getComponentName() { - return "image_modal"; + public function getComponentName(): string { + return "modal"; + // changed to modal at version 5.2.0 + #return "image_modal"; } /** @@ -157,23 +169,23 @@ public function getId() { * @param string|bool $time Timestamp of the file, set as false for current * @param string $res Final HTML output, used if this returns false * - * @throws \MWException cascading {@see \BootstrapComponents\NestingController::open} - * @throws \ConfigException cascading {@see \BootstrapComponents\ImageModal::generateTrigger} + * @throws MWException cascading {@see NestingController::open} + * @throws \ConfigException cascading {@see ImageModal::generateTrigger} * * @return bool */ - public function parse( &$frameParams, &$handlerParams, &$time, &$res ) { + public function parse( array &$frameParams, array &$handlerParams, &$time, &$res ): bool { if ( !$this->assertResponsibility( $this->getFile(), $frameParams ) ) { wfDebugLog( 'BootstrapComponents', 'Image modal relegating image rendering back to Linker.php.' ); return true; } // it's on us, let's do some modal-ing - $this->augmentParserOutput(); + $this->getBootstrapComponentsService()->registerComponentAsActive( $this->getComponentName() ); $this->getNestingController()->open( $this ); $sanitizedFrameParams = $this->sanitizeFrameParams( $frameParams ); - $handlerParams['page'] = isset( $handlerParams['page'] ) ? $handlerParams['page'] : false; + $handlerParams['page'] = $handlerParams['page'] ?? false; $res = $this->turnParamsIntoModal( $sanitizedFrameParams, $handlerParams ); @@ -192,18 +204,19 @@ public function parse( &$frameParams, &$handlerParams, &$time, &$res ) { * After this, all bool params ( 'thumbnail', 'framed', 'frameless', 'border' ) are true, if they were present before, false otherwise and all * string params are set (to the original value or the empty string). * - * This method is public, because it is used in {@see \BootstrapComponents\Tests\ImageModalTest::doTestCompareTriggerWithOriginalThumb} + * This method is public, because it is used in + * {@see \MediaWiki\Extension\BootstrapComponents\Tests\ImageModalTest::doTestCompareTriggerWithOriginalThumb} * * @param array $frameParams * * @return array */ - public function sanitizeFrameParams( $frameParams ) { + public function sanitizeFrameParams( array $frameParams ): array { foreach ( [ 'thumbnail', 'framed', 'frameless', 'border' ] as $boolField ) { $frameParams[$boolField] = isset( $frameParams[$boolField] ); } foreach ( [ 'align', 'alt', 'caption', 'class', 'title', 'valign' ] as $stringField ) { - $frameParams[$stringField] = !empty( $frameParams[$stringField] ) ? $frameParams[$stringField] : false; + $frameParams[$stringField] = $frameParams[$stringField] ?? false; } $frameParams['caption'] = $this->preventModalInception( $frameParams['caption'] ); $frameParams['title'] = $this->preventModalInception( $frameParams['title'] ); @@ -213,7 +226,7 @@ public function sanitizeFrameParams( $frameParams ) { /** * Disables the source link in modal content. */ - public function disableSourceLink() { + public function disableSourceLink(): void { $this->disableSourceLink = true; } @@ -227,12 +240,12 @@ public function disableSourceLink() { * * no magic word suppressing image modals is on the page * * image does not have the "no-modal" class {@see ImageModal::CSS_CLASS_PREVENTING_MODAL} * - * @param \File $file + * @param File $file * @param array $frameParams * * @return bool true, if all assertions hold, false if one fails (see above) */ - protected function assertResponsibility( $file, $frameParams ) { + protected function assertResponsibility( File $file, array $frameParams ): bool { if ( !$this->assertImageTagValid( $file, $frameParams ) ) { return false; } @@ -240,15 +253,15 @@ protected function assertResponsibility( $file, $frameParams ) { } /** - * @param \File $file + * @param File $file * @param array $sanitizedFrameParams * @param array $handlerParams * * @return array bool|string bool (large image yes or no) */ - protected function generateContent( $file, $sanitizedFrameParams, $handlerParams ) { + protected function generateContent( File $file, array $sanitizedFrameParams, array $handlerParams ): array { - /** @var \MediaTransformOutput $img $img */ + /** @var MediaTransformOutput $img $img */ $img = $file->getUnscaledThumb( [ 'page' => $handlerParams['page'] ] ); @@ -262,57 +275,65 @@ protected function generateContent( $file, $sanitizedFrameParams, $handlerParams } /** - * @return \DummyLinker + * @return BootstrapComponentsService + */ + protected function getBootstrapComponentsService(): BootstrapComponentsService { + return $this->bootstrapComponentService; + } + + /** + * @return DummyLinker */ /** @scrutinizer ignore-unused */ - protected function getDummyLinker() { + protected function getDummyLinker(): DummyLinker { return $this->dummyLinker; } /** - * @return \File + * @return File */ - protected function getFile() { + protected function getFile(): File { return $this->file; } /** * @return NestingController */ - protected function getNestingController() { + protected function getNestingController(): NestingController { return $this->nestingController; } /** * @return null|NestableInterface */ - protected function getParentComponent() { + protected function getParentComponent(): bool|NestableInterface|null { return $this->parentComponent; } /** * @return ParserOutputHelper + * @deprecated */ - protected function getParserOutputHelper() { + protected function getParserOutputHelper(): ParserOutputHelper { return $this->parserOutputHelper; } /** * @return Title */ - protected function getTitle() { + protected function getTitle(): Title { return $this->title; } /** - * @param $sanitizedFrameParams - * @param $handlerParams - * - * @throws \ConfigException + * @param array $sanitizedFrameParams + * @param array $handlerParams * * @return string rendered modal on success, empty string on failure. + * @throws \ConfigException|\Exception + * */ - protected function turnParamsIntoModal( $sanitizedFrameParams, $handlerParams ) { + protected function turnParamsIntoModal( array $sanitizedFrameParams, array $handlerParams ): string { $trigger = new ImageModalTrigger( $this->getId(), $this->getFile() @@ -363,12 +384,12 @@ protected function turnParamsIntoModal( $sanitizedFrameParams, $handlerParams ) } /** - * @param \File $file + * @param File $file * @param array $frameParams * * @return bool */ - private function assertImageTagValid( $file, $frameParams ) { + private function assertImageTagValid( File $file, array $frameParams ): bool { if ( !$file || !$file->exists() ) { return false; } @@ -391,39 +412,30 @@ private function assertImageTagValid( $file, $frameParams ) { */ private function assertImageModalNotSuppressed( array $frameParams ): bool { - if ( $this->getParentComponent() && in_array( $this->getParentComponent()->getComponentName(), self::PARENTS_PREVENTING_MODAL ) ) { + if ( $this->getParentComponent() + && in_array( $this->getParentComponent()->getComponentName(), self::PARENTS_PREVENTING_MODAL ) + ) { return false; } - if ( isset( $frameParams['class'] ) && in_array( self::CSS_CLASS_PREVENTING_MODAL, explode( ' ', $frameParams['class'] ) ) ) { + if ( isset( $frameParams['class'] ) + && in_array( self::CSS_CLASS_PREVENTING_MODAL, explode( ' ', $frameParams['class'] ) ) + ) { return false; } /** @see ParserOutputHelper::areImageModalsSuppressed as to why we need to use the global parser! */ - //$parser = $GLOBALS['wgParser']; // Use of $wgParser was deprecated in MediaWiki 1.32. $parser = MediaWikiServices::getInstance()->getParser(); // the is_null test has to be added because otherwise some unit tests will fail - return is_null( $parser->getOutput() ) || !$parser->getOutput()->getExtensionData( 'bsc_no_image_modal' ); - } - - /** - * Performs all the mandatory actions on the parser output for the component class - * - * @throws \MWException cascading {@see \BootstrapComponents\ApplicationFactory::getComponentLibrary} - */ - private function augmentParserOutput() { - $skin = $this->getParserOutputHelper()->getNameOfActiveSkin(); - $this->getParserOutputHelper()->loadBootstrapModules(); - $this->getParserOutputHelper()->addModules( - ApplicationFactory::getInstance()->getComponentLibrary()->getModulesFor( 'modal', $skin ) - ); + return is_null( $parser->getOutput() ) + || !$parser->getOutput()->getExtensionData( BootstrapComponents::EXTENSION_DATA_NO_IMAGE_MODAL ); } /** - * @param \MediaTransformOutput $img + * @param MediaTransformOutput $img * @param array $sanitizedFrameParams * * @return string */ - private function buildContentImageString( $img, $sanitizedFrameParams ) { + private function buildContentImageString( MediaTransformOutput $img, array $sanitizedFrameParams ): string { $imgParams = [ 'alt' => $sanitizedFrameParams['alt'], 'title' => $sanitizedFrameParams['title'], @@ -446,7 +458,7 @@ private function buildContentImageString( $img, $sanitizedFrameParams ) { * * @return string */ - private function generateButtonToSource( $title, $handlerParams ) { + private function generateButtonToSource( Title $title, array $handlerParams ): string { $url = $title->getLocalURL(); if ( isset( $handlerParams['page'] ) ) { $url = wfAppendQuery( $url, [ 'page' => $handlerParams['page'] ] ); @@ -471,7 +483,7 @@ private function generateButtonToSource( $title, $handlerParams ) { * * @return string */ - private function preventModalInception( $text ) { + private function preventModalInception( string $text ): string { if ( preg_match( '~div class="modal-dialog.+div class="modal-content.+div class="modal-body.+' . '(]*/>).+ class="modal-footer.+~Ds', $text, $matches ) ) { @@ -485,7 +497,7 @@ private function preventModalInception( $text ) { * * @return string */ - private function sanitizeCaption( $caption ) { + private function sanitizeCaption( string $caption ): string { return preg_replace( '/([^\n])\n([^\n])/m', '\1\2', $caption ); } } diff --git a/src/ImageModalTrigger.php b/src/ImageModalTrigger.php index cce6f20..0555c8b 100644 --- a/src/ImageModalTrigger.php +++ b/src/ImageModalTrigger.php @@ -24,7 +24,7 @@ * @author Tobias Oetterer */ -namespace BootstrapComponents; +namespace MediaWiki\Extension\BootstrapComponents; use Config; use ConfigException; diff --git a/src/LuaLibrary.php b/src/LuaLibrary.php index 10a8d0b..0a0266a 100644 --- a/src/LuaLibrary.php +++ b/src/LuaLibrary.php @@ -24,8 +24,9 @@ * @author Tobias Oetterer */ -namespace BootstrapComponents; +namespace MediaWiki\Extension\BootstrapComponents; +use MediaWiki\MediaWikiServices; use MWException; use ReflectionClass; use ReflectionException; @@ -46,6 +47,11 @@ class LuaLibrary extends Scribunto_LuaLibraryBase { */ private ApplicationFactory $applicationFactory; + /** + * @var BootstrapComponentsService + */ + private BootstrapComponentsService $bootstrapComponentService; + /** * LuaLibrary constructor. * @@ -54,13 +60,13 @@ class LuaLibrary extends Scribunto_LuaLibraryBase { public function __construct( Scribunto_LuaEngine $engine ) { parent::__construct( $engine ); $this->applicationFactory = ApplicationFactory::getInstance(); + $this->bootstrapComponentService = MediaWikiServices::getInstance()->getService( 'BootstrapComponentsService' ); } /** * @return array */ - public function register(): array - { + public function register(): array { $lib = [ 'parse' => [ $this, 'parse' ], 'getSkin' => [ $this, 'getSkin' ], @@ -82,12 +88,11 @@ public function register(): array * * @throws ReflectionException */ - public function parse( ?string $componentName, ?string $input, ?array $arguments, ?bool $noStrip = false ): array - { + public function parse( ?string $componentName, ?string $input, ?array $arguments, ?bool $noStrip = false ): array { if ( empty( $componentName ) ) { return [ wfMessage( 'bootstrap-components-lua-error-no-component' )->text() ]; } - $componentLibrary = $this->getApplicationFactory()->getComponentLibrary(); + $componentLibrary = MediaWikiServices::getInstance()->getService( 'BootstrapComponents.ComponentLibrary' ); if ( !in_array( $componentName, $componentLibrary->getRegisteredComponents() ) ) { return [ wfMessage( 'bootstrap-components-lua-error-invalid-component', $componentName )->text() ]; } @@ -108,26 +113,24 @@ public function parse( ?string $componentName, ?string $input, ?array $arguments } /** - * @throws MWException - * * @return string[] */ - public function getSkin(): array - { - return [ $this->getApplicationFactory()->getParserOutputHelper( $this->getParser() )->getNameOfActiveSkin() ]; + public function getSkin(): array { + return [ $this->getBootstrapComponentsService()->getNameOfActiveSkin() ]; } /** * @param string $input - * @param null|string|array $arguments + * @param array|string|null $arguments * @param null|string $component * - * @throws MWException - * * @return ParserRequest + *@throws MWException + * */ - protected function buildParserRequest( string $input, $arguments, ?string $component = null ): ParserRequest - { + protected function buildParserRequest( + string $input, array|string|null $arguments, ?string $component = null + ): ParserRequest { // prepare the arguments array $parserRequestArguments = $this->processLuaArguments( $arguments ); array_unshift( $parserRequestArguments, $input ); @@ -150,9 +153,9 @@ protected function getComponent( string $componentClass ): AbstractComponent { /** @var AbstractComponent $component */ $component = $objectReflection->newInstanceArgs( [ - $this->getApplicationFactory()->getComponentLibrary(), + MediaWikiServices::getInstance()->getService( 'BootstrapComponents.ComponentLibrary' ), $this->getApplicationFactory()->getParserOutputHelper( $this->getParser() ), - $this->getApplicationFactory()->getNestingController(), + MediaWikiServices::getInstance()->getService( 'BootstrapComponents.NestingController' ), ] ); return $component; @@ -161,11 +164,18 @@ protected function getComponent( string $componentClass ): AbstractComponent { /** * @return ApplicationFactory */ - protected function getApplicationFactory(): ApplicationFactory - { + protected function getApplicationFactory(): ApplicationFactory { return $this->applicationFactory; } + /** + * @return BootstrapComponentsService + */ + protected function getBootstrapComponentsService(): BootstrapComponentsService + { + return $this->bootstrapComponentService; + } + /** * Takes the $arguments passed from lua and pre-processes them: make sure, * we have a sequence array (not associative) @@ -174,8 +184,7 @@ protected function getApplicationFactory(): ApplicationFactory * * @return array */ - private function processLuaArguments( $arguments ): array - { + private function processLuaArguments( $arguments ): array { // make sure, we have an array of parameters if ( !is_array( $arguments ) ) { $arguments = preg_split( "/(?<=[^\|])\|(?=[^\|])/", $arguments ); diff --git a/src/ModalBuilder.php b/src/ModalBuilder.php index 958a9e8..1e89bfe 100644 --- a/src/ModalBuilder.php +++ b/src/ModalBuilder.php @@ -24,7 +24,7 @@ * @author Tobias Oetterer */ -namespace BootstrapComponents; +namespace MediaWiki\Extension\BootstrapComponents; use \Html; @@ -135,9 +135,9 @@ public static function wrapTriggerElement( $element, $id ) { * * Takes $id, $trigger and $content and produces a modal with the html id $id, using $content as the * body content of the opening modal. For trigger, you can use a generic html code and wrap it in - * {@see \BootstrapComponents\ModalBase::wrapTriggerElement}, or you make sure you generate + * {@see ModalBase::wrapTriggerElement}, or you make sure you generate * a correct trigger for yourself, using the necessary attributes and especially the id, you supplied - * here (see {@see \BootstrapComponents\Components\Modal::generateButton} for example). + * here (see {@see Components\Modal::generateButton} for example). * * Do not instantiate directly, but use {@see ApplicationFactory::getNewModalBuilder} * instead. @@ -147,8 +147,8 @@ public static function wrapTriggerElement( $element, $id ) { * @param string $content must be fully parsed html (use {@see Parser::recursiveTagParseFully}) * @param ParserOutputHelper $parserOutputHelper * - *@see ApplicationFactory::getNewModalBuilder - * @see \BootstrapComponents\Components\Modal::generateButton + * @see ApplicationFactory::getNewModalBuilder + * @see Components\Modal::generateButton * */ diff --git a/src/NestableInterface.php b/src/NestableInterface.php index 8e650b5..ccf62d9 100644 --- a/src/NestableInterface.php +++ b/src/NestableInterface.php @@ -24,7 +24,7 @@ * @author Tobias Oetterer */ -namespace BootstrapComponents; +namespace MediaWiki\Extension\BootstrapComponents; /** * Interface Nestable @@ -47,4 +47,4 @@ public function getComponentName(); * @return string */ public function getId(); -} \ No newline at end of file +} diff --git a/src/NestingController.php b/src/NestingController.php index 0273c4e..b1b670b 100644 --- a/src/NestingController.php +++ b/src/NestingController.php @@ -24,9 +24,10 @@ * @author Tobias Oetterer */ -namespace BootstrapComponents; +namespace MediaWiki\Extension\BootstrapComponents; -use \MWException; +use MediaWiki\MediaWikiServices; +use MWException; /** * Class NestingController @@ -43,7 +44,7 @@ class NestingController { * * @var array $autoincrementPerComponent */ - private $autoincrementPerComponent; + private array $autoincrementPerComponent; /** * Holds information about the bootstrap component stack, so that components @@ -53,14 +54,13 @@ class NestingController { * * @var array $componentStack */ - private $componentStack; + private array $componentStack; /** * NestingController constructor. * - * Do not instantiate directly, but use {@see ApplicationFactory::getNestingController} instead. - * - * @see ApplicationFactory::getNestingController + * Do not instantiate directly, but use {@see MediaWikiServices::get()} with argument + * 'NestingController' instead. */ public function __construct() { $this->autoincrementPerComponent = []; @@ -74,7 +74,7 @@ public function __construct() { * * @throws MWException if current and closing component is different */ - public function close( $id ) { + public function close( $id ): void { $current = $this->getCurrentElement(); if ( !$current ) { throw new MWException( 'Nesting error. Tried to close an empty stack.' ); @@ -92,7 +92,7 @@ public function close( $id ) { * * @return string */ - public function generateUniqueId( $componentName ) { + public function generateUniqueId( string $componentName ): string { if ( !isset( $this->autoincrementPerComponent[$componentName] ) ) { $this->autoincrementPerComponent[$componentName] = 0; } @@ -102,6 +102,8 @@ public function generateUniqueId( $componentName ) { /** * Returns a reference to the last opened component. * + * @note do not declare a return type for it will break unit tests. + * * @return false|NestableInterface */ public function getCurrentElement() { @@ -113,7 +115,7 @@ public function getCurrentElement() { * * @return int */ - public function getStackSize() { + public function getStackSize(): int { return count( $this->componentStack ); } @@ -124,10 +126,10 @@ public function getStackSize() { * * @throws MWException when open is called with an invalid object */ - public function open( &$nestable ) { + public function open( mixed &$nestable ): void { if ( !$nestable instanceof NestableInterface ) { throw new MWException( 'Nesting error. Trying to put an object other than a Component an the nesting stack.' ); } - array_push( $this->componentStack, $nestable ); + $this->componentStack[] = $nestable; } -} \ No newline at end of file +} diff --git a/src/ParserOutputHelper.php b/src/ParserOutputHelper.php index 96a53a9..235fac5 100644 --- a/src/ParserOutputHelper.php +++ b/src/ParserOutputHelper.php @@ -24,12 +24,10 @@ * @author Tobias Oetterer */ -namespace BootstrapComponents; +namespace MediaWiki\Extension\BootstrapComponents; use \Html; -use \MediaWiki\MediaWikiServices; use \ParserOutput; -use \RequestContext; use \Title; /** @@ -41,21 +39,6 @@ */ class ParserOutputHelper { - /** - * @var string - */ - const EXTENSION_DATA_DEFERRED_CONTENT_KEY = 'bsc_deferredContent'; - - /** - * @var string - */ - const INJECTION_PREFIX = ''; - - /** - * @var string - */ - const INJECTION_SUFFIX = ''; - /** * To make sure, we only add the tracking category once. * @@ -70,13 +53,6 @@ class ParserOutputHelper { */ private $articleTrackedOnError; - /** - * Holds the name of the skin we use (or false, if there is no skin). - * - * @var string $nameOfActiveSkin - */ - private $nameOfActiveSkin; - /** * @var \Parser $parser */ @@ -96,9 +72,6 @@ public function __construct( $parser ) { $this->articleTracked = false; $this->articleTrackedOnError = false; $this->parser = $parser; - $this->nameOfActiveSkin = $this->detectSkinInUse( - defined( 'MW_NO_SESSION' ) - ); } /** @@ -116,6 +89,8 @@ public function addErrorTrackingCategory() { * Adds the supplied modules to the parser output. * * @param array $modulesToAdd + * + * @deprecated use \MediaWiki\Extension\BootstrapComponents\BootstrapComponentsService::registerComponentAsActive */ public function addModules( $modulesToAdd ) { $parserOutput = $this->getParser()->getOutput(); @@ -147,33 +122,8 @@ public function addTrackingCategory() { * @return bool|null */ public function areImageModalsSuppressed() { - return $this->getParser()->getOutput()->getExtensionData( 'bsc_no_image_modal' ); - } - - /** - * Returns the raw html that is be inserted at the end of the page. - * - * @param \ParserOutput $parserOutput - * - * @return string - */ - public function getContentForLaterInjection( \ParserOutput $parserOutput ): string { - $deferredContent = $parserOutput->getExtensionData( self::EXTENSION_DATA_DEFERRED_CONTENT_KEY ); - - if ( empty( $deferredContent ) || !is_array( $deferredContent ) ) { - return ''; - } - - // clearing extension data for unit and integration tests to work - $parserOutput->setExtensionData( self::EXTENSION_DATA_DEFERRED_CONTENT_KEY, null ); - return self::INJECTION_PREFIX . implode( array_values( $deferredContent ) ) . self::INJECTION_SUFFIX; - } - - /** - * @return string - */ - public function getNameOfActiveSkin() { - return $this->nameOfActiveSkin; + return $this->getParser()->getOutput() + ->getExtensionData( BootstrapComponents::EXTENSION_DATA_NO_IMAGE_MODAL ); } /** @@ -190,28 +140,16 @@ public function injectLater( $id, $rawHtml ) { return $this; } if ( !empty( $rawHtml ) ) { - $deferredContent = $this->getParser()->getOutput()->getExtensionData( self::EXTENSION_DATA_DEFERRED_CONTENT_KEY ); + $deferredContent = $this->getParser()->getOutput()->getExtensionData( BootstrapComponents::EXTENSION_DATA_DEFERRED_CONTENT_KEY ); if ( empty( $deferredContent ) ) { $deferredContent = []; } $deferredContent[$id] = $rawHtml; - $this->getParser()->getOutput()->setExtensionData( self::EXTENSION_DATA_DEFERRED_CONTENT_KEY, $deferredContent ); + $this->getParser()->getOutput()->setExtensionData( BootstrapComponents::EXTENSION_DATA_DEFERRED_CONTENT_KEY, $deferredContent ); } return $this; } - /** - * Adds the bootstrap modules and styles to the page, if not done already - * @todo augment signature to include bsModule to add via BootstrapManager::getInstance()->addBootstrapModule(); - */ - public function loadBootstrapModules() { - $parserOutput = $this->getParser()->getOutput(); - if ( is_a( $parserOutput, ParserOutput::class ) ) { - // Q: when do we expect \Parser->getOutput() not to be a \ParserOutput? A: During tests. - $parserOutput->setExtensionData( 'bsc_load_modules', true ); - } - } - /** * Formats a text as error text, so it can be added to the output. * @@ -231,15 +169,6 @@ public function renderErrorMessage( $errorMessageName ) { ); } - /** - * Returns true, if active skin is vector - * - * @return bool - */ - public function vectorSkinInUse() { - return in_array( strtolower( $this->getNameOfActiveSkin() ), [ 'vector', 'vector-2022' ] ) ; - } - /** * @return \Parser */ @@ -247,27 +176,6 @@ protected function getParser() { return $this->parser; } - /** - * @param bool $useConfig set to true, if we can't rely on {@see \RequestContext::getSkin} - * - * @return string - */ - private function detectSkinInUse( $useConfig = false ) { - if ( !$useConfig ) { - $skin = RequestContext::getMain()->getSkin(); - } - if ( !empty( $skin ) && is_a( $skin, 'Skin' ) ) { - return $skin->getSkinName(); - } - $mainConfig = MediaWikiServices::getInstance()->getMainConfig(); - try { - $defaultSkin = $mainConfig->get( 'DefaultSkin' ); - } catch ( \ConfigException $e ) { - $defaultSkin = 'unknown'; - } - return empty( $defaultSkin ) ? 'unknown' : $defaultSkin; - } - /** * Adds current page to the indicated tracking category, if not done already. * @@ -280,11 +188,7 @@ private function placeTrackingCategory( $trackingCategoryMessageName ) { // Q: when do we expect \Parser->getOutput() no to be a \ParserOutput? A:During tests. $cat = Title::makeTitleSafe( NS_CATEGORY, $categoryMessage->text() ); if ( $cat ) { - if ( version_compare( $GLOBALS['wgVersion'], '1.38', 'lt' ) ) { - $sort = (string)$parserOutput->getProperty('defaultsort') ?? ''; - } else { - $sort = (string)$parserOutput->getPageProperty('defaultsort') ?? ''; - } + $sort = (string)$parserOutput->getPageProperty('defaultsort') ?? ''; $parserOutput->addCategory( $cat->getDBkey(), $sort ); } else { wfDebug( __METHOD__ . ": [[MediaWiki:{$trackingCategoryMessageName}]] is not a valid title!\n" ); diff --git a/src/ParserRequest.php b/src/ParserRequest.php index 3cfe377..20dfa3f 100644 --- a/src/ParserRequest.php +++ b/src/ParserRequest.php @@ -24,9 +24,11 @@ * @author Tobias Oetterer */ -namespace BootstrapComponents; +namespace MediaWiki\Extension\BootstrapComponents; use MWException; +use Parser; +use PPFrame; /** * Class ParserRequest @@ -39,22 +41,22 @@ class ParserRequest { /** * @var string[] $attributes */ - private $attributes; + private array $attributes; /** * @var string $input */ - private $input; + private string $input; /** - * @var \PPFrame $frame + * @var PPFrame|null $frame */ - private $frame; + private PPFrame|null $frame; /** - * @var \Parser $parser + * @var Parser $parser */ - private $parser; + private Parser $parser; /** * ParserRequest constructor. @@ -70,13 +72,20 @@ class ParserRequest { * * @throws MWException */ - public function __construct( $argumentsPassedByParser, $isParserFunction, $componentName = 'unknown' ) { - list( $this->input, $attributes, $this->parser, $this->frame ) = + public function __construct( + array $argumentsPassedByParser, bool $isParserFunction, string $componentName = 'unknown' + ) { + list( $this->input, $attributes, $parser, $frame ) = $this->processArguments( $argumentsPassedByParser, $isParserFunction, $componentName ); $this->attributes = (array)$attributes; - if ( !$this->parser || !is_a( $this->parser, 'Parser' ) ) { + if ( !$parser || !is_a( $parser, 'Parser' ) ) { throw new MWException( 'Invalid parser object passed to component ' . $componentName . '!' ); } + if ( $frame && !is_a( $frame, 'PPFrame' ) ) { + throw new MWException( 'Invalid frame object passed to component ' . $componentName . '!' ); + } + $this->parser = $parser; + $this->frame = $frame; } /** @@ -84,7 +93,7 @@ public function __construct( $argumentsPassedByParser, $isParserFunction, $compo * * @return string[] associative array `attribute => value` */ - public function getAttributes() { + public function getAttributes(): array { return $this->attributes; } @@ -99,25 +108,25 @@ public function getAttributes() { * * @return string */ - public function getInput() { + public function getInput(): string { return $this->input; } /** * Tag extensions supply a frame. * - * @return \PPFrame + * @return null|PPFrame */ - public function getFrame() { + public function getFrame(): PPFrame|null { return $this->frame; } /** * This is the parser object passed to the parser function or the tag extension. * - * @return \Parser + * @return Parser */ - public function getParser() { + public function getParser(): Parser { return $this->parser; } @@ -136,14 +145,16 @@ public function getParser() { * @throws MWException * @return array $results */ - private function extractParserFunctionOptions( $options, $componentName ) { + private function extractParserFunctionOptions( array $options, string $componentName ): array { if ( empty( $options ) ) { return []; } $results = []; foreach ( $options as $option ) { if ( !is_string( $option ) ) { - throw new MWException( 'Arguments passed to bootstrap component "' . $componentName . '" are invalid!' ); + throw new MWException( + 'Arguments passed to bootstrap component "' . $componentName . '" are invalid!' + ); } list( $key, $value ) = $this->getKeyValuePairFrom( $option ); if ( strlen( $key ) ) { @@ -158,7 +169,7 @@ private function extractParserFunctionOptions( $options, $componentName ) { * * @return string[] */ - private function getKeyValuePairFrom( $option ) { + private function getKeyValuePairFrom( string $option ): array { $pair = explode( '=', $option, 2 ); @@ -186,11 +197,12 @@ private function getKeyValuePairFrom( $option ) { * @throws MWException if argument list does not match handler type or unknown handler type detected * @return array array consisting of (string) $input, (array) $options, (Parser) $parser, and optional (PPFrame) $frame */ - private function processArguments( $argumentsPassedByParser, $isParserFunction, $componentName ) { - $argumentsPassedByParser = (array)$argumentsPassedByParser; + private function processArguments( + array $argumentsPassedByParser, bool $isParserFunction, string $componentName + ): array { if ( $isParserFunction ) { $parser = array_shift( $argumentsPassedByParser ); - $input = isset( $argumentsPassedByParser[0] ) ? $argumentsPassedByParser[0] : ''; + $input = $argumentsPassedByParser[0] ?? ''; unset( $argumentsPassedByParser[0] ); $attributes = $this->extractParserFunctionOptions( $argumentsPassedByParser, $componentName ); @@ -198,9 +210,11 @@ private function processArguments( $argumentsPassedByParser, $isParserFunction, return [ $input, $attributes, $parser, null ]; } else { if ( count( $argumentsPassedByParser ) != 4 ) { - throw new MWException( 'Argument list passed to bootstrap tag component "' . $componentName . '" is invalid!' ); + throw new MWException( + 'Argument list passed to bootstrap tag component "' . $componentName . '" is invalid!' + ); } return $argumentsPassedByParser; } } -} \ No newline at end of file +} diff --git a/src/ServiceWiring.php b/src/ServiceWiring.php new file mode 100644 index 0000000..3f50158 --- /dev/null +++ b/src/ServiceWiring.php @@ -0,0 +1,32 @@ + + static function ( MediaWikiServices $services ): BootstrapComponentsService { + return new BootstrapComponentsService( + $services->getMainConfig() + ); + }, + 'BootstrapComponents.ComponentLibrary' => + static function ( MediaWikiServices $services ): ComponentLibrary { + $myConfig = $services->getConfigFactory()->makeConfig('BootstrapComponents'); + $whileList = $myConfig->has( 'BootstrapComponentsWhitelist' ) + ?$myConfig->get( 'BootstrapComponentsWhitelist' ) : true; + return new ComponentLibrary( $whileList ); + }, + 'BootstrapComponents.NestingController' => + static function ( MediaWikiServices $services ): NestingController { + return new NestingController(); + }, +]; diff --git a/tests/PhpUnitEnvironment.php b/tests/PhpUnitEnvironment.php index 4e94066..f4dbc06 100644 --- a/tests/PhpUnitEnvironment.php +++ b/tests/PhpUnitEnvironment.php @@ -1,6 +1,6 @@ addPsr4( 'BootstrapComponents\\Tests\\Unit\\', __DIR__ . '/phpunit/Unit' ); #$autoloader->addPsr4( 'BootstrapComponents\\Tests\\Integration\\', __DIR__ . '/phpunit/Integration' ); -$autoloader->addPsr4( 'BootstrapComponents\\Tests\\', __DIR__ . '/phpunit' ); +$autoloader->addPsr4( 'Mediawiki\\Extension\\BootstrapComponents\\Tests\\', __DIR__ . '/phpunit' ); @include_once __DIR__ . '/phpunit/Unit/ComponentsTestBase.php'; return $autoloader; diff --git a/tests/bootstrap.php b/tests/bootstrap.php index e568de7..9f8ffe0 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -25,13 +25,13 @@ require __DIR__ . '/TestInfoScreen.php'; require __DIR__ . '/PhpUnitEnvironment.php'; -$phpUnitEnvironment = new \BootstrapComponents\Tests\PHPUnitEnvironment(); +$phpUnitEnvironment = new MediaWiki\Extension\BootstrapComponents\Tests\PHPUnitEnvironment(); if ( $phpUnitEnvironment->hasDebugRequest( $GLOBALS['argv'] ) === false ) { $phpUnitEnvironment->emptyDebugVars(); } -$testInfoScreen = new \BootstrapComponents\Tests\TestInfoScreen( 25 ); +$testInfoScreen = new MediaWiki\Extension\BootstrapComponents\Tests\TestInfoScreen( 25 ); $testInfoScreen->addInfoToBlock( "MediaWiki:", $phpUnitEnvironment->getSoftwareInfo( 'mw' ) ); $testInfoScreen->addInfoToBlock( "Bootstrap:", $phpUnitEnvironment->getSoftwareInfo( 'Bootstrap' ) ); diff --git a/tests/phpunit/ExecutionTimeTestListener.php b/tests/phpunit/ExecutionTimeTestListener.php index 8b7d453..58fa0fc 100644 --- a/tests/phpunit/ExecutionTimeTestListener.php +++ b/tests/phpunit/ExecutionTimeTestListener.php @@ -1,6 +1,6 @@ originalSetting = $this->prepareConfig( $prefix ); + $this->settings = $this->originalSetting; + } + + public function get( $name ) { + if ( !$this->has( $name ) ) { + throw new ConfigException( __METHOD__ . ": undefined option: '$name'" ); + } + + return $this->settings[$name]; + } + + public function getIterator(): Traversable { + return new ArrayIterator( $this->settings ); + } + + public function getNames(): array { + return array_keys( $this->settings ); + } + + public function has( $name ): bool { + return array_key_exists( $name, $this->settings ); + } + + public function reset(): array { + return $this->settings = $this->originalSetting; + } + + /** + * @see MutableConfig::set + * @param string $name + * @param mixed $value + */ + public function set( $name, $value ): void { + $this->settings[$name] = $value; + } + + protected function prepareConfig( string $prefix): array { + $config = []; + foreach ( $GLOBALS as $key => $value ) { + $matches = []; + if ( preg_match( '/^' . $prefix . '(.+)$/', $key, $matches ) ) { + $config[$matches[1]] = $value; + } + } + return $config; + } +} diff --git a/tests/phpunit/Integration/I18nJsonFileIntegrityTest.php b/tests/phpunit/Integration/I18nJsonFileIntegrityTest.php index 2ee7f4f..6725451 100644 --- a/tests/phpunit/Integration/I18nJsonFileIntegrityTest.php +++ b/tests/phpunit/Integration/I18nJsonFileIntegrityTest.php @@ -1,6 +1,6 @@ clear(); - $hookCallbackList = $hookRegistry->buildHookCallbackListFor( - HookRegistry::AVAILABLE_HOOKS - ); - $hookRegistry->register( $hookCallbackList ); + // hook registry was removed in 5.2.0 + #$hookRegistry = new HookRegistry(); + #$hookRegistry->clear(); + #$hookCallbackList = $hookRegistry->buildHookCallbackListFor( + # HookRegistry::AVAILABLE_HOOKS + #); + #$hookRegistry->register( $hookCallbackList ); } /** diff --git a/tests/phpunit/Integration/JSONScript/readmeContentsBuilder.php b/tests/phpunit/Integration/JSONScript/readmeContentsBuilder.php index 49caff3..f58e29e 100644 --- a/tests/phpunit/Integration/JSONScript/readmeContentsBuilder.php +++ b/tests/phpunit/Integration/JSONScript/readmeContentsBuilder.php @@ -1,6 +1,6 @@ assertInstanceOf( - 'BootstrapComponents\\ApplicationFactory', + ApplicationFactory::class, new ApplicationFactory() ); } @@ -36,7 +39,7 @@ public function testCanConstruct() { public function testGetApplicationAndReset( $application ) { $instance = new ApplicationFactory(); $this->assertInstanceOf( - 'BootstrapComponents\\' . $application, + 'MediaWiki\\Extension\\BootstrapComponents\\' . $application, call_user_func( [ $instance, 'get' . $application ] ) ); $this->assertTrue( @@ -44,12 +47,12 @@ public function testGetApplicationAndReset( $application ) { ); // again $this->assertInstanceOf( - 'BootstrapComponents\\' . $application, + 'MediaWiki\\Extension\\BootstrapComponents\\' . $application, call_user_func( [ $instance, 'get' . $application ] ) ); // and again $this->assertInstanceOf( - 'BootstrapComponents\\' . $application, + 'MediaWiki\\Extension\\BootstrapComponents\\' . $application, call_user_func( [ $instance, 'get' . $application ] ) ); } @@ -58,23 +61,20 @@ public function testGetNewAttributeManager() { $instance = new ApplicationFactory(); $this->assertInstanceOf( - 'BootstrapComponents\\AttributeManager', + 'MediaWiki\\Extension\\BootstrapComponents\\AttributeManager', $instance->getNewAttributeManager( [], [] ) ); } public function testGetNewModalBuilder() { - $parserOutputHelper = $this->getMockBuilder( 'BootstrapComponents\\ParserOutputHelper' ) - ->disableOriginalConstructor() - ->getMock(); + $parserOutputHelper = $this->createMock( ParserOutputHelper::class ); $factory = new ApplicationFactory(); - /** @noinspection PhpParamsInspection */ $modalBuilder = $factory->getNewModalBuilder( '', '', '', $parserOutputHelper ); $this->assertInstanceOf( - 'BootstrapComponents\\ModalBuilder', + ModalBuilder::class, $modalBuilder ); } @@ -91,7 +91,7 @@ public function testGetNewParserRequest( $arguments, $isParserFunction ) { $instance = new ApplicationFactory(); $this->assertInstanceOf( - 'BootstrapComponents\\ParserRequest', + ParserRequest::class, $instance->getNewParserRequest( $arguments, $isParserFunction ) ); } @@ -106,7 +106,7 @@ public function testGetParserOutputHelper() { ->disableOriginalConstructor() ->getMock(); $this->assertInstanceOf( - 'BootstrapComponents\\ParserOutputHelper', + ParserOutputHelper::class, $instance->getParserOutputHelper( $parser ) ); } @@ -117,7 +117,7 @@ public function testGetParserOutputHelper() { * * @dataProvider parserRequestFailureProvider */ - public function testFailingGetNewParserRequest( $arguments, $isParserFunction ) { + public function testFailingGetNewParserRequest( array $arguments, bool $isParserFunction ) { $instance = new ApplicationFactory(); $this->expectException( 'MWException' ); @@ -165,12 +165,12 @@ public function testCanResetLookup() { } /** + * Why have this provider? once there were more applications that could be requested from ApplicationFactory. + * * @return array[] */ - public function applicationNameProvider() { + public function applicationNameProvider(): array { return [ - 'ComponentLibrary' => [ 'ComponentLibrary' ], - 'NestingController' => [ 'NestingController' ], 'ParserOutputHelper' => [ 'ParserOutputHelper' ], ]; } @@ -178,7 +178,7 @@ public function applicationNameProvider() { /** * @return array[] */ - public function parserRequestProvider() { + public function parserRequestProvider(): array { $parser = $this->getMockBuilder( 'Parser' ) ->disableOriginalConstructor() ->getMock(); @@ -200,7 +200,7 @@ public function parserRequestProvider() { /** * @return array[] */ - public function parserRequestFailureProvider() { + public function parserRequestFailureProvider(): array { $parser = $this->getMockBuilder( 'Parser' ) ->disableOriginalConstructor() ->getMock(); diff --git a/tests/phpunit/Unit/AttributeManagerTest.php b/tests/phpunit/Unit/AttributeManagerTest.php index 01e0eba..4c1f0aa 100644 --- a/tests/phpunit/Unit/AttributeManagerTest.php +++ b/tests/phpunit/Unit/AttributeManagerTest.php @@ -1,12 +1,12 @@ assertInstanceOf( - 'BootstrapComponents\\AttributeManager', + 'MediaWiki\\Extension\\BootstrapComponents\\AttributeManager', new AttributeManager( [], [] ) ); } diff --git a/tests/phpunit/Unit/BootstrapComponentServiceTest.php b/tests/phpunit/Unit/BootstrapComponentServiceTest.php new file mode 100644 index 0000000..fa3d70d --- /dev/null +++ b/tests/phpunit/Unit/BootstrapComponentServiceTest.php @@ -0,0 +1,88 @@ +assertInstanceOf( + BootstrapComponentsService::class, + new BootstrapComponentsService( $this->getMockBuilder( Config::class )->getMock() ) + ); + } + + + public function testCanGetNameOfActiveSkin() { + $instance = new BootstrapComponentsService( $this->getMockBuilder( Config::class )->getMock() ); + + $this->assertEquals( + 'vector', + $instance->getNameOfActiveSkin() + ); + } + + public function testRegisterModules() { + $instance = new BootstrapComponentsService( $this->getMockBuilder( Config::class )->getMock() ); + + $instance->registerComponentAsActive( 'Foo' ); + $instance->registerComponentAsActive( 'Bar' ); + $instance->registerComponentAsActive( 'modal' ); + + $this->assertEquals( + ['Foo', 'Bar', 'modal'], + $instance->getActiveComponents() + ); + } + + public function testVectorSkinInUse() { + $instance = new BootstrapComponentsService( $this->getMockBuilder( Config::class )->getMock() ); + $this->assertIsBool( $instance->vectorSkinInUse() ); + } + + /** + * @throws ReflectionException + */ + public function testPrivateCanDetectSkinInUse() { + $config = new TestConfig(); + $instance = new BootstrapComponentsService( $config ); + + $reflection = new ReflectionClass( BootstrapComponentsService::class ); + $method = $reflection->getMethod( 'detectSkinInUse' ); + + // this is default + $this->assertEquals( + 'vector', + $method->invokeArgs( $instance, [ false ] ) + ); + + $config->set( 'DefaultSkin', 'serenity' ); + // this was introduced due to issue #9 + $this->assertEquals( + 'serenity', + $method->invokeArgs( $instance, [ true ] ) + ); + $config->reset(); + } +} diff --git a/tests/phpunit/Unit/CarouselGalleryTest.php b/tests/phpunit/Unit/CarouselGalleryTest.php index debce56..be566f6 100644 --- a/tests/phpunit/Unit/CarouselGalleryTest.php +++ b/tests/phpunit/Unit/CarouselGalleryTest.php @@ -1,15 +1,15 @@ assertInstanceOf( - 'BootstrapComponents\\CarouselGallery', + 'MediaWiki\\Extension\\BootstrapComponents\\CarouselGallery', new CarouselGallery( 'carousel' ) ); } @@ -40,7 +40,7 @@ public function testCanConstruct() { * @dataProvider galleryDataProvider */ public function testToHtml( $imageList, $additionalAttributes, $expectedOutput ) { - $parserOutputHelper = $this->getMockBuilder( 'BootstrapComponents\\ParserOutputHelper' ) + $parserOutputHelper = $this->getMockBuilder( 'MediaWiki\\Extension\\BootstrapComponents\\ParserOutputHelper' ) ->disableOriginalConstructor() ->getMock(); $parserOutputHelper->expects( $this->any() ) diff --git a/tests/phpunit/Unit/ComponentLibraryTest.php b/tests/phpunit/Unit/ComponentLibraryTest.php index faa6a15..970aae4 100644 --- a/tests/phpunit/Unit/ComponentLibraryTest.php +++ b/tests/phpunit/Unit/ComponentLibraryTest.php @@ -1,12 +1,13 @@ assertEquals( [ 'bootstrap_badge' => [ 0, 'bootstrap_badge' ], @@ -58,12 +59,12 @@ public function testCanCompileMagicWordsArray() { /** * @param string $componentName * - * @throws \ConfigException + * @throws ConfigException * * @dataProvider componentNameAndClassProvider */ - public function testIsRegistered( $componentName ) { - $instance = new ComponentLibrary(); + public function testIsRegistered( string $componentName ) { + $instance = new ComponentLibrary( true ); $this->assertEquals( true, $instance->isRegistered( $componentName ) @@ -74,12 +75,12 @@ public function testIsRegistered( $componentName ) { * @param string $component * @param string[] $expectedAliases * - * @throws \ConfigException + * @throws ConfigException * @throws \MWException * * @dataProvider componentAliasesProvider */ - public function testGetAliasesFor( $component, $expectedAliases ) { + public function testGetAliasesFor( string $component, array $expectedAliases ) { $instance = new ComponentLibrary(); $this->assertEquals( $expectedAliases, @@ -91,12 +92,12 @@ public function testGetAliasesFor( $component, $expectedAliases ) { * @param string $component * @param string[] $expectedAttributes * - * @throws \ConfigException + * @throws ConfigException * @throws \MWException * * @dataProvider componentAttributesProvider */ - public function testGetAttributesFor( $component, $expectedAttributes ) { + public function testGetAttributesFor( string $component, array $expectedAttributes ) { $instance = new ComponentLibrary(); $this->assertEquals( $expectedAttributes, @@ -108,11 +109,11 @@ public function testGetAttributesFor( $component, $expectedAttributes ) { * @param string $componentName * @param string $componentClass * - * @throws \ConfigException + * @throws ConfigException * * @dataProvider componentNameAndClassProvider */ - public function testGetClassFor( $componentName, $componentClass ) { + public function testGetClassFor( string $componentName, string $componentClass ) { $instance = new ComponentLibrary(); $this->assertEquals( $componentClass, @@ -121,12 +122,14 @@ public function testGetClassFor( $componentName, $componentClass ) { } /** - * @throws \ConfigException + * @throws ConfigException */ public function testGetAllRegisteredComponents() { $instance = new ComponentLibrary(); + $allKeys = array_keys( $this->componentNameAndClassProvider() ); + sort( $allKeys ); $this->assertEquals( - array_keys( $this->componentNameAndClassProvider() ), + $allKeys, $instance->getRegisteredComponents() ); } @@ -134,11 +137,11 @@ public function testGetAllRegisteredComponents() { /** * @param string $componentName * - * @throws \ConfigException + * @throws ConfigException * * @dataProvider componentNameAndClassProvider */ - public function testGetHandlerTypeFor( $componentName ) { + public function testGetHandlerTypeFor( string $componentName ) { $instance = new ComponentLibrary(); $this->assertContains( @@ -148,7 +151,7 @@ public function testGetHandlerTypeFor( $componentName ) { } /** - * @throws \ConfigException + * @throws ConfigException */ public function testGetHandlerTypeForUnknownComponent() { $instance = new ComponentLibrary(); @@ -163,11 +166,11 @@ public function testGetHandlerTypeForUnknownComponent() { * @param string $componentName * @param bool $isParserFunction * - * @throws \ConfigException + * @throws ConfigException * * @dataProvider handlerTypeProvider */ - public function testIsHandlerType( $componentName, $isParserFunction ) { + public function testIsHandlerType( string $componentName, bool $isParserFunction ) { $instance = new ComponentLibrary(); $this->assertTrue( @@ -180,14 +183,14 @@ public function testIsHandlerType( $componentName, $isParserFunction ) { /** * @param string $componentName - * @param string $skinName + * @param string|null $skinName * @param array $expectedModules * - * @throws \ConfigException + * @throws ConfigException * * @dataProvider modulesForComponentsProvider */ - public function testGetModulesFor( $componentName, $skinName, $expectedModules ) { + public function testGetModulesFor( string $componentName, ?string $skinName, array $expectedModules ) { $instance = new ComponentLibrary(); $this->assertEquals( $expectedModules, @@ -199,12 +202,12 @@ public function testGetModulesFor( $componentName, $skinName, $expectedModules ) * @param string $componentName * @param string $componentClass * - * @throws \ConfigException + * @throws ConfigException * @throws \MWException * * @dataProvider componentNameAndClassProvider */ - public function testGetNameFor( $componentName, $componentClass ) { + public function testGetNameFor( string $componentName, string $componentClass ) { $instance = new ComponentLibrary(); $this->assertEquals( $componentName, @@ -216,11 +219,11 @@ public function testGetNameFor( $componentName, $componentClass ) { * @param bool|string[] $whiteList * @param string[] $expectedComponents * - * @throws \ConfigException + * @throws ConfigException * * @dataProvider whiteListProvider */ - public function testSetWhiteList( $whiteList, $expectedComponents ) { + public function testSetWhiteList( bool|array $whiteList, array $expectedComponents ) { $instance = new ComponentLibrary( $whiteList ); $this->assertEquals( $expectedComponents, @@ -230,26 +233,27 @@ public function testSetWhiteList( $whiteList, $expectedComponents ) { /** * @param string $method + * @param mixed $param * - * @throws \ConfigException + * @throws ConfigException * * @dataProvider exceptionThrowingMethodsProvider */ - public function testFails( $method ) { + public function testFails( $method, $param ) { $instance = new ComponentLibrary(); $this->expectException( 'MWException' ); - call_user_func_array( [ $instance, $method ], [ null ] ); + call_user_func_array( [ $instance, $method ], [ $param ] ); } /** - * @throws \ConfigException + * @throws ConfigException */ public function testRegisterVsKnown() { - $instance = new ComponentLibrary( [ 'alert', 'modal', 'panel' ] ); + $instance = new ComponentLibrary( [ 'alert', 'badge', 'modal', 'panel' ] ); $this->assertEquals( - [ 'alert', 'modal', 'panel', ], + [ 'alert', 'badge', 'modal', 'panel', ], $instance->getRegisteredComponents() ); $this->assertEquals( @@ -260,7 +264,8 @@ public function testRegisterVsKnown() { [ $component, $skin, $expectedModules ] = $args; $this->assertEquals( $expectedModules, - $instance->getModulesFor( $component, $skin ) + $instance->getModulesFor( $component, $skin ), + 'Failed ComponentLibrary:getModulesFor() for test-data ' . $component ); } $this->assertFalse( @@ -269,7 +274,7 @@ public function testRegisterVsKnown() { } /** - * @throws \ConfigException + * @throws ConfigException */ public function testUnknownComponentName() { $instance = new ComponentLibrary( true ); @@ -279,7 +284,7 @@ public function testUnknownComponentName() { } /** - * @throws \ConfigException + * @throws ConfigException */ public function testUnknownComponentClass() { $instance = new ComponentLibrary( true ); @@ -291,7 +296,7 @@ public function testUnknownComponentClass() { /** * @return array */ - public function compileParserHookStringProvider() { + public function compileParserHookStringProvider(): array { return [ 'accordion' => [ 'accordion', 'bootstrap_accordion' ], 'alert' => [ 'alert', 'bootstrap_alert' ], @@ -313,29 +318,29 @@ public function compileParserHookStringProvider() { /** * @return array[] */ - public function componentNameAndClassProvider() { + public function componentNameAndClassProvider(): array { return [ - 'accordion' => [ 'accordion', 'BootstrapComponents\\Components\\Accordion' ], - 'alert' => [ 'alert', 'BootstrapComponents\\Components\\Alert' ], - 'badge' => [ 'badge', 'BootstrapComponents\\Components\\Badge' ], - 'button' => [ 'button', 'BootstrapComponents\\Components\\Button' ], - 'card' => [ 'card', 'BootstrapComponents\\Components\\Card' ], - 'carousel' => [ 'carousel', 'BootstrapComponents\\Components\\Carousel' ], - 'collapse' => [ 'collapse', 'BootstrapComponents\\Components\\Collapse' ], - 'jumbotron' => [ 'jumbotron', 'BootstrapComponents\\Components\\Jumbotron' ], - 'modal' => [ 'modal', 'BootstrapComponents\\Components\\Modal' ], - 'popover' => [ 'popover', 'BootstrapComponents\\Components\\Popover' ], - 'tooltip' => [ 'tooltip', 'BootstrapComponents\\Components\\Tooltip' ], - 'label' => [ 'badge', 'BootstrapComponents\\Components\\Badge' ], - 'panel' => [ 'card', 'BootstrapComponents\\Components\\Card' ], - 'well' => [ 'card', 'BootstrapComponents\\Components\\Card' ], + 'accordion' => [ 'accordion', 'MediaWiki\\Extension\\BootstrapComponents\\Components\\Accordion' ], + 'alert' => [ 'alert', 'MediaWiki\\Extension\\BootstrapComponents\\Components\\Alert' ], + 'badge' => [ 'badge', 'MediaWiki\\Extension\\BootstrapComponents\\Components\\Badge' ], + 'button' => [ 'button', 'MediaWiki\\Extension\\BootstrapComponents\\Components\\Button' ], + 'card' => [ 'card', 'MediaWiki\\Extension\\BootstrapComponents\\Components\\Card' ], + 'carousel' => [ 'carousel', 'MediaWiki\\Extension\\BootstrapComponents\\Components\\Carousel' ], + 'collapse' => [ 'collapse', 'MediaWiki\\Extension\\BootstrapComponents\\Components\\Collapse' ], + 'jumbotron' => [ 'jumbotron', 'MediaWiki\\Extension\\BootstrapComponents\\Components\\Jumbotron' ], + 'modal' => [ 'modal', 'MediaWiki\\Extension\\BootstrapComponents\\Components\\Modal' ], + 'popover' => [ 'popover', 'MediaWiki\\Extension\\BootstrapComponents\\Components\\Popover' ], + 'tooltip' => [ 'tooltip', 'MediaWiki\\Extension\\BootstrapComponents\\Components\\Tooltip' ], + 'label' => [ 'badge', 'MediaWiki\\Extension\\BootstrapComponents\\Components\\Badge' ], + 'panel' => [ 'card', 'MediaWiki\\Extension\\BootstrapComponents\\Components\\Card' ], + 'well' => [ 'card', 'MediaWiki\\Extension\\BootstrapComponents\\Components\\Card' ], ]; } /** * @return array */ - public function componentAliasesProvider() { + public function componentAliasesProvider(): array { return [ 'alert' => [ 'alert', [] ], 'button' => [ 'button', [] ], @@ -347,7 +352,7 @@ public function componentAliasesProvider() { /** * @return array */ - public function componentAttributesProvider() { + public function componentAttributesProvider(): array { return [ 'accordion' => [ 'accordion', [ 'class', 'id', 'style' ] ], 'alert' => [ 'alert', [ 'color', 'dismissible', 'class', 'id', 'style' ] ], @@ -358,18 +363,18 @@ public function componentAttributesProvider() { /** * @return array[] */ - public function exceptionThrowingMethodsProvider() { + public function exceptionThrowingMethodsProvider(): array { return [ - 'getAttributesFor' => [ 'getAttributesFor' ], - 'getClassFor' => [ 'getClassFor' ], - 'getNameFor' => [ 'getNameFor' ], + 'getAttributesFor' => [ 'getAttributesFor', 'FooBar' ], + 'getClassFor' => [ 'getClassFor', 'FooBar' ], + 'getNameFor' => [ 'getNameFor', 'FooBar' ], ]; } /** * @return array */ - public function handlerTypeProvider() { + public function handlerTypeProvider(): array { return [ 'accordion' => [ 'accordion', false ], 'panel' => [ 'panel', false ], @@ -382,8 +387,13 @@ public function handlerTypeProvider() { /** * @return array[] */ - public function modulesForComponentsProvider() { + public function modulesForComponentsProvider(): array { return [ + 'badge' => [ + 'badge', + null, + [] + ], 'button' => [ 'button', null, @@ -440,12 +450,14 @@ public function modulesForComponentsProvider() { /** * @return array */ - public function whiteListProvider() { + public function whiteListProvider(): array { + $allKeys = array_keys( $this->componentNameAndClassProvider() ); + sort( $allKeys ); return [ - 'true' => [ - true, array_keys( $this->componentNameAndClassProvider() ), + 'true' => [ + true, $allKeys, ], - 'false' => [ + 'false' => [ false, [], ], 'normal' => [ diff --git a/tests/phpunit/Unit/Components/AccordionTest.php b/tests/phpunit/Unit/Components/AccordionTest.php index 25db36a..657cc11 100644 --- a/tests/phpunit/Unit/Components/AccordionTest.php +++ b/tests/phpunit/Unit/Components/AccordionTest.php @@ -1,13 +1,13 @@ assertInstanceOf( - '\\BootstrapComponents\\Components\\Accordion', + 'MediaWiki\\Extension\\BootstrapComponents\\Components\\Accordion', new Accordion( $this->getComponentLibrary(), $this->getParserOutputHelper(), diff --git a/tests/phpunit/Unit/Components/AlertTest.php b/tests/phpunit/Unit/Components/AlertTest.php index 90a4679..0293bd1 100644 --- a/tests/phpunit/Unit/Components/AlertTest.php +++ b/tests/phpunit/Unit/Components/AlertTest.php @@ -1,13 +1,13 @@ assertInstanceOf( - '\\BootstrapComponents\\Components\\Alert', + 'MediaWiki\\Extension\\BootstrapComponents\\Components\\Alert', new Alert( $this->getComponentLibrary(), $this->getParserOutputHelper(), diff --git a/tests/phpunit/Unit/Components/BadgeTest.php b/tests/phpunit/Unit/Components/BadgeTest.php index 855a4de..1550ad4 100644 --- a/tests/phpunit/Unit/Components/BadgeTest.php +++ b/tests/phpunit/Unit/Components/BadgeTest.php @@ -1,13 +1,13 @@ assertInstanceOf( - '\\BootstrapComponents\\Components\\Badge', + 'MediaWiki\\Extension\\BootstrapComponents\\Components\\Badge', new Badge( $this->getComponentLibrary(), $this->getParserOutputHelper(), diff --git a/tests/phpunit/Unit/Components/ButtonTest.php b/tests/phpunit/Unit/Components/ButtonTest.php index d032b2b..dfd6adb 100644 --- a/tests/phpunit/Unit/Components/ButtonTest.php +++ b/tests/phpunit/Unit/Components/ButtonTest.php @@ -1,13 +1,13 @@ assertInstanceOf( - 'BootstrapComponents\\Components\\Button', + 'MediaWiki\\Extension\\BootstrapComponents\\Components\\Button', new Button( $this->getComponentLibrary(), $this->getParserOutputHelper(), diff --git a/tests/phpunit/Unit/Components/CardTest.php b/tests/phpunit/Unit/Components/CardTest.php index 59bbe85..f7d4563 100644 --- a/tests/phpunit/Unit/Components/CardTest.php +++ b/tests/phpunit/Unit/Components/CardTest.php @@ -1,15 +1,15 @@ assertInstanceOf( - 'BootstrapComponents\\Components\\Carousel', + 'MediaWiki\\Extension\\BootstrapComponents\\Components\\Carousel', new Carousel( $this->getComponentLibrary(), $this->getParserOutputHelper(), diff --git a/tests/phpunit/Unit/Components/CollapseTest.php b/tests/phpunit/Unit/Components/CollapseTest.php index de903c2..04c4ba9 100644 --- a/tests/phpunit/Unit/Components/CollapseTest.php +++ b/tests/phpunit/Unit/Components/CollapseTest.php @@ -1,13 +1,13 @@ assertInstanceOf( - '\\BootstrapComponents\\Components\\Collapse', + 'MediaWiki\\Extension\\BootstrapComponents\\Components\\Collapse', new Collapse( $this->getComponentLibrary(), $this->getParserOutputHelper(), diff --git a/tests/phpunit/Unit/Components/JumbotronTest.php b/tests/phpunit/Unit/Components/JumbotronTest.php index 96231d7..024457b 100644 --- a/tests/phpunit/Unit/Components/JumbotronTest.php +++ b/tests/phpunit/Unit/Components/JumbotronTest.php @@ -1,13 +1,13 @@ assertInstanceOf( - 'BootstrapComponents\\Components\\Jumbotron', + 'MediaWiki\\Extension\\BootstrapComponents\\Components\\Jumbotron', new Jumbotron( $this->getComponentLibrary(), $this->getParserOutputHelper(), diff --git a/tests/phpunit/Unit/Components/ModalTest.php b/tests/phpunit/Unit/Components/ModalTest.php index c788053..f0d6991 100644 --- a/tests/phpunit/Unit/Components/ModalTest.php +++ b/tests/phpunit/Unit/Components/ModalTest.php @@ -1,13 +1,13 @@ assertInstanceOf( - 'BootstrapComponents\\Components\\Modal', + 'MediaWiki\\Extension\\BootstrapComponents\\Components\\Modal', new Modal( $this->getComponentLibrary(), $this->getParserOutputHelper(), @@ -50,7 +50,7 @@ public function testCanConstruct() { public function testCanRender( $input, $arguments, $expectedTriggerOutput, $expectedModalOutput ) { $modalInjection = ''; - $parserOutputHelper = $this->getMockBuilder( 'BootstrapComponents\\ParserOutputHelper' ) + $parserOutputHelper = $this->getMockBuilder( 'MediaWiki\\Extension\\BootstrapComponents\\ParserOutputHelper' ) ->disableOriginalConstructor() ->getMock(); $parserOutputHelper->expects( $this->any() ) diff --git a/tests/phpunit/Unit/Components/PopoverTest.php b/tests/phpunit/Unit/Components/PopoverTest.php index 596487e..ef44556 100644 --- a/tests/phpunit/Unit/Components/PopoverTest.php +++ b/tests/phpunit/Unit/Components/PopoverTest.php @@ -1,13 +1,13 @@ assertInstanceOf( - '\\BootstrapComponents\\Components\\Popover', + 'MediaWiki\\Extension\\BootstrapComponents\\Components\\Popover', new Popover( $this->getComponentLibrary(), $this->getParserOutputHelper(), diff --git a/tests/phpunit/Unit/Components/TooltipTest.php b/tests/phpunit/Unit/Components/TooltipTest.php index 2d2faf6..8fee9eb 100644 --- a/tests/phpunit/Unit/Components/TooltipTest.php +++ b/tests/phpunit/Unit/Components/TooltipTest.php @@ -1,13 +1,14 @@ assertInstanceOf( - '\\BootstrapComponents\\Components\\Tooltip', + Tooltip::class, new Tooltip( $this->getComponentLibrary(), $this->getParserOutputHelper(), - $this->getNestingController() + $this->createMock( NestingController::class ), ) ); } diff --git a/tests/phpunit/Unit/ComponentsTestBase.php b/tests/phpunit/Unit/ComponentsTestBase.php index 4d04376..c7ef2ab 100644 --- a/tests/phpunit/Unit/ComponentsTestBase.php +++ b/tests/phpunit/Unit/ComponentsTestBase.php @@ -1,14 +1,14 @@ componentLibrary = new ComponentLibrary(); - $this->frame = $this->getMockBuilder( 'PPFrame' ) - ->disableOriginalConstructor() - ->getMock(); - $this->nestingController = $this->getMockBuilder( 'BootstrapComponents\\NestingController' ) - ->disableOriginalConstructor() - ->getMock(); + $this->frame = $this->createMock( PPFrame::class ); + $this->nestingController = $this->createMock( NestingController::class ); $this->nestingController->expects( $this->any() ) ->method( 'generateUniqueId' ) ->will( $this->returnCallback( function( $componentName ) { return 'bsc_' . $componentName . '_NULL'; } ) ); - $this->parser = $this->getMockBuilder( 'Parser' ) - ->disableOriginalConstructor() - ->getMock(); + $this->parser = $this->createMock( Parser::class ); $this->parser->expects( $this->any() ) ->method( 'recursiveTagParse' ) ->will( $this->returnArgument( 0 ) ); $this->parser->expects( $this->any() ) ->method( 'recursiveTagParseFully' ) ->will( $this->returnArgument( 0 ) ); - $this->parserOutputHelper = $this->getMockBuilder( 'BootstrapComponents\\ParserOutputHelper' ) - ->disableOriginalConstructor() - ->getMock(); + $this->parserOutputHelper = $this->createMock( ParserOutputHelper::class ); $this->parserOutputHelper->expects( $this->any() ) ->method( 'renderErrorMessage' ) ->will( $this->returnArgument( 0 ) ); @@ -88,9 +80,7 @@ public function setUp(): void { * @return ParserRequest */ protected function buildParserRequest( $input, $options ) { - $parserRequest = $this->getMockBuilder( 'BootstrapComponents\\ParserRequest' ) - ->disableOriginalConstructor() - ->getMock(); + $parserRequest = $this->createMock( ParserRequest::class ); $parserRequest->expects( $this->any() ) ->method( 'getAttributes' ) ->willReturn( $options ); diff --git a/tests/phpunit/Unit/HookRegistryTest.php b/tests/phpunit/Unit/HookRegistryTest.php deleted file mode 100644 index 9709155..0000000 --- a/tests/phpunit/Unit/HookRegistryTest.php +++ /dev/null @@ -1,496 +0,0 @@ -assertInstanceOf( - 'BootstrapComponents\\HookRegistry', - new HookRegistry() - ); - } - - /** - * @param string[] $hookList - * - * @throws \ConfigException - * - * @dataProvider buildHookCallbackListForProvider - */ - public function testCanBuildHookCallbackListFor( array $hookList ) { - - $instance = new HookRegistry(); - - $hookCallbackList = $instance->buildHookCallbackListFor( $hookList ); - list ( $expectedHookList, $invertedHookList ) = $this->buildHookListsForCanBuildHookListCheck( $hookList ); - - foreach ( $expectedHookList as $hook ) { - $this->doTestHookIsRegistered( $instance, $hookCallbackList, $hook, false ); - } - foreach ( $invertedHookList as $hook ) { - $this->doTestHookIsNotRegistered( $hookCallbackList, $hook ); - } - } - - /** - * @throws \ConfigException - * @throws \MWException - */ - public function disable_testCanClear() { - - $instance = new HookRegistry(); - $instance->register( - $instance->buildHookCallbackListFor( HookRegistry::AVAILABLE_HOOKS ) - ); - foreach ( HookRegistry::AVAILABLE_HOOKS as $hook ) { - $this->assertTrue( - $instance->isRegistered( $hook ), - 'Hook ' . $hook . ' is not registered!' - ); - } - $instance->clear(); - foreach ( [ 'GalleryGetModes', 'ImageBeforeProduceHTML' ] as $hook ) { - $this->assertTrue( - !$instance->isRegistered( $hook ), - 'Hook ' . $hook . ' is still registered!' - ); - } - } - - /** - * @param string[] $listOfConfigSettingsSet - * @param string[] $expectedHookList - * - * @throws \ConfigException - * - * @dataProvider hookRegistryProvider - */ - public function testCanCompileRequestedHooksListFor( array $listOfConfigSettingsSet, array $expectedHookList ) { - $myConfig = $this->getMockBuilder( 'Config' ) - ->disableOriginalConstructor() - ->getMock(); - $myConfig->expects( $this->any() ) - ->method( 'has' ) - ->will( $this->returnCallback( - function( $configSetting ) use ( $listOfConfigSettingsSet ) - { - return in_array( $configSetting, $listOfConfigSettingsSet ); - } - ) ); - $myConfig->expects( $this->any() ) - ->method( 'get' ) - ->will( $this->returnCallback( - function( $configSetting ) use ( $listOfConfigSettingsSet ) - { - return in_array( $configSetting, $listOfConfigSettingsSet ); - } - ) ); - - $instance = new HookRegistry(); - - /** @noinspection PhpParamsInspection */ - $compiledHookList = $instance->compileRequestedHooksListFor( $myConfig ); - - $this->assertEquals( - $expectedHookList, - $compiledHookList - ); - } - - /** - * @throws \ConfigException - * @throws \MWException - */ - public function testCanGetCompleteHookDefinitionList() { - - $myConfig = $this->getMockBuilder( 'Config' ) - ->disableOriginalConstructor() - ->getMock(); - $componentLibrary = $this->getMockBuilder( 'BootstrapComponents\\ComponentLibrary' ) - ->disableOriginalConstructor() - ->getMock(); - $nestingController = $this->getMockBuilder( 'BootstrapComponents\\NestingController' ) - ->disableOriginalConstructor() - ->getMock(); - - $instance = new HookRegistry(); - - /** @noinspection PhpParamsInspection */ - $completeHookDefinitionList = $instance->getCompleteHookDefinitionList( $myConfig, $componentLibrary, $nestingController ); - $this->assertEquals( - HookRegistry::AVAILABLE_HOOKS, - array_keys( $completeHookDefinitionList ) - ); - - foreach ( $completeHookDefinitionList as $callback ) { - $this->assertTrue( - is_callable( $callback ) - ); - } - } - - /** - * @throws \ConfigException - * @throws \MWException - */ - public function testCanInitializeApplications() { - - $myConfig = $this->getMockBuilder( 'Config' ) - ->disableOriginalConstructor() - ->getMock(); - - $instance = new HookRegistry(); - - /** @noinspection PhpParamsInspection */ - list ( $componentLibrary, $nestingController ) = $instance->initializeApplications( $myConfig ); - - $this->assertInstanceOf( - 'BootstrapComponents\\ComponentLibrary', - $componentLibrary - ); - $this->assertInstanceOf( - 'BootstrapComponents\\NestingController', - $nestingController - ); - } - - /** - * @param array $listOfConfigSettingsSet - * @param array $expectedRegisteredHooks - * @param array $expectedNotRegisteredHooks - * - * @throws \ConfigException cascading {@see \Config::get} - * @throws \MWException - * - * @dataProvider hookRegistryProvider - */ - public function testHookRegistrationProcess( - array $listOfConfigSettingsSet, array $expectedRegisteredHooks, array $expectedNotRegisteredHooks - ) { - $instance = new HookRegistry(); - - $hookCallbackList = $instance->buildHookCallbackListFor( - $expectedRegisteredHooks - ); - - $this->assertTrue( - is_array( $listOfConfigSettingsSet ) - ); - - $this->assertEquals( - count( $expectedRegisteredHooks ), - $instance->register( $hookCallbackList ) - ); - - foreach ( $expectedRegisteredHooks as $expectedHook ) { - $this->doTestHookIsRegistered( $instance, $hookCallbackList, $expectedHook ); - } - - foreach ( $expectedNotRegisteredHooks as $notExpectedHook ) { - $this->doTestHookIsNotRegistered( $hookCallbackList, $notExpectedHook ); - } - } - - /** - * @throws \ConfigException cascading {@see \Config::get} - */ - public function testCanRun() { - - $instance = new HookRegistry(); - - $this->assertIsInt( $instance->run() ); - } - - /* - * Here end the tests for all the public methods. - * Following one test per hook function and one test for all the parser hook registrations. - */ - - /** - * @throws \ConfigException - */ - public function testHookGalleryGetModes() { - - $callback = $this->getCallBackForHook( 'GalleryGetModes' ); - $modesForTest = [ 'default' => 'TestGallery' ]; - - $callback( $modesForTest ); - $this->assertEquals( - 2, - count( $modesForTest ) - ); - $this->assertArrayHasKey( - 'carousel', - $modesForTest - ); - $this->assertEquals( - 'BootstrapComponents\\CarouselGallery', - $modesForTest['carousel'] - ); - } - - /** - * @throws \ConfigException - */ - public function testHookImageBeforeProduceHTML() { - $callback = $this->getCallBackForHook( 'ImageBeforeProduceHTML' ); - $linker = $title = $file = $frameParams = $handlerParams = $time = $res = false; - - $this->assertTrue( - $callback( $linker, $title, $file, $frameParams, $handlerParams, $time, $res ) - ); - } - - /** - * @throws \ConfigException - */ - public function testHookInternalParseBeforeLinks() { - $parserOutput = $this->getMockBuilder( 'ParserOutput' ) - ->disableOriginalConstructor() - ->getMock(); - $parserOutput->expects( $this->exactly( 2 ) ) - ->method( 'setExtensionData' ) - ->with( - $this->stringContains( 'bsc_no_image_modal' ), - $this->isType( 'boolean' ) - ); - - $parser = $this->getMockBuilder( 'Parser' ) - ->disableOriginalConstructor() - ->getMock(); - $parser->expects( $this->exactly( 2 ) ) - ->method( 'getOutput' ) - ->willReturn( $parserOutput ); - - $callback = $this->getCallBackForHook( 'InternalParseBeforeLinks' ); - - $text = ''; - $this->assertTrue( - $callback( $parser, $text ) - ); - $this->assertEquals( - '', - $text - ); - $text = '__NOIMAGEMODAL__'; - $this->assertTrue( - $callback( $parser, $text ) - ); - $this->assertEquals( - '', - $text - ); - } - - /** - * @throws \ConfigException - */ - public function testHookOutputPageParserOutput() { - $content = 'CONTENT'; - $parser = $this->getMockBuilder( 'Parser' ) - ->disableOriginalConstructor() - ->getMock(); - $parserOutput = $this->getMockBuilder( 'ParserOutput' ) - ->disableOriginalConstructor() - ->getMock(); - $parserOutput->expects( $this->exactly( 2 ) ) - ->method( 'getExtensionData' ) - ->with( - $this->stringContains( 'bsc_deferredContent' ) - ) - ->willReturnOnConsecutiveCalls( [], [ 'call2' ] ); - $outputPage = $this->getMockBuilder( 'OutputPage' ) - ->disableOriginalConstructor() - ->getMock(); - $outputPage->expects( $this->once() ) - ->method( 'addHTML' ) - ->will( $this->returnCallback( function( $injection ) use ( &$content ) { - $content .= $injection; - } ) ); - $outputPage->expects( $this->exactly( 2 ) ) - ->method( 'addModules' ) - ->with( - $this->equalTo( [ 'ext.bootstrapComponents.vector-fix' ] ) - ); - - - /** @noinspection PhpParamsInspection */ - $parserOutputHelper = new ParserOutputHelper( $parser ); - - $callback = $this->getCallBackForHook( 'OutputPageParserOutput' ); - - $this->assertTrue( - $callback( $outputPage, $parserOutput, $parserOutputHelper ) - ); - $this->assertEquals( - 'CONTENT', - $content - ); - $this->assertTrue( - $callback( $outputPage, $parserOutput, $parserOutputHelper ) - ); - $this->assertEquals( - 'CONTENTcall2', - $content - ); - } - - /** - * Note: Hook ParserFirstCallInit is tested in detail in {@see \BootstrapComponents\Tests\Unit\Hooks\ParserFirstCallInitTest}. - * - * @throws \ConfigException - */ - public function testHookParserFirstCallInit() { - $parser = $this->getMockBuilder( 'Parser' ) - ->disableOriginalConstructor() - ->getMock(); - - $callback = $this->getCallBackForHook( 'ParserFirstCallInit' ); - - $this->assertTrue( - $callback( $parser ) - ); - } - - - /** - * @return array - */ - public function buildHookCallbackListForProvider(): array { - return [ - 'empty' => [ [] ], - 'default' => [ [ 'OutputPageParserOutput', 'ParserFirstCallInit' ] ], - 'alsoImageModal' => [ [ 'ImageBeforeProduceHTML', 'InternalParseBeforeLinks', 'OutputPageParserOutput', 'ParserFirstCallInit' ] ], - 'alsoCarouselGallery' => [ [ 'GalleryGetModes', 'OutputPageParserOutput', 'ParserFirstCallInit' ] ], - 'all' => [ [ 'GalleryGetModes', 'ImageBeforeProduceHTML', 'InternalParseBeforeLinks', 'OutputPageParserOutput', 'ParserFirstCallInit' ] ], - 'invalid' => [ [ 'nonExistingHook', 'PageContentSave' ] ], - ]; - } - - /** - * @return array - */ - public function hookRegistryProvider(): array { - return [ - 'onlydefault' => [ - [], - [ 'OutputPageParserOutput', 'ParserFirstCallInit', ], - [ 'GalleryGetModes', 'ImageBeforeProduceHTML', 'InternalParseBeforeLinks', ], - ], - 'gallery activated' => [ - [ 'BootstrapComponentsEnableCarouselGalleryMode' ], - [ 'OutputPageParserOutput', 'ParserFirstCallInit', 'GalleryGetModes', ], - [ 'ImageBeforeProduceHTML', 'InternalParseBeforeLinks', ], - ], - 'image replacement activated' => [ - [ 'BootstrapComponentsModalReplaceImageTag' ], - [ 'OutputPageParserOutput', 'ParserFirstCallInit', 'ImageBeforeProduceHTML', 'InternalParseBeforeLinks', ], - [ 'GalleryGetModes', ], - ], - 'both activated' => [ - [ 'BootstrapComponentsEnableCarouselGalleryMode', 'BootstrapComponentsModalReplaceImageTag' ], - [ 'OutputPageParserOutput', 'ParserFirstCallInit', 'GalleryGetModes', 'ImageBeforeProduceHTML', 'InternalParseBeforeLinks', ], - [], - ], - ]; - } - - /** - * @param array $hookList - * - * @return array $expectedHookList, $invertedHookList - */ - private function buildHookListsForCanBuildHookListCheck( array $hookList ): array { - $expectedHookList = []; - $invertedHookList = []; - foreach ( $hookList as $hook ) { - if ( in_array( $hook, HookRegistry::AVAILABLE_HOOKS ) ) { - $expectedHookList[] = $hook; - } - } - foreach ( HookRegistry::AVAILABLE_HOOKS as $availableHook ) { - if ( !in_array( $availableHook, $hookList ) ) { - $invertedHookList[] = $availableHook; - } - } - return [ $expectedHookList, $invertedHookList ]; - } - - /** - * @param HookRegistry $instance - * @param array $registeredHooks - * @param string $expectedHook - * @param bool $hardRegisterTest - */ - private function doTestHookIsRegistered( - HookRegistry $instance, array $registeredHooks, string $expectedHook, bool $hardRegisterTest = true - ) { - if ( $hardRegisterTest ) { - $this->assertTrue( - $instance->isRegistered( $expectedHook ) - ); - } - $this->assertArrayHasKey( - $expectedHook, - $registeredHooks, - 'Expected hook "' . $expectedHook . '" to be registered but was not! ' - ); - $this->assertTrue( - is_callable( $registeredHooks[$expectedHook] ) - ); - } - - /** - * @param array $registeredHooks - * @param string $notExpectedHook - */ - private function doTestHookIsNotRegistered( array $registeredHooks, string $notExpectedHook ) { - $this->assertArrayNotHasKey( - $notExpectedHook, - $registeredHooks, - 'Expected hook "' . $notExpectedHook . '" to not be registered but was! ' - ); - } - - /** - * @param string $hook - * - * @return \Closure - */ - private function getCallBackForHook( string $hook ): \Closure { - $instance = new HookRegistry(); - $hookCallbackList = $instance->buildHookCallbackListFor( - [ $hook ] - ); - $this->assertArrayHasKey( - $hook, - $hookCallbackList - ); - $this->assertTrue( - is_callable( $hookCallbackList[$hook] ) - ); - return $hookCallbackList[$hook]; - } -} diff --git a/tests/phpunit/Unit/Hooks/DefaultHooksHandlerTest.php b/tests/phpunit/Unit/Hooks/DefaultHooksHandlerTest.php deleted file mode 100644 index 25f75dd..0000000 --- a/tests/phpunit/Unit/Hooks/DefaultHooksHandlerTest.php +++ /dev/null @@ -1,47 +0,0 @@ -assertTrue( - DefaultHooksHandler::onScribuntoExternalLibraries( '', $libraries ) - ); - $this->assertEquals( - [], - $libraries - ); - $this->assertTrue( - DefaultHooksHandler::onScribuntoExternalLibraries( 'lua', $libraries ) - ); - $this->assertArrayHasKey( - 'mw.bootstrap', - $libraries - ); - $this->assertEquals( - 'BootstrapComponents\\LuaLibrary', - $libraries['mw.bootstrap'] - ); - } -} diff --git a/tests/phpunit/Unit/Hooks/OutputPageParserOutputTest.php b/tests/phpunit/Unit/Hooks/OutputPageParserOutputTest.php index 318ea98..7b72fd4 100644 --- a/tests/phpunit/Unit/Hooks/OutputPageParserOutputTest.php +++ b/tests/phpunit/Unit/Hooks/OutputPageParserOutputTest.php @@ -1,14 +1,16 @@ getMockBuilder( 'OutputPage' ) - ->disableOriginalConstructor() - ->getMock(); - $parserOutput = $this->getMockBuilder( 'ParserOutput' ) - ->disableOriginalConstructor() - ->getMock(); - $parserOutputHelper = $this->getMockBuilder( 'BootstrapComponents\ParserOutputHelper' ) - ->disableOriginalConstructor() - ->getMock(); + $outputPage = $this->createMock( OutputPage::class ); - /** @noinspection PhpParamsInspection */ - $instance = new OutputPageParserOutput( $outputPage, $parserOutput, $parserOutputHelper ); + $instance = new OutputPageParserOutput( + $outputPage, + $this->createMock( ParserOutput::class ), + $this->createMock( BootstrapComponentsService::class ) + ); $this->assertInstanceOf( - 'BootstrapComponents\\Hooks\\OutputPageParserOutput', + 'MediaWiki\\Extension\\BootstrapComponents\\Hooks\\OutputPageParserOutput', $instance ); } public function testHookOutputPageParserOutput() { $content = 'CONTENT'; - $parser = $this->getMockBuilder( 'Parser' ) - ->disableOriginalConstructor() - ->getMock(); - $outputPage = $this->getMockBuilder( 'OutputPage' ) - ->disableOriginalConstructor() - ->getMock(); + + $outputPage = $this->createMock( OutputPage::class ); $outputPage->expects( $this->once() ) ->method( 'addHTML' ) ->will( $this->returnCallback( function( $injection ) use ( &$content ) { @@ -62,9 +55,7 @@ public function testHookOutputPageParserOutput() { $this->equalTo( [ 'ext.bootstrapComponents.vector-fix' ] ) ); - $observerParserOutput = $this->getMockBuilder(ParserOutput::class ) - ->disableOriginalConstructor() - ->getMock(); + $observerParserOutput = $this->createMock( ParserOutput::class ); $observerParserOutput->expects( $this->exactly( 1 ) ) ->method( 'getExtensionData' ) ->with( @@ -72,15 +63,14 @@ public function testHookOutputPageParserOutput() { ) ->willReturn( [ 'test' ] ); - /** @noinspection PhpParamsInspection */ - $parserOutputHelper = new ParserOutputHelper( $parser ); + $bootstrapService = $this->createMock( BootstrapComponentsService::class ); + $bootstrapService->expects( $this->once() ) + ->method( 'vectorSkinInUse' ) + ->willReturn( true ); - /** @noinspection PhpParamsInspection */ - $instance = new OutputPageParserOutput( $outputPage, $observerParserOutput, $parserOutputHelper ); + $instance = new OutputPageParserOutput( $outputPage, $observerParserOutput, $bootstrapService ); + $instance->process(); - $this->assertTrue( - $instance->process() - ); $this->assertEquals( 'CONTENTtest', $content diff --git a/tests/phpunit/Unit/Hooks/ParserFirstCallInitTest.php b/tests/phpunit/Unit/Hooks/ParserFirstCallInitTest.php index 18fdc09..e1b32ea 100644 --- a/tests/phpunit/Unit/Hooks/ParserFirstCallInitTest.php +++ b/tests/phpunit/Unit/Hooks/ParserFirstCallInitTest.php @@ -1,14 +1,14 @@ getMockBuilder( Parser::class ) ->disableOriginalConstructor() ->getMock(); - $componentLibrary = $this->getMockBuilder( 'BootstrapComponents\ComponentLibrary' ) + $componentLibrary = $this->getMockBuilder( 'MediaWiki\Extension\BootstrapComponents\ComponentLibrary' ) ->disableOriginalConstructor() ->getMock(); - $nestingController = $this->getMockBuilder( 'BootstrapComponents\NestingController' ) + $nestingController = $this->getMockBuilder( 'MediaWiki\Extension\BootstrapComponents\NestingController' ) ->disableOriginalConstructor() ->getMock(); @@ -38,7 +38,7 @@ public function testCanConstruct() { $instance = new ParserFirstCallInit( $parser, $componentLibrary, $nestingController ); $this->assertInstanceOf( - 'BootstrapComponents\\Hooks\\ParserFirstCallInit', + 'MediaWiki\\Extension\\BootstrapComponents\\Hooks\\ParserFirstCallInit', $instance ); } @@ -58,8 +58,8 @@ public function testHookParserFirstCallInit() { [ $this->equalTo( $prefix . 'badge' ), $this->callback( 'is_callable' ) ], [ $this->equalTo( $prefix . 'button' ), $this->callback( 'is_callable' ) ], [ $this->equalTo( $prefix . 'carousel' ), $this->callback( 'is_callable' ) ], - [ $this->equalTo( $prefix . 'tooltip' ), $this->callback( 'is_callable' ) ], - [ $this->equalTo( $prefix . 'label' ), $this->callback( 'is_callable' ) ] + [ $this->equalTo( $prefix . 'label' ), $this->callback( 'is_callable' ) ], + [ $this->equalTo( $prefix . 'tooltip' ), $this->callback( 'is_callable' ) ] ); $observerParser->expects( $this->exactly( 9 ) ) ->method( 'setHook' ) @@ -70,8 +70,8 @@ public function testHookParserFirstCallInit() { [ $this->equalTo( $prefix . 'collapse' ), $this->callback( 'is_callable' ) ], [ $this->equalTo( $prefix . 'jumbotron' ), $this->callback( 'is_callable' ) ], [ $this->equalTo( $prefix . 'modal' ), $this->callback( 'is_callable' ) ], - [ $this->equalTo( $prefix . 'popover' ), $this->callback( 'is_callable' ) ], [ $this->equalTo( $prefix . 'panel' ), $this->callback( 'is_callable' ) ], + [ $this->equalTo( $prefix . 'popover' ), $this->callback( 'is_callable' ) ], [ $this->equalTo( $prefix . 'well' ), $this->callback( 'is_callable' ) ] ); $componentLibrary = new ComponentLibrary( true ); @@ -108,7 +108,7 @@ public function testCanCreateParserHooks() { } ) ); $componentLibrary = new ComponentLibrary( true ); - $nestingController = $this->getMockBuilder( 'BootstrapComponents\NestingController' ) + $nestingController = $this->getMockBuilder( 'MediaWiki\Extension\BootstrapComponents\NestingController' ) ->disableOriginalConstructor() ->getMock(); diff --git a/tests/phpunit/Unit/HooksHandlerTest.php b/tests/phpunit/Unit/HooksHandlerTest.php new file mode 100644 index 0000000..827d40d --- /dev/null +++ b/tests/phpunit/Unit/HooksHandlerTest.php @@ -0,0 +1,75 @@ +assertTrue( + HooksHandler::onScribuntoExternalLibraries( '', $libraries ) + ); + $this->assertEquals( + [], + $libraries + ); + $this->assertTrue( + HooksHandler::onScribuntoExternalLibraries( 'lua', $libraries ) + ); + $this->assertArrayHasKey( + 'mw.bootstrap', + $libraries + ); + $this->assertEquals( + 'MediaWiki\\Extension\\BootstrapComponents\\LuaLibrary', + $libraries['mw.bootstrap'] + ); + } + + public function testOnGalleryGetModes() { + $modes = []; + $hooksHandler = new HooksHandler( + $this->createMock( BootstrapComponentsService::class ), + $this->createMock( ComponentLibrary::class ), + $this->createMock( NestingController::class ), + ); + $this->assertTrue( + $hooksHandler->onGalleryGetModes( $modes ) + ); + $this->assertTrue( isset( $modes['carousel'] ) ); + $this->assertEquals( 'MediaWiki\\Extension\\BootstrapComponents\\CarouselGallery', $modes['carousel'] ); + } + + /** + * this hook is tested in + * @see OutputPageParserOutputTest::testHookOutputPageParserOutput + * + * @return void + */ + public function testOnOutputPageParserOutput() { + $this->assertTrue( true ); + } +} diff --git a/tests/phpunit/Unit/ImageModalTest.php b/tests/phpunit/Unit/ImageModalTest.php index a65f216..9007e01 100644 --- a/tests/phpunit/Unit/ImageModalTest.php +++ b/tests/phpunit/Unit/ImageModalTest.php @@ -1,14 +1,22 @@ getMockBuilder( 'DummyLinker' ) - ->disableOriginalConstructor() - ->getMock(); - - $title = $this->getMockBuilder( 'Title' ) - ->disableOriginalConstructor() - ->getMock(); - - $localFile = $this->getMockBuilder( 'LocalFile' ) - ->disableOriginalConstructor() - ->getMock(); - - $file = $this->getMockBuilder( 'File' ) - ->disableOriginalConstructor() - ->getMock(); + $file = $this->createMock( File::class ); - /** @noinspection PhpParamsInspection */ $this->assertInstanceOf( - 'BootstrapComponents\\ImageModal', - new ImageModal( - $dummyLinker, - $title, - $localFile - ) + ImageModal::class, + $this->createImageModalWithMocks() ); - /** @noinspection PhpParamsInspection */ $this->assertInstanceOf( - 'BootstrapComponents\\ImageModal', - new ImageModal( - $dummyLinker, - $title, - $file - ) + ImageModal::class, + $this->createImageModalWithMocks( null, null, $file ) ); } @@ -72,17 +56,7 @@ public function testCanConstruct() { * @throws \ConfigException */ public function testCanParseOnFileNonExistent() { - $dummyLinker = $this->getMockBuilder( 'DummyLinker' ) - ->disableOriginalConstructor() - ->getMock(); - - $title = $this->getMockBuilder( 'Title' ) - ->disableOriginalConstructor() - ->getMock(); - - $file = $this->getMockBuilder( 'LocalFile' ) - ->disableOriginalConstructor() - ->getMock(); + $file = $this->createMock( LocalFile::class ); $file->expects( $this->any() ) ->method( 'allowInlineDisplay' ) ->willReturn( true ); @@ -90,16 +64,9 @@ public function testCanParseOnFileNonExistent() { ->method( 'exists' ) ->willReturn( false ); - $nestingController = $this->getMockBuilder( 'BootstrapComponents\\NestingController' ) - ->disableOriginalConstructor() - ->getMock(); - - $parserOutputHelper = $this->getMockBuilder( 'BootstrapComponents\\ParserOutputHelper' ) - ->disableOriginalConstructor() - ->getMock(); - - /** @noinspection PhpParamsInspection */ - $instance = new ImageModal( $dummyLinker, $title, $file, $nestingController, $parserOutputHelper ); + $instance = $this->createImageModalWithMocks( null, null, $file ); + $fp = []; + $hp = []; $time = false; $res = ''; @@ -115,17 +82,7 @@ public function testCanParseOnFileNonExistent() { * @throws \ConfigException */ public function testCanParseOnFileNoAllowInlineParse() { - $dummyLinker = $this->getMockBuilder( 'DummyLinker' ) - ->disableOriginalConstructor() - ->getMock(); - - $title = $this->getMockBuilder( 'Title' ) - ->disableOriginalConstructor() - ->getMock(); - - $file = $this->getMockBuilder( 'LocalFile' ) - ->disableOriginalConstructor() - ->getMock(); + $file = $this->createMock( LocalFile::class ); $file->expects( $this->any() ) ->method( 'allowInlineDisplay' ) ->willReturn( false ); @@ -133,16 +90,9 @@ public function testCanParseOnFileNoAllowInlineParse() { ->method( 'exists' ) ->willReturn( true ); - $nestingController = $this->getMockBuilder( 'BootstrapComponents\\NestingController' ) - ->disableOriginalConstructor() - ->getMock(); - - $parserOutputHelper = $this->getMockBuilder( 'BootstrapComponents\\ParserOutputHelper' ) - ->disableOriginalConstructor() - ->getMock(); - - /** @noinspection PhpParamsInspection */ - $instance = new ImageModal( $dummyLinker, $title, $file, $nestingController, $parserOutputHelper ); + $instance = $this->createImageModalWithMocks( null, null, $file ); + $fp = []; + $hp = []; $time = false; $res = ''; @@ -158,14 +108,6 @@ public function testCanParseOnFileNoAllowInlineParse() { * @throws \ConfigException */ public function testCanParseOnOnInvalidManualThumb() { - $dummyLinker = $this->getMockBuilder( 'DummyLinker' ) - ->disableOriginalConstructor() - ->getMock(); - - $title = $this->getMockBuilder( 'Title' ) - ->disableOriginalConstructor() - ->getMock(); - $file = $this->getMockBuilder( 'LocalFile' ) ->disableOriginalConstructor() ->getMock(); @@ -176,19 +118,11 @@ public function testCanParseOnOnInvalidManualThumb() { ->method( 'exists' ) ->willReturn( true ); - $nestingController = $this->getMockBuilder( 'BootstrapComponents\\NestingController' ) - ->disableOriginalConstructor() - ->getMock(); - - $parserOutputHelper = $this->getMockBuilder( 'BootstrapComponents\\ParserOutputHelper' ) - ->disableOriginalConstructor() - ->getMock(); - - /** @noinspection PhpParamsInspection */ - $instance = new ImageModal( $dummyLinker, $title, $file, $nestingController, $parserOutputHelper ); + $instance = $this->createImageModalWithMocks( null, null, $file ); $time = false; $res = ''; $fp = [ 'manualthumb' => 'ImageInvalid.png' ]; + $hp = []; $resultOfParseCall = $instance->parse( $fp, $hp, $time, $res ); @@ -202,20 +136,12 @@ public function testCanParseOnOnInvalidManualThumb() { * @throws \ConfigException */ public function testCanParseOnOnInvalidContentImage() { - $dummyLinker = $this->getMockBuilder( 'DummyLinker' ) - ->disableOriginalConstructor() - ->getMock(); - - $title = $this->getMockBuilder( 'Title' ) - ->disableOriginalConstructor() - ->getMock(); + $title = $this->createMock( Title::class ); $title->expects( $this->any() ) ->method( 'getLocalUrl' ) ->willReturn( '/File:Serenity.png' ); - $thumb = $this->getMockBuilder( 'ThumbnailImage' ) - ->disableOriginalConstructor() - ->getMock(); + $thumb = $this->createMock( ThumbnailImage::class ); $thumb->expects( $this->any() ) ->method( 'getWidth' ) ->willReturn( 52 ); @@ -232,9 +158,7 @@ function( $params ) { return ''; } ) ); - $file = $this->getMockBuilder( 'LocalFile' ) - ->disableOriginalConstructor() - ->getMock(); + $file = $this->createMock( LocalFile::class ); $file->expects( $this->any() ) ->method( 'allowInlineDisplay' ) ->willReturn( true ); @@ -254,18 +178,11 @@ function( $params ) { ->method( 'transform' ) ->willReturn( $thumb ); - $nestingController = $this->getMockBuilder( 'BootstrapComponents\\NestingController' ) - ->disableOriginalConstructor() - ->getMock(); - $parserOutputHelper = $this->getMockBuilder( 'BootstrapComponents\\ParserOutputHelper' ) - ->disableOriginalConstructor() - ->getMock(); - - /** @noinspection PhpParamsInspection */ - $instance = new ImageModal( $dummyLinker, $title, $file, $nestingController, $parserOutputHelper ); + $instance = $this->createImageModalWithMocks( null, $title, $file ); $time = false; $res = ''; $fp = [ 'align' => 'left' ]; # otherwise, this test produces an exception while trying to call $title->getPageLanguage()->alignEnd() + $hp = []; $resultOfParseCall = $instance->parse( $fp, $hp, $time, $res ); @@ -286,20 +203,13 @@ function( $params ) { * @dataProvider canParseDataProvider */ public function testCanParse( $fp, $hp, $expectedTrigger, $expectedModal ) { - $dummyLinker = $this->getMockBuilder( 'DummyLinker' ) - ->disableOriginalConstructor() - ->getMock(); - $title = $this->getMockBuilder( 'Title' ) - ->disableOriginalConstructor() - ->getMock(); + $title = $this->createMock( Title::class ); $title->expects( $this->any() ) ->method( 'getLocalUrl' ) ->willReturn( '/File:Serenity.png' ); - $thumb = $this->getMockBuilder( 'ThumbnailImage' ) - ->disableOriginalConstructor() - ->getMock(); + $thumb = $this->createMock( ThumbnailImage::class ); $thumb->expects( $this->any() ) ->method( 'getWidth' ) ->willReturn( 640 ); @@ -316,9 +226,7 @@ function( $params ) { return ''; } ) ); - $file = $this->getMockBuilder( 'LocalFile' ) - ->disableOriginalConstructor() - ->getMock(); + $file = $this->createMock( LocalFile::class ); $file->expects( $this->any() ) ->method( 'allowInlineDisplay' ) ->willReturn( true ); @@ -338,9 +246,7 @@ function( $params ) { ->method( 'transform' ) ->willReturn( $thumb ); - $nestingController = $this->getMockBuilder( 'BootstrapComponents\\NestingController' ) - ->disableOriginalConstructor() - ->getMock(); + $nestingController = $this->createMock( NestingController::class ); $nestingController->expects( $this->any() ) ->method( 'generateUniqueId' ) ->will( $this->returnCallback( @@ -350,23 +256,19 @@ function( $component ) { ) ); $modalInjection = ''; - $parserOutputHelper = $this->getMockBuilder( 'BootstrapComponents\\ParserOutputHelper' ) - ->disableOriginalConstructor() - ->getMock(); + $parserOutputHelper = $this->createMock( ParserOutputHelper::class ); $parserOutputHelper->expects( $this->any() ) ->method( 'injectLater' ) ->will( $this->returnCallback( function( $id, $text ) use ( &$modalInjection ) { $modalInjection .= $text; } ) ); - /** @noinspection PhpParamsInspection */ - $instance = new ImageModal( $dummyLinker, $title, $file, $nestingController, $parserOutputHelper ); + $instance = $this->createImageModalWithMocks( null, $title, $file, $nestingController, null, $parserOutputHelper ); $time = false; $res = ''; $resultOfParseCall = $instance->parse( $fp, $hp, $time, $res ); - /** @noinspection PhpParamsInspection */ $this->assertEquals( $expectedTrigger, $resultOfParseCall ?: $res, @@ -404,8 +306,8 @@ public function canParseDataProvider(): array 'no params' => [ [], [], - '', - '