For the complete documentation index, see llms.txt. This page is also available as Markdown.

Module installer class

The PbxExtensionSetup lifecycle: install, setup and uninstall an extension module.

Every MikoPBX module ships a setup class at Setup/PbxExtensionSetup.php. MikoPBX calls this class when the module is uploaded (install/upgrade) and when it is removed. The class implements the full install/uninstall lifecycle: compatibility checks, license activation, file copying and symlinking, database table creation, module registration and rollback.

The setup class extends the abstract base PbxExtensionSetupBase (MikoPBX\Modules\Setup\PbxExtensionSetupBase), which implements the MikoPBX\Modules\Setup\PbxExtensionSetupInterface contract. The base is a template-method implementation: it provides a working default for every step, so a module's Setup/PbxExtensionSetup.php only overrides the methods it actually needs to customize.

The minimal subclass is literally empty — it inherits every step from the base. See the working example in Extensions/EXAMPLES/WebInterface/ModuleExampleForm/Setup/PbxExtensionSetup.php, whose entire body is class PbxExtensionSetup extends PbxExtensionSetupBase {}.

Throughout this page the running example is the fictional ModuleBlackList module (unique ID ModuleBlackList, model BlackListNumbers, table m_BlackListNumbers). Each pattern is anchored to a real example module by repo-relative path so you can read the source yourself.

How MikoPBX invokes the setup class

There are two ways to install a module:

  • from a locally uploaded ZIP archive (recommended during development),

  • from the MIKO modules repository (the Marketplace).

In both cases MikoPBX unzips the module files into the modules directory and then instantiates the module's own PbxExtensionSetup class and calls installModule():

How the Core calls your setup class
$pbxExtensionSetupClass = "\\Modules\\{$moduleUniqueID}\\Setup\\PbxExtensionSetup";
$setup = new $pbxExtensionSetupClass($moduleUniqueID);
$result = $setup->installModule();
if (!$result) {
    // The UI shows the strings collected in $setup->getMessages()
    $errors = $setup->getMessages();
}

installModule() returns bool. On failure it stops at the first failing step and records a human-readable message; the REST layer surfaces those messages (getMessages()) to the user.

Constructor and inherited properties

The base constructor (__construct(string $moduleUniqueID)) wires up the dependency-injected services and reads the module's module.json, populating these protected properties for every step to use:

PbxExtensionSetupBase extends Phalcon\Di\Injectable, so it also resolves the license, translation and config services from the DI container via magic properties.

The install pipeline: installModule()

installModule() is the orchestrator. It is implemented once in the base and you normally do not override it — you override the individual steps it calls. The verified call order is:

After all steps succeed it calls PBXConfModulesProvider::getVersionsHash(true) to rebuild the cache-busting hash for module JS files and translations. Each step is described below.

checkCompatibility()

Compares the running PBX version against the module's min_pbx_version (from module.json). If the PBX is older, it records a message and returns false, aborting the install. The base reads the current version with the PbxSettings::PBX_VERSION constant and strips any -dev suffix before comparing:

You rarely override this. Override it only if ModuleBlackList needs an additional check (for example, refusing to install when a conflicting module is enabled). Call parent::checkCompatibility() first so the version check still runs.

activateLicense()

Used only by commercial modules. The base activates a license only when lic_product_id > 0 (i.e. the module declares a lic_product_id in module.json). For free modules it is a no-op that returns true:

ModuleBlackList is a free module, so it leaves this step alone. For commercial modules see Licensing.

installFiles()

Copies files and creates the symlinks the web UI and the dialplan need. The base default performs four jobs and you only override it if you have extra files to place. Internally it delegates to three static helpers on MikoPBX\Modules\PbxExtensionUtils (src/Modules/PbxExtensionUtils.php):

Helper
What it links
Source → target

PbxExtensionUtils::createAssetsSymlinks($id)

JS / CSS / IMG assets

<module>/public/assets/{js,css,img}sites/admin-cabinet/assets/{js,css,img}/cache/<id>

PbxExtensionUtils::createViewSymlinks($id)

Volt view templates

<module>/App/Viewssrc/AdminCabinet/Views/Modules/<id>

PbxExtensionUtils::createAgiBinSymlinks($id)

AGI dialplan scripts

