commit 7afe14b8acd19b19d04522ef7e888d64332d2043 Author: Matt Pugh Date: Tue Jan 3 09:08:45 2017 +0000 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3faf0f7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# PHPUnit +phpunit.xml + +# Composer +composer.phar +composer.lock +vendor/* diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b06866a --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2017 WHMCS, Limited + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..0310f6d --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# WHMCS Sample Addon Module # + +## Summary ## + +An addon module allows you to add additional functionality to WHMCS. It +can provide both client and admin facing user interfaces, as well as +utilise hook functionality within WHMCS. + +This sample file demonstrates how an addon module for WHMCS should be +structured and exercises all supported functionality. + +For more information, please refer to the online documentation at +https://developers.whmcs.com/addon-modules/ + +## Recommended Module Content ## + +The recommended structure of an addon module is as follows. + +``` + addonmodule/ + |- lang/ + |- lib/ + |- templates/ + | addonmodule.php + | hooks.php + | logo.png +``` + +## Minimum Requirements ## + +For the latest WHMCS minimum system requirements, please refer to +http://docs.whmcs.com/System_Requirements + +We recommend your module follows the same minimum requirements wherever +possible. + +## Tests ## + +We strongly encourage you to write unit tests for your work. Within this SDK we +provide a sample unit test based upon the widely used PHPUnit. + +## Useful Resources +* [Developer Portal](https://developers.whmcs.com/) +* [Hook Documentation](https://developers.whmcs.com/hooks/) +* [API Documentation](https://developers.whmcs.com/api/) + +[WHMCS Limited](https://www.whmcs.com) diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..7248aa8 --- /dev/null +++ b/composer.json @@ -0,0 +1,27 @@ +{ + "name": "whmcs/sample-addon-module", + "description": "Sample Addon Module for WHMCS Billing Automation Platform", + "keywords": [ + "whmcs", + "web host automation platform", + "addon module" + ], + "homepage": "http://www.whmcs.com/", + "license": "MIT", + "authors": [ + { + "name": "WHMCS Development Team", + "email": "development@whmcs.com", + "homepage": "http://www.whmcs.com/", + "role": "Developer" + } + ], + "support": { + "email": "support@whmcs.com", + "forum": "http://forums.whmcs.com/", + "wiki": "http://docs.whmcs.com/" + }, + "require-dev": { + "phpunit/phpunit": "@stable" + } +} diff --git a/modules/addons/addonmodule/addonmodule.php b/modules/addons/addonmodule/addonmodule.php new file mode 100644 index 0000000..b82c5b3 --- /dev/null +++ b/modules/addons/addonmodule/addonmodule.php @@ -0,0 +1,290 @@ + 'Addon Module Sample', // Display name for your module + 'description' => 'This module provides an example WHMCS Addon Module which can be used as a basis for building a custom addon module.', // Description displayed within the admin interface + 'author' => 'Your name goes here', // Module author name + 'language' => 'english', // Default language + 'version' => '1.0', // Version number + 'fields' => array( + // a text field type allows for single line text input + 'Text Field Name' => array( + 'FriendlyName' => 'Text Field Name', + 'Type' => 'text', + 'Size' => '25', + 'Default' => 'Default value', + 'Description' => 'Description goes here', + ), + // a password field type allows for masked text input + 'Password Field Name' => array( + 'FriendlyName' => 'Password Field Name', + 'Type' => 'password', + 'Size' => '25', + 'Default' => '', + 'Description' => 'Enter secret value here', + ), + // the yesno field type displays a single checkbox option + 'Checkbox Field Name' => array( + 'FriendlyName' => 'Checkbox Field Name', + 'Type' => 'yesno', + 'Description' => 'Tick to enable', + ), + // the dropdown field type renders a select menu of options + 'Dropdown Field Name' => array( + 'FriendlyName' => 'Dropdown Field Name', + 'Type' => 'dropdown', + 'Options' => array( + 'option1' => 'Display Value 1', + 'option2' => 'Second Option', + 'option3' => 'Another Option', + ), + 'Description' => 'Choose one', + ), + // the radio field type displays a series of radio button options + 'Radio Field Name' => array( + 'FriendlyName' => 'Radio Field Name', + 'Type' => 'radio', + 'Options' => 'First Option,Second Option,Third Option', + 'Description' => 'Choose your option!', + ), + // the textarea field type allows for multi-line text input + 'Textarea Field Name' => array( + 'FriendlyName' => 'Textarea Field Name', + 'Type' => 'textarea', + 'Rows' => '3', + 'Cols' => '60', + 'Description' => 'Freeform multi-line text input field', + ), + ) + ); +} + +/** + * Activate. + * + * Called upon activation of the module for the first time. + * Use this function to perform any database and schema modifications + * required by your module. + * + * This function is optional. + * + * @return array Optional success/failure message + */ +function addonmodule_activate() +{ + // Create custom tables and schema required by your module + $query = "CREATE TABLE `mod_addonexample` (`id` INT( 1 ) NOT NULL AUTO_INCREMENT PRIMARY KEY ,`demo` TEXT NOT NULL )"; + full_query($query); + + return array( + 'status' => 'success', // Supported values here include: success, error or info + 'description' => 'This is a demo module only. In a real module you might report an error/failure or instruct a user how to get started with it here.', + ); +} + +/** + * Deactivate. + * + * Called upon deactivation of the module. + * Use this function to undo any database and schema modifications + * performed by your module. + * + * This function is optional. + * + * @return array Optional success/failure message + */ +function addonmodule_deactivate() +{ + // Undo any database and schema modifications made by your module here + $query = "DROP TABLE `mod_addonexample`"; + full_query($query); + + return array( + 'status' => 'success', // Supported values here include: success, error or info + 'description' => 'This is a demo module only. In a real module you might report an error/failure here.', + ); +} + +/** + * Upgrade. + * + * Called the first time the module is accessed following an update. + * Use this function to perform any required database and schema modifications. + * + * This function is optional. + * + * @return void + */ +function addonmodule_upgrade($vars) +{ + $currentlyInstalledVersion = $vars['version']; + + /// Perform SQL schema changes required by the upgrade to version 1.1 of your module + if ($currentlyInstalledVersion < 1.1) { + $query = "ALTER `mod_addonexample` ADD `demo2` TEXT NOT NULL "; + full_query($query); + } + + /// Perform SQL schema changes required by the upgrade to version 1.2 of your module + if ($currentlyInstalledVersion < 1.2) { + $query = "ALTER `mod_addonexample` ADD `demo3` TEXT NOT NULL "; + full_query($query); + } +} + +/** + * Admin Area Output. + * + * Called when the addon module is accessed via the admin area. + * Should return HTML output for display to the admin user. + * + * This function is optional. + * + * @see AddonModule\Admin\Controller@index + * + * @return string + */ +function addonmodule_output($vars) +{ + // Get common module parameters + $modulelink = $vars['modulelink']; // eg. addonmodules.php?module=addonmodule + $version = $vars['version']; // eg. 1.0 + $_lang = $vars['_lang']; // an array of the currently loaded language variables + + // Get module configuration parameters + $configTextField = $vars['Text Field Name']; + $configPasswordField = $vars['Password Field Name']; + $configCheckboxField = $vars['Checkbox Field Name']; + $configDropdownField = $vars['Dropdown Field Name']; + $configRadioField = $vars['Radio Field Name']; + $configTextareaField = $vars['Textarea Field Name']; + + // Dispatch and handle request here. What follows is a demonstration of one + // possible way of handling this using a very basic dispatcher implementation. + + $action = isset($_REQUEST['action']) ? $_REQUEST['action'] : ''; + + $dispatcher = new AdminDispatcher(); + $response = $dispatcher->dispatch($action, $vars); + echo $response; +} + +/** + * Admin Area Sidebar Output. + * + * Used to render output in the admin area sidebar. + * This function is optional. + * + * @param array $vars + * + * @return string + */ +function addonmodule_sidebar($vars) +{ + // Get common module parameters + $modulelink = $vars['modulelink']; + $version = $vars['version']; + $_lang = $vars['_lang']; + + // Get module configuration parameters + $configTextField = $vars['Text Field Name']; + $configPasswordField = $vars['Password Field Name']; + $configCheckboxField = $vars['Checkbox Field Name']; + $configDropdownField = $vars['Dropdown Field Name']; + $configRadioField = $vars['Radio Field Name']; + $configTextareaField = $vars['Textarea Field Name']; + + $sidebar = '

