From d07ab55860b3b0013b4e3f807329793c41e22b8f Mon Sep 17 00:00:00 2001 From: Jeroen De Dauw Date: Thu, 9 Apr 2026 16:03:02 +0200 Subject: [PATCH 1/5] Migrate cl_to queries to linktarget for MW 1.45 compatibility Fixes https://github.com/ProfessionalWiki/PageApprovals/issues/147 MediaWiki 1.45 removed the cl_to column from categorylinks in favor of cl_target_id referencing the linktarget table. Detect the schema at runtime via fieldExists() and branch to the appropriate query, keeping MW 1.43 compatibility. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../DatabasePendingApprovalRetriever.php | 63 +++++++++++++++++++ .../DatabasePendingApprovalRetrieverTest.php | 2 +- 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/Adapters/DatabasePendingApprovalRetriever.php b/src/Adapters/DatabasePendingApprovalRetriever.php index caaba4b..ba98ad5 100644 --- a/src/Adapters/DatabasePendingApprovalRetriever.php +++ b/src/Adapters/DatabasePendingApprovalRetriever.php @@ -38,6 +38,18 @@ public function getPendingApprovalsForApprover( int $approverId ): array { * @param string[] $categories */ private function queryPendingApprovals( array $categories ): IResultWrapper { + if ( $this->db->fieldExists( 'categorylinks', 'cl_to', __METHOD__ ) ) { + return $this->queryPendingApprovalsLegacy( $categories ); + } + + return $this->queryPendingApprovalsWithLinkTarget( $categories ); + } + + /** + * Query for MW 1.43–1.44 where categorylinks has cl_to. + * @param string[] $categories + */ + private function queryPendingApprovalsLegacy( array $categories ): IResultWrapper { $latestApprovalSubquery = $this->getLatestApprovalSubquery(); return $this->db->select( @@ -81,6 +93,57 @@ private function queryPendingApprovals( array $categories ): IResultWrapper { ); } + /** + * Query for MW 1.45+ where categorylinks uses cl_target_id referencing the linktarget table. + * @param string[] $categories + */ + private function queryPendingApprovalsWithLinkTarget( array $categories ): IResultWrapper { + $latestApprovalSubquery = $this->getLatestApprovalSubquery(); + + return $this->db->select( + [ + 'page', + 'revision', + 'categorylinks', + 'linktarget', + 'latest_approval' => $latestApprovalSubquery + ], + [ + 'page_id', + 'page_namespace', + 'page_title', + 'rev_timestamp', + 'rev_actor', + 'GROUP_CONCAT(DISTINCT lt_title) AS categories', + 'latest_approval.al_is_approved', + 'latest_approval.al_timestamp' + ], + [ + 'lt_namespace' => NS_CATEGORY, + 'lt_title' => $this->normalizeCategoryTitles( $categories ), + $this->db->makeList( + [ + 'latest_approval.al_is_approved' => 0, + 'latest_approval.al_is_approved IS NULL' + ], + IDatabase::LIST_OR + ) + ], + __METHOD__, + [ + 'GROUP BY' => 'page_id', + 'ORDER BY' => 'rev_timestamp DESC', + 'LIMIT' => $this->limit + ], + [ + 'revision' => [ 'INNER JOIN', 'page_latest = rev_id' ], + 'categorylinks' => [ 'INNER JOIN', 'page_id = cl_from' ], + 'linktarget' => [ 'INNER JOIN', 'cl_target_id = lt_id' ], + 'latest_approval' => [ 'LEFT JOIN', 'page_id = latest_approval.al_page_id' ] + ] + ); + } + private function getLatestApprovalSubquery(): Subquery { return $this->db->buildSelectSubquery( [ diff --git a/tests/Adapters/DatabasePendingApprovalRetrieverTest.php b/tests/Adapters/DatabasePendingApprovalRetrieverTest.php index 50bbaad..e4a405e 100644 --- a/tests/Adapters/DatabasePendingApprovalRetrieverTest.php +++ b/tests/Adapters/DatabasePendingApprovalRetrieverTest.php @@ -22,7 +22,7 @@ class DatabasePendingApprovalRetrieverTest extends PageApprovalsIntegrationTest protected function setUp(): void { parent::setUp(); - $this->tablesUsed = [ 'page', 'revision', 'categorylinks', 'approval_log' ]; + $this->tablesUsed = [ 'page', 'revision', 'categorylinks', 'linktarget', 'approval_log' ]; $this->approverRepository = new InMemoryApproverRepository(); $this->retriever = new DatabasePendingApprovalRetriever( $this->db, $this->approverRepository ); From 539e7671a7cc283cb7317ce99db684b18df3d7cc Mon Sep 17 00:00:00 2001 From: Jeroen De Dauw Date: Thu, 9 Apr 2026 16:58:35 +0200 Subject: [PATCH 2/5] Fix static analysis: use MW_VERSION check instead of fieldExists fieldExists() is on IMaintainableDatabase, not IDatabase. Use version_compare(MW_VERSION) for the schema detection instead. Also update the Psalm baseline for the new query method. Co-Authored-By: Claude Opus 4.6 (1M context) --- psalm-baseline.xml | 12 ++++++++++++ src/Adapters/DatabasePendingApprovalRetriever.php | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 909c43f..d2e003d 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -63,6 +63,7 @@ + @@ -83,6 +84,17 @@ IDatabase::LIST_OR ) ]]]> + NS_CATEGORY, + 'lt_title' => $this->normalizeCategoryTitles( $categories ), + $this->db->makeList( + [ + 'latest_approval.al_is_approved' => 0, + 'latest_approval.al_is_approved IS NULL' + ], + IDatabase::LIST_OR + ) + ]]]> diff --git a/src/Adapters/DatabasePendingApprovalRetriever.php b/src/Adapters/DatabasePendingApprovalRetriever.php index ba98ad5..fb5525e 100644 --- a/src/Adapters/DatabasePendingApprovalRetriever.php +++ b/src/Adapters/DatabasePendingApprovalRetriever.php @@ -38,7 +38,7 @@ public function getPendingApprovalsForApprover( int $approverId ): array { * @param string[] $categories */ private function queryPendingApprovals( array $categories ): IResultWrapper { - if ( $this->db->fieldExists( 'categorylinks', 'cl_to', __METHOD__ ) ) { + if ( version_compare( MW_VERSION, '1.45', '<' ) ) { return $this->queryPendingApprovalsLegacy( $categories ); } From 8342422da224ba3e8217a69a3cb725f06cfb620b Mon Sep 17 00:00:00 2001 From: Jeroen De Dauw Date: Thu, 9 Apr 2026 17:01:56 +0200 Subject: [PATCH 3/5] Fix static analysis baselines for version-branched code PHPStan sees MW_VERSION as a compile-time constant and flags the version check as always-true and the linktarget path as unreachable. Psalm sees MW_VERSION as mixed. Add baseline entries for both. Co-Authored-By: Claude Opus 4.6 (1M context) --- phpstan-baseline.neon | 15 +++++++++++++++ psalm-baseline.xml | 1 + src/Adapters/DatabasePendingApprovalRetriever.php | 6 +++++- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 34c6cb6..fa22665 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -35,6 +35,21 @@ parameters: count: 1 path: src/Adapters/DatabaseHtmlRepository.php + - + message: "#^If condition is always true\\.$#" + count: 1 + path: src/Adapters/DatabasePendingApprovalRetriever.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: src/Adapters/DatabasePendingApprovalRetriever.php + + - + message: "#^Method ProfessionalWiki\\\\PageApprovals\\\\Adapters\\\\DatabasePendingApprovalRetriever\\:\\:queryPendingApprovalsWithLinkTarget\\(\\) is unused\\.$#" + count: 1 + path: src/Adapters/DatabasePendingApprovalRetriever.php + - message: "#^Cannot access property \\$categories on array\\|stdClass\\|false\\.$#" count: 1 diff --git a/psalm-baseline.xml b/psalm-baseline.xml index d2e003d..0d5b057 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -70,6 +70,7 @@ categories]]> page_title]]> + diff --git a/src/Adapters/DatabasePendingApprovalRetriever.php b/src/Adapters/DatabasePendingApprovalRetriever.php index fb5525e..19bdc0a 100644 --- a/src/Adapters/DatabasePendingApprovalRetriever.php +++ b/src/Adapters/DatabasePendingApprovalRetriever.php @@ -38,13 +38,17 @@ public function getPendingApprovalsForApprover( int $approverId ): array { * @param string[] $categories */ private function queryPendingApprovals( array $categories ): IResultWrapper { - if ( version_compare( MW_VERSION, '1.45', '<' ) ) { + if ( $this->hasLegacyCategoryLinksSchema() ) { return $this->queryPendingApprovalsLegacy( $categories ); } return $this->queryPendingApprovalsWithLinkTarget( $categories ); } + private function hasLegacyCategoryLinksSchema(): bool { + return version_compare( MW_VERSION, '1.45', '<' ); + } + /** * Query for MW 1.43–1.44 where categorylinks has cl_to. * @param string[] $categories From 289b33334b9dcb1e254ddb3981ae163696bf404c Mon Sep 17 00:00:00 2001 From: Jeroen De Dauw Date: Thu, 9 Apr 2026 17:05:48 +0200 Subject: [PATCH 4/5] Remove unneeded PHPStan baseline entries Extracting hasLegacyCategoryLinksSchema() hides the constant expression from PHPStan, so the always-true/unreachable/unused errors no longer appear. Co-Authored-By: Claude Opus 4.6 (1M context) --- phpstan-baseline.neon | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index fa22665..34c6cb6 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -35,21 +35,6 @@ parameters: count: 1 path: src/Adapters/DatabaseHtmlRepository.php - - - message: "#^If condition is always true\\.$#" - count: 1 - path: src/Adapters/DatabasePendingApprovalRetriever.php - - - - message: "#^Unreachable statement \\- code above always terminates\\.$#" - count: 1 - path: src/Adapters/DatabasePendingApprovalRetriever.php - - - - message: "#^Method ProfessionalWiki\\\\PageApprovals\\\\Adapters\\\\DatabasePendingApprovalRetriever\\:\\:queryPendingApprovalsWithLinkTarget\\(\\) is unused\\.$#" - count: 1 - path: src/Adapters/DatabasePendingApprovalRetriever.php - - message: "#^Cannot access property \\$categories on array\\|stdClass\\|false\\.$#" count: 1 From 402a213a2949608051b8ddcbfd0f43707a422e3f Mon Sep 17 00:00:00 2001 From: Jeroen De Dauw Date: Thu, 9 Apr 2026 17:23:34 +0200 Subject: [PATCH 5/5] Remove MW master from PHPUnit CI matrix MW master removed tests/phpunit/phpunit.php, breaking this job. Drop it until the CI is updated to use the new entry point. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ci.yml | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f2f4233..30d8cb3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,23 +7,16 @@ on: jobs: test: name: "PHPUnit: MW ${{ matrix.mw }}, PHP ${{ matrix.php }}" - continue-on-error: ${{ matrix.experimental }} strategy: matrix: include: - mw: 'REL1_43' php: '8.1' - experimental: false - mw: 'REL1_43' php: '8.2' - experimental: false - mw: 'REL1_43' php: '8.3' - experimental: false - - mw: 'master' - php: '8.4' - experimental: true runs-on: ubuntu-latest @@ -75,15 +68,6 @@ jobs: - name: Run PHPUnit run: php tests/phpunit/phpunit.php -c extensions/PageApprovals/ - if: matrix.mw != 'master' - - - name: Run PHPUnit with code coverage - run: php tests/phpunit/phpunit.php -c extensions/PageApprovals/ --coverage-clover coverage.xml - if: matrix.mw == 'master' - - - name: Upload code coverage - run: bash <(curl -s https://codecov.io/bash) - if: matrix.mw == 'master'