<module>/agi-bin/*.php → the Asterisk agi-bin directory

Settings preservation #1 — restore. When a previous uninstall ran with $keepSettings = true, the module's db folder was copied to <modulesDir>/Backup/<moduleUniqueID>. installFiles() copies it back, so a reinstall/upgrade keeps the user's data. This is distinct from the legacy migration in transferOldSettings() described below.

Sound files are intentionally not installed here. They are managed by the module's enable/disable hooks (onAfterModuleEnable / onAfterModuleDisable) — see Module class.

Override installFiles() only to place extra files (for example, ModuleBlackList copying a firewall ruleset into a system path). Always call parent::installFiles() so the symlinks above are still created.

installDB()

Builds the module's database structure, then registers the module and (optionally) adds a sidebar item. This is the step modules customize most. The base default chains three calls:

createSettingsTableByModelsAnnotations()

You do not ship a prebuilt SQLite file. Instead you describe each table as a Phalcon model with column annotations, and this helper creates or alters the matching tables — both on first install and on upgrade. It registers the module's DB connection, iterates every file in <module>/Models/*.php, and runs each through UpdateDatabase::createUpdateDbTableByAnnotations():

For ModuleBlackList, the model BlackListNumbers maps to the table m_BlackListNumbers:

See Data model for the full annotation reference. After createSettingsTableByModelsAnnotations() succeeds you can seed defaults through the model:

registerNewModule()

Adds (or updates) a row in the PbxExtensionModules table from module.json data. The new record is created disabled (disabled = '1'); the user enables the module later. Name, description and wiki links are taken from translations and the parsed module.json:

addToSidebar()

Adds a left-menu entry by writing an AdditionalMenuItem<moduleUniqueID> row into PbxSettings. The base default uses the puzzle icon and the modules group:

Override addToSidebar() to change the icon, menu group, or to add an explicit href. The working override in Extensions/ModuleBackup/Setup/PbxExtensionSetup.php places the item in the maintenance group with a history icon and a custom link. Modernized to the current Text helper, an equivalent ModuleBlackList override looks like:

Text::uncamelize() (MikoPBX\Common\Library\Text) converts ModuleBlackList to the module-black-list route segment.

Legacy migration with transferOldSettings(). transferOldSettings() is not a base method — it is a migration helper a module defines in its own subclass to move data out of legacy system-database tables into the module's own database. The working example is Extensions/ModuleTelegramNotify/Setup/PbxExtensionSetup.php, which overrides installDB() to call createSettingsTableByModelsAnnotations(), registerNewModule(), and then its own transferOldSettings(). It reads old m_ModuleTelegramNotify* rows via $this->db, copies matching fields into the new models, saves, and drops the legacy table. Implement this only when you are migrating from an older schema; new modules do not need it. This is separate from the backup/restore handled by installFiles().

fixFilesRights()

The final install step. It applies the standard www ownership to the whole module folder and adds the executable bit to anything under agi-bin/ and bin/:

Override this only if ModuleBlackList ships extra executables outside agi-bin/ and bin/. Keep your folder layout aligned with the example modules so the defaults work unchanged.

The uninstall pipeline: uninstallModule()

When a module is deleted (or upgraded, which deletes then reinstalls), MikoPBX calls:

uninstallModule() runs unInstallDB() then unInstallFiles(), passing the $keepSettings flag through, and finally rebuilds the version hash:

unInstallDB($keepSettings)

By default it just unregisters the module from PbxExtensionModules via unregisterModule():

Override unInstallDB() to remove references to your module from system tables before calling parent::unInstallDB($keepSettings) (for example, ModuleBlackList deleting firewall rules it created). Use $keepSettings to decide whether to also clear stored user data.

unInstallFiles($keepSettings)

Deletes the installed files, folders and asset symlinks. When $keepSettings = true it first copies the module's db folder to <modulesDir>/Backup/<moduleUniqueID> so a later reinstall can restore it (the restore happens in installFiles(), see above):

If your module runs background binaries you must stop them before the files are removed, and clean up any temporary or log files you created. Override unInstallFiles(), do your cleanup, then delegate to the parent:

Choosing what to override — summary

Method
Override when ModuleBlackList needs to…
Real example

(nothing)

use the defaults for everything

Extensions/EXAMPLES/WebInterface/ModuleExampleForm/Setup/PbxExtensionSetup.php

installDB()

seed default rows, register, add a custom sidebar item, run a legacy migration

Extensions/ModuleTelegramNotify/Setup/PbxExtensionSetup.php, Extensions/ModuleBackup/Setup/PbxExtensionSetup.php

addToSidebar()

change menu group / icon / href

Extensions/ModuleBackup/Setup/PbxExtensionSetup.php

installFiles()

copy extra files outside the standard folders

base default suffices for most modules

fixFilesRights()

mark extra executables

base default suffices for most modules

unInstallDB()

clean references from system tables

extend parent::unInstallDB()

unInstallFiles()

stop workers, delete logs/temp files

extend parent::unInstallFiles()

  • module.json reference — every field consumed by the constructor.

  • Data model — model column annotations used by createSettingsTableByModelsAnnotations().

  • Module class — the enable/disable hooks (onAfterModuleEnable / onAfterModuleDisable) that manage sound files and runtime state.

Last updated

Was this helpful?