From 7afe14b8acd19b19d04522ef7e888d64332d2043 Mon Sep 17 00:00:00 2001 From: Matt Pugh Date: Tue, 3 Jan 2017 09:08:45 +0000 Subject: [PATCH] Initial commit --- .gitignore | 7 + LICENSE | 22 ++ README.md | 47 +++ composer.json | 27 ++ modules/addons/addonmodule/addonmodule.php | 290 ++++++++++++++++++ modules/addons/addonmodule/hooks.php | 38 +++ modules/addons/addonmodule/lang/english.php | 6 + .../addonmodule/lib/Admin/AdminDispatcher.php | 34 ++ .../addonmodule/lib/Admin/Controller.php | 104 +++++++ .../lib/Client/ClientDispatcher.php | 32 ++ .../addonmodule/lib/Client/Controller.php | 86 ++++++ modules/addons/addonmodule/logo.png | Bin 0 -> 12464 bytes .../addons/addonmodule/templates/js/sample.js | 0 .../addonmodule/templates/publicpage.tpl | 43 +++ .../addonmodule/templates/secretpage.tpl | 45 +++ phpunit.xml.dist | 13 + tests/WHMCSModuleTest.php | 44 +++ tests/_bootstrap.php | 10 + 18 files changed, 848 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 composer.json create mode 100644 modules/addons/addonmodule/addonmodule.php create mode 100644 modules/addons/addonmodule/hooks.php create mode 100644 modules/addons/addonmodule/lang/english.php create mode 100644 modules/addons/addonmodule/lib/Admin/AdminDispatcher.php create mode 100644 modules/addons/addonmodule/lib/Admin/Controller.php create mode 100644 modules/addons/addonmodule/lib/Client/ClientDispatcher.php create mode 100644 modules/addons/addonmodule/lib/Client/Controller.php create mode 100644 modules/addons/addonmodule/logo.png create mode 100644 modules/addons/addonmodule/templates/js/sample.js create mode 100644 modules/addons/addonmodule/templates/publicpage.tpl create mode 100644 modules/addons/addonmodule/templates/secretpage.tpl create mode 100644 phpunit.xml.dist create mode 100644 tests/WHMCSModuleTest.php create mode 100644 tests/_bootstrap.php 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 0000000000000000000000000000000000000000..314fffcd5dc64ce8d40f2eadc8c70b711f2ccd77 GIT binary patch literal 12464 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 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 @@ +