diff --git a/.github/workflows/api-integration-tests.yml b/.github/workflows/api-integration-tests.yml index ee2905d1d..51fa1c00f 100644 --- a/.github/workflows/api-integration-tests.yml +++ b/.github/workflows/api-integration-tests.yml @@ -103,7 +103,7 @@ jobs: database-password: ${{ env.POSTGRES_PASSWORD }} - name: Prime app build - run: make composer + run: make php-test-dependencies-appstore - name: Configure server with app uses: SMillerDev/nextcloud-actions/setup-nextcloud-app@b13a04eb6a4e48972d31fa07559619843fcc2102 # main diff --git a/CHANGELOG.md b/CHANGELOG.md index 12f1bed81..6b9c3a4af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ You can also check [on GitHub](https://github.com/nextcloud/news/releases), the ### Changed ### Fixed +- Fixed News not starting since 28.5.0 due to php class loading issues (#3780) # Releases ## [28.5.0] - 2026-06-03 diff --git a/Makefile b/Makefile index 7e3644b8d..20a8abc4a 100644 --- a/Makefile +++ b/Makefile @@ -51,9 +51,13 @@ appstore_sign_dir=$(appstore_build_directory)/sign cert_dir=$(HOME)/.nextcloud/certificates npm:=$(shell which npm 2> /dev/null) composer:=$(shell which composer 2> /dev/null) +phpunit:=$(shell which phpunit 2> /dev/null) ifeq (,$(composer)) composer:=php "$(build_tools_directory)/composer.phar" endif +ifeq (,$(phpunit)) + phpunit:=./vendor/phpunit/phpunit/phpunit +endif #Support xDebug 3.0+ export XDEBUG_MODE=coverage @@ -99,12 +103,12 @@ clean: # Reports PHP codestyle violations .PHONY: phpcs -phpcs: +phpcs: php-dev-dependencies-if-needed ./vendor/bin/phpcs --standard=PSR2 --ignore=lib/Migration/Version*.php,lib/Vendor/* lib # Reports PHP static violations .PHONY: phpstan -phpstan: +phpstan: php-dev-dependencies-if-needed ./vendor/bin/phpstan analyse --level=1 lib # Same as clean but also removes dependencies installed by composer and @@ -207,6 +211,13 @@ php-test-dependencies: $(composer) install --prefer-dist $(composer) scope-dependencies +.PHONY: php-test-dependencies-appstore +php-test-dependencies-appstore: + COMPOSER_NO_PLUGINS=1 $(composer) install --prefer-dist --no-dev --no-scripts + COMPOSER_NO_PLUGINS=1 $(composer) scope-dependencies + php ./bin/tools/fix_scoped_autoload.php + find "vendor" -mindepth 1 -maxdepth 1 ! -name 'composer' ! -name 'autoload.php' -exec rm -rf {} + + .PHONY: php-test-dependencies-if-needed php-test-dependencies-if-needed: @if [ ! -x "./vendor/phpunit/phpunit/phpunit" ]; then \ @@ -214,6 +225,20 @@ php-test-dependencies-if-needed: $(MAKE) php-test-dependencies; \ fi +.PHONY: php-dev-dependencies-if-needed +php-dev-dependencies-if-needed: + @if [ ! -x "./vendor/bin/phpstan" ]; then \ + echo "Dev dependencies not found. Installing dev dependencies..."; \ + $(MAKE) php-test-dependencies; \ + fi + +.PHONY: php-test-dependencies-if-needed-appstore +php-test-dependencies-if-needed-appstore: + @if [ ! -f "vendor/autoload.php" ] || [ ! -d "lib/Vendor" ]; then \ + echo "Appstore-style PHP test dependencies not found. Installing scoped runtime dependencies..."; \ + $(MAKE) php-test-dependencies-appstore; \ + fi + .PHONY: scope-if-needed scope-if-needed: @if [ ! -d "lib/Vendor" ]; then \ @@ -225,6 +250,10 @@ scope-if-needed: unit-test: php-test-dependencies-if-needed scope-if-needed ./vendor/phpunit/phpunit/phpunit -c phpunit.xml --coverage-clover build/php-unit.clover +.PHONY: unit-test-appstore +unit-test-appstore: php-test-dependencies-if-needed-appstore scope-if-needed + $(phpunit) -c phpunit.xml --coverage-clover build/php-unit.clover + # Command for running JS and PHP tests. Works for package.json files in the js/ # and root directory. If phpunit is not installed systemwide, a copy is fetched # from the internet diff --git a/bin/tools/fix_scoped_autoload.php b/bin/tools/fix_scoped_autoload.php new file mode 100644 index 000000000..4934f167b --- /dev/null +++ b/bin/tools/fix_scoped_autoload.php @@ -0,0 +1,95 @@ +#!/usr/bin/env php + $scopedRelative) { + $autoloadFilesContent = str_replace( + "\$vendorDir . '/" . $vendorRelative . "'", + "\$baseDir . '/lib/Vendor/" . $scopedRelative . "'", + $autoloadFilesContent + ); + $autoloadStaticContent = str_replace( + "__DIR__ . '/..' . '/" . $vendorRelative . "'", + "__DIR__ . '/../..' . '/lib/Vendor/" . $scopedRelative . "'", + $autoloadStaticContent + ); +} + +if (file_put_contents($autoloadFilesPath, $autoloadFilesContent) === false + || file_put_contents($autoloadStaticPath, $autoloadStaticContent) === false) { + fwrite(STDERR, 'Unable to update Composer autoload files' . PHP_EOL); + exit(1); +} + +function mapVendorPathToScopedPath(string $relativeVendorPath, string $scopedDir): ?string +{ + $parts = explode('/', $relativeVendorPath); + if (count($parts) < 3) { + return null; + } + + $vendor = normalizePackageSegment($parts[0]); + $packageParts = array_map('normalizePackageSegment', explode('-', $parts[1])); + $tail = array_slice($parts, 2); + + $candidate = $vendor . '/' . implode('/', $packageParts); + if ($tail !== []) { + $candidate .= '/' . implode('/', $tail); + } + + return is_file($scopedDir . '/' . $candidate) ? $candidate : null; +} + +function normalizePackageSegment(string $segment): string +{ + $words = preg_split('/[^a-zA-Z0-9]+/', $segment) ?: []; + $words = array_filter($words, static fn (string $word): bool => $word !== ''); + return implode('', array_map(static fn (string $word): string => ucfirst(strtolower($word)), $words)); +} diff --git a/composer.json b/composer.json index 068fe417b..f893764e3 100644 --- a/composer.json +++ b/composer.json @@ -101,7 +101,8 @@ "rm -Rf lib/Vendor", "@php lib-vendor-organizer.php build/ lib/Vendor/ \"OCA\\\\News\\\\Vendor\"", "rm -Rf build", - "composer dump-autoload" + "composer dump-autoload", + "@php bin/tools/fix_scoped_autoload.php" ] } }