PHP plugin selling DirectAdmin licenses as a VPS addon in MyAdmin billing.
- Namespace:
Detain\MyAdminVpsDirectadmin→src/ - Test Namespace:
Detain\MyAdminVpsDirectadmin\Tests→tests/ - Module:
vps· Type:addon - Key deps:
symfony/event-dispatcher ^5.0
composer install
vendor/bin/phpunit tests/ -v
vendor/bin/phpunit tests/ --coverage-clover coverage.xml --whitelist src/Config: phpunit.xml.dist
CI/CD: .github/ contains workflows/tests.yml for automated testing pipelines. .idea/ contains IDE configuration including inspectionProfiles/Project_Default.xml, deployment.xml, and encodings.xml.
Plugin class: src/Plugin.php → Detain\MyAdminVpsDirectadmin\Plugin
Addon function: src/vps_add_directadmin.php → vps_add_directadmin()
Hooks registered in Plugin::getHooks():
function.requirements→getRequirements()— registersvps_add_directadminpage requirementvps.load_addons→getAddon()— registersAddonHandlerwith costVPS_DA_COSTvps.settings→getSettings()— addsvps_da_costtext setting to admin panel
Hook registration pattern:
public static function getHooks()
{
return [
'function.requirements' => [__CLASS__, 'getRequirements'],
self::$module.'.load_addons' => [__CLASS__, 'getAddon'],
self::$module.'.settings' => [__CLASS__, 'getSettings'],
];
}Lifecycle:
doEnable(\ServiceHandler, $repeatInvoiceId)— callsactivate_directadmin()afterfunction_requirements('activate_directadmin')doDisable(\ServiceHandler, $repeatInvoiceId)— callsdeactivate_directadmin(), sends admin email via\MyAdmin\Mail()->adminMail()usingadmin/vps_da_canceled.tpl
MyAdmin helpers:
get_module_settings(self::$module)→PREFIX,TABLE,TBLNAMEmyadmin_log($module, 'info', $msg, __LINE__, __FILE__, $module, $serviceId)function_requirements('name')— lazy-loads function/class filesrun_event('parse_service_extra', $extra, $module)— hook dispatchvps_get_password($id, $custid)— get VPS root passworddirectadmin_get_best_type($module, $type, $serviceInfo, $serviceExtra)
License functions: require_once __DIR__.'/../../../../include/licenses/license.functions.inc.php'
- Tabs for indentation (enforced in
.scrutinizer.yml) - camelCase properties and parameters;
UPPERCASEconstants (e.g.VPS_DA_COST) - All logging via
myadmin_log()with__LINE__, __FILE__ - Load external functions via
function_requirements(), not directrequire_onceexcept license functions - Static class methods for all plugin handlers; constructor is empty
tests/PluginTest.php— plugin class structuretests/VpsAddDirectadminTest.php— source-reading tests onsrc/vps_add_directadmin.php- Pattern:
file_get_contents()+assertStringContains/assertMatchesRegularExpressionon raw PHP source - PHPUnit 9
vendor/bin/phpunit tests/PluginTest.php -v
vendor/bin/phpunit tests/VpsAddDirectadminTest.php -vIMPORTANT: Before every git commit, you MUST ensure Caliber syncs agent configs with the latest code changes.
First, check if the pre-commit hook is already installed:
grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"- If hook-active: the hook handles sync automatically — just commit normally. Tell the user: "Caliber will sync your agent configs automatically via the pre-commit hook."
- If no-hook: run Caliber manually before committing:
- Tell the user: "Caliber: Syncing agent configs with your latest changes..."
- Run:
caliber refresh && git add CLAUDE.md .claude/ .cursor/ .cursorrules .github/copilot-instructions.md .github/instructions/ AGENTS.md CALIBER_LEARNINGS.md 2>/dev/null - After it completes, briefly tell the user what Caliber updated. Then proceed with the commit.
If caliber is not found, tell the user: "This project uses Caliber for agent config sync. Run /setup-caliber to get set up."
Read CALIBER_LEARNINGS.md for patterns and anti-patterns learned from previous sessions.
These are auto-extracted from real tool usage — treat them as project-specific rules.