MyAdmin plugin for cPanel license management via the cPanel Manage2 API. Part of the detain/myadmin-* plugin ecosystem.
composer install # install PHP deps
vendor/bin/phpunit # run all tests
vendor/bin/phpunit --coverage-html coverage/ # HTML coverage report
php -l src/Plugin.php # lint a single fileNamespace: MyAdmin\Licenses\Cpanel\ → src/ · Tests: MyAdmin\Licenses\Cpanel\Tests\ → tests/
Plugin entry: src/Plugin.php — static class with getHooks() returning event→callable map for Symfony GenericEvent dispatch
Core files:
src/Plugin.php— event handlers:getActivate,getDeactivate,getDeactivateIp,getChangeIp,getMenu,getRequirements,getSettingssrc/cpanel.inc.php— procedural functions:activate_cpanel(),deactivate_cpanel(),verify_cpanel(),get_cpanel_license_data_by_ip(),get_cpanel_licenses(),get_cpanel_accounts_for_license_ip()src/cpanel_kcare_addon.php— KernelCare addon via\Detain\Cloudlinux\Cloudlinuxsrc/cpanel_ksplice_addon.php— Ksplice addon via\Detain\MyAdminKsplice\Ksplicesrc/cpanel_list.php— admin-only license list page usingTFTablesrc/unbilled_cpanel.php— unbilled license detection, cross-references licenses/VPS/servers DBs
CLI scripts (bin/):
bin/activate_cpanel.php— manual license activate/deactivatebin/find_unbilled_cpanel.php— find unbilled licenses with detailed error reportingbin/unbilled_cpanel.php— web/CLI unbilled license reportbin/update_cpanel_data.php— sync license data, updaterepeat_invoicescosts via\MyAdmin\Orm\Repeat_Invoice
Tests (tests/):
tests/PluginTest.php— reflection-based tests onPluginclass structure, hooks, method signaturestests/SourceFileAnalysisTest.php— static analysis ofsrc/files viafile_get_contents+ regex (no framework bootstrap needed)
Config: phpunit.xml.dist — PHPUnit 9, bootstrap vendor/autoload.php, coverage on src/
detain/cpanel-licensing—\Detain\Cpanel\CpanelAPI wrapper (credentials:CPANEL_LICENSING_USERNAME,CPANEL_LICENSING_PASSWORD)symfony/event-dispatcher^5.0 —GenericEventfor hook dispatchext-soap— required by cPanel API
All handlers in src/Plugin.php are public static methods accepting GenericEvent. They check $event['category'] == get_service_define('CPANEL') before acting, then call $event->stopPropagation().
public static function getActivate(GenericEvent $event)
{
$serviceClass = $event->getSubject();
if ($event['category'] == get_service_define('CPANEL')) {
myadmin_log(self::$module, 'info', 'cPanel Activation', __LINE__, __FILE__, self::$module, $serviceClass->getId());
function_requirements('activate_cpanel');
$response = activate_cpanel($serviceClass->getIp(), $event['field1']);
$serviceClass->setKey($response['licenseid'])->save();
$event->stopPropagation();
}
}Uses MyAdmin DB abstraction — never PDO:
$db = get_module_db('licenses');
$db->query("SELECT * FROM licenses WHERE license_ip='{$ip}'", __LINE__, __FILE__);
if ($db->num_rows() > 0) {
$db->next_record(MYSQL_ASSOC);
$row = $db->Record;
}- Procedural functions in
src/*.inc.phpandsrc/*.php— loaded viafunction_requirements() - Admin-only pages check
$GLOBALS['tf']->ima == 'admin'and/orhas_acl('view_service') - Logging:
myadmin_log('licenses', $level, $message, __LINE__, __FILE__) - API requests logged via
request_log($module, $accountId, $function, 'cpanel', $method, $request, $response) - License operations use
$cpl = new \Detain\Cpanel\Cpanel(CPANEL_LICENSING_USERNAME, CPANEL_LICENSING_PASSWORD) - ORM updates via
\MyAdmin\Orm\Repeat_Invoiceand\MyAdmin\Orm\License—load_real(), setters,save() - Tests use reflection and
file_get_contentsregex — no framework globals needed - Indentation: tabs, size 4
- Commit messages: lowercase, descriptive
- No closing
?>tag in source files
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.