Sidebar output HTML goes here

'; + return $sidebar; +} + +/** + * Client Area Output. + * + * Called when the addon module is accessed via the client area. + * Should return an array of output parameters. + * + * This function is optional. + * + * @see AddonModule\Client\Controller@index + * + * @return array + */ +function addonmodule_clientarea($vars) +{ + // Get common module parameters + $modulelink = $vars['modulelink']; // eg. index.php?m=addonmodule + $version = $vars['version']; // eg. 1.0 + $_lang = $vars['_lang']; // an array of the currently loaded language variables + + // Get module configuration parameters + $configTextField = $vars['Text Field Name']; + $configPasswordField = $vars['Password Field Name']; + $configCheckboxField = $vars['Checkbox Field Name']; + $configDropdownField = $vars['Dropdown Field Name']; + $configRadioField = $vars['Radio Field Name']; + $configTextareaField = $vars['Textarea Field Name']; + + // Dispatch and handle request here. What follows is a demonstration of one + // possible way of handling this using a very basic dispatcher implementation. + + $action = isset($_REQUEST['action']) ? $_REQUEST['action'] : ''; + + $dispatcher = new ClientDispatcher(); + return $dispatcher->dispatch($action, $vars); +} diff --git a/modules/addons/addonmodule/hooks.php b/modules/addons/addonmodule/hooks.php new file mode 100644 index 0000000..d6924e8 --- /dev/null +++ b/modules/addons/addonmodule/hooks.php @@ -0,0 +1,38 @@ +$action($parameters); + } + + return '

