Composer package detain/myadmin-cpanel-webhosting — MyAdmin plugin providing cPanel/WHM integration for webhosting account lifecycle management.
composer install # install dependencies
vendor/bin/phpunit # run all tests
vendor/bin/phpunit --coverage-text # run tests with coverage
vendor/bin/phpunit tests/PluginTest.php # run single test fileNamespace: Detain\MyAdminCpanel\ → src/ · Tests: Detain\MyAdminCpanel\Tests\ → tests/
Core files:
src/Plugin.php— main plugin class, registers Symfony EventDispatcher hooks viagetHooks()src/xmlapi.php— cPanel/WHM XML-API client class (global namespacexmlapi), supports curl/fopensrc/api.php— procedural API:api_auto_cpanel_login($id)for cPanel session token logincomposer.json— PSR-4 autoload, requiressymfony/event-dispatcher^5.0,ext-soap, PHPUnit 9 devphpunit.xml.dist— test config, bootstraptests/bootstrap.php, coverage onsrc/
Plugin hooks (Plugin::getHooks()): webhosting.settings · webhosting.activate · webhosting.reactivate · webhosting.deactivate · webhosting.terminate · api.register · function.requirements · ui.menu
Plugin lifecycle in src/Plugin.php:
getActivate()— creates cPanel account viaxmlapi::xmlapi_query('createacct', $options), handles reseller setup withsetupreseller(),saveacllist(),setacls(),setresellerlimits(), optional Softaculous script installgetReactivate()— callsunsuspendacct()orunsuspendreseller()getDeactivate()— callssuspendacct()orsuspendreseller()getTerminate()— callsremoveacct()orterminatereseller(), cleans up DNS viadumpzone()/killdns()getChangeIp()— usesDetain\Cpanel\Cpanelto calleditIp()
XML-API client (src/xmlapi.php):
- Constructor:
new xmlapi($host, $user?, $pass?)— default port2087, protocolhttps, outputsimplexml - Auth:
hash_auth($user, $hash)orpassword_auth($user, $pass)·set_output('json'|'xml'|'simplexml'|'array') - HTTP:
set_http_client('curl'|'fopen')· query methods:xmlapi_query(),api1_query(),api2_query() - Account ops:
createacct(),removeacct(),suspendacct(),unsuspendacct(),listaccts(),accountsummary() - DNS:
adddns(),killdns(),dumpzone(),listzones(),resetzone(),addzonerecord(),editzonerecord() - Packages:
addpkg(),killpkg(),editpkg(),listpkgs() - Reseller:
setupreseller(),listresellers(),terminatereseller(),setacls(),saveacllist() - SSL:
fetchsslinfo(),generatessl(),installssl(),listcrts() - Server:
gethostname(),version(),loadavg(),listips(),showbw()
CLI scripts (bin/): 38 scripts wrapping xmlapi methods — all follow identical pattern:
require_once __DIR__.'/../../../../include/functions.inc.php'get_module_db('webhosting')to querywebsite_masterstablefunction_requirements('whm_api')thenwhm_api($db->Record['website_id'])- Switch on
website_type(WEB_CPANEL,WEB_PLESK,WEB_VESTA,WEB_PPA) - Call corresponding
xmlapimethod,json_decode()andprint_r()the response
Examples: bin/listaccts.php <hostname> · bin/suspendacct.php <hostname> <username> · bin/dumpzone.php <hostname> <domain>
tests/bootstrap.php— loads autoloader, requiressrc/xmlapi.phpifxmlapiclass missingtests/PluginTest.php— reflection-based tests onPluginclass: hooks, methods, static properties,GenericEventparamstests/XmlapiTest.php— unit tests forxmlapiclass: constructor, getters/setters, port/protocol/output validation, auth methods, exception casestests/ApiTest.php— source-level assertions onsrc/api.php: checks function signature, curl usage, error handling
- DB access:
get_module_db('webhosting')→$db->query()·$db->next_record(MYSQL_ASSOC)·$db->Record - Logging:
myadmin_log(self::$module, $level, $msg, __LINE__, __FILE__, self::$module, $id) - Request logging:
request_log(self::$module, $custid, __FUNCTION__, 'cpanel', $action, $params, $response, $id) - Service helpers:
get_service($id, $module)·get_module_settings($module)·get_service_master($serverId, $module) - WHM init pattern:
new \xmlapi($ip)→set_port('2087')→set_protocol('https')→set_output('json')→set_auth_type('hash')→set_user('root')→set_hash($hash) - Event handling: check
$event['category']or$event['type']againstget_service_define('WEB_CPANEL'), call$event->stopPropagation()after processing - Commit messages: lowercase, descriptive
.scrutinizer.yml— PHP 7.0 build, PHPUnit with coverage, static analysis checks.travis.yml— PHP 5.4-7.1 matrix, CodeClimate/Codecov/Coveralls integration.codeclimate.yml— duplication/phpmd engines, excludestests/.bettercodehub.yml— PHP language config
IMPORTANT: 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 .agents/ .opencode/ 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.
This project uses Caliber to keep AI agent configs in sync across Claude Code, Cursor, Copilot, and Codex.
Configs update automatically before each commit via caliber refresh.
If the pre-commit hook is not set up, run /setup-caliber to configure everything automatically.