├── composer.json ├── src ├── PageController.php └── ItemList.php └── README.md /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "akerbel/whmcs-easy-addons", 3 | "version": "v1.2.0", 4 | "description": "Whmcs Addon Admin Page Controller", 5 | "keywords": ["whmcs"], 6 | "homepage": "https://github.com/akerbel/whmcs-easy-addons", 7 | "authors": [ 8 | { 9 | "name": "Anton Kerbel", 10 | "email": "apefromfuture@gmail.com" 11 | } 12 | ], 13 | "autoload": { 14 | "psr-4": { 15 | "whmcsEasyAddons\\": "src/" 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/PageController.php: -------------------------------------------------------------------------------- 1 | view = new \Smarty(); 37 | $this->view->template_dir = ROOTDIR.'/modules/addons/'.$module.'/templates/'; 38 | $this->view->compile_dir = $templates_compiledir; 39 | 40 | // Assing WHMCS system params 41 | $this->view->assign('_LANG', $_LANG); 42 | $this->view->assign('_CONFIG', $CONFIG); 43 | $this->view->assign('csrfToken', generate_token('plain')); 44 | 45 | // Assing our module params 46 | $this->vars = $vars; 47 | $this->view->assign('vars', $this->vars); 48 | $this->view->assign('customadminpath', $customadminpath); 49 | $this->modulelink = '/'.$customadminpath.'/addonmodules.php?module='.$module; 50 | $this->view->assign('modulelink', $this->modulelink); 51 | if (isset($_REQUEST['action'])) { 52 | $this->action = $_REQUEST['action']; 53 | } 54 | $this->view->assign('action', $this->action); 55 | } 56 | 57 | /** 58 | * Execute chosen tabs action. 59 | * 60 | * @throws \Exception If it is not possible to find a method for chosen action. 61 | */ 62 | public function run() 63 | { 64 | // Show tabs 65 | $this->_displayMenu(); 66 | 67 | // Try to execute action method 68 | $methodName = $this->action.'Action'; 69 | try { 70 | if (method_exists($this, $methodName)) { 71 | $this->$methodName(); 72 | echo $this->view->fetch($this->action.'.tpl'); 73 | } else { 74 | throw new \Exception('No controller for '.$this->action); 75 | } 76 | } catch (\Exception $e) { 77 | echo $e->getMessage(); 78 | } 79 | } 80 | 81 | /** 82 | * Generate and echo html-code for tabs. 83 | */ 84 | private function _displayMenu() 85 | { 86 | if (count($this->menu)) { 87 | $menu = '
'; 98 | echo $menu; 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | WHMCS Easy Addons 2 | ================= 3 | 4 | WHMCS Easy Addons is a package for fast creating of addon admin pages. 5 | 6 | Homepage: https://github.com/akerbel/whmcs-easy-addons 7 | 8 | How to install: 9 | =============== 10 | The recommended way to install WHMCS Easy Addons is through Composer. 11 | 12 | ```bash 13 | # Install Composer 14 | curl -sS https://getcomposer.org/installer | php 15 | ``` 16 | 17 | Next, run the Composer command to install the latest stable version of WHMCS Easy Addons: 18 | 19 | ```bash 20 | php composer.phar require akerbel/whmcs-easy-addons 21 | ``` 22 | 23 | How to use: 24 | ============== 25 | I. Admin tabs. 26 | - Install Composer 27 | ```bash 28 | curl -sS https://getcomposer.org/installer | php 29 | ``` 30 | - Create composer.json file inside your addon: 31 | ```json 32 | { 33 | "name": "EasyAddonsSkeleton", 34 | "version": "1.0.0", 35 | "description": "An example addon for WHMCS Easy Addons", 36 | "autoload": { 37 | "psr-4": { 38 | "EasyAddonsSkeleton\\": "src/" 39 | } 40 | }, 41 | "require": { 42 | "akerbel/whmcs-easy-addons": "1.*" 43 | } 44 | } 45 | ``` 46 | - Your "_output" function should look like this: 47 | ```php 48 | function EasyAddonsSkeleton_output($vars) { 49 | global $module; 50 | include_once(ROOTDIR."/modules/addons/".$module.'/src/output.php'); 51 | } 52 | ``` 53 | - Create 'src' folder. 54 | - Create output.php file in 'src' folder. 55 | 56 | output.php: 57 | ```php 58 | namespace EasyAddonsSkeleton; 59 | global $module; 60 | include_once(ROOTDIR."/modules/addons/".$module.'/vendor/autoload.php'); 61 | 62 | $EasyAddonsSkeleton = new EasyAddonsSkeletonController($vars); 63 | $EasyAddonsSkeleton->run(); 64 | ``` 65 | - Create EasyAddonsSkeletonController.php in 'src' folder. 66 | 67 | EasyAddonsSkeletonController.php: 68 | ```php 69 | namespace EasyAddonsSkeleton; 70 | 71 | use whmcsEasyAddons; 72 | 73 | class EasyAddonsSkeletonController extends whmcsEasyAddons\PageController { 74 | // This tab will be chosen by dafault 75 | public $action = 'firsttab'; 76 | 77 | // This is array of your tabs and their names. 78 | public $menu = array( 79 | 'First Tab' => 'firsttab', 80 | 'Second Tab' => 'secondtab', 81 | ); 82 | 83 | public function firsttabAction(){ 84 | 85 | // Your code for "First tab" 86 | 87 | } 88 | 89 | public function secondtabAction(){ 90 | 91 | // Your code for "Second tab" 92 | 93 | } 94 | } 95 | ``` 96 | - Create folder 'templates'. 97 | - Create .tpl file for each your tab in 'templates' folder with names like 'firsttab.tpl' and 'secondtab.tpl'. 98 | - You can assing your php variables in each 'Action' method: 99 | ```php 100 | $this->view->assign('smarty_variable', $your_php_variable); 101 | ``` 102 | 103 | ==================== 104 | II. Fast item lists. 105 | - Example code: 106 | ```php 107 | $list = new whmcsEasyAddons\ItemList( 108 | 109 | // SQL params array. Read more in ItemList.php 110 | array( 111 | 'SELECT' => '*', 112 | 'FROM' => 'tblmodulelog', 113 | ), 114 | 115 | // Filters array. Read more in ItemList.php . Optional. 116 | array( 117 | array( 118 | 'name' => 'module', 'value' => $_GET['filter']['module'], 'description' => 'module' 119 | ), 120 | array( 121 | 'name' => 'action', 'value' => $_GET['filter']['action'], 'description' => 'action' 122 | ), 123 | array( 124 | 'name' => 'request', 'value' => $_GET['filter']['request'], 'description' => 'request', 'type' => 'LIKE' 125 | ), 126 | array( 127 | 'name' => 'response', 'value' => $_GET['filter']['response'], 'description' => 'response', 'type' => 'LIKE' 128 | ), 129 | ), 130 | 131 | // Paginators type. Read more in a changelog for v1.2 . Optional. 132 | 'dafault' 133 | ); 134 | $this->view->assign('result', $list->result); 135 | $this->view->assign('paginator', $list->paginator); 136 | $this->view->assign('tablehead', $list->tablehead); 137 | $this->view->assign('filter', $list->filter); 138 | ``` 139 | - Now you can use this variables in your .tpl file: 140 | 141 | 1. $filter - show a filter form. 142 | 2. $paginator - show a page navigation. 143 | 3. $tablehead - show a tablehead with sort buttons. 144 | 4. $result - show an array of items. 145 | 146 | New in v1.1: 147 | ============== 148 | 1. Now JOIN parameters can be arrays. It means, now you can use few similar JOINs in your query. 149 | 2. ORDER and SORT parameters was added . If you use JOINs in your query, you must define you ORDER parameter, because 'ORDER BY' statement will use default 'id' and it will not work with JOINs. 150 | 151 | Fixes in v1.1: 152 | ============== 153 | 1. The bug with namespaces of Exception class was fixed. 154 | 2. Spacebars was added between pages in a paginator. 155 | 156 | New in v1.2: 157 | ============== 158 | 1. Types of the paginator added. Enabled 'default' and 'short'. 159 | 'default' type is an old paginator with numbers. 160 | 'short' type is a new fast paginator. Use it if you work with a very big table. It includes only two buttons: previous page and next page. Stylized as a default WHMCS module logs` paginator. -------------------------------------------------------------------------------- /src/ItemList.php: -------------------------------------------------------------------------------- 1 | sql_array = $sql; 84 | $this->filter_params = $filter_params; 85 | $this->paginator_type = $paginator_type; 86 | $this->createList(); 87 | } 88 | 89 | /** 90 | * Get items from a database and create a list. 91 | */ 92 | public function createList() 93 | { 94 | global $whmcsmysql; 95 | 96 | // Get an information about sorting and chosen page. 97 | if ($_GET['page'] != null) { 98 | $this->page = $_GET['page']; 99 | } 100 | if ($_GET['order'] != null) { 101 | $this->order = $_GET['order']; 102 | } elseif ($this->sql_array['ORDER']) { 103 | $this->order = $this->sql_array['ORDER']; 104 | } 105 | if ($_GET['sort'] != null) { 106 | $this->sort = $_GET['sort']; 107 | } elseif ($this->sql_array['SORT']) { 108 | $this->sort = $this->sql_array['SORT']; 109 | } 110 | 111 | // Include filters to request. 112 | if (count($this->filter_params) and is_array($this->filter_params)) { 113 | foreach ($this->filter_params as $param) { 114 | if ($param['value']) { 115 | if ($param['type'] == 'LIKE') { 116 | $this->sql_array['WHERE'] .= ' '.$param['name'].' LIKE "%'.$param['value'].'%" '; 117 | } elseif ($param['type'] == 'IN') { 118 | $this->sql_array['WHERE'] .= ' '.$param['name'].' IN ('.$param['value'].') '; 119 | } else { 120 | $this->sql_array['WHERE'] .= ' '.$param['name'].' = "'.$param['value'].'" '; 121 | } 122 | } 123 | } 124 | } 125 | 126 | // Create SQL. 127 | $this->raw_sql = 128 | ' SELECT '.$this->sql_array['SELECT']. 129 | ' FROM '.$this->sql_array['FROM']. 130 | (isset($this->sql_array['INNER JOIN']) ? 131 | (is_array($this->sql_array['INNER JOIN'])) ? 132 | ' INNER JOIN '.implode(' INNER JOIN ', $this->sql_array['INNER JOIN']) 133 | : ' INNER JOIN '.$this->sql_array['INNER JOIN'] 134 | : '' 135 | ). 136 | (isset($this->sql_array['LEFT JOIN']) ? 137 | (is_array($this->sql_array['LEFT JOIN'])) ? 138 | ' LEFT JOIN '.implode(' LEFT JOIN ', $this->sql_array['LEFT JOIN']) 139 | : ' LEFT JOIN '.$this->sql_array['LEFT JOIN'] 140 | : '' 141 | ). 142 | (isset($this->sql_array['RIGHT JOIN']) ? 143 | (is_array($this->sql_array['RIGHT JOIN'])) ? 144 | ' RIGHT JOIN '.implode(' RIGHT JOIN ', $this->sql_array['RIGHT JOIN']) 145 | : ' RIGHT JOIN '.$this->sql_array['RIGHT JOIN'] 146 | : '' 147 | ). 148 | (isset($this->sql_array['WHERE']) ? ' WHERE '.$this->sql_array['WHERE'] : ''). 149 | ' ORDER BY '.$this->order.' '.$this->sort. 150 | ' LIMIT '.(($this->page - 1) * $this->perpage).', '.$this->perpage.' 151 | '; 152 | 153 | // Do SQL request. 154 | $result_data = full_query($this->raw_sql); 155 | $result = array(); 156 | 157 | // Fetch answer. 158 | while ($line = mysql_fetch_assoc($result_data)) { 159 | $result[] = $line; 160 | } 161 | $this->result = $result; 162 | 163 | // Create concomitants. 164 | $this->createTablehead(); 165 | $this->createPaginator(); 166 | $this->createFilter(); 167 | } 168 | 169 | /** 170 | * Create a tablehead with sort buttons. 171 | */ 172 | private function createTablehead() 173 | { 174 | $th = ''; 175 | if (isset($this->result[0]) and is_array($this->result[0])) { 176 | $th .= ''; 177 | foreach (array_keys($this->result[0]) as $head) { 178 | $th .= ''; 179 | if ($this->order != $head) { 180 | $th .= ''.$head.''; 181 | } else { 182 | $th .= ''; 183 | $th .= $head; 184 | $th .= ''; 185 | $th .= ''; 186 | } 187 | $th .= ''; 188 | } 189 | $th .= ''; 190 | } 191 | $this->tablehead = $th; 192 | } 193 | 194 | /** 195 | * Create the paginator. 196 | */ 197 | private function createPaginator() 198 | { 199 | 200 | // If sql_array doesn`t contain 'FROM' parameter, we can`t create the paginator. 201 | if ($this->sql_array['FROM']) { 202 | 203 | $res = ''; 204 | 205 | // Make a short form of the paginator 206 | if ($this->paginator_type == 'short') { 207 | 208 | $res .= ''; 221 | 222 | // Make a default form of the paginator 223 | } else { 224 | 225 | // Do request about a count of items in table 226 | $count = mysql_fetch_assoc(full_query(' 227 | SELECT count(*) AS count 228 | FROM '.$this->sql_array['FROM']. 229 | (isset($this->sql_array['INNER JOIN']) ? 230 | (is_array($this->sql_array['INNER JOIN'])) ? 231 | ' INNER JOIN '.implode(' INNER JOIN ', $this->sql_array['INNER JOIN']) 232 | : ' INNER JOIN '.$this->sql_array['INNER JOIN'] 233 | : '' 234 | ). 235 | (isset($this->sql_array['LEFT JOIN']) ? 236 | (is_array($this->sql_array['LEFT JOIN'])) ? 237 | ' LEFT JOIN '.implode(' LEFT JOIN ', $this->sql_array['LEFT JOIN']) 238 | : ' LEFT JOIN '.$this->sql_array['LEFT JOIN'] 239 | : '' 240 | ). 241 | (isset($this->sql_array['RIGHT JOIN']) ? 242 | (is_array($this->sql_array['RIGHT JOIN'])) ? 243 | ' RIGHT JOIN '.implode(' RIGHT JOIN ', $this->sql_array['RIGHT JOIN']) 244 | : ' RIGHT JOIN '.$this->sql_array['RIGHT JOIN'] 245 | : '' 246 | ). 247 | (isset($this->sql_array['WHERE']) ? ' WHERE '.$this->sql_array['WHERE'] : '') 248 | )); 249 | $this->count = $count['count']; 250 | 251 | // Count of pages 252 | $this->maxpage = floor($count['count'] / $this->perpage); 253 | 254 | // If we have an incomplete page in the end, when do maxpage+1 255 | if (ceil($count['count'] / $this->perpage) != $this->maxpage) { 256 | $this->maxpage += 1; 257 | } 258 | 259 | // Create html-code of the paginator. 260 | 261 | // Previous page 262 | if ($this->page > 1) { 263 | $res .= '<< '; 264 | } 265 | // First page 266 | if ($this->page - $this->pages_in_paginator > 1) { 267 | $res .= '1 .. '; 268 | } 269 | // Simple pages 270 | for ($i = $this->page - $this->pages_in_paginator; $i <= $this->page + $this->pages_in_paginator; ++$i) { 271 | if (($i > 0) and ($i <= $this->maxpage)) { 272 | // Simple page 273 | if ($i != $this->page) { 274 | $res .= ''.$i.' '; 275 | // Chosen page 276 | } else { 277 | $res .= ''.$i.' '; 278 | } 279 | } 280 | } 281 | // Last page 282 | if ($this->page + $this->pages_in_paginator < $this->maxpage) { 283 | $res .= '.. '.$this->maxpage.' '; 284 | } 285 | // Next page 286 | if ($this->page < $this->maxpage) { 287 | $res .= '>> '; 288 | } 289 | } 290 | 291 | $this->paginator = $res; 292 | } else { 293 | $this->paginator = 'You should pass the value "FROM"'; 294 | } 295 | } 296 | 297 | /** 298 | * Create a filter-code. 299 | */ 300 | private function createFilter() 301 | { 302 | $filter = '
'; 303 | 304 | $filter .= ''; 305 | 306 | foreach ($this->filter_params as $param) { 307 | $filter .= ''; 308 | $filter .= ''; 309 | $filter .= ''; 310 | } 311 | 312 | foreach ($_GET as $name => $value) { 313 | if (($name != 'filter') and ($name != 'page')) { 314 | $filter .= ''; 315 | } 316 | } 317 | 318 | $filter .= ''; 319 | 320 | $filter .= '
'.$param['description'].'
'; 321 | 322 | $filter .= '
'; 323 | 324 | $filter .= '
'; 325 | 326 | $filter .= ''; 332 | 333 | $this->filter = $filter; 334 | } 335 | 336 | /** 337 | * Create URL for a page with all sorting and filter parameters. 338 | * 339 | * @param array $newdata The array of parameters, which values is different from default values. 340 | */ 341 | public function getUrl($newdata = array()) 342 | { 343 | global $customadminpath, $module; 344 | $data = array( 345 | 'page' => $this->page, 346 | 'order' => $this->order, 347 | 'sort' => $this->sort, 348 | ); 349 | foreach ($_GET as $k => $v) { 350 | $data[$k] = $v; 351 | } 352 | foreach ($newdata as $k => $v) { 353 | $data[$k] = $v; 354 | } 355 | $url = '/'.$customadminpath.'/addonmodules.php?'; 356 | foreach ($data as $key => $value) { 357 | if (is_array($value)) { 358 | foreach ($value as $k => $v) { 359 | $url .= '&'.$key.'['.$k.']='.$v; 360 | } 361 | } else { 362 | $url .= '&'.$key.'='.$value; 363 | } 364 | } 365 | 366 | return $url; 367 | } 368 | 369 | /** 370 | * Toggle sort direction. 371 | * 372 | * @param string $sort The sort direction (DESC|ASC). 373 | * 374 | * @return string New sort direction. 375 | */ 376 | public function toggleSort($sort) 377 | { 378 | if ($sort == 'DESC') { 379 | return 'ASC'; 380 | } 381 | if ($sort == 'ASC') { 382 | return 'DESC'; 383 | } 384 | 385 | return $sort; 386 | } 387 | } 388 | --------------------------------------------------------------------------------