Invalid action requested. Please go back and try again.

'; + } +} diff --git a/modules/addons/addonmodule/lib/Admin/Controller.php b/modules/addons/addonmodule/lib/Admin/Controller.php new file mode 100644 index 0000000..0c13dcc --- /dev/null +++ b/modules/addons/addonmodule/lib/Admin/Controller.php @@ -0,0 +1,104 @@ +Index + +

This is the index action output of the sample addon module.

+ +

The currently installed version is: {$version}

+ +

Values of the configuration field are as follows:

+ +
+ Text Field: {$configTextField}
+ Password Field: {$configPasswordField}
+ Checkbox Field: {$configCheckboxField}
+ Dropdown Field: {$configDropdownField}
+ Radio Field: {$configRadioField}
+ Textarea Field: {$configTextareaField} +
+ +

+ + + Visit valid action link + + + + Visit invalid action link + +

+ +EOF; + } + + /** + * Show action. + * + * @param array $vars Module configuration parameters + * + * @return string + */ + public function show($vars) + { + // Get common module parameters + $modulelink = $vars['modulelink']; // eg. addonmodules.php?module=addonmodule + $version = $vars['version']; // eg. 1.0 + $LANG = $vars['_lang']; // an array of the currently loaded language variables + + // Get module configuration parameters + $configTextField = $vars['Text Field Name']; + $configPasswordField = $vars['Password Field Name']; + $configCheckboxField = $vars['Checkbox Field Name']; + $configDropdownField = $vars['Dropdown Field Name']; + $configRadioField = $vars['Radio Field Name']; + $configTextareaField = $vars['Textarea Field Name']; + + return <<Show + +

This is the show action output of the sample addon module.

+ +

The currently installed version is: {$version}

+ +

+ + + Back to home + +

+ +EOF; + } +} diff --git a/modules/addons/addonmodule/lib/Client/ClientDispatcher.php b/modules/addons/addonmodule/lib/Client/ClientDispatcher.php new file mode 100644 index 0000000..34e13f4 --- /dev/null +++ b/modules/addons/addonmodule/lib/Client/ClientDispatcher.php @@ -0,0 +1,32 @@ +$action($parameters); + } + } +} diff --git a/modules/addons/addonmodule/lib/Client/Controller.php b/modules/addons/addonmodule/lib/Client/Controller.php new file mode 100644 index 0000000..0cddf88 --- /dev/null +++ b/modules/addons/addonmodule/lib/Client/Controller.php @@ -0,0 +1,86 @@ + 'Sample Addon Module', + 'breadcrumb' => array( + 'index.php?m=addonmodule' => 'Sample Addon Module', + ), + 'templatefile' => 'publicpage', + 'requirelogin' => false, // Set true to restrict access to authenticated client users + 'forcessl' => false, // Deprecated as of Version 7.0. Requests will always use SSL if available. + 'vars' => array( + 'modulelink' => $modulelink, + 'configTextField' => $configTextField, + 'customVariable' => 'your own content goes here', + ), + ); + } + + /** + * Secret action. + * + * @param array $vars Module configuration parameters + * + * @return array + */ + public function secret($vars) + { + // Get common module parameters + $modulelink = $vars['modulelink']; // eg. addonmodules.php?module=addonmodule + $version = $vars['version']; // eg. 1.0 + $LANG = $vars['_lang']; // an array of the currently loaded language variables + + // Get module configuration parameters + $configTextField = $vars['Text Field Name']; + $configPasswordField = $vars['Password Field Name']; + $configCheckboxField = $vars['Checkbox Field Name']; + $configDropdownField = $vars['Dropdown Field Name']; + $configRadioField = $vars['Radio Field Name']; + $configTextareaField = $vars['Textarea Field Name']; + + return array( + 'pagetitle' => 'Sample Addon Module', + 'breadcrumb' => array( + 'index.php?m=addonmodule' => 'Sample Addon Module', + 'index.php?m=addonmodule&action=secret' => 'Secret Page', + ), + 'templatefile' => 'secretpage', + 'requirelogin' => true, // Set true to restrict access to authenticated client users + 'forcessl' => false, // Deprecated as of Version 7.0. Requests will always use SSL if available. + 'vars' => array( + 'modulelink' => $modulelink, + 'configTextField' => $configTextField, + 'customVariable' => 'your own content goes here', + ), + ); + } +} diff --git a/modules/addons/addonmodule/logo.png b/modules/addons/addonmodule/logo.png new file mode 100644 index 0000000..314fffc Binary files /dev/null and b/modules/addons/addonmodule/logo.png differ diff --git a/modules/addons/addonmodule/templates/js/sample.js b/modules/addons/addonmodule/templates/js/sample.js new file mode 100644 index 0000000..e69de29 diff --git a/modules/addons/addonmodule/templates/publicpage.tpl b/modules/addons/addonmodule/templates/publicpage.tpl new file mode 100644 index 0000000..3ff831e --- /dev/null +++ b/modules/addons/addonmodule/templates/publicpage.tpl @@ -0,0 +1,43 @@ +

