From ea0d48e7c399dede1f25822d552545acb0c4d95e Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 6 Oct 2026 09:34:09 +0000 Subject: [PATCH 1/3] chore(nc35): declare Nextcloud 35 support - appinfo/info.xml: max-version 34 -> 35 (min stays 32, matching openregister's floor) - composer.json: nextcloud/ocp ^34.0 -> ^35.0, composer.lock refreshed (ocp v35.0.1; pulls symfony/polyfill-php84/85/86 as ocp deps) - code-quality.yml: drop the hard-coded nextcloud-test-refs override so the shared quality.yml derives the matrix (incl. stable35) from info.xml Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01M7DxSg7d7wqqZsUppLWf7J --- .github/workflows/code-quality.yml | 11 +- appinfo/info.xml | 2 +- composer.json | 2 +- composer.lock | 266 +++++++++++++++++++++++++++-- 4 files changed, 260 insertions(+), 21 deletions(-) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 4c731a7..7aa395b 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -53,14 +53,9 @@ jobs: app-name: apptemplate php-version: "8.3" php-test-versions: '["8.3", "8.4"]' - # The whole range `appinfo/info.xml` declares (min-version="32", - # max-version="34"), not just its top. With only `stable34` listed, 32 and - # 33 were advertised as supported and tested by nothing. - # `stable34` MUST stay FIRST: newman, playwright and journeydoc-capture - # each read `fromJSON(inputs.nextcloud-test-refs)[0]` as their single - # server, so element 0 is the ref those legs run against — it has to be - # the newest supported release, not whichever ref happens to sort first. - nextcloud-test-refs: '["stable34", "stable32", "stable33"]' + # No `nextcloud-test-refs` override: the shared quality.yml derives the + # Nextcloud matrix from appinfo/info.xml (min/max-version), so every + # declared major (32..35) gets a leg without editing this file. enable-psalm: true enable-phpstan: true enable-phpmetrics: true diff --git a/appinfo/info.xml b/appinfo/info.xml index c7bfcac..63ba6c2 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -32,7 +32,7 @@ at top level, repair-steps precedes settings and navigations follows it. --> - + diff --git a/composer.json b/composer.json index 5883107..91fe402 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "conduction/hydra-gates": "^1.18.0", "cyclonedx/cyclonedx-php-composer": "^6.2", "edgedesign/phpqa": "^1.27", - "nextcloud/ocp": "^34.0", + "nextcloud/ocp": "^35.0", "phpcsstandards/phpcsextra": "^1.4", "phpmd/phpmd": "^2.15", "phpmetrics/phpmetrics": "^2.8", diff --git a/composer.lock b/composer.lock index ccac174..8113c92 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ad26c68419e9fe6ff946b6c2c755d3b9", + "content-hash": "c851b8dd29b9a0b4fa2d4347d568f1ae", "packages": [], "packages-dev": [ { @@ -1870,30 +1870,34 @@ }, { "name": "nextcloud/ocp", - "version": "v34.0.2", + "version": "v35.0.1", "source": { "type": "git", "url": "https://github.com/nextcloud-deps/ocp.git", - "reference": "81cbb2c594afe0fa978885bf7accd0440199520a" + "reference": "088e38c04842e027ff255d90b689817b74a54e60" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/81cbb2c594afe0fa978885bf7accd0440199520a", - "reference": "81cbb2c594afe0fa978885bf7accd0440199520a", + "url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/088e38c04842e027ff255d90b689817b74a54e60", + "reference": "088e38c04842e027ff255d90b689817b74a54e60", "shasum": "" }, "require": { - "php": "~8.2 || ~8.3 || ~8.4 || ~8.5", + "php": "~8.3 || ~8.4 || ~8.5", "psr/clock": "^1.0", "psr/container": "^2.0.2", "psr/event-dispatcher": "^1.0", "psr/http-client": "^1.0.3", - "psr/log": "^3.0.2" + "psr/log": "^3.0.2", + "symfony/polyfill-intl-normalizer": "^1.38", + "symfony/polyfill-php84": "^1.38", + "symfony/polyfill-php85": "^1.41", + "symfony/polyfill-php86": "^1.41" }, "type": "library", "extra": { "branch-alias": { - "dev-stable34": "34.0.0-dev" + "dev-stable35": "35.0.0-dev" } }, "notification-url": "https://packagist.org/downloads/", @@ -1913,9 +1917,9 @@ "description": "Composer package containing Nextcloud's public OCP API and the unstable NCU API", "support": { "issues": "https://github.com/nextcloud-deps/ocp/issues", - "source": "https://github.com/nextcloud-deps/ocp/tree/v34.0.2" + "source": "https://github.com/nextcloud-deps/ocp/tree/v35.0.1" }, - "time": "2026-07-16T01:28:13+00:00" + "time": "2026-09-23T02:18:13+00:00" }, { "name": "nikic/php-parser", @@ -7213,6 +7217,246 @@ ], "time": "2026-05-26T12:45:58+00:00" }, + { + "name": "symfony/polyfill-php84", + "version": "v1.43.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "22c6548499cbe5c5bb072fc0c42312dff062a015" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/22c6548499cbe5c5bb072fc0c42312dff062a015", + "reference": "22c6548499cbe5c5bb072fc0c42312dff062a015", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.43.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-09-23T22:01:09+00:00" + }, + { + "name": "symfony/polyfill-php85", + "version": "v1.43.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "86a42cbcc07adc331481a5ab310a4024bfc227f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/86a42cbcc07adc331481a5ab310a4024bfc227f6", + "reference": "86a42cbcc07adc331481a5ab310a4024bfc227f6", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php85\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php85/tree/v1.43.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-09-25T15:46:47+00:00" + }, + { + "name": "symfony/polyfill-php86", + "version": "v1.43.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php86.git", + "reference": "8840df45b24f33a49dc419fa18dc0057231ae1d8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php86/zipball/8840df45b24f33a49dc419fa18dc0057231ae1d8", + "reference": "8840df45b24f33a49dc419fa18dc0057231ae1d8", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php86\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.6+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php86/tree/v1.43.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-09-25T15:46:47+00:00" + }, { "name": "symfony/process", "version": "v7.4.13", @@ -7934,5 +8178,5 @@ "platform-overrides": { "php": "8.3" }, - "plugin-api-version": "2.9.0" + "plugin-api-version": "2.6.0" } From 25a234e783391eaa8d46d1eeb6c37187746ce54d Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 6 Oct 2026 09:35:34 +0000 Subject: [PATCH 2/3] feat(nc35): ship an NC35-safe OpenRegisterAutoloader prelude in the template Apps generated from this template reference OCA\OpenRegister\ from lib/, and `apptemplate` (like most app ids) sorts before `openregister`, so OpenRegister's PSR-4 prefix is not registered yet when register() runs. The fleet worked around that with \OC_App::registerAutoloading(), which Nextcloud 35 removed. Add OpenRegisterAutoloader (ported from decidiq / keepiq#712): registers a PSR-4 loader for OCA\OpenRegister\ -> /lib/ via spl_autoload_register, using only public IAppManager::isEnabledForAnyone() and getAppPath(). Never throws, never requires OpenRegister's vendor autoloader, never calls loadApp(). Called first thing in Application::register() so generated apps start from the safe pattern. Unit test covers: no OC_App in code, registration + idempotency, disabled -> false without touching getAppPath, never throws, and the class-file mapping only answering for OCA\OpenRegister\. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01M7DxSg7d7wqqZsUppLWf7J --- lib/AppInfo/Application.php | 12 ++ lib/AppInfo/OpenRegisterAutoloader.php | 195 ++++++++++++++++++ .../AppInfo/OpenRegisterAutoloaderTest.php | 108 ++++++++++ 3 files changed, 315 insertions(+) create mode 100644 lib/AppInfo/OpenRegisterAutoloader.php create mode 100644 tests/unit/AppInfo/OpenRegisterAutoloaderTest.php diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 58f879d..49a15c8 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -74,8 +74,20 @@ public function __construct() { * @param IRegistrationContext $context The registration context * * @return void + * + * @SuppressWarnings(PHPMD.StaticAccess) OpenRegisterAutoloader is a static + * prelude: this method is the composition root, so + * there is no container to inject it from yet. */ public function register(IRegistrationContext $context): void { + // `apptemplate` sorts before `openregister`, so Nextcloud has not put + // OpenRegister's PSR-4 prefix on the autoloader yet when this runs. Do + // it here, through public API only (NC 35 removed the private + // \OC_App::registerAutoloading()). Never throws; returns false when + // OpenRegister is absent. Keep any OCA\OpenRegister\ class USE below + // this line (a ::class constant alone does not autoload). + OpenRegisterAutoloader::register(); + // Register deep link patterns with OpenRegister's unified search provider. // Only fires when OpenRegister is installed and dispatches the event. $context->registerEventListener( diff --git a/lib/AppInfo/OpenRegisterAutoloader.php b/lib/AppInfo/OpenRegisterAutoloader.php new file mode 100644 index 0000000..2b2c969 --- /dev/null +++ b/lib/AppInfo/OpenRegisterAutoloader.php @@ -0,0 +1,195 @@ + + * @copyright 2026 Conduction B.V. + * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * SPDX-FileCopyrightText: 2026 Conduction B.V. + * SPDX-License-Identifier: EUPL-1.2 + * + * @version GIT: + * + * @link https://conduction.nl + */ + +declare(strict_types=1); + +namespace OCA\AppTemplate\AppInfo; + +/** + * Registers OpenRegister's autoload prefix before any of its classes is used. + * + * ## Why this is needed (ADR-040) + * + * `OC_App::getEnabledApps()` does `sort($apps)`, and + * `Coordinator::registerApps()` walks THAT sorted list registering one app's + * autoloader and then calling `$app->register()`, one app at a time. So every + * app's `register()` runs BEFORE the PSR-4 prefix of every alphabetically-LATER + * app exists. + * + * Most app ids sort before `openregister`, so `OCA\OpenRegister\` is NOT + * autoloadable inside `Application::register()` on a perfectly healthy + * instance with OpenRegister enabled. A `::class` constant does not autoload + * and is safe; anything that does (`class_exists()`, `instanceof` against a + * loaded object, a static call such as AppHost's `Bootstrap::register()`) + * throws or answers FALSE. An uncaught `\Error` aborts the whole of + * `register()` while `Coordinator` logs an `emergency` and carries on, so the + * app stays enabled and looks fine. + * + * Apps generated from this template: call `OpenRegisterAutoloader::register()` + * first thing in `Application::register()` (as the template does) and keep + * every OpenRegister reference after it behind a `class_exists()` guard. + * + * Lives in its own class rather than inline in `Application::register()` for + * one reason: `Application` cannot be constructed without a Nextcloud DI + * container, so an inline prelude is unreachable from a unit test. Here the + * degraded-path contract — "this NEVER throws, whatever the instance looks + * like" — is directly assertable, and it is asserted. + */ +final class OpenRegisterAutoloader { + + /** + * The app whose autoload prefix this prelude registers. + */ + private const OPENREGISTER_APP_ID = 'openregister'; + + /** + * The PSR-4 namespace prefix OpenRegister's `lib/` serves. + */ + private const OPENREGISTER_NAMESPACE = 'OCA\\OpenRegister\\'; + + /** + * The registered loader, or null when none is on the SPL chain. + * + * @var (\Closure(string): void)|null + */ + private static ?\Closure $loader = null; + + /** + * Register OpenRegister's PSR-4 prefix on the autoloader. + * + * MUST be called before any `OCA\OpenRegister\…` reference in + * `Application::register()`, including a `class_exists()` probe — the probe + * answers FALSE, not "not yet loaded", and a FALSE is indistinguishable + * from OpenRegister being absent. + * + * ## Public API only (Nextcloud 35) + * + * Earlier fleet copies of this prelude called `\OC_App::registerAutoloading()`. + * That is private API and Nextcloud 35 REMOVED it (it moved to the equally + * private `OC\App\AppManager::registerAutoloading()`), so the call threw, + * the catch below returned false and the caller's OpenRegister wiring was + * skipped in silence. Do not reintroduce it. For an app shipping `vendor/autoload.php` — which OpenRegister + * does — Nextcloud's own registration reduces to a PSR-4 prefix over `lib/`, + * so that is exactly what is registered here, with `spl_autoload_register()` + * and the public `IAppManager`. Same approach as keepiq#712. + * + * OpenRegister's `vendor/autoload.php` is deliberately NOT required: that + * would pull its whole dependency tree into this process, where versions + * differing from ours would win first-come. + * + * Deliberately NOT `IAppManager::loadApp('openregister')`: that marks + * OpenRegister loaded and calls `Coordinator::bootApp()`, booting it before + * its own `register()` has run. + * + * Idempotent: a second call while registered is a no-op. + * + * @param \OCP\App\IAppManager|null $appManager Injected for tests; resolved + * from the server when null. + * + * @return bool True when the prefix is registered, false when OpenRegister + * is absent, disabled, or otherwise unresolvable — in which + * case the caller MUST fall through to its degraded path. + * + * @SuppressWarnings(PHPMD.StaticAccess) `\OCP\Server::get()` is the public + * service locator, and this runs at the composition root where no + * container is available to inject from. + */ + public static function register(?\OCP\App\IAppManager $appManager=null): bool { + try { + $appManager ??= \OCP\Server::get(\OCP\App\IAppManager::class); + + // Checked before the short-circuit: under a long-lived worker this + // static outlives the request, and an OpenRegister disabled since + // must not stay wired. + if ($appManager->isEnabledForAnyone(self::OPENREGISTER_APP_ID) === false) { + self::unregister(); + return false; + } + + if (self::$loader !== null) { + return true; + } + + $path = rtrim($appManager->getAppPath(self::OPENREGISTER_APP_ID), '/'); + if (is_dir($path.'/lib') === false) { + return false; + } + + self::$loader = static function (string $class) use ($path): void { + $file = self::classFile(appPath: $path, class: $class); + if ($file !== null && is_file($file) === true) { + require_once $file; + } + }; + spl_autoload_register(self::$loader); + + return true; + } catch (\Throwable) { + // OpenRegister absent, or the server container is not up (unit + // tests). The caller's class_exists() guard then skips the AppHost + // plumbing. Never rethrow: an exception escaping here would abort + // the caller's entire register(), which is the exact defect this + // prelude exists to prevent. + return false; + }//end try + + }//end register() + + /** + * Take the loader off the SPL chain (tests, and OpenRegister disabled). + * + * @return void + */ + public static function unregister(): void { + if (self::$loader !== null) { + spl_autoload_unregister(self::$loader); + self::$loader = null; + } + + }//end unregister() + + /** + * Map an `OCA\OpenRegister\…` class name to its file under `lib/`. + * + * Returns null for any class that is not OpenRegister's, so this loader + * never answers for (and shadows) names another loader owns. + * + * @param string $appPath Absolute path to the openregister app, no trailing slash. + * @param string $class The fully qualified class name being resolved. + * + * @return string|null The candidate file, or null when not OpenRegister's. + */ + public static function classFile(string $appPath, string $class): ?string { + if (str_starts_with($class, self::OPENREGISTER_NAMESPACE) === false) { + return null; + } + + $relative = substr($class, strlen(self::OPENREGISTER_NAMESPACE)); + if ($relative === '') { + return null; + } + + return $appPath.'/lib/'.str_replace('\\', '/', $relative).'.php'; + + }//end classFile() +}//end class diff --git a/tests/unit/AppInfo/OpenRegisterAutoloaderTest.php b/tests/unit/AppInfo/OpenRegisterAutoloaderTest.php new file mode 100644 index 0000000..969cdf6 --- /dev/null +++ b/tests/unit/AppInfo/OpenRegisterAutoloaderTest.php @@ -0,0 +1,108 @@ + + * @copyright 2026 Conduction B.V. + * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * @version GIT: + * + * @link https://conduction.nl + * + * SPDX-FileCopyrightText: 2026 Conduction B.V. + * SPDX-License-Identifier: EUPL-1.2 + */ + +declare(strict_types=1); + +namespace OCA\AppTemplate\Tests\Unit\AppInfo; + +use OCA\AppTemplate\AppInfo\OpenRegisterAutoloader; +use OCP\App\IAppManager; +use PHPUnit\Framework\TestCase; + +/** + * The prelude must work on Nextcloud 35, which removed the private + * `OC_App::registerAutoloading()` it used to call, so it may only use public + * API and plain PHP — and it must never throw. + */ +class OpenRegisterAutoloaderTest extends TestCase { + + /** + * Temporary fake openregister app directory. + * + * @var string + */ + private string $appPath; + + protected function setUp(): void { + parent::setUp(); + $this->appPath = sys_get_temp_dir().'/apptemplate-or-'.bin2hex(random_bytes(4)); + mkdir($this->appPath.'/lib/Fake', 0777, true); + file_put_contents( + $this->appPath.'/lib/Fake/Probe.php', + "appPath.'/lib/Fake/Probe.php'); + @rmdir($this->appPath.'/lib/Fake'); + @rmdir($this->appPath.'/lib'); + @rmdir($this->appPath); + parent::tearDown(); + + }//end tearDown() + + public function testSourceUsesNoPrivateOcAppApi(): void { + $source = (string) file_get_contents(__DIR__.'/../../../lib/AppInfo/OpenRegisterAutoloader.php'); + $code = (string) preg_replace('#/\*.*?\*/|//[^\n]*#s', '', $source); + $this->assertStringNotContainsString('OC_App', $code); + + }//end testSourceUsesNoPrivateOcAppApi() + + public function testRegistersPsr4PrefixWhenEnabled(): void { + $appManager = $this->createMock(IAppManager::class); + $appManager->method('isEnabledForAnyone')->with('openregister')->willReturn(true); + $appManager->method('getAppPath')->with('openregister')->willReturn($this->appPath.'/'); + + $this->assertTrue(OpenRegisterAutoloader::register(appManager: $appManager)); + $this->assertTrue(OpenRegisterAutoloader::register(appManager: $appManager)); + $this->assertTrue(class_exists('OCA\\OpenRegister\\Fake\\Probe')); + + }//end testRegistersPsr4PrefixWhenEnabled() + + public function testReturnsFalseWhenDisabled(): void { + $appManager = $this->createMock(IAppManager::class); + $appManager->method('isEnabledForAnyone')->willReturn(false); + $appManager->expects($this->never())->method('getAppPath'); + + $this->assertFalse(OpenRegisterAutoloader::register(appManager: $appManager)); + + }//end testReturnsFalseWhenDisabled() + + public function testNeverThrows(): void { + $appManager = $this->createMock(IAppManager::class); + $appManager->method('isEnabledForAnyone')->willThrowException(new \RuntimeException('boom')); + + $this->assertFalse(OpenRegisterAutoloader::register(appManager: $appManager)); + + }//end testNeverThrows() + + public function testClassFileOnlyAnswersForOpenRegister(): void { + $this->assertSame( + '/x/lib/Db/Schema.php', + OpenRegisterAutoloader::classFile(appPath: '/x', class: 'OCA\\OpenRegister\\Db\\Schema') + ); + $this->assertNull(OpenRegisterAutoloader::classFile(appPath: '/x', class: 'OCA\\AppTemplate\\Foo')); + $this->assertNull(OpenRegisterAutoloader::classFile(appPath: '/x', class: 'OCA\\OpenRegister\\')); + + }//end testClassFileOnlyAnswersForOpenRegister() +}//end class From 952da6b2eda22e500e49149fc2cdc269dbeb25ac Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 6 Oct 2026 09:35:34 +0000 Subject: [PATCH 3/3] refactor(nc35): use OCP\Server in the admin settings template \OC::$server is private API; \OCP\Server::get() is the public PSR container accessor with identical semantics. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01M7DxSg7d7wqqZsUppLWf7J --- templates/settings/admin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/settings/admin.php b/templates/settings/admin.php index c0bca20..425fbdb 100644 --- a/templates/settings/admin.php +++ b/templates/settings/admin.php @@ -9,7 +9,7 @@ // Inject the app version via Nextcloud's IInitialState API so the Vue settings // app can read it with loadState('apptemplate', 'version') — the NC-standard // CSP-compliant approach (replaces the prior data-version DOM attribute). -\OC::$server->get(\OCP\IInitialStateService::class) +\OCP\Server::get(\OCP\IInitialStateService::class) ->provideInitialState($appId, 'version', $_['version'] ?? ''); // webpack splitChunks emits shared chunks that every entry-point depends on