From f6505e5f41a9f8c6d7d476583809e385445d3060 Mon Sep 17 00:00:00 2001 From: Markus <92986684+gesinn-it-wam@users.noreply.github.com> Date: Tue, 12 Jul 2022 09:30:24 +0200 Subject: [PATCH 1/6] Added extension.json --- AutoCreatePage.php | 173 +++++++++++++++++++-------------------------- extension.json | 35 +++++++++ 2 files changed, 106 insertions(+), 102 deletions(-) create mode 100644 extension.json diff --git a/AutoCreatePage.php b/AutoCreatePage.php index 7ad941e..79252c5 100644 --- a/AutoCreatePage.php +++ b/AutoCreatePage.php @@ -3,10 +3,10 @@ /** * This extension provides a parser function #createpageifnotex that can be used to create * additional auxiliary pages when a page is saved. New pages are only created if they do - * not exist yet. The function takes two parameters: (1) the title of the new page, + * not exist yet. The function takes two parameters: (1) the title of the new page, * (2) the text to be used on the new page. It is possible to use <nowiki> tags in the * text to inserst wiki markup more conveniently. - * + * * The created page is attributed to the user who made the edit. The original idea for this * code was edveloped by Daniel Herzig at AIFB Karlsruhe. In his code, there were some further * facilities to show a message to the user about the pages that have been auto-created. This @@ -22,127 +22,96 @@ * @file */ -if ( !defined( 'MEDIAWIKI' ) ) { - die( 'Not an entry point.' ); -} - -/** - * This is decreased during page creation to avoid infinite recursive creation of pages. - */ -$egAutoCreatePageMaxRecursion = 1; - -$egAutoCreatePageIgnoreEmptyTitle = false; - -$egAutoCreatePageNamespaces = $wgContentNamespaces; - -$GLOBALS['wgExtensionCredits']['other'][] = array( - 'name' => 'AutoCreatePage', - 'version' => '0.6', - 'author' => '[http://korrekt.org Markus Krötzsch], Daniel Herzig', - 'url' => ' ', - 'description' => 'Provides a parser function to create additional wiki pages with default content when saving a page.', //TODO i18n - 'license-name' => 'GPL-2.0+' -); +class AutoCreatePage { -$GLOBALS['wgExtensionMessagesFiles']['AutoCreatePageMagic'] = dirname(__FILE__) . '/AutoCreatePage.i18n.magic.php'; - -$GLOBALS['wgExtensionFunctions'][] = function() { - - $GLOBALS['wgHooks']['ParserFirstCallInit'][] = function ( \Parser &$parser ) { - - $parser->setFunctionHook( 'createPage', function( $parser ) { - return createPageIfNotExisting( func_get_args() ); - } ); + public static function onParserFirstCallInit( $parser ) { + $parser->setFunctionHook( 'createPage', [ self::class, 'createPageIfNotExisting' ]); + } - }; + public function onArticleEditUpdates( $wikiPage, $editInfo, $changed ) { + self::doCreatePages($wikiPage, $editInfo, $changed); + } - $GLOBALS['wgHooks']['ArticleEditUpdates'][] = 'doCreatePages'; -}; + /** + * Handles the parser function for creating pages that don't exist yet, + * filling them with the given default content. It is possible to use <nowiki> + * in the default text parameter to insert verbatim wiki text. + */ + public static function createPageIfNotExisting( $parser, $newPageTitleText, $newPageContent ) { + global $egAutoCreatePageMaxRecursion, $egAutoCreatePageIgnoreEmptyTitle, + $egAutoCreatePageNamespaces, $wgContentNamespaces; -/** - * Handles the parser function for creating pages that don't exist yet, - * filling them with the given default content. It is possible to use <nowiki> - * in the default text parameter to insert verbatim wiki text. - */ -function createPageIfNotExisting( array $rawParams ) { - global $egAutoCreatePageMaxRecursion, $egAutoCreatePageIgnoreEmptyTitle, $egAutoCreatePageNamespaces; + $autoCreatePageNamespaces = $egAutoCreatePageNamespaces ?? $wgContentNamespaces; - if ( $egAutoCreatePageMaxRecursion <= 0 ) { - return 'Error: Recursion level for auto-created pages exeeded.'; //TODO i18n - } + if ( $egAutoCreatePageMaxRecursion <= 0 ) { + return 'Error: Recursion level for auto-created pages exceeded.'; //TODO i18n + } - if ( isset( $rawParams[0] ) && isset( $rawParams[1] ) && isset( $rawParams[2] ) ) { - $parser = $rawParams[0]; - $newPageTitleText = $rawParams[1]; - $newPageContent = $rawParams[2]; - } else { - throw new MWException( 'Hook invoked with missing parameters.' ); - } + if ( empty( $newPageTitleText ) ) { + if ( $egAutoCreatePageIgnoreEmptyTitle === false ) { + return 'Error: this function must be given a valid title text for the page to be created.'; //TODO i18n + } else { + return ''; + } + } - if ( empty( $newPageTitleText ) ) { - if ( $egAutoCreatePageIgnoreEmptyTitle === false ) { - return 'Error: this function must be given a valid title text for the page to be created.'; //TODO i18n - } else { + // Create pages only if the page calling the parser function is within defined namespaces + if ( !in_array( $parser->getTitle()->getNamespace(), $autoCreatePageNamespaces ) ) { return ''; } - } - // Create pages only if the page calling the parser function is within defined namespaces - if ( !in_array( $parser->getTitle()->getNamespace(), $egAutoCreatePageNamespaces ) ) { - return ''; - } + // Get the raw text of $newPageContent as it was before stripping : + $newPageContent = $parser->mStripState->unstripNoWiki( $newPageContent ); - // Get the raw text of $newPageContent as it was before stripping : - $newPageContent = $parser->mStripState->unstripNoWiki( $newPageContent ); + // Store data in the parser output for later use: + $createPageData = $parser->getOutput()->getExtensionData( 'createPage' ); + if ( is_null( $createPageData ) ) { + $createPageData = array(); + } + $createPageData[$newPageTitleText] = $newPageContent; + $parser->getOutput()->setExtensionData( 'createPage', $createPageData ); - // Store data in the parser output for later use: - $createPageData = $parser->getOutput()->getExtensionData( 'createPage' ); - if ( is_null( $createPageData ) ) { - $createPageData = array(); + return ""; } - $createPageData[$newPageTitleText] = $newPageContent; - $parser->getOutput()->setExtensionData( 'createPage', $createPageData ); - return ""; -} + /** + * Creates pages that have been requested by the create page parser function. This is done only + * after the safe is complete to avoid any concurrent article modifications. + * Note that article is, in spite of its name, a WikiPage object since MW 1.21. + */ + private static function doCreatePages( $article, $editInfo, $changed ) { + global $egAutoCreatePageMaxRecursion; + + $createPageData = $editInfo->output->getExtensionData( 'createPage' ); + if ( is_null( $createPageData ) ) { + return true; // no pages to create + } -/** - * Creates pages that have been requested by the creat page parser function. This is done only - * after the safe is complete to avoid any concurrent article modifications. - * Note that article is, in spite of its name, a WikiPage object since MW 1.21. - */ -function doCreatePages( &$article, &$editInfo, $changed ) { - global $egAutoCreatePageMaxRecursion; + // Prevent pages to be created by pages that are created to avoid loops: + $egAutoCreatePageMaxRecursion--; - $createPageData = $editInfo->output->getExtensionData( 'createPage' ); - if ( is_null( $createPageData ) ) { - return true; // no pages to create - } + $sourceTitle = $article->getTitle(); + $sourceTitleText = $sourceTitle->getPrefixedText(); - // Prevent pages to be created by pages that are created to avoid loops: - $egAutoCreatePageMaxRecursion--; + foreach ( $createPageData as $pageTitleText => $pageContentText ) { + $pageTitle = Title::newFromText( $pageTitleText ); + // wfDebugLog( 'createpage', "CREATE " . $pageTitle->getText() . " Text: " . $pageContent ); - $sourceTitle = $article->getTitle(); - $sourceTitleText = $sourceTitle->getPrefixedText(); + if ( !is_null( $pageTitle ) && !$pageTitle->isKnown() && $pageTitle->canExist() ){ + $newWikiPage = new WikiPage( $pageTitle ); + $pageContent = ContentHandler::makeContent( $pageContentText, $sourceTitle ); + $newWikiPage->doEditContent( $pageContent, + "Page created automatically by parser function on page [[$sourceTitleText]]" ); //TODO i18n - foreach ( $createPageData as $pageTitleText => $pageContentText ) { - $pageTitle = Title::newFromText( $pageTitleText ); - // wfDebugLog( 'createpage', "CREATE " . $pageTitle->getText() . " Text: " . $pageContent ); + // wfDebugLog( 'createpage', "CREATED PAGE " . $pageTitle->getText() . " Text: " . $pageContent ); + } + } - if ( !is_null( $pageTitle ) && !$pageTitle->isKnown() && $pageTitle->canExist() ){ - $newWikiPage = new WikiPage( $pageTitle ); - $pageContent = ContentHandler::makeContent( $pageContentText, $sourceTitle ); - $newWikiPage->doEditContent( $pageContent, - "Page created automatically by parser function on page [[$sourceTitleText]]" ); //TODO i18n + // Reset state. Probably not needed since parsing is usually done here anyway: + $editInfo->output->setExtensionData( 'createPage', null ); + $egAutoCreatePageMaxRecursion++; - // wfDebugLog( 'createpage', "CREATED PAGE " . $pageTitle->getText() . " Text: " . $pageContent ); - } + return true; } - // Reset state. Probably not needed since parsing is usually done here anyway: - $editInfo->output->setExtensionData( 'createPage', null ); - $egAutoCreatePageMaxRecursion++; - - return true; } - diff --git a/extension.json b/extension.json new file mode 100644 index 0000000..c388189 --- /dev/null +++ b/extension.json @@ -0,0 +1,35 @@ +{ + "name": "AutoCreatePage", + "version": "0.6-dev", + "author": [ + "[http://korrekt.org Markus Krötzsch]", + "Daniel Herzig" + ], + "url": "https://www.mediawiki.org/wiki/Extension:AutoCreatePage", + "description": "Provides a parser function to create additional wiki pages with default content when saving a page.", + "license-name": "GPL-2.0+", + "requires": { + "MediaWiki": ">= 1.31" + }, + "AutoloadClasses": { + "AutoCreatePage": "AutoCreatePage.php" + }, + "Hooks": { + "ParserFirstCallInit": [ + "AutoCreatePage::onParserFirstCallInit" + ], + "ArticleEditUpdates": [ + "AutoCreatePage::onArticleEditUpdates" + ] + }, + "config": { + "AutoCreatePageMaxRecursion": 1, + "AutoCreatePageIgnoreEmptyTitle": false, + "AutoCreatePageNamespaces": null, + "_prefix": "eg" + }, + "ExtensionMessagesFiles": { + "AutoCreatePageMagic": "AutoCreatePage.i18n.magic.php" + }, + "manifest_version": 1 +} From 1597e3d895118cedd2d052c0f9904f0426508c02 Mon Sep 17 00:00:00 2001 From: Markus <92986684+gesinn-it-wam@users.noreply.github.com> Date: Tue, 12 Jul 2022 13:14:49 +0200 Subject: [PATCH 2/6] Added CI (some static code analysis for now) --- .github/workflows/ci.yml | 32 ++++++++++++++++++++++ .gitignore | 1 + .phpcs.xml | 16 +++++++++++ AutoCreatePage.i18n.magic.php | 10 +++---- AutoCreatePage.php | 18 ++++++------- Dockerfile | 15 +++++++++++ Makefile | 51 +++++++++++++++++++++++++++++++++++ composer.json | 26 ++++++++++++++++++ phpstan-baseline.neon | 0 phpstan.neon | 11 ++++++++ 10 files changed, 166 insertions(+), 14 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 .phpcs.xml create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 composer.json create mode 100644 phpstan-baseline.neon create mode 100644 phpstan.neon diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..2eb29f0 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,32 @@ +name: CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + workflow_dispatch: + +jobs: + + ci: + + runs-on: ubuntu-20.04 + continue-on-error: ${{ matrix.experimental }} + + strategy: + matrix: + include: + - mediawiki_version: '1.35' + experimental: false + - mediawiki_version: '1.36' + experimental: false + - mediawiki_version: '1.37' + experimental: false + + env: + MW_VERSION: ${{ matrix.mediawiki_version }} + + steps: + - uses: actions/checkout@v3 + - run: make ci diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..22d0d82 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +vendor diff --git a/.phpcs.xml b/.phpcs.xml new file mode 100644 index 0000000..a1d2312 --- /dev/null +++ b/.phpcs.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + . + + + + diff --git a/AutoCreatePage.i18n.magic.php b/AutoCreatePage.i18n.magic.php index ab99a63..9d8e4bd 100644 --- a/AutoCreatePage.i18n.magic.php +++ b/AutoCreatePage.i18n.magic.php @@ -2,13 +2,13 @@ /** * Magic words - * + * * @file */ -$magicWords = array(); +$magicWords = []; /** English (English) */ -$magicWords['en'] = array( - 'createPage' => array( 0, 'createpageifnotex' ), -); +$magicWords['en'] = [ + 'createPage' => [ 0, 'createpageifnotex' ], +]; diff --git a/AutoCreatePage.php b/AutoCreatePage.php index 79252c5..51d95c8 100644 --- a/AutoCreatePage.php +++ b/AutoCreatePage.php @@ -25,11 +25,11 @@ class AutoCreatePage { public static function onParserFirstCallInit( $parser ) { - $parser->setFunctionHook( 'createPage', [ self::class, 'createPageIfNotExisting' ]); + $parser->setFunctionHook( 'createPage', [ self::class, 'createPageIfNotExisting' ] ); } public function onArticleEditUpdates( $wikiPage, $editInfo, $changed ) { - self::doCreatePages($wikiPage, $editInfo, $changed); + self::doCreatePages( $wikiPage, $editInfo, $changed ); } /** @@ -44,12 +44,12 @@ public static function createPageIfNotExisting( $parser, $newPageTitleText, $new $autoCreatePageNamespaces = $egAutoCreatePageNamespaces ?? $wgContentNamespaces; if ( $egAutoCreatePageMaxRecursion <= 0 ) { - return 'Error: Recursion level for auto-created pages exceeded.'; //TODO i18n + return 'Error: Recursion level for auto-created pages exceeded.'; // TODO i18n } if ( empty( $newPageTitleText ) ) { if ( $egAutoCreatePageIgnoreEmptyTitle === false ) { - return 'Error: this function must be given a valid title text for the page to be created.'; //TODO i18n + return 'Error: this function must be given a valid title text for the page to be created.'; // TODO i18n } else { return ''; } @@ -65,8 +65,8 @@ public static function createPageIfNotExisting( $parser, $newPageTitleText, $new // Store data in the parser output for later use: $createPageData = $parser->getOutput()->getExtensionData( 'createPage' ); - if ( is_null( $createPageData ) ) { - $createPageData = array(); + if ( $createPageData === null ) { + $createPageData = []; } $createPageData[$newPageTitleText] = $newPageContent; $parser->getOutput()->setExtensionData( 'createPage', $createPageData ); @@ -83,7 +83,7 @@ private static function doCreatePages( $article, $editInfo, $changed ) { global $egAutoCreatePageMaxRecursion; $createPageData = $editInfo->output->getExtensionData( 'createPage' ); - if ( is_null( $createPageData ) ) { + if ( $createPageData === null ) { return true; // no pages to create } @@ -97,11 +97,11 @@ private static function doCreatePages( $article, $editInfo, $changed ) { $pageTitle = Title::newFromText( $pageTitleText ); // wfDebugLog( 'createpage', "CREATE " . $pageTitle->getText() . " Text: " . $pageContent ); - if ( !is_null( $pageTitle ) && !$pageTitle->isKnown() && $pageTitle->canExist() ){ + if ( $pageTitle !== null && !$pageTitle->isKnown() && $pageTitle->canExist() ) { $newWikiPage = new WikiPage( $pageTitle ); $pageContent = ContentHandler::makeContent( $pageContentText, $sourceTitle ); $newWikiPage->doEditContent( $pageContent, - "Page created automatically by parser function on page [[$sourceTitleText]]" ); //TODO i18n + "Page created automatically by parser function on page [[$sourceTitleText]]" ); // TODO i18n // wfDebugLog( 'createpage', "CREATED PAGE " . $pageTitle->getText() . " Text: " . $pageContent ); } diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9cd9bc3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +ARG MW_VERSION=1.35 +FROM gesinn/docker-mediawiki-sqlite:${MW_VERSION} + +ENV EXTENSION=AutoCreatePage +COPY composer*.json /var/www/html/extensions/$EXTENSION/ + +RUN cd extensions/$EXTENSION && \ + composer update + +COPY . /var/www/html/extensions/$EXTENSION + +RUN sed -i s/80/8080/g /etc/apache2/sites-available/000-default.conf /etc/apache2/ports.conf && \ + echo \ + "wfLoadExtension( '$EXTENSION' );\n" \ + >> LocalSettings.php diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ce7ddb6 --- /dev/null +++ b/Makefile @@ -0,0 +1,51 @@ +EXTENSION := AutoCreatePage + +MW_VERSION ?= 1.35 + +EXTENSION_FOLDER := /var/www/html/extensions/$(EXTENSION) +IMAGE_NAME := $(shell echo $(EXTENSION) | tr A-Z a-z}):test-$(MW_VERSION)-$(SMW_VERSION) +PWD := $(shell bash -c "pwd -W 2>/dev/null || pwd")# this way it works on Windows and Linux +DOCKER_RUN_ARGS := --rm -v $(PWD)/coverage:$(EXTENSION_FOLDER)/coverage -w $(EXTENSION_FOLDER) $(IMAGE_NAME) +docker_run := docker run $(DOCKER_RUN_ARGS) + +.PHONY: all +all: + +# ======== CI ======== + +.PHONY: ci +ci: build test + +.PHONY: ci-coverage +ci-coverage: build test-coverage + +.PHONY: build +build: + docker build --tag $(IMAGE_NAME) \ + --build-arg=MW_VERSION=$(MW_VERSION) \ + . + +.PHONY: test +test: composer-test + +.PHONY: test-coverage +test-coverage: composer-test-coverage + +.PHONY: composer-test +composer-test: + $(docker_run) composer test + +.PHONY: composer-test-coverage +composer-test-coverage: + $(docker_run) composer test-coverage + +.PHONY: bash +bash: + docker run -it -v $(PWD):/src $(DOCKER_RUN_ARGS) bash + +.PHONY: dev-bash +dev-bash: + docker run -it --rm -p 8080:8080 \ + -v $(PWD):$(EXTENSION_FOLDER) \ + -v $(EXTENSION_FOLDER)/vendor/ \ + -w $(EXTENSION_FOLDER) $(IMAGE_NAME) bash -c 'service apache2 start && bash' diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..f772f4d --- /dev/null +++ b/composer.json @@ -0,0 +1,26 @@ +{ + "require-dev": { + "mediawiki/mediawiki-codesniffer": "39.0.0", + "php-parallel-lint/php-console-highlighter": "1.0.0", + "php-parallel-lint/php-parallel-lint": "1.3.2", + "phpstan/phpstan": "^1.7" + }, + "scripts": { + "test": [ + "@analyze" + ], + "test-coverage": [ + "@analyze" + ], + "analyze": [ + "@lint", + "@phpcs", + "@phpstan" + ], + "lint": "parallel-lint . --exclude vendor", + "phpcs": "phpcs -p -s .", + "phpcs-fix": "phpcbf .", + "phpstan": "phpstan analyse --configuration=phpstan.neon --memory-limit=2G", + "phpstan-baseline": "phpstan analyse --configuration=phpstan.neon --memory-limit=2G --generate-baseline" + } +} diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 0000000..e69de29 diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..b8b43f1 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,11 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: 5 + paths: + - . + excludePaths: + - vendor + scanDirectories: + - ../../includes From 5dfeeda9801ca998663d79e385436b36be4076a6 Mon Sep 17 00:00:00 2001 From: Markus <92986684+gesinn-it-wam@users.noreply.github.com> Date: Tue, 12 Jul 2022 16:00:37 +0200 Subject: [PATCH 3/6] Fixed for usage in MW 1.37 - use Hook interfaces and HookHandlers - register for RevisionDataUpdates hook instead of ArticleEditUpdates (which has been removed in MW 1.37) --- AutoCreatePage.php | 28 +++++++++++++++++++--------- Makefile | 2 +- extension.json | 15 ++++++++------- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/AutoCreatePage.php b/AutoCreatePage.php index 51d95c8..8969260 100644 --- a/AutoCreatePage.php +++ b/AutoCreatePage.php @@ -1,5 +1,8 @@ setFunctionHook( 'createPage', [ self::class, 'createPageIfNotExisting' ] ); } - public function onArticleEditUpdates( $wikiPage, $editInfo, $changed ) { - self::doCreatePages( $wikiPage, $editInfo, $changed ); + public function onRevisionDataUpdates( $title, $renderedRevision, &$updates ) { + return self::doCreatePages( $title, $renderedRevision->getRevisionParserOutput() ); } /** * Handles the parser function for creating pages that don't exist yet, * filling them with the given default content. It is possible to use <nowiki> * in the default text parameter to insert verbatim wiki text. + * @param Parser $parser + * @param string $newPageTitleText + * @param string $newPageContent + * @return string */ public static function createPageIfNotExisting( $parser, $newPageTitleText, $newPageContent ) { global $egAutoCreatePageMaxRecursion, $egAutoCreatePageIgnoreEmptyTitle, @@ -78,11 +85,16 @@ public static function createPageIfNotExisting( $parser, $newPageTitleText, $new * Creates pages that have been requested by the create page parser function. This is done only * after the safe is complete to avoid any concurrent article modifications. * Note that article is, in spite of its name, a WikiPage object since MW 1.21. + * @param Title $sourceTitle + * @param ParserOutput $output + * @return bool + * @throws MWContentSerializationException + * @throws MWException */ - private static function doCreatePages( $article, $editInfo, $changed ) { + private static function doCreatePages( $sourceTitle, $output ) { global $egAutoCreatePageMaxRecursion; - $createPageData = $editInfo->output->getExtensionData( 'createPage' ); + $createPageData = $output->getExtensionData( 'createPage' ); if ( $createPageData === null ) { return true; // no pages to create } @@ -90,7 +102,6 @@ private static function doCreatePages( $article, $editInfo, $changed ) { // Prevent pages to be created by pages that are created to avoid loops: $egAutoCreatePageMaxRecursion--; - $sourceTitle = $article->getTitle(); $sourceTitleText = $sourceTitle->getPrefixedText(); foreach ( $createPageData as $pageTitleText => $pageContentText ) { @@ -102,13 +113,12 @@ private static function doCreatePages( $article, $editInfo, $changed ) { $pageContent = ContentHandler::makeContent( $pageContentText, $sourceTitle ); $newWikiPage->doEditContent( $pageContent, "Page created automatically by parser function on page [[$sourceTitleText]]" ); // TODO i18n - // wfDebugLog( 'createpage', "CREATED PAGE " . $pageTitle->getText() . " Text: " . $pageContent ); } } // Reset state. Probably not needed since parsing is usually done here anyway: - $editInfo->output->setExtensionData( 'createPage', null ); + $output->setExtensionData( 'createPage', null ); $egAutoCreatePageMaxRecursion++; return true; diff --git a/Makefile b/Makefile index ce7ddb6..0c18372 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ EXTENSION := AutoCreatePage MW_VERSION ?= 1.35 EXTENSION_FOLDER := /var/www/html/extensions/$(EXTENSION) -IMAGE_NAME := $(shell echo $(EXTENSION) | tr A-Z a-z}):test-$(MW_VERSION)-$(SMW_VERSION) +IMAGE_NAME := $(shell echo $(EXTENSION) | tr A-Z a-z}):test-$(MW_VERSION) PWD := $(shell bash -c "pwd -W 2>/dev/null || pwd")# this way it works on Windows and Linux DOCKER_RUN_ARGS := --rm -v $(PWD)/coverage:$(EXTENSION_FOLDER)/coverage -w $(EXTENSION_FOLDER) $(IMAGE_NAME) docker_run := docker run $(DOCKER_RUN_ARGS) diff --git a/extension.json b/extension.json index c388189..4d13f63 100644 --- a/extension.json +++ b/extension.json @@ -9,18 +9,19 @@ "description": "Provides a parser function to create additional wiki pages with default content when saving a page.", "license-name": "GPL-2.0+", "requires": { - "MediaWiki": ">= 1.31" + "MediaWiki": ">= 1.35" }, "AutoloadClasses": { "AutoCreatePage": "AutoCreatePage.php" }, "Hooks": { - "ParserFirstCallInit": [ - "AutoCreatePage::onParserFirstCallInit" - ], - "ArticleEditUpdates": [ - "AutoCreatePage::onArticleEditUpdates" - ] + "ParserFirstCallInit": "main", + "RevisionDataUpdates": "main" + }, + "HookHandlers": { + "main": { + "class": "AutoCreatePage" + } }, "config": { "AutoCreatePageMaxRecursion": 1, From 23029c1514ff74f942a22bf255b54e8564c240cf Mon Sep 17 00:00:00 2001 From: Markus <92986684+gesinn-it-wam@users.noreply.github.com> Date: Wed, 13 Jul 2022 11:23:01 +0200 Subject: [PATCH 4/6] Added basic integration tests --- .gitignore | 2 + composer.json | 10 +- composer.lock | 566 ++++++++++++++++++ extension.json | 6 +- phpstan-baseline.neon | 6 + phpstan.neon | 2 + phpunit.xml.dist | 24 + AutoCreatePage.php => src/AutoCreatePage.php | 16 +- .../Integration/AutoCreatePageTest.php | 78 +++ 9 files changed, 698 insertions(+), 12 deletions(-) create mode 100644 composer.lock create mode 100644 phpunit.xml.dist rename AutoCreatePage.php => src/AutoCreatePage.php (94%) create mode 100644 tests/phpunit/Integration/AutoCreatePageTest.php diff --git a/.gitignore b/.gitignore index 22d0d82..19ec1f0 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ vendor +coverage +.phpunit.result.cache diff --git a/composer.json b/composer.json index f772f4d..f57a79a 100644 --- a/composer.json +++ b/composer.json @@ -7,10 +7,12 @@ }, "scripts": { "test": [ - "@analyze" + "@analyze", + "@phpunit" ], "test-coverage": [ - "@analyze" + "@analyze", + "@phpunit-coverage" ], "analyze": [ "@lint", @@ -21,6 +23,8 @@ "phpcs": "phpcs -p -s .", "phpcs-fix": "phpcbf .", "phpstan": "phpstan analyse --configuration=phpstan.neon --memory-limit=2G", - "phpstan-baseline": "phpstan analyse --configuration=phpstan.neon --memory-limit=2G --generate-baseline" + "phpstan-baseline": "phpstan analyse --configuration=phpstan.neon --memory-limit=2G --generate-baseline", + "phpunit": "php ../../tests/phpunit/phpunit.php -c phpunit.xml.dist --testdox", + "phpunit-coverage": "php ../../tests/phpunit/phpunit.php -c phpunit.xml.dist --testdox --coverage-text --coverage-html coverage/php --coverage-clover coverage/php/coverage.xml" } } diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..7c4f6e8 --- /dev/null +++ b/composer.lock @@ -0,0 +1,566 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "6d145d97b56592c80ec22dbfbdb7cb9f", + "packages": [], + "packages-dev": [ + { + "name": "composer/semver", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/3953f23262f2bff1919fc82183ad9acb13ff62c9", + "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-04-01T19:23:25+00:00" + }, + { + "name": "composer/spdx-licenses", + "version": "1.5.7", + "source": { + "type": "git", + "url": "https://github.com/composer/spdx-licenses.git", + "reference": "c848241796da2abf65837d51dce1fae55a960149" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/c848241796da2abf65837d51dce1fae55a960149", + "reference": "c848241796da2abf65837d51dce1fae55a960149", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.55", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Spdx\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "SPDX licenses list and validation library.", + "keywords": [ + "license", + "spdx", + "validator" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/spdx-licenses/issues", + "source": "https://github.com/composer/spdx-licenses/tree/1.5.7" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-05-23T07:37:50+00:00" + }, + { + "name": "mediawiki/mediawiki-codesniffer", + "version": "v39.0.0", + "source": { + "type": "git", + "url": "https://github.com/wikimedia/mediawiki-tools-codesniffer.git", + "reference": "9d6d7ea4314f928e8967bb386bb54c17bb96fb77" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wikimedia/mediawiki-tools-codesniffer/zipball/9d6d7ea4314f928e8967bb386bb54c17bb96fb77", + "reference": "9d6d7ea4314f928e8967bb386bb54c17bb96fb77", + "shasum": "" + }, + "require": { + "composer/semver": "3.3.2", + "composer/spdx-licenses": "~1.5.2", + "ext-json": "*", + "ext-mbstring": "*", + "php": ">=7.2.0", + "squizlabs/php_codesniffer": "3.6.2" + }, + "require-dev": { + "mediawiki/mediawiki-phan-config": "0.11.1", + "mediawiki/minus-x": "1.1.1", + "php-parallel-lint/php-console-highlighter": "1.0.0", + "php-parallel-lint/php-parallel-lint": "1.3.2", + "phpunit/phpunit": "^8.5" + }, + "type": "phpcodesniffer-standard", + "autoload": { + "psr-4": { + "MediaWiki\\Sniffs\\": "MediaWiki/Sniffs/", + "MediaWiki\\Sniffs\\Tests\\": "MediaWiki/Tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "MediaWiki CodeSniffer Standards", + "homepage": "https://www.mediawiki.org/wiki/Manual:Coding_conventions/PHP", + "keywords": [ + "codesniffer", + "mediawiki" + ], + "support": { + "source": "https://github.com/wikimedia/mediawiki-tools-codesniffer/tree/v39.0.0" + }, + "time": "2022-05-04T16:19:01+00:00" + }, + { + "name": "php-parallel-lint/php-console-color", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-parallel-lint/PHP-Console-Color.git", + "reference": "7adfefd530aa2d7570ba87100a99e2483a543b88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-parallel-lint/PHP-Console-Color/zipball/7adfefd530aa2d7570ba87100a99e2483a543b88", + "reference": "7adfefd530aa2d7570ba87100a99e2483a543b88", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "replace": { + "jakub-onderka/php-console-color": "*" + }, + "require-dev": { + "php-parallel-lint/php-code-style": "^2.0", + "php-parallel-lint/php-parallel-lint": "^1.0", + "php-parallel-lint/php-var-dump-check": "0.*", + "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHP_Parallel_Lint\\PhpConsoleColor\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Jakub Onderka", + "email": "jakub.onderka@gmail.com" + } + ], + "description": "Simple library for creating colored console ouput.", + "support": { + "issues": "https://github.com/php-parallel-lint/PHP-Console-Color/issues", + "source": "https://github.com/php-parallel-lint/PHP-Console-Color/tree/v1.0.1" + }, + "time": "2021-12-25T06:49:29+00:00" + }, + { + "name": "php-parallel-lint/php-console-highlighter", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-parallel-lint/PHP-Console-Highlighter.git", + "reference": "5b4803384d3303cf8e84141039ef56c8a123138d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-parallel-lint/PHP-Console-Highlighter/zipball/5b4803384d3303cf8e84141039ef56c8a123138d", + "reference": "5b4803384d3303cf8e84141039ef56c8a123138d", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.2", + "php-parallel-lint/php-console-color": "^1.0.1" + }, + "replace": { + "jakub-onderka/php-console-highlighter": "*" + }, + "require-dev": { + "php-parallel-lint/php-code-style": "^2.0", + "php-parallel-lint/php-parallel-lint": "^1.0", + "php-parallel-lint/php-var-dump-check": "0.*", + "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHP_Parallel_Lint\\PhpConsoleHighlighter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jakub Onderka", + "email": "acci@acci.cz", + "homepage": "http://www.acci.cz/" + } + ], + "description": "Highlight PHP code in terminal", + "support": { + "issues": "https://github.com/php-parallel-lint/PHP-Console-Highlighter/issues", + "source": "https://github.com/php-parallel-lint/PHP-Console-Highlighter/tree/v1.0.0" + }, + "time": "2022-02-18T08:23:19+00:00" + }, + { + "name": "php-parallel-lint/php-parallel-lint", + "version": "v1.3.2", + "source": { + "type": "git", + "url": "https://github.com/php-parallel-lint/PHP-Parallel-Lint.git", + "reference": "6483c9832e71973ed29cf71bd6b3f4fde438a9de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-parallel-lint/PHP-Parallel-Lint/zipball/6483c9832e71973ed29cf71bd6b3f4fde438a9de", + "reference": "6483c9832e71973ed29cf71bd6b3f4fde438a9de", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=5.3.0" + }, + "replace": { + "grogy/php-parallel-lint": "*", + "jakub-onderka/php-parallel-lint": "*" + }, + "require-dev": { + "nette/tester": "^1.3 || ^2.0", + "php-parallel-lint/php-console-highlighter": "0.* || ^1.0", + "squizlabs/php_codesniffer": "^3.6" + }, + "suggest": { + "php-parallel-lint/php-console-highlighter": "Highlight syntax in code snippet" + }, + "bin": [ + "parallel-lint" + ], + "type": "library", + "autoload": { + "classmap": [ + "./src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Jakub Onderka", + "email": "ahoj@jakubonderka.cz" + } + ], + "description": "This tool check syntax of PHP files about 20x faster than serial check.", + "homepage": "https://github.com/php-parallel-lint/PHP-Parallel-Lint", + "support": { + "issues": "https://github.com/php-parallel-lint/PHP-Parallel-Lint/issues", + "source": "https://github.com/php-parallel-lint/PHP-Parallel-Lint/tree/v1.3.2" + }, + "time": "2022-02-21T12:50:22+00:00" + }, + { + "name": "phpcompatibility/php-compatibility", + "version": "9.3.5", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibility.git", + "reference": "9fb324479acf6f39452e0655d2429cc0d3914243" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243", + "reference": "9fb324479acf6f39452e0655d2429cc0d3914243", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.3 || ^3.0.2" + }, + "conflict": { + "squizlabs/php_codesniffer": "2.6.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Wim Godden", + "homepage": "https://github.com/wimg", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors" + } + ], + "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.", + "homepage": "http://techblog.wimgodden.be/tag/codesniffer/", + "keywords": [ + "compatibility", + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues", + "source": "https://github.com/PHPCompatibility/PHPCompatibility" + }, + "time": "2019-12-27T09:44:58+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.8.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "8dbba631fa32f4b289404469c2afd6122fd61d67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/8dbba631fa32f4b289404469c2afd6122fd61d67", + "reference": "8dbba631fa32f4b289404469c2afd6122fd61d67", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.8.1" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-07-12T16:08:06+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-12-12T21:44:58+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.1.0" +} diff --git a/extension.json b/extension.json index 4d13f63..eebabbe 100644 --- a/extension.json +++ b/extension.json @@ -11,8 +11,8 @@ "requires": { "MediaWiki": ">= 1.35" }, - "AutoloadClasses": { - "AutoCreatePage": "AutoCreatePage.php" + "AutoloadNamespaces": { + "ACP\\": "src/" }, "Hooks": { "ParserFirstCallInit": "main", @@ -20,7 +20,7 @@ }, "HookHandlers": { "main": { - "class": "AutoCreatePage" + "class": "ACP\\AutoCreatePage" } }, "config": { diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index e69de29..ecf4983 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -0,0 +1,6 @@ +parameters: + ignoreErrors: + - + message: "#^Call to an undefined method Content\\:\\:getText\\(\\)\\.$#" + count: 1 + path: tests/phpunit/Integration/AutoCreatePageTest.php diff --git a/phpstan.neon b/phpstan.neon index b8b43f1..66dc403 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -9,3 +9,5 @@ parameters: - vendor scanDirectories: - ../../includes + - ../../tests/phpunit + - ../../vendor/phpunit diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..b8aeb9a --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,24 @@ + + + + + tests/phpunit + + + + + src + + + diff --git a/AutoCreatePage.php b/src/AutoCreatePage.php similarity index 94% rename from AutoCreatePage.php rename to src/AutoCreatePage.php index 8969260..2230577 100644 --- a/AutoCreatePage.php +++ b/src/AutoCreatePage.php @@ -1,17 +1,24 @@ : - $newPageContent = $parser->mStripState->unstripNoWiki( $newPageContent ); + $newPageContent = $parser->getStripState()->unstripNoWiki( $newPageContent ); // Store data in the parser output for later use: $createPageData = $parser->getOutput()->getExtensionData( 'createPage' ); @@ -88,8 +94,6 @@ public static function createPageIfNotExisting( $parser, $newPageTitleText, $new * @param Title $sourceTitle * @param ParserOutput $output * @return bool - * @throws MWContentSerializationException - * @throws MWException */ private static function doCreatePages( $sourceTitle, $output ) { global $egAutoCreatePageMaxRecursion; diff --git a/tests/phpunit/Integration/AutoCreatePageTest.php b/tests/phpunit/Integration/AutoCreatePageTest.php new file mode 100644 index 0000000..38de792 --- /dev/null +++ b/tests/phpunit/Integration/AutoCreatePageTest.php @@ -0,0 +1,78 @@ +randomize( [ 'x', 'y' ] ); + $this->insertPage( $x, "{{#createpageifnotex:$y|" . self::EXPECTED_TEXT . "}}" ); + + $text = $this->textOf( $y ); + $this->assertEquals( self::EXPECTED_TEXT, $text ); + + $text = $this->textOf( $x ); + $this->assertEquals( "{{#createpageifnotex:$y|" . self::EXPECTED_TEXT . "}}", $text ); + } + + public function testOnlyCreatesPageIfItDoesntExist() { + [ $x, $y ] = self::randomize( [ 'x', 'y' ] ); + $this->insertPage( $y, self::EXPECTED_TEXT ); + $this->insertPage( $x, "{{#createpageifnotex:$y|other text}}" ); + + $text = $this->textOf( $y ); + + $this->assertEquals( self::EXPECTED_TEXT, $text ); + } + + public function testIgnoresRequestToCreateCurrentPage() { + [ $x ] = self::randomize( [ 'x' ] ); + $this->insertPage( $x, "{{#createpageifnotex:$x|x}}" ); + + $text = $this->textOf( $x ); + + $this->assertEquals( "{{#createpageifnotex:$x|x}}", $text ); + } + + public function testRecurses() { + [ $x1, $x2, $x3 ] = self::randomize( [ 'x1', 'x2', 'x3' ] ); + $this->insertPage( $x1, + "{{#createpageifnotex:$x2|{{#createpageifnotex:$x3|" . self::EXPECTED_TEXT . '}}}}' ); + + $text = $this->textOf( $x3 ); + $this->assertEquals( self::EXPECTED_TEXT, $text ); + +// todo: $x2 should not remain empty! +// $text = $this->textOf( $x2 ); +// $this->assertEquals( "{{#createpageifnotex:$x3|" . self::EXPECTED_TEXT . '}}', $text ); + +// todo: should throw an exception instead! +// public function testDoesNotRecurseTooDeeply() { +// [ $x1, $x2, $x3, $x4 ] = self::randomize( [ 'x1', 'x2', 'x3', 'x4' ] ); +// $this->insertPage( $x1, +// "{{#createpageifnotex:$x2|{{#createpageifnotex:$x3|{{#createpageifnotex:$x4|" . +// self::EXPECTED_TEXT . '}}}}}}' ); +// +// $text = $this->textOf( $x4 ); +// +// $this->assertEquals( self::EXPECTED_TEXT, $text ); + } + + private function textOf( $title ) { + return $this->getExistingTestPage( $title )->getContent()->getText(); + } + + private static function randomize( $titles ) { + return array_map( static function ( $t ) { return $t . '-' . mt_rand(); + }, $titles ); + } + +} From 8f143e05e4ffe65b2e4446482973e0e4a186443b Mon Sep 17 00:00:00 2001 From: Markus <92986684+gesinn-it-wam@users.noreply.github.com> Date: Wed, 13 Jul 2022 16:56:14 +0200 Subject: [PATCH 5/6] Fixed test for recursion - required, of course! - max-recursion failing for MW >= 1.36 --- .github/workflows/ci.yml | 4 +- .phpcs.xml | 1 + .../Integration/AutoCreatePageTest.php | 63 +++++++++++++------ 3 files changed, 46 insertions(+), 22 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2eb29f0..b732529 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,9 +20,9 @@ jobs: - mediawiki_version: '1.35' experimental: false - mediawiki_version: '1.36' - experimental: false + experimental: true - mediawiki_version: '1.37' - experimental: false + experimental: true env: MW_VERSION: ${{ matrix.mediawiki_version }} diff --git a/.phpcs.xml b/.phpcs.xml index a1d2312..8969a1c 100644 --- a/.phpcs.xml +++ b/.phpcs.xml @@ -6,6 +6,7 @@ + diff --git a/tests/phpunit/Integration/AutoCreatePageTest.php b/tests/phpunit/Integration/AutoCreatePageTest.php index 38de792..0303909 100644 --- a/tests/phpunit/Integration/AutoCreatePageTest.php +++ b/tests/phpunit/Integration/AutoCreatePageTest.php @@ -3,6 +3,8 @@ namespace ACP\Tests\Integration; use MediaWikiIntegrationTestCase; +use Title; +use WikiPage; /** * @covers \ACP\AutoCreatePage @@ -11,6 +13,19 @@ class AutoCreatePageTest extends MediaWikiIntegrationTestCase { private const EXPECTED_TEXT = 'expected text'; + private static $defaultAutoCreatePageMaxRecursion; + + public static function setUpBeforeClass(): void { + parent::setUpBeforeClass(); + global $egAutoCreatePageMaxRecursion; + self::$defaultAutoCreatePageMaxRecursion = $egAutoCreatePageMaxRecursion; + } + + protected function setUp(): void { + parent::setUp(); + global $egAutoCreatePageMaxRecursion; + $egAutoCreatePageMaxRecursion = self::$defaultAutoCreatePageMaxRecursion; + } public function testCreatesPage() { [ $x, $y ] = $this->randomize( [ 'x', 'y' ] ); @@ -42,36 +57,44 @@ public function testIgnoresRequestToCreateCurrentPage() { $this->assertEquals( "{{#createpageifnotex:$x|x}}", $text ); } - public function testRecurses() { + public function testDoesntRecurseByDefault() { [ $x1, $x2, $x3 ] = self::randomize( [ 'x1', 'x2', 'x3' ] ); $this->insertPage( $x1, - "{{#createpageifnotex:$x2|{{#createpageifnotex:$x3|" . self::EXPECTED_TEXT . '}}}}' ); + "{{#createpageifnotex:$x2|{{#createpageifnotex:$x3|" . self::EXPECTED_TEXT . + '}}}}' ); - $text = $this->textOf( $x3 ); - $this->assertEquals( self::EXPECTED_TEXT, $text ); + $text2 = $this->textOf( $x2 ); + $this->assertEquals( "{{#createpageifnotex:$x3|" . self::EXPECTED_TEXT . '}}', $text2 ); -// todo: $x2 should not remain empty! -// $text = $this->textOf( $x2 ); -// $this->assertEquals( "{{#createpageifnotex:$x3|" . self::EXPECTED_TEXT . '}}', $text ); - -// todo: should throw an exception instead! -// public function testDoesNotRecurseTooDeeply() { -// [ $x1, $x2, $x3, $x4 ] = self::randomize( [ 'x1', 'x2', 'x3', 'x4' ] ); -// $this->insertPage( $x1, -// "{{#createpageifnotex:$x2|{{#createpageifnotex:$x3|{{#createpageifnotex:$x4|" . -// self::EXPECTED_TEXT . '}}}}}}' ); -// -// $text = $this->textOf( $x4 ); -// -// $this->assertEquals( self::EXPECTED_TEXT, $text ); + $text3 = $this->textOf( $x3 ); + $this->assertNull( $text3 ); + } + + public function testRecursesIfToldSo() { + global $egAutoCreatePageMaxRecursion; + $egAutoCreatePageMaxRecursion = 2; + + [ $x1, $x2, $x3 ] = self::randomize( [ 'x1', 'x2', 'x3' ] ); + $this->insertPage( $x1, + "{{#createpageifnotex:$x2|{{#createpageifnotex:$x3|" . self::EXPECTED_TEXT . + '}}}}' ); + + $text2 = $this->textOf( $x2 ); + $this->assertEquals( "{{#createpageifnotex:$x3|" . self::EXPECTED_TEXT . '}}', $text2 ); + + $text3 = $this->textOf( $x3 ); + $this->assertEquals( self::EXPECTED_TEXT, $text3 ); } private function textOf( $title ) { - return $this->getExistingTestPage( $title )->getContent()->getText(); + $page = WikiPage::factory( Title::newFromText( $title ) ); + + return $page->exists() ? $page->getContent()->getText() : null; } private static function randomize( $titles ) { - return array_map( static function ( $t ) { return $t . '-' . mt_rand(); + return array_map( static function ( $t ) { + return $t . '-' . mt_rand(); }, $titles ); } From 1945b9dbcdb69d63a31df50467451e4b4d3a5290 Mon Sep 17 00:00:00 2001 From: Markus <92986684+gesinn-it-wam@users.noreply.github.com> Date: Tue, 4 Jul 2023 13:24:29 +0000 Subject: [PATCH 6/6] Create pages also on special pages (#3) Restricted to specific special pages listed in $egAutoCreatePageOnSpecialPages --- .gitignore | 1 + extension.json | 5 +- src/AutoCreatePage.php | 112 ++++++++++++------ src/SpecialPage.php | 25 ++++ .../Integration/AutoCreatePageTest.php | 36 ++++++ 5 files changed, 143 insertions(+), 36 deletions(-) create mode 100644 src/SpecialPage.php diff --git a/.gitignore b/.gitignore index 19ec1f0..1f402cf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ vendor coverage .phpunit.result.cache +.vscode diff --git a/extension.json b/extension.json index eebabbe..2b1d4dc 100644 --- a/extension.json +++ b/extension.json @@ -16,7 +16,9 @@ }, "Hooks": { "ParserFirstCallInit": "main", - "RevisionDataUpdates": "main" + "RevisionDataUpdates": "main", + "SpecialPageBeforeExecute": "main", + "SpecialPageAfterExecute": "main" }, "HookHandlers": { "main": { @@ -27,6 +29,7 @@ "AutoCreatePageMaxRecursion": 1, "AutoCreatePageIgnoreEmptyTitle": false, "AutoCreatePageNamespaces": null, + "AutoCreatePageOnSpecialPages": [], "_prefix": "eg" }, "ExtensionMessagesFiles": { diff --git a/src/AutoCreatePage.php b/src/AutoCreatePage.php index 2230577..97bed39 100644 --- a/src/AutoCreatePage.php +++ b/src/AutoCreatePage.php @@ -4,9 +4,10 @@ use ContentHandler; use MediaWiki\Hook\ParserFirstCallInitHook; +use MediaWiki\SpecialPage\Hook\SpecialPageAfterExecuteHook; +use MediaWiki\SpecialPage\Hook\SpecialPageBeforeExecuteHook; use MediaWiki\Storage\Hook\RevisionDataUpdatesHook; use Parser; -use ParserOutput; use Title; use WikiPage; @@ -31,16 +32,22 @@ * @author Daniel Herzig * @file */ -class AutoCreatePage implements ParserFirstCallInitHook, RevisionDataUpdatesHook { +class AutoCreatePage implements + ParserFirstCallInitHook, + RevisionDataUpdatesHook, + SpecialPageBeforeExecuteHook, + SpecialPageAfterExecuteHook +{ + + /** + * @var SpecialPage[] + */ + private static $specialPageStack = []; public function onParserFirstCallInit( $parser ) { $parser->setFunctionHook( 'createPage', [ self::class, 'createPageIfNotExisting' ] ); } - public function onRevisionDataUpdates( $title, $renderedRevision, &$updates ) { - return self::doCreatePages( $title, $renderedRevision->getRevisionParserOutput() ); - } - /** * Handles the parser function for creating pages that don't exist yet, * filling them with the given default content. It is possible to use <nowiki> @@ -68,36 +75,37 @@ public static function createPageIfNotExisting( $parser, $newPageTitleText, $new } } - // Create pages only if the page calling the parser function is within defined namespaces - if ( !in_array( $parser->getTitle()->getNamespace(), $autoCreatePageNamespaces ) ) { - return ''; - } - // Get the raw text of $newPageContent as it was before stripping : $newPageContent = $parser->getStripState()->unstripNoWiki( $newPageContent ); - // Store data in the parser output for later use: - $createPageData = $parser->getOutput()->getExtensionData( 'createPage' ); - if ( $createPageData === null ) { - $createPageData = []; + $enabledSpecialPage = self::enabledSpecialPage(); + if ( $enabledSpecialPage ) { + // Store data in static variable for later use in onSpecialPageAfterExecute: + $enabledSpecialPage->addPageToCreate( $newPageTitleText, $newPageContent ); + } elseif ( in_array( $parser->getTitle()->getNamespace(), $autoCreatePageNamespaces ) ) { + // For pages with namespace in $autoCreatePageNamespaces store data in the parser output + // for later use in onRevisionDataUpdates:s + $createPageData = $parser->getOutput()->getExtensionData( 'createPage' ); + if ( $createPageData === null ) { + $createPageData = []; + } + $createPageData[$newPageTitleText] = $newPageContent; + $parser->getOutput()->setExtensionData( 'createPage', $createPageData ); } - $createPageData[$newPageTitleText] = $newPageContent; - $parser->getOutput()->setExtensionData( 'createPage', $createPageData ); return ""; } /** - * Creates pages that have been requested by the create page parser function. This is done only - * after the safe is complete to avoid any concurrent article modifications. + * Create pages that have been requested by the create page parser function for pages with + * namespace in $egAutoCreatePageNamespaces. + * This is done only after the safe is complete to avoid any concurrent article modifications. * Note that article is, in spite of its name, a WikiPage object since MW 1.21. - * @param Title $sourceTitle - * @param ParserOutput $output - * @return bool */ - private static function doCreatePages( $sourceTitle, $output ) { + public function onRevisionDataUpdates( $title, $renderedRevision, &$updates ) { global $egAutoCreatePageMaxRecursion; + $output = $renderedRevision->getRevisionParserOutput(); $createPageData = $output->getExtensionData( 'createPage' ); if ( $createPageData === null ) { return true; // no pages to create @@ -106,19 +114,8 @@ private static function doCreatePages( $sourceTitle, $output ) { // Prevent pages to be created by pages that are created to avoid loops: $egAutoCreatePageMaxRecursion--; - $sourceTitleText = $sourceTitle->getPrefixedText(); - foreach ( $createPageData as $pageTitleText => $pageContentText ) { - $pageTitle = Title::newFromText( $pageTitleText ); - // wfDebugLog( 'createpage', "CREATE " . $pageTitle->getText() . " Text: " . $pageContent ); - - if ( $pageTitle !== null && !$pageTitle->isKnown() && $pageTitle->canExist() ) { - $newWikiPage = new WikiPage( $pageTitle ); - $pageContent = ContentHandler::makeContent( $pageContentText, $sourceTitle ); - $newWikiPage->doEditContent( $pageContent, - "Page created automatically by parser function on page [[$sourceTitleText]]" ); // TODO i18n - // wfDebugLog( 'createpage', "CREATED PAGE " . $pageTitle->getText() . " Text: " . $pageContent ); - } + self::createPage( $title, $pageTitleText, $pageContentText ); } // Reset state. Probably not needed since parsing is usually done here anyway: @@ -128,4 +125,49 @@ private static function doCreatePages( $sourceTitle, $output ) { return true; } + /** + * Register special page in static $specialPageStack + */ + public function onSpecialPageBeforeExecute( $special, $subPage ) { + global $egAutoCreatePageOnSpecialPages; + + $isEnabled = in_array( $special->getName(), $egAutoCreatePageOnSpecialPages ); + array_push( self::$specialPageStack, new SpecialPage( $isEnabled ) ); + } + + /** + * Create pages requested on enabled special pages + */ + public function onSpecialPageAfterExecute( $special, $subPage ) { + $specialPage = array_pop( self::$specialPageStack ); + + if ( $specialPage->isEnabled() ) { + foreach ( $specialPage->pagesToCreate() as $pageTitleText => $pageContentText ) { + self::createPage( $special->getFullTitle(), $pageTitleText, $pageContentText ); + } + } + } + + /** + * @return SpecialPage|null the current special page if it is enabled, null otherwise + */ + private static function enabledSpecialPage() { + $count = count( self::$specialPageStack ); + $currentSpecialPage = $count === 0 ? null : self::$specialPageStack[ $count - 1 ]; + return $currentSpecialPage !== null && $currentSpecialPage->isEnabled() ? $currentSpecialPage : null; + } + + private static function createPage( $sourceTitle, $pageTitleText, $pageContentText ) { + $sourceTitleText = $sourceTitle->getPrefixedText(); + $pageTitle = Title::newFromText( $pageTitleText ); + // wfDebugLog( 'createpage', "CREATE " . $pageTitle->getText() . " Text: " . $pageContent ); + + if ( $pageTitle !== null && !$pageTitle->isKnown() && $pageTitle->canExist() ) { + $newWikiPage = new WikiPage( $pageTitle ); + $pageContent = ContentHandler::makeContent( $pageContentText, $sourceTitle ); + $newWikiPage->doEditContent( $pageContent, + "Page created automatically by parser function on page [[$sourceTitleText]]" ); // TODO i18n + // wfDebugLog( 'createpage', "CREATED PAGE " . $pageTitle->getText() . " Text: " . $pageContent ); + } + } } diff --git a/src/SpecialPage.php b/src/SpecialPage.php new file mode 100644 index 0000000..6562b8f --- /dev/null +++ b/src/SpecialPage.php @@ -0,0 +1,25 @@ +isEnabled = $isEnabled; + } + + public function isEnabled() { + return $this->isEnabled; + } + + public function addPageToCreate( $titleText, $content ) { + $this->pagesToCreate[$titleText] = $content; + } + + public function pagesToCreate() { + return $this->pagesToCreate; + } +} diff --git a/tests/phpunit/Integration/AutoCreatePageTest.php b/tests/phpunit/Integration/AutoCreatePageTest.php index 0303909..41eab9e 100644 --- a/tests/phpunit/Integration/AutoCreatePageTest.php +++ b/tests/phpunit/Integration/AutoCreatePageTest.php @@ -2,7 +2,10 @@ namespace ACP\Tests\Integration; +use FauxRequest; +use MediaWiki\MediaWikiServices; use MediaWikiIntegrationTestCase; +use RequestContext; use Title; use WikiPage; @@ -27,6 +30,12 @@ protected function setUp(): void { $egAutoCreatePageMaxRecursion = self::$defaultAutoCreatePageMaxRecursion; } + protected function tearDown(): void { + parent::tearDown(); + global $egAutoCreatePageOnSpecialPages; + $egAutoCreatePageOnSpecialPages = []; + } + public function testCreatesPage() { [ $x, $y ] = $this->randomize( [ 'x', 'y' ] ); $this->insertPage( $x, "{{#createpageifnotex:$y|" . self::EXPECTED_TEXT . "}}" ); @@ -86,6 +95,33 @@ public function testRecursesIfToldSo() { $this->assertEquals( self::EXPECTED_TEXT, $text3 ); } + public function testIsNotCalledFromUnenabledSpecialPage() { + $page = $page = self::createOnSpecialExpandTemplates( 'NotCreated' ); + + $this->assertFalse( $page->exists() ); + } + + public function testIsCalledFromEnabledSpecialPage() { + global $egAutoCreatePageOnSpecialPages; + $egAutoCreatePageOnSpecialPages = [ 'ExpandTemplates' ]; + + $page = self::createOnSpecialExpandTemplates( 'Created' ); + + $this->assertTrue( $page->exists() ); + } + + private static function createOnSpecialExpandTemplates( $title ) { + $ctx = new RequestContext(); + $ctx->setRequest( new FauxRequest( [ + 'wpContextTitle' => 'X', + 'wpInput' => "{{#createpageifnotex:$title|Some content...}}", + ] ) ); + $sp = Title::makeTitle( NS_SPECIAL, 'ExpandTemplates' ); + MediaWikiServices::getInstance()->getSpecialPageFactory()->executePath( $sp, $ctx ); + + return WikiPage::factory( Title::newFromText( $title ) ); + } + private function textOf( $title ) { $page = WikiPage::factory( Title::newFromText( $title ) );