Public Client Area Sample Page

+ +

This is an example of a public client area page that does not require a login to view.

+ +

All the template variables you define along with some additional standard template variables are available within this template.
You can use the Smarty {ldelim}debug{rdelim} function call to see a full list.

+ +
+ +
+
+ Module Link +
+
+ {$modulelink} +
+
+ +
+
+ Config Text Field Value +
+
+ {$configTextField} +
+
+ +
+
+ Custom Variable +
+
+ {$customVariable} +
+
+ +
+ +

+ + + Go to page that requires authentication + +

diff --git a/modules/addons/addonmodule/templates/secretpage.tpl b/modules/addons/addonmodule/templates/secretpage.tpl new file mode 100644 index 0000000..b527be7 --- /dev/null +++ b/modules/addons/addonmodule/templates/secretpage.tpl @@ -0,0 +1,45 @@ +

Secret Client Area Sample Page

+ +

This is an example of a client area page that requires authentication to access.

+ +

You will have either been prompted to login or already have an active login state to access this page.

+ +

All the template variables you define along with some additional standard template variables are available within this template.
You can use the Smarty {ldelim}debug{rdelim} function call to see a full list.

+ +
+ +
+
+ Module Link +
+
+ {$modulelink} +
+
+ +
+
+ Config Text Field Value +
+
+ {$configTextField} +
+
+ +
+
+ Custom Variable +
+
+ {$customVariable} +
+
+ +
+ +

+ + + Return to addon module default page + +

diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..6148c8e --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,13 @@ + + + + + ./tests/ + + + diff --git a/tests/WHMCSModuleTest.php b/tests/WHMCSModuleTest.php new file mode 100644 index 0000000..8145485 --- /dev/null +++ b/tests/WHMCSModuleTest.php @@ -0,0 +1,44 @@ +assertTrue(function_exists($this->moduleName . '_config')); + } + + /** + * Asserts the required config option array keys are present. + */ + public function testRequiredConfigOptionsParametersAreDefined() + { + $result = call_user_func($this->moduleName . '_config'); + + $this->assertArrayHasKey('name', $result); + $this->assertArrayHasKey('description', $result); + $this->assertArrayHasKey('author', $result); + $this->assertArrayHasKey('language', $result); + $this->assertArrayHasKey('version', $result); + $this->assertArrayHasKey('fields', $result); + } +} diff --git a/tests/_bootstrap.php b/tests/_bootstrap.php new file mode 100644 index 0000000..fa554ce --- /dev/null +++ b/tests/_bootstrap.php @@ -0,0 +1,10 @@ +