{"record":{"id":"63e5cee758384f31","repo":"passbolt/passbolt_api","slug":"the-class-name-is-not-a-valid-plugin","errorCode":null,"errorMessage":"The class {$name} is not a valid plugin.","messagePattern":"The class (.+?) is not a valid plugin\\.","errorType":"exception","errorClass":"InternalErrorException","httpStatus":500,"severity":"error","filePath":"src/Utility/Application/FeaturePluginAwareTrait.php","lineNumber":67,"sourceCode":"    {\n        Configure::write($this->getPluginEnabledConfigurationKey($name), false);\n    }\n\n    /**\n     * @param string $name Plugin class name or plugin name, either upper case or lower case first (without the \"Passbolt/\" prefix)\n     * @return string\n     * @throws \\Cake\\Http\\Exception\\InternalErrorException if the plugin name is a class and not a plugin interface\n     */\n    protected function getPluginEnabledConfigurationKey(string $name): string\n    {\n        if (class_exists($name)) {\n            if (!is_subclass_of($name, PluginInterface::class)) {\n                throw new InternalErrorException(\"The class {$name} should implement PluginInterface::class.\");\n            }\n\n            $extractedName = substr(substr(strrchr($name, '\\\\'), 1), 0, -6);\n            if (empty($extractedName)) {\n                throw new InternalErrorException(\"The class {$name} is not a valid plugin.\");\n            }\n\n            $name = $extractedName;\n        }\n\n        $name = lcfirst($name);\n\n        return \"passbolt.plugins.{$name}.enabled\";\n    }\n}\n","sourceCodeStart":49,"sourceCodeEnd":78,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/src/Utility/Application/FeaturePluginAwareTrait.php#L49-L78","documentation":"After confirming $name is a valid PluginInterface subclass, getPluginEnabledConfigurationKey() strips the namespace and the trailing 'Plugin' suffix (substr(..., 0, -6)) to derive the short plugin name. If the result is empty — meaning the class name itself is literally 'Plugin' or the extraction yields nothing — an InternalErrorException is thrown since no config key can be built.","triggerScenarios":"Passing a class whose basename is exactly 'Plugin' (strrchr finds '\\', substr strips the last 6 chars 'Plugin' leaving empty string); e.g. a class named just \\Some\\Plugin that implements PluginInterface.","commonSituations":"An abstract base plugin class named 'Plugin' or a generically-named class accidentally passed to isFeaturePluginEnabled/enableFeaturePlugin/disableFeaturePlugin instead of the concrete plugin class.","solutions":["Pass the concrete plugin class (e.g. \\PassboltCe\\Folders\\FoldersPlugin) instead of a base/generic class named 'Plugin'.","Pass the plain short plugin name string instead of a class name.","Rename the class so it follows the '<Name>Plugin' convention with a non-empty short name."],"exampleFix":"// before\n$this->enableFeaturePlugin('\\My\\Namespace\\Plugin'); // basename 'Plugin' -> empty short name\n// after\n$this->enableFeaturePlugin('\\My\\Namespace\\MyFeaturePlugin');","handlingStrategy":"validation","validationCode":"$short = substr(strrchr($plugin, '\\\\') ?: $plugin, 1);\nif ($short === '' || $short === 'Plugin') {\n    throw new \\InvalidArgumentException(\"$plugin is not a concrete plugin class\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    $this->enableFeaturePlugin($plugin);\n} catch (\\Cake\\Http\\Exception\\InternalErrorException $e) {\n    if (str_contains($e->getMessage(), 'is not a valid plugin')) {\n        Log::error('Plugin class name yields empty short name: ' . $plugin);\n    }\n    throw $e;\n}","preventionTips":["Name plugin classes with a meaningful prefix before the 'Plugin' suffix (e.g. FoldersPlugin, never just Plugin).","Never pass abstract base classes to feature plugin enable/disable helpers.","Add static analysis rules flagging classes named exactly 'Plugin'.","Prefer short plugin name strings when the class FQCN is unnecessary."],"tags":["plugin-system","configuration","internal-error"],"backgroundTag":"invalid-argument-value","analyzedSha":"31c1bbc10f32808a607fa9bd81891e898779c0bc","analyzedAt":"2026-09-17T00:04:38.960Z","contentChangedAt":"2026-09-17T00:04:38.960Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}