0%

hello,whmcs-addon-module

hello,whmcs-plugin

之前的一篇域名解析文章,描述的是利用hooks及composer进行whmcs的功能定制开发,那么官方版本的插件-模块式开发又是怎么样的呢,笔者通过这篇文章,把自己的理解记录了下来。

了解对象

首先,我们的了解对象是官方给出的源码:sample-addon-module,同时结合官方文档

下载安装

  • 下载源码,并解压到modules/addons/下面。

  • 登陆后台,在系统设置=>模块插件选项里面,找到我们新安装的“Addon Module Sample”,点击“激活按钮”。

  • 激活后,点击“配置”,并输入相关配置信息。

  • 进入whmcs系统后台,我们接下来要运行一下新安装的插件,但这里我碰到了一个意外,官方提供的源码居然跑不起来…跑不起来…不起来….

    错误如下所示:

    2.pic.jpg

    经过排查,可能是因为版本更新导致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');

    一切正常情况下,我们能够看到如下界面

    3.pic.jpg

    本着开源精神,我将这段改动pull request 给了原作者,希望能够被采纳吧,这样大家下次使用的时候就不会碰到这个麻烦。

    4.pic.jpg

  • 好,接下来,我们去前台检查下插件的运行情况

    回到前台,我们在浏览器输入http://yourdomain.com/whmcs/index.php?m=addonmodule,不出意外的话,应该能够得到下面的结果:

    5.pic.jpg

  • 走到这一步的话,我们的额插件安装及配置就完成了,下面来看源码。

如何创建一个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',
      ),
      )
     );
    }

    上述配置文件最终将会产生一个配置页面:

    1.pic.jpg

  • 在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);
      }
    }