hello,whmcs-plugin
之前的一篇域名解析文章,描述的是利用hooks及composer进行whmcs的功能定制开发,那么官方版本的插件-模块式开发又是怎么样的呢,笔者通过这篇文章,把自己的理解记录了下来。
了解对象
首先,我们的了解对象是官方给出的源码:sample-addon-module,同时结合官方文档。
下载安装
下载源码,并解压到modules/addons/下面。
登陆后台,在系统设置=>模块插件选项里面,找到我们新安装的“Addon Module Sample”,点击“激活按钮”。
激活后,点击“配置”,并输入相关配置信息。
进入whmcs系统后台,我们接下来要运行一下新安装的插件,但这里我碰到了一个意外,官方提供的源码居然跑不起来…跑不起来…不起来….
错误如下所示:
经过排查,可能是因为版本更新导致namespace找不到了,所以我这里自己写了一个新的classLoader,简单点说,就是在addonmodule.php的开头,添加以下代码:
function classLoadAddonModule ($class) { $path = str_replace('\\', DIRECTORY_SEPARATOR, $class); $path = str_replace('WHMCS' . DIRECTORY_SEPARATOR . 'Module' . DIRECTORY_SEPARATOR . 'Addon' . DIRECTORY_SEPARATOR . 'AddonModule' , 'lib' , $path); $file = __DIR__ . DIRECTORY_SEPARATOR . $path . '.php'; if (file_exists($file)) { require_once $file; } } spl_autoload_register('classLoadAddonModule');
一切正常情况下,我们能够看到如下界面
本着开源精神,我将这段改动pull request 给了原作者,希望能够被采纳吧,这样大家下次使用的时候就不会碰到这个麻烦。
好,接下来,我们去前台检查下插件的运行情况
回到前台,我们在浏览器输入http://yourdomain.com/whmcs/index.php?m=addonmodule,不出意外的话,应该能够得到下面的结果:
走到这一步的话,我们的额插件安装及配置就完成了,下面来看源码。
如何创建一个addon-module
命名自己的module,必须全部小写,并且只能有字母、数字和下划线,但要以字母开头。这里我们可以看到下载的源码项目名称为addonmodule
在modules/addons目录下,创建一个目录,目录名称为你的项目名称,也就是addonmodule
在modules/addons/addonmodule下创建核心文件addonmodule.php
在addonmodule.php下创建配置函数,代码如下:
function addonmodule_config() { return array( 'name' => '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', ), ) ); }
上述配置文件最终将会产生一个配置页面:
在addonmodule.php下创建激活和禁用函数,代码如下:
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.', ); }
如上所示,激活函数,相当于管理员安装模块的时候做的事情,一般而言为建表、询问激活码等事情。
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.', ); }
而禁用函数,就是把激活函数创建的东西卸载掉就可以了,这里需要注意一件事情,那就是在这两个函数里面,whmcs都已经提供了一个处于active状态的数据库链接。所以我们不需要再创建链接。
在addonmodule.php下创建output函数,代码如下:
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; }
其中,AdminDispatcher类的代码如下:
namespace WHMCS\Module\Addon\AddonModule\Admin; /** * Sample Admin Area Dispatch Handler */class AdminDispatcher { /** * Dispatch request. * * @param string $action * @param array $parameters * * @return string */ public function dispatch($action, $parameters) { if (!$action) { // Default to index if no action specified $action = 'index'; } $controller = new Controller(); // Verify requested action is valid and callable if (is_callable(array($controller, $action))) { return $controller->$action($parameters); } return 'Invalid action requested. Please go back and try again.'; } }
AdminDispatcher类中的controller类的代码如下:
namespace WHMCS\Module\Addon\AddonModule\Admin; /** * Sample Admin Area Controller */class Controller { /** * Index action. * * @param array $vars Module configuration parameters * * @return string */ public function index($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 << 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} {$modulelink}&action=show" class="btn btn-success"> Visit valid action link {$modulelink}&action=invalid" class="btn btn-default"> 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} {$modulelink}" class="btn btn-info"> Back to home EOF; } }
在这个addonmodule_output方法里面,我们可以获取到配置参数、用户访问的链接、用户在链接中添加的额外参数等等信息,然后按照MVC的规范,我们需要写一个控制器来实现处理这些变量的逻辑代码,再到最后的渲染视图,所以addonmodule_output最终就是处理输入数据,并返回给用户渲染的视图。
我们可以看到,在Controller类里面,还有一个方法名称是show,要访问这个show方法,前台对应的URL为:addonmodules.php?module=addonmodule&action=show
如果在admin面板,我们需要获取到admin的ID,那么就要通过session去执行,$SESSION[‘adminid’]。
在admin,还有一块siderbar面板,这个区域的视图输出不是通过addonmodule_output来实现的,而是通过addonmodule_sidebar($vars)来实现,原理同addonmodule_output,类似代码如下所示:
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;
}
看完了后台,我们再看看前台的输出,从前文可以知道,要访问前台,URL地址为http://yourdomain.com/whmcs/index.php?m=addonmodule,这个地址访问后,执行的方法为addonmodule.php下的addonmodule_clientarea($vars)方法,其原理跟上面两个方法一样,举例代码如下:
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); }
语言翻译:在模块的lang目录下,针对不同语言创建对应的PHP文件,如英文,则为english.php,然后在里面使用$_ADDONLANG[‘变量名’]去进行翻译。
值得注意的是,我们可以在配置中,指定默认使用的语言,例如:
function demo_config() {
$configarray = array(
"name" => "Addon Example",
"description" => "This is a sample config function for an addon module",
"version" => "1.0",
"author" => "WHMCS",
"language" => "english",//就直接指定默认的语言为english
"fields" => etc...
- hooks , 直接在模块目录下创建一个名为hooks.php文件,然后在里面按照whmcs对于hooks的定义去编码即可。
Admin Dashboard Widget , 其实这一块的内容跟模块没有太大的关联,之所以在这里讲,是因为后台进入模块页面需要手动去输入链接,有一些不方便,那既然这样,还不如直接在后台的首页,增加一个小块,把跳转链接写在小块的一个按钮上面,例子如下:(通过hooks实现)
<?php add_hook('AdminHomeWidgets', 1, function() { return new HelloWorldWidget(); }); /** * Hello World Widget. */ class HelloWorldWidget extends \WHMCS\Module\AbstractWidget { protected $title = 'Hello World'; protected $description = ''; protected $weight = 150; protected $columns = 1; protected $cache = false; protected $cacheExpiry = 120; protected $requiredPermission = ''; public function getData() { return array(); } public function generateOutput($data) { return <<<EOF <div class="widget-content-padded"> Hello World! </div> EOF;//在这里面写跳转的html代码 } }
module 本身还有一个update功能,我暂时没有去进行测试,但按照理解,当模块第一次执行的时候,这个update函数会被执行,举例代码如下:
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); } }