├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── config.json ├── config └── pterodactyl.php ├── language └── en_us │ ├── pterodactyl.php │ ├── pterodactyl_package.php │ ├── pterodactyl_rule.php │ └── pterodactyl_service.php ├── lib ├── pterodactyl_package.php ├── pterodactyl_rule.php └── pterodactyl_service.php ├── pterodactyl.php └── views └── default ├── add_row.pdt ├── admin_service_info.pdt ├── client_service_info.pdt ├── css └── styles.css ├── edit_row.pdt ├── images └── logo.png ├── manage.pdt ├── tab_actions.pdt └── tab_client_actions.pdt /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore external vendor code 2 | /vendor/ 3 | 4 | # Ignore composer lock 5 | /composer.lock 6 | /nbproject/ 7 | /.idea/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | dist: trusty 3 | matrix: 4 | include: 5 | - php: 5.4 6 | - php: 5.5 7 | - php: 5.6 8 | - php: 7.0 9 | - php: 7.1 10 | - php: 7.2 11 | before_script: 12 | - composer install --dev 13 | script: 14 | - ./vendor/bin/phpunit --coverage-text --coverage-clover ./build/logs/clover.xml 15 | - ./vendor/bin/phpcs --extensions=php,pdt --report=summary --ignore=./vendor/* --standard=./phpcs.xml.dist ./ 16 | after_script: 17 | - php ./vendor/bin/coveralls -v 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Phillips Data, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pterodactyl Module 2 | 3 | [](https://travis-ci.org/blesta/module-pterodactyl) [](https://coveralls.io/github/blesta/module-pterodactyl?branch=master) 4 | 5 | This is a module for Blesta that integrates with [Pterodactyl](https://pterodactyl.com/). 6 | 7 | ## Install the Module 8 | 9 | 1. You can install the module via composer: 10 | 11 | ``` 12 | composer require blesta/pterodactyl 13 | ``` 14 | 15 | 2. OR upload the source code to a /components/modules/pterodactyl/ directory within 16 | your Blesta installation path. 17 | 18 | For example: 19 | 20 | ``` 21 | /var/www/html/blesta/components/modules/pterodactyl/ 22 | ``` 23 | 24 | 3. Log in to your admin Blesta account and navigate to 25 | > Settings > Modules 26 | 27 | 4. Find the Pterodactyl module and click the "Install" button to install it 28 | 29 | 5. Add a server with your Pterodactyl credentials 30 | 31 | 6. You're done! 32 | 33 | ### Blesta Compatibility 34 | 35 | |Blesta Version|Module Version| 36 | |--------------|--------------| 37 | |< v4.9.0|v1.3.0| 38 | |>= v4.9.0|v1.4.0+| 39 | |>= v5.0.0|v1.6.0+| 40 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blesta/pterodactyl", 3 | "description": "Pterodactyl Module", 4 | "license": "proprietary", 5 | "type": "blesta-module", 6 | "require": { 7 | "php": ">=5.4.0", 8 | "blesta/composer-installer": "~1.0", 9 | "blesta/pterodactyl-sdk": "~1.0" 10 | }, 11 | "require-dev": { 12 | "phpunit/phpunit": "~4.6", 13 | "squizlabs/php_codesniffer": "~3.0", 14 | "satooshi/php-coveralls": "~0.7" 15 | }, 16 | "authors": [ 17 | { 18 | "name": "Phillips Data, Inc.", 19 | "email": "jonathan@phpillipsdata.com" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.8.2", 3 | "name": "Pterodactyl.name", 4 | "description": "Pterodactyl.description", 5 | "authors": [ 6 | { 7 | "name": "Phillips Data, Inc.", 8 | "url": "http://www.blesta.com" 9 | } 10 | ], 11 | "service": { 12 | "name_key": "server_name" 13 | }, 14 | "module": { 15 | "row": "Pterodactyl.module_row", 16 | "rows": "Pterodactyl.module_row_plural", 17 | "row_key": "server_name", 18 | "group": "Pterodactyl.module_group" 19 | }, 20 | "email_tags": { 21 | "module": ["server_name", "host_name"], 22 | "package": ["location_id", "nest_id", "egg_id", "image"], 23 | "service": ["server_name", "server_ip", "server_port", "server_username", "server_password"] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /config/pterodactyl.php: -------------------------------------------------------------------------------- 1 | [ 13 | 'lang' => 'en_us', 14 | 'text' => 'Thank you for ordering your Minecraft Server! 15 | 16 | Server Name: {service.server_name} 17 | Server Username: {service.server_username} 18 | Server Password: {service.server_password} 19 | Server IP and Port: {service.server_ip}:{service.server_port} 20 | 21 | Log into your account to start and manage your Minecraft Server! Be sure to start your Minecraft server for the first time from within Pterodactyl Panel so that you can agree to the Mojang EULA.', 22 | 'html' => '
Thank you for ordering your Minecraft Server!
23 |Server Name: {service.server_name}
Server Username: {service.server_username}
Server Password: {service.server_password}
Server IP and Port: {service.server_ip}:{service.server_port}
Log into your account to start and manage your Minecraft Server! Be sure to start your Minecraft server for the first time from within Pterodactyl Panel so that you can agree to the Mojang EULA.
' 25 | ] 26 | ]); 27 | -------------------------------------------------------------------------------- /language/en_us/pterodactyl.php: -------------------------------------------------------------------------------- 1 | Input->errors(); 28 | } 29 | 30 | /** 31 | * Validates input data when attempting to add a package, returns the meta 32 | * data to save when adding a package. Performs any action required to add 33 | * the package on the remote server. Sets Input errors on failure, 34 | * preventing the package from being added. 35 | * 36 | * @param array $packageLists An array of package fields lists from the API including: 37 | * 38 | * - locations A list of location IDs 39 | * - nests A list of nest IDs 40 | * - eggs A list of eggs keyed by their IDs 41 | * @param array $vars An array of key/value pairs used to add the package (optional) including: 42 | * 43 | * - location_id The ID of the Location to automatically deploy servers to. 44 | * - nest_id The ID of the Nest to use for created servers. 45 | * - egg_id The ID of the Egg to use for created servers. 46 | * - dedicated_ip Whether to assign a dedicated ip to created servers (optional) 47 | * - port_range Comma seperated port ranges to assign to created servers (optional) 48 | * - pack_id The ID of the Pack to use for created servers (optional) 49 | * - memory The memory limit in megabytes to assign created servers 50 | * - swap The swap memory limit in megabytes to assign created servers 51 | * - cpu The CPU limit in percentage to assign created servers 52 | * - disk The disk space limit in megabytes to assign created servers 53 | * - io The block IO adjustment number to assign created servers 54 | * - startup The custom startup command to assign created servers (optional) 55 | * - image The custom docker image to assign created servers (optional) 56 | * - databases The database limit to assign created servers (optional) 57 | * - allocations The allocations limit to assign created servers (optional) 58 | * - backups The backups limit to assign created servers (optional) 59 | * - * Egg variables should also be submitted 60 | * @return array A numerically indexed array of meta fields to be stored for this package containing: 61 | * 62 | * - key The key for this meta field 63 | * - value The value for this key 64 | * - encrypted Whether or not this field should be encrypted (default 0, not encrypted) 65 | * @see Module::getModule() 66 | * @see Module::getModuleRow() 67 | */ 68 | public function add(array $packageLists, array $vars = null) 69 | { 70 | // Set missing checkboxes 71 | $checkboxes = ['dedicated_ip']; 72 | foreach ($checkboxes as $checkbox) { 73 | if (empty($vars['meta'][$checkbox])) { 74 | $vars['meta'][$checkbox] = '0'; 75 | } 76 | } 77 | 78 | // Get the rule helper 79 | Loader::load(dirname(__FILE__) . DS . 'pterodactyl_rule.php'); 80 | $rule_helper = new PterodactylRule(); 81 | 82 | $rules = $this->getRules($packageLists, $vars); 83 | // Get egg variable rules 84 | if (isset($vars['meta']['egg_id']) && isset($packageLists['eggs'][$vars['meta']['egg_id']])) { 85 | $egg = $packageLists['eggs'][$vars['meta']['egg_id']]; 86 | foreach ($egg->attributes->relationships->variables->data as $envVariable) { 87 | $fieldName = strtolower($envVariable->attributes->env_variable); 88 | $rules['meta[' . $fieldName . ']'] = $rule_helper->parseEggVariable($envVariable); 89 | 90 | foreach ($rules['meta[' . $fieldName . ']'] as $rule) { 91 | if (array_key_exists('if_set', $rule) 92 | && $rule['if_set'] == true 93 | && empty($vars['meta'][$fieldName]) 94 | ) { 95 | unset($rules['meta[' . $fieldName . ']']); 96 | } 97 | } 98 | } 99 | } 100 | 101 | // Set rules to validate input data 102 | $this->Input->setRules($rules); 103 | 104 | // Build meta data to return 105 | $meta = []; 106 | if ($this->Input->validates($vars)) { 107 | // Return all package meta fields 108 | foreach ($vars['meta'] as $key => $value) { 109 | $meta[] = [ 110 | 'key' => $key, 111 | 'value' => $value, 112 | 'encrypted' => 0 113 | ]; 114 | } 115 | } 116 | 117 | return $meta; 118 | } 119 | 120 | /** 121 | * Returns all fields used when adding/editing a package, including any 122 | * javascript to execute when the page is rendered with these fields. 123 | * 124 | * @param array $packageLists An array of package fields lists from the API including: 125 | * 126 | * - locations A list of location IDs 127 | * - nests A list of nest IDs 128 | * - eggs A list of eggs keyed by their IDs 129 | * @param stdClass $vars A stdClass object representing a set of post fields (optional) including: 130 | * 131 | * - location_id The ID of the Location to automatically deploy servers to. 132 | * - nest_id The ID of the Nest to use for created servers. 133 | * - egg_id The ID of the Egg to use for created servers. 134 | * - dedicated_ip Whether to assign a dedicated ip to created servers (optional) 135 | * - port_range Comma seperated port ranges to assign to created servers (optional) 136 | * - pack_id The ID of the Pack to use for created servers (optional) 137 | * - memory The memory limit in megabytes to assign created servers 138 | * - swap The swap memory limit in megabytes to assign created servers 139 | * - cpu The CPU limit in percentage to assign created servers 140 | * - disk The disk space limit in megabytes to assign created servers 141 | * - io The block IO adjustment number to assign created servers 142 | * - startup The custom startup command to assign created servers (optional) 143 | * - image The custom docker image to assign created servers (optional) 144 | * - databases The database limit to assign created servers (optional) 145 | * - allocations The allocations limit to assign created servers (optional) 146 | * - backups The backups limit to assign created servers (optional) 147 | * - * Egg variables should also be submitted 148 | * @return ModuleFields A ModuleFields object, containing the fields 149 | * to render as well as any additional HTML markup to include 150 | */ 151 | public function getFields(array $packageLists, $vars = null) 152 | { 153 | Loader::loadHelpers($this, ['Html']); 154 | 155 | $fields = new ModuleFields(); 156 | 157 | // Set js to refetch options when the nest or egg is changed 158 | $fields->setHtml(" 159 | 168 | "); 169 | 170 | // Set the select fields 171 | $selectFields = [ 172 | 'location_id' => isset($packageLists['locations']) ? $packageLists['locations'] : [], 173 | 'nest_id' => isset($packageLists['nests']) ? $packageLists['nests'] : [], 174 | 'egg_id' => isset($packageLists['eggs']) 175 | ? array_combine(array_keys($packageLists['eggs']), array_keys($packageLists['eggs'])) 176 | : [], 177 | ]; 178 | foreach ($selectFields as $selectField => $list) { 179 | // Create the select field label 180 | $field = $fields->label( 181 | Language::_('PterodactylPackage.package_fields.' . $selectField, true), 182 | 'Pterodactyl_' . $selectField 183 | ); 184 | // Set the select field 185 | $field->attach( 186 | $fields->fieldSelect( 187 | 'meta[' . $selectField . ']', 188 | $list, 189 | (isset($vars->meta[$selectField]) ? $vars->meta[$selectField] : null), 190 | ['id' => 'Pterodactyl_' . $selectField] 191 | ) 192 | ); 193 | // Add a tooltip based on the select field 194 | $tooltip = $fields->tooltip(Language::_('PterodactylPackage.package_fields.tooltip.' . $selectField, true)); 195 | $field->attach($tooltip); 196 | $fields->setField($field); 197 | } 198 | 199 | // Set the Dedicated IP 200 | $dedicatedIp = $fields->label( 201 | Language::_('PterodactylPackage.package_fields.dedicated_ip', true), 202 | 'Pterodactyl_dedicated_ip', 203 | ['class' => 'inline'] 204 | ); 205 | $dedicatedIp->attach( 206 | $fields->fieldCheckbox( 207 | 'meta[dedicated_ip]', 208 | '1', 209 | (isset($vars->meta['dedicated_ip']) ? $vars->meta['dedicated_ip'] : null) == 1, 210 | ['id' => 'Pterodactyl_dedicated_ip', 'class' => 'inline'] 211 | ) 212 | ); 213 | $tooltip = $fields->tooltip(Language::_('PterodactylPackage.package_fields.tooltip.dedicated_ip', true)); 214 | $dedicatedIp->attach($tooltip); 215 | $fields->setField($dedicatedIp); 216 | 217 | // Set text fields 218 | $textFields = [ 219 | 'port_range', 'pack_id', 'memory', 'swap', 'cpu', 'disk', 220 | 'io', 'startup', 'image', 'databases', 'allocations', 'backups' 221 | ]; 222 | foreach ($textFields as $textField) { 223 | // Create the text field label 224 | $field = $fields->label( 225 | Language::_('PterodactylPackage.package_fields.' . $textField, true), 226 | 'Pterodactyl_' . $textField 227 | ); 228 | // Set the text field 229 | $field->attach( 230 | $fields->fieldText( 231 | 'meta[' . $textField . ']', 232 | (isset($vars->meta[$textField]) ? $vars->meta[$textField] : null), 233 | ['id' => 'Pterodactyl_' . $textField] 234 | ) 235 | ); 236 | // Add a tooltip based on the text field 237 | $tooltip = $fields->tooltip(Language::_('PterodactylPackage.package_fields.tooltip.' . $textField, true)); 238 | $field->attach($tooltip); 239 | $fields->setField($field); 240 | } 241 | 242 | // Return standard package fields and attach any applicable egg fields 243 | return isset($packageLists['eggs'][(isset($vars->meta['egg_id']) ? $vars->meta['egg_id'] : null)]) 244 | ? $this->attachEggFields($packageLists['eggs'][(isset($vars->meta['egg_id']) ? $vars->meta['egg_id'] : null)], $fields, $vars) 245 | : $fields; 246 | } 247 | 248 | /** 249 | * Attaches package fields for each environment from the Pterodactyle egg 250 | * 251 | * @param stdClass $pterodactyl_egg The egg to pull environment variables from 252 | * @param ModuleFields $fields The fields object to attach the new fields to 253 | * @param stdClass $vars A stdClass object representing a set of post fields (optional) 254 | * @return ModuleFields The new fields object with all environment variable fields attached 255 | */ 256 | private function attachEggFields($pterodactyl_egg, $fields, $vars = null) 257 | { 258 | if (!is_object($pterodactyl_egg)) { 259 | return $fields; 260 | } 261 | 262 | // Get service fields from the egg 263 | foreach ($pterodactyl_egg->attributes->relationships->variables->data as $env_variable) { 264 | // Create a label for the environment variable 265 | $label = strpos($env_variable->attributes->rules, 'required') === 0 266 | ? $env_variable->attributes->name 267 | : Language::_('PterodactylPackage.package_fields.optional', true, $env_variable->attributes->name); 268 | $key = strtolower($env_variable->attributes->env_variable); 269 | $field = $fields->label($label, $key); 270 | // Create the environment variable field and attach to the label 271 | $field->attach( 272 | $fields->fieldText( 273 | 'meta[' . $key . ']', 274 | (isset($vars->meta[$key]) ? $vars->meta[$key] : $env_variable->attributes->default_value), 275 | ['id' => $key] 276 | ) 277 | ); 278 | // Add tooltip based on the description from Pterodactyl 279 | $tooltip = $fields->tooltip( 280 | $env_variable->attributes->description 281 | . ' ' 282 | . Language::_('PterodactylPackage.package_fields.tooltip.display', true) 283 | ); 284 | // Create a field for whether to display the environment variable to the client 285 | $checkboxKey = $key . '_display'; 286 | $field->attach($tooltip); 287 | $field->attach( 288 | $fields->fieldCheckbox( 289 | 'meta[' . $checkboxKey . ']', 290 | '1', 291 | (isset($vars->meta[$checkboxKey]) ? $vars->meta[$checkboxKey] : '0') == '1', 292 | ['id' => $checkboxKey, 'class' => 'inline'] 293 | ) 294 | ); 295 | // Set the label as a field 296 | $fields->setField($field); 297 | } 298 | 299 | return $fields; 300 | } 301 | 302 | /** 303 | * Builds and returns the rules required to add/edit a package 304 | * 305 | * @param array $packageLists An array of package fields lists from the API including: 306 | * 307 | * - locations A list of location IDs 308 | * - nests A list of nest IDs 309 | * - eggs A list of eggs keyed by their IDs 310 | * @param array $vars An array of key/value data pairs 311 | * 312 | * - location_id The ID of the Location to automatically deploy servers to. 313 | * - nest_id The ID of the Nest to use for created servers. 314 | * - egg_id The ID of the Egg to use for created servers. 315 | * - dedicated_ip Whether to assign a dedicated ip to created servers (optional) 316 | * - port_range Comma seperated port ranges to assign to created servers (optional) 317 | * - pack_id The ID of the Pack to use for created servers (optional) 318 | * - memory The memory limit in megabytes to assign created servers 319 | * - swap The swap memory limit in megabytes to assign created servers 320 | * - cpu The CPU limit in percentage to assign created servers 321 | * - disk The disk space limit in megabytes to assign created servers 322 | * - io The block IO adjustment number to assign created servers 323 | * - startup The custom startup command to assign created servers (optional) 324 | * - image The custom docker image to assign created servers (optional) 325 | * - databases The database limit to assign created servers (optional) 326 | * - allocations The allocations limit to assign created servers (optional) 327 | * - backups The backups limit to assign created servers (optional) 328 | * - * Egg variables should also be submitted 329 | * @return array An array of Input rules suitable for Input::setRules() 330 | */ 331 | public function getRules(array $packageLists, array $vars) 332 | { 333 | $rules = [ 334 | 'meta[location_id]' => [ 335 | 'format' => [ 336 | 'rule' => ['matches', '/^[0-9]+$/'], 337 | 'message' => Language::_('PterodactylPackage.!error.meta[location_id].format', true) 338 | ], 339 | 'valid' => [ 340 | 'rule' => [ 341 | 'array_key_exists', 342 | isset($packageLists['locations']) ? $packageLists['locations'] : [] 343 | ], 344 | 'message' => Language::_('PterodactylPackage.!error.meta[location_id].valid', true) 345 | ] 346 | ], 347 | 'meta[dedicated_ip]' => [ 348 | 'format' => [ 349 | 'rule' => ['in_array', [0, 1]], 350 | 'message' => Language::_('PterodactylPackage.!error.meta[dedicated_ip].format', true) 351 | ] 352 | ], 353 | 'meta[port_range]' => [ 354 | 'format' => [ 355 | 'rule' => function ($portRanges) { 356 | $ranges = explode(',', $portRanges); 357 | foreach ($ranges as $range) { 358 | if (!preg_match('/^[0-9]+\-[0-9]+$/', $range)) { 359 | return false; 360 | } 361 | } 362 | 363 | return true; 364 | }, 365 | 'message' => Language::_('PterodactylPackage.!error.meta[port_range].format', true) 366 | ] 367 | ], 368 | 'meta[nest_id]' => [ 369 | 'format' => [ 370 | 'rule' => ['matches', '/^[0-9]+$/'], 371 | 'message' => Language::_('PterodactylPackage.!error.meta[nest_id].format', true) 372 | ], 373 | 'valid' => [ 374 | 'rule' => [ 375 | 'array_key_exists', 376 | isset($packageLists['nests']) ? $packageLists['nests'] : [] 377 | ], 378 | 'message' => Language::_('PterodactylPackage.!error.meta[nest_id].valid', true) 379 | ] 380 | ], 381 | 'meta[egg_id]' => [ 382 | 'format' => [ 383 | 'rule' => ['matches', '/^[0-9]+$/'], 384 | 'message' => Language::_('PterodactylPackage.!error.meta[egg_id].format', true) 385 | ], 386 | 'valid' => [ 387 | 'rule' => [ 388 | 'array_key_exists', 389 | isset($packageLists['eggs']) ? $packageLists['eggs'] : [] 390 | ], 391 | 'message' => Language::_('PterodactylPackage.!error.meta[egg_id].valid', true) 392 | ] 393 | ], 394 | 'meta[pack_id]' => [ 395 | 'format' => [ 396 | 'rule' => function ($packId) { 397 | return empty($packId) || preg_match('/^[0-9]+$/', $packId); 398 | }, 399 | 'message' => Language::_('PterodactylPackage.!error.meta[pack_id].format', true) 400 | ] 401 | ], 402 | 'meta[memory]' => [ 403 | 'format' => [ 404 | 'rule' => ['matches', '/^[0-9]+$/'], 405 | 'message' => Language::_('PterodactylPackage.!error.meta[memory].format', true) 406 | ] 407 | ], 408 | 'meta[swap]' => [ 409 | 'format' => [ 410 | 'rule' => ['matches', '/^(?:\-1|[0-9]+)$/'], 411 | 'message' => Language::_('PterodactylPackage.!error.meta[swap].format', true) 412 | ] 413 | ], 414 | 'meta[cpu]' => [ 415 | 'format' => [ 416 | 'rule' => ['matches', '/^[0-9]+$/'], 417 | 'message' => Language::_('PterodactylPackage.!error.meta[cpu].format', true) 418 | ] 419 | ], 420 | 'meta[disk]' => [ 421 | 'format' => [ 422 | 'rule' => ['matches', '/^[0-9]+$/'], 423 | 'message' => Language::_('PterodactylPackage.!error.meta[disk].format', true) 424 | ] 425 | ], 426 | 'meta[io]' => [ 427 | 'format' => [ 428 | 'rule' => ['matches', '/^[0-9]+$/'], 429 | 'message' => Language::_('PterodactylPackage.!error.meta[io].format', true) 430 | ] 431 | ], 432 | 'meta[image]' => [ 433 | 'length' => [ 434 | 'rule' => ['maxLength', 255], 435 | 'message' => Language::_('PterodactylPackage.!error.meta[image].length', true) 436 | ] 437 | ], 438 | 'meta[databases]' => [ 439 | 'format' => [ 440 | 'rule' => function ($databaseLimit) { 441 | return empty($databaseLimit) || preg_match('/^[0-9]+$/', $databaseLimit); 442 | }, 443 | 'message' => Language::_('PterodactylPackage.!error.meta[databases].format', true) 444 | ] 445 | ], 446 | 'meta[allocations]' => [ 447 | 'format' => [ 448 | 'rule' => function ($allocationLimit) { 449 | return empty($allocationLimit) || preg_match('/^[0-9]+$/', $allocationLimit); 450 | }, 451 | 'message' => Language::_('PterodactylPackage.!error.meta[allocations].format', true) 452 | ] 453 | ], 454 | 'meta[backups]' => [ 455 | 'format' => [ 456 | 'rule' => function ($backupLimit) { 457 | return empty($backupLimit) || preg_match('/^[0-9]+$/', $backupLimit); 458 | }, 459 | 'message' => Language::_('PterodactylPackage.!error.meta[backups].format', true) 460 | ] 461 | ], 462 | ]; 463 | 464 | return $rules; 465 | } 466 | } 467 | -------------------------------------------------------------------------------- /lib/pterodactyl_rule.php: -------------------------------------------------------------------------------- 1 | attributes->name; 24 | 25 | // Parse rule string for regexes and remove them to simplify parsing 26 | $ruleString = $eggVariable->attributes->rules; 27 | 28 | // Match the regex strings and store them in an array 29 | $regexRuleStrings = []; 30 | preg_match_all("/regex\\:(\\/.*?\\/)/i", $ruleString, $regexRuleStrings); 31 | 32 | // Remove the regex stings and replace them with {{{regex}}} 33 | $regexFilteredRuleString = str_replace($regexRuleStrings[1] ?? [], '{{{regex}}}', $ruleString); 34 | 35 | // Get a the list of OR separated validation rules 36 | $ruleStrings = explode('|', $regexFilteredRuleString); 37 | 38 | // Parse rules from the string 39 | $rules = []; 40 | foreach ($ruleStrings as $ruleString) { 41 | $ruleParts = explode(':', $ruleString); 42 | $ruleName = str_replace('_', '', lcfirst(ucwords($ruleParts[0], '_'))); 43 | 44 | $ruleParameters = []; 45 | if (isset($ruleParts[1])) { 46 | $ruleParameters = explode(',', $ruleParts[1]); 47 | } 48 | 49 | // Re-add filtered regexes 50 | if (!empty($regexRuleStrings[1])) { 51 | foreach ($ruleParameters as &$ruleParameter) { 52 | $ruleParameter = str_replace('{{{regex}}}', array_shift($regexRuleStrings[1]), $ruleParameter); 53 | } 54 | } 55 | 56 | // Generate validation rule 57 | if (method_exists($this, $ruleName)) { 58 | $rules[$ruleName] = call_user_func_array( 59 | [$this, $ruleName], 60 | [$fieldName, $ruleParameters] 61 | ); 62 | } 63 | } 64 | 65 | // Make all rules conditional on field existence 66 | if (strpos($eggVariable->attributes->rules, 'required') === false) { 67 | foreach ($rules as &$rule) { 68 | $rule['if_set'] = true; 69 | } 70 | } 71 | 72 | return $rules; 73 | } 74 | 75 | /** 76 | * Gets a rule to require the given field 77 | * 78 | * @param string $fieldName The name of the field to validate 79 | * @return array An array representing the validation rule 80 | */ 81 | private function required($fieldName) 82 | { 83 | return [ 84 | 'rule' => 'isEmpty', 85 | 'negate' => true, 86 | 'message' => Language::_('PterodactylRule.!error.required', true, $fieldName) 87 | ]; 88 | } 89 | 90 | /** 91 | * Gets a rule to validate the given field against a regex 92 | * 93 | * @param string $fieldName The name of the field to validate 94 | * @param array $params A list parameters for the validation rule 95 | * @return array An array representing the validation rule 96 | */ 97 | private function regex($fieldName, array $params) 98 | { 99 | return [ 100 | 'rule' => ['matches', $params[0]], 101 | 'message' => Language::_('PterodactylRule.!error.regex', true, $fieldName, $params[0]) 102 | ]; 103 | } 104 | 105 | /** 106 | * Gets a rule to validate the given field is numeric 107 | * 108 | * @param string $fieldName The name of the field to validate 109 | * @return array An array representing the validation rule 110 | */ 111 | private function numeric($fieldName) 112 | { 113 | return [ 114 | 'rule' => 'is_numeric', 115 | 'message' => Language::_('PterodactylRule.!error.numeric', true, $fieldName) 116 | ]; 117 | } 118 | 119 | /** 120 | * Gets a rule to validate the given field is an integer 121 | * 122 | * @param string $fieldName The name of the field to validate 123 | * @return array An array representing the validation rule 124 | */ 125 | private function integer($fieldName) 126 | { 127 | return [ 128 | 'rule' => function ($value) { 129 | return is_numeric($value) && intval($value) == $value; 130 | }, 131 | 'message' => Language::_('PterodactylRule.!error.integer', true, $fieldName) 132 | ]; 133 | } 134 | 135 | /** 136 | * Gets a rule to validate the given field is a string 137 | * 138 | * @param string $fieldName The name of the field to validate 139 | * @return array An array representing the validation rule 140 | */ 141 | private function string($fieldName) 142 | { 143 | return [ 144 | 'rule' => 'is_string', 145 | 'message' => Language::_('PterodactylRule.!error.string', true, $fieldName) 146 | ]; 147 | } 148 | 149 | /** 150 | * Gets a rule to validate the given field is alpha_numeric 151 | * 152 | * @param string $fieldName The name of the field to validate 153 | * @return array An array representing the validation rule 154 | */ 155 | private function alphaNum($fieldName) 156 | { 157 | return [ 158 | 'rule' => 'ctype_alnum', 159 | 'message' => Language::_('PterodactylRule.!error.alphaNum', true, $fieldName) 160 | ]; 161 | } 162 | 163 | /** 164 | * Gets a rule to validate the given field is only alpha_numeric characters or dashes and underscores 165 | * 166 | * @param string $fieldName The name of the field to validate 167 | * @return array An array representing the validation rule 168 | */ 169 | private function alphaDash($fieldName) 170 | { 171 | return [ 172 | 'rule' => function($value) { 173 | return ctype_alnum(preg_replace('/-|_/', '', $value)); 174 | }, 175 | 'message' => Language::_('PterodactylRule.!error.alphaDash', true, $fieldName) 176 | ]; 177 | } 178 | 179 | /** 180 | * Gets a rule to validate the given field is a valid URL 181 | * 182 | * @param string $fieldName The name of the field to validate 183 | * @return array An array representing the validation rule 184 | */ 185 | private function url($fieldName) 186 | { 187 | return [ 188 | 'rule' => function($value) { 189 | $validator = new Server(); 190 | return $validator->isUrl($value); 191 | }, 192 | 'message' => Language::_('PterodactylRule.!error.url', true, $fieldName) 193 | ]; 194 | } 195 | 196 | /** 197 | * Gets a rule to validate the given field has a value with a given minimum 198 | * 199 | * @param string $fieldName The name of the field to validate 200 | * @param array $params A list parameters for the validation rule 201 | * @return array An array representing the validation rule 202 | */ 203 | private function min($fieldName, array $params) 204 | { 205 | return [ 206 | 'rule' => function ($value) use ($params) { 207 | switch (gettype($value)) { 208 | case 'string': 209 | return strlen($value) >= $params[0]; 210 | case 'integer': 211 | // Same as double 212 | case 'double': 213 | return $value >= $params[0]; 214 | case 'array': 215 | return count($value) >= $params[0]; 216 | } 217 | }, 218 | 'message' => Language::_('PterodactylRule.!error.min', true, $fieldName, $params[0]) 219 | ]; 220 | } 221 | 222 | /** 223 | * Gets a rule to validate the given field has a value with a given maximum 224 | * 225 | * @param string $fieldName The name of the field to validate 226 | * @param array $params A list parameters for the validation rule 227 | * @return array An array representing the validation rule 228 | */ 229 | private function max($fieldName, array $params) 230 | { 231 | return [ 232 | 'rule' => function ($value) use ($params) { 233 | switch (gettype($value)) { 234 | case 'string': 235 | return strlen($value) <= $params[0]; 236 | case 'integer': 237 | // Same as double 238 | case 'double': 239 | return $value <= $params[0]; 240 | case 'array': 241 | return count($value) <= $params[0]; 242 | } 243 | }, 244 | 'message' => Language::_('PterodactylRule.!error.max', true, $fieldName, $params[0]) 245 | ]; 246 | } 247 | 248 | /** 249 | * Gets a rule to validate the given field has a value within a given range 250 | * 251 | * @param string $fieldName The name of the field to validate 252 | * @param array $params A list parameters for the validation rule 253 | * @return array An array representing the validation rule 254 | */ 255 | private function between($fieldName, array $params) 256 | { 257 | return [ 258 | 'rule' => function ($value) use ($params) { 259 | switch (gettype($value)) { 260 | case 'string': 261 | return strlen($value) >= $params[0] && strlen($value) <= $params[1]; 262 | case 'integer': 263 | // Same as double 264 | case 'double': 265 | return $value >= $params[0] && $value <= $params[1]; 266 | case 'array': 267 | return count($value) >= $params[0] && count($value) <= $params[1]; 268 | } 269 | }, 270 | 'message' => Language::_('PterodactylRule.!error.between', true, $fieldName, $params[0], $params[1]) 271 | ]; 272 | } 273 | 274 | /** 275 | * Gets a rule to validate the given field has a numeric value within a given range 276 | * 277 | * @param string $fieldName The name of the field to validate 278 | * @param array $params A list parameters for the validation rule 279 | * @return array An array representing the validation rule 280 | */ 281 | private function digitsBetween($fieldName, array $params) 282 | { 283 | return [ 284 | 'rule' => function ($value) use ($params) { 285 | return is_numeric($value) && strlen($value) >= $params[0] && strlen($value) <= $params[1]; 286 | }, 287 | 'message' => Language::_('PterodactylRule.!error.digitsBetween', true, $fieldName, $params[0], $params[1]) 288 | ]; 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /lib/pterodactyl_service.php: -------------------------------------------------------------------------------- 1 | Input->errors(); 28 | } 29 | 30 | /** 31 | * Gets a list of parameters to submit to Pterodactyl for user creation 32 | * 33 | * @param array $vars A list of input data 34 | * @return array A list containing the parameters 35 | */ 36 | public function addUserParameters(array $vars) 37 | { 38 | Loader::loadModels($this, ['Clients']); 39 | $client = $this->Clients->get($vars['client_id']); 40 | return [ 41 | 'username' => 'bl_' . $client->id, 42 | 'password' => $this->generatePassword(), 43 | 'email' => $client->email, 44 | 'first_name' => $client->first_name, 45 | 'last_name' => $client->last_name, 46 | 'external_id' => 'bl-' . $client->id, 47 | ]; 48 | } 49 | 50 | /** 51 | * Generates a password. 52 | * 53 | * @param int $min_length The minimum character length for the password (5 or larger) 54 | * @param int $max_length The maximum character length for the password (14 or fewer) 55 | * @return string The generated password 56 | */ 57 | private function generatePassword($min_length = 10, $max_length = 14) 58 | { 59 | $pool = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()'; 60 | $pool_size = strlen($pool); 61 | $length = mt_rand(max($min_length, 5), min($max_length, 14)); 62 | $password = ''; 63 | 64 | for ($i = 0; $i < $length; $i++) { 65 | $password .= substr($pool, mt_rand(0, $pool_size - 1), 1); 66 | } 67 | 68 | return $password; 69 | } 70 | 71 | /** 72 | * Gets a list of parameters to submit to Pterodactyl for server creation 73 | * 74 | * @param array $vars An array of post fields 75 | * @param stdClass $package The package to pull server info from 76 | * @param stdClass $pterodactylUser An object representing the Pterodacytl user 77 | * @param stdClass $pterodactylEgg An object representing the Pterodacytl egg 78 | * @return array The list of parameters 79 | */ 80 | public function addServerParameters(array $vars, $package, $pterodactylUser, $pterodactylEgg) 81 | { 82 | // Gather server data 83 | return [ 84 | 'external_id' => $vars['client_id'] . '-' . uniqid(), 85 | 'name' => $vars['server_name'], 86 | 'description' => $vars['server_description'], 87 | 'user' => $pterodactylUser->attributes->id, 88 | 'nest' => $package->meta->nest_id, 89 | 'egg' => $package->meta->egg_id, 90 | 'pack' => $package->meta->pack_id, 91 | 'docker_image' => !empty($package->meta->image) 92 | ? $package->meta->image 93 | : $pterodactylEgg->attributes->docker_image, 94 | 'startup' => !empty($package->meta->startup) 95 | ? $package->meta->startup 96 | : $pterodactylEgg->attributes->startup, 97 | 'limits' => [ 98 | 'memory' => $package->meta->memory, 99 | 'swap' => $package->meta->swap, 100 | 'io' => $package->meta->io, 101 | 'cpu' => $package->meta->cpu, 102 | 'disk' => $package->meta->disk, 103 | ], 104 | 'feature_limits' => [ 105 | 'databases' => !empty($package->meta->databases) ? $package->meta->databases : null, 106 | 'allocations' => !empty($package->meta->allocations) ? $package->meta->allocations : null, 107 | 'backups' => !empty($package->meta->backups) ? $package->meta->backups : null, 108 | ], 109 | 'deploy' => [ 110 | 'locations' => [$package->meta->location_id], 111 | 'dedicated_ip' => !empty($package->meta->dedicated_ip) ? $package->meta->dedicated_ip : false, 112 | 'port_range' => explode(',', $package->meta->port_range), 113 | ], 114 | 'environment' => $this->getEnvironmentVariables($vars, $package, $pterodactylEgg), 115 | 'start_on_completion' => true, 116 | ]; 117 | } 118 | 119 | /** 120 | * Gets a list of parameters to submit to Pterodactyl for editing server details 121 | * 122 | * @param array $vars An array of post fields 123 | * @param stdClass $pterodactylUser An object representing the Pterodacytl user 124 | * @return array The list of parameters 125 | */ 126 | public function editServerParameters(array $vars, $pterodactylUser) 127 | { 128 | // Gather server data 129 | return [ 130 | 'external_id' => $vars['client_id'] . '-' . (isset($vars['service_id']) ? $vars['service_id'] : uniqid()), 131 | 'name' => $vars['server_name'], 132 | 'description' => $vars['server_description'], 133 | 'user' => $pterodactylUser->attributes->id, 134 | ]; 135 | } 136 | 137 | /** 138 | * Gets a list of parameters to submit to Pterodactyl for editing the server build parameters 139 | * 140 | * @param array $vars An array of post fields 141 | * @param stdClass $package The package to pull server info from 142 | * @return array The list of parameters 143 | */ 144 | public function editServerBuildParameters(array $vars, $package) 145 | { 146 | // Gather server data 147 | return [ 148 | 'allocation' => !empty($vars['allocation']) ? $vars['allocation'] : 1, 149 | 'memory' => $package->meta->memory, 150 | 'swap' => $package->meta->swap, 151 | 'io' => $package->meta->io, 152 | 'cpu' => $package->meta->cpu, 153 | 'disk' => $package->meta->disk, 154 | 'feature_limits' => [ 155 | 'databases' => !empty($package->meta->databases) ? $package->meta->databases : null, 156 | 'allocations' => !empty($package->meta->allocations) ? $package->meta->allocations : null, 157 | 'backups' => !empty($package->meta->backups) ? $package->meta->backups : null, 158 | ] 159 | ]; 160 | } 161 | 162 | /** 163 | * Gets a list of parameters to submit to Pterodactyl for editing server startup parameters 164 | * 165 | * @param array $vars An array of post fields 166 | * @param stdClass $package The package to pull server info from 167 | * @param stdClass $pterodactylEgg An object representing the Pterodacytl egg 168 | * @param stdClass $serviceFields An object representing the fields set on the current service (optional) 169 | * @return array The list of parameters 170 | */ 171 | public function editServerStartupParameters(array $vars, $package, $pterodactylEgg, $serviceFields = null) 172 | { 173 | // Gather server data 174 | return [ 175 | 'egg' => $package->meta->egg_id, 176 | 'pack' => $package->meta->pack_id, 177 | 'image' => !empty($package->meta->image) 178 | ? $package->meta->image 179 | : $pterodactylEgg->attributes->docker_image, 180 | 'startup' => !empty($package->meta->startup) 181 | ? $package->meta->startup 182 | : $pterodactylEgg->attributes->startup, 183 | 'environment' => $this->getEnvironmentVariables($vars, $package, $pterodactylEgg, $serviceFields), 184 | 'skip_scripts' => false, 185 | ]; 186 | } 187 | 188 | /** 189 | * Gets a list of environment variables to submit to Pterodactyl 190 | * 191 | * @param array $vars An array of post fields 192 | * @param stdClass $package The package to pull server info from 193 | * @param stdClass $pterodactylEgg An object representing the Pterodacytl egg 194 | * @param stdClass $serviceFields An object representing the fields set on the current service (optional) 195 | * @return array The list of environment variables and their values 196 | */ 197 | public function getEnvironmentVariables(array $vars, $package, $pterodactylEgg, $serviceFields = null) 198 | { 199 | // Get environment data from the egg 200 | $environment = []; 201 | foreach ($pterodactylEgg->attributes->relationships->variables->data as $envVariable) { 202 | $variableName = $envVariable->attributes->env_variable; 203 | $blestaVariableName = strtolower($variableName); 204 | // Set the variable value based on values submitted in the following 205 | // priority order: config option, service field, package field, Pterodactyl default 206 | if (isset($vars['configoptions']) && isset($vars['configoptions'][$blestaVariableName])) { 207 | // Use a config option 208 | $environment[$variableName] = $vars['configoptions'][$blestaVariableName]; 209 | } elseif (isset($vars[$blestaVariableName])) { 210 | // Use the service field 211 | $environment[$variableName] = $vars[$blestaVariableName]; 212 | } elseif (isset($serviceFields) && isset($serviceFields->{$blestaVariableName})) { 213 | // Reset the previously saved value 214 | $environment[$variableName] = $serviceFields->{$blestaVariableName}; 215 | } elseif (isset($package->meta->{$blestaVariableName})) { 216 | // Default to the value set on the package 217 | $environment[$variableName] = $package->meta->{$blestaVariableName}; 218 | } else { 219 | // Default to the default value from Pterodactyl 220 | $environment[$variableName] = $envVariable->attributes->default_value; 221 | } 222 | } 223 | 224 | return $environment; 225 | } 226 | 227 | /** 228 | * Returns all fields used when adding/editing a service, including any 229 | * javascript to execute when the page is rendered with these fields. 230 | * 231 | * @param stdClass $pterodactylEgg An object representing the Pterodacytl egg 232 | * @param stdClass $package The package to pull server info from 233 | * @param stdClass $vars A stdClass object representing a set of post fields (optional) 234 | * @param bool $admin Whether these fields will be displayed to an admin (optional) 235 | * @return ModuleFields A ModuleFields object, containing the fields 236 | * to render as well as any additional HTML markup to include 237 | */ 238 | public function getFields($pterodactylEgg, $package, $vars = null, $admin = false) 239 | { 240 | Loader::loadHelpers($this, ['Html']); 241 | 242 | $fields = new ModuleFields(); 243 | 244 | if ($admin) { 245 | // Set the server ID 246 | $serverId = $fields->label( 247 | Language::_('PterodactylService.service_fields.server_id', true), 248 | 'server_id' 249 | ); 250 | $serverId->attach( 251 | $fields->fieldText( 252 | 'server_id', 253 | (isset($vars->server_id) ? $vars->server_id : null), 254 | ['id' => 'server_id'] 255 | ) 256 | ); 257 | $tooltip = $fields->tooltip(Language::_('PterodactylService.service_fields.tooltip.server_id', true)); 258 | $serverId->attach($tooltip); 259 | $fields->setField($serverId); 260 | } 261 | 262 | // Set the server name 263 | $serverName = $fields->label( 264 | Language::_('PterodactylService.service_fields.server_name', true), 265 | 'server_name' 266 | ); 267 | $serverName->attach( 268 | $fields->fieldText( 269 | 'server_name', 270 | (isset($vars->server_name) ? $vars->server_name : null), 271 | ['id' => 'server_name'] 272 | ) 273 | ); 274 | $tooltip = $fields->tooltip(Language::_('PterodactylService.service_fields.tooltip.server_name', true)); 275 | $serverName->attach($tooltip); 276 | $fields->setField($serverName); 277 | 278 | // Set the server description 279 | $serverDescription = $fields->label( 280 | Language::_('PterodactylService.service_fields.server_description', true), 281 | 'server_description' 282 | ); 283 | $serverDescription->attach( 284 | $fields->fieldText( 285 | 'server_description', 286 | (isset($vars->server_description) ? $vars->server_description : null), 287 | ['id' => 'server_description'] 288 | ) 289 | ); 290 | $tooltip = $fields->tooltip(Language::_('PterodactylService.service_fields.tooltip.server_description', true)); 291 | $serverDescription->attach($tooltip); 292 | $fields->setField($serverDescription); 293 | 294 | if ($pterodactylEgg) { 295 | // Get service fields from the egg 296 | foreach ($pterodactylEgg->attributes->relationships->variables->data as $envVariable) { 297 | // Hide the field from clients unless it is marked for display on the package 298 | $key = strtolower($envVariable->attributes->env_variable); 299 | if (!$admin 300 | && (!isset($package->meta->{$key . '_display'}) || $package->meta->{$key . '_display'} != '1') 301 | ) { 302 | continue; 303 | } 304 | 305 | // Create a label for the environment variable 306 | $label = strpos($envVariable->attributes->rules, 'required') === 0 307 | ? $envVariable->attributes->name 308 | : Language::_('PterodactylService.service_fields.optional', true, $envVariable->attributes->name); 309 | $field = $fields->label($label, $key); 310 | // Create the environment variable field and attach to the label 311 | $field->attach( 312 | $fields->fieldText( 313 | $key, 314 | (isset($vars->{$key}) 315 | ? $vars->{$key} 316 | : (isset($package->meta->{$key}) 317 | ? $package->meta->{$key} 318 | : $envVariable->attributes->default_value 319 | ) 320 | ), 321 | ['id' => $key] 322 | ) 323 | ); 324 | // Add tooltip based on the description from Pterodactyl 325 | $tooltip = $fields->tooltip($envVariable->attributes->description); 326 | $field->attach($tooltip); 327 | // Set the label as a field 328 | $fields->setField($field); 329 | } 330 | } 331 | 332 | $egg_id = isset($package->configurable_options['egg_id']) ? $package->configurable_options['egg_id'] : 0; 333 | $nest_id = isset($package->configurable_options['nest_id']) ? $package->configurable_options['nest_id'] : 0; 334 | $location_id = isset($package->configurable_options['location_id']) 335 | ? $package->configurable_options['location_id'] 336 | : 0; 337 | // Set js to refetch options when the nest or egg is changed 338 | $fields->setHtml(" 339 | 354 | "); 355 | 356 | return $fields; 357 | } 358 | 359 | /** 360 | * Returns the rule set for adding/editing a service 361 | * 362 | * @param array $vars A list of input vars (optional) 363 | * @param stdClass $package A stdClass object representing the selected package (optional) 364 | * @param bool $edit True to get the edit rules, false for the add rules (optional) 365 | * @param stdClass $pterodactylEgg An egg object from Pterodactyl (optional) 366 | * @return array Service rules 367 | */ 368 | public function getServiceRules(array $vars = null, $package = null, $edit = false, $pterodactylEgg = null) 369 | { 370 | // Set rules 371 | $rules = [ 372 | 'server_name' => [ 373 | 'empty' => [ 374 | 'rule' => 'isEmpty', 375 | 'negate' => true, 376 | 'message' => Language::_('PterodactylService.!error.server_name.empty', true) 377 | ] 378 | ] 379 | ]; 380 | 381 | // Get the rule helper 382 | Loader::load(dirname(__FILE__). DS . 'pterodactyl_rule.php'); 383 | $rule_helper = new PterodactylRule(); 384 | 385 | // Get egg variable rules 386 | if ($pterodactylEgg) { 387 | foreach ($pterodactylEgg->attributes->relationships->variables->data as $envVariable) { 388 | $fieldName = strtolower($envVariable->attributes->env_variable); 389 | $rules[$fieldName] = $rule_helper->parseEggVariable($envVariable); 390 | 391 | foreach ($rules[$fieldName] as $rule) { 392 | if (array_key_exists('if_set', $rule) 393 | && $rule['if_set'] == true 394 | && empty($vars[$fieldName]) 395 | ) { 396 | unset($rules[$fieldName]); 397 | } 398 | } 399 | } 400 | } 401 | 402 | return $rules; 403 | } 404 | } 405 | -------------------------------------------------------------------------------- /pterodactyl.php: -------------------------------------------------------------------------------- 1 | loadConfig(dirname(__FILE__) . DS . 'config.json'); 32 | 33 | // Load additional config values 34 | Configure::load('pterodactyl', dirname(__FILE__) . DS . 'config' . DS); 35 | } 36 | 37 | /** 38 | * Performs migration of data from $current_version (the current installed version) 39 | * to the given file set version. Sets Input errors on failure, preventing 40 | * the module from being upgraded. 41 | * 42 | * @param string $current_version The current installed version of this module 43 | */ 44 | public function upgrade($current_version) 45 | { 46 | if (version_compare($current_version, '1.1.0', '<')) { 47 | if (!isset($this->ModuleManager)) { 48 | Loader::loadModels($this, ['ModuleManager']); 49 | } 50 | 51 | // Update all module rows to set host_name instead of panel_url 52 | $modules = $this->ModuleManager->getByClass('pterodactyl'); 53 | foreach ($modules as $module) { 54 | $rows = $this->ModuleManager->getRows($module->id); 55 | foreach ($rows as $row) { 56 | $meta = (array)$row->meta; 57 | if (isset($meta['panel_url'])) { 58 | $meta['host_name'] = $meta['panel_url']; 59 | unset($meta['panel_url']); 60 | $this->ModuleManager->editRow($row->id, $meta); 61 | } 62 | } 63 | } 64 | } 65 | } 66 | 67 | /** 68 | * Loads a library class 69 | * 70 | * @param string $command The filename of the class to load 71 | */ 72 | private function loadLib($command) { 73 | Loader::load(dirname(__FILE__) . DS . 'lib' . DS . $command . '.php'); 74 | } 75 | 76 | /** 77 | * Returns an array of available service deligation order methods. The module 78 | * will determine how each method is defined. For example, the method "first" 79 | * may be implemented such that it returns the module row with the least number 80 | * of services assigned to it. 81 | * 82 | * @return array An array of order methods in key/value paris where the key is 83 | * the type to be stored for the group and value is the name for that option 84 | * @see Module::selectModuleRow() 85 | */ 86 | public function getGroupOrderOptions() 87 | { 88 | return [ 89 | 'first' => Language::_('Pterodactyl.order_options.first', true) 90 | ]; 91 | } 92 | 93 | /** 94 | * Determines which module row should be attempted when a service is provisioned 95 | * for the given group based upon the order method set for that group. 96 | * 97 | * @return int The module row ID to attempt to add the service with 98 | * @see Module::getGroupOrderOptions() 99 | */ 100 | public function selectModuleRow($module_group_id) 101 | { 102 | if (!isset($this->ModuleManager)) { 103 | Loader::loadModels($this, ['ModuleManager']); 104 | } 105 | 106 | $group = $this->ModuleManager->getGroup($module_group_id); 107 | 108 | if ($group) { 109 | switch ($group->add_order) { 110 | default: 111 | case 'first': 112 | 113 | foreach ($group->rows as $row) { 114 | return $row->id; 115 | } 116 | 117 | break; 118 | } 119 | } 120 | return 0; 121 | } 122 | 123 | /** 124 | * Attempts to validate service info. This is the top-level error checking method. Sets Input errors on failure. 125 | * 126 | * @param stdClass $package A stdClass object representing the selected package 127 | * @param array $vars An array of user supplied info to satisfy the request (optional) 128 | * @return bool True if the service validates, false otherwise. Sets Input errors when false. 129 | */ 130 | public function validateService($package, array $vars = null) 131 | { 132 | return $this->getServiceRules($vars, $package); 133 | } 134 | 135 | /** 136 | * Attempts to validate an existing service against a set of service info updates. Sets Input errors on failure. 137 | * 138 | * @param stdClass $service A stdClass object representing the service to validate for editing 139 | * @param array $vars An array of user-supplied info to satisfy the request (optional) 140 | * @return bool True if the service update validates or false otherwise. Sets Input errors when false. 141 | */ 142 | public function validateServiceEdit($service, array $vars = null) 143 | { 144 | $package = isset($service->package) ? $service->package : null; 145 | return $this->getServiceRules($vars, $package, true); 146 | } 147 | 148 | /** 149 | * Returns the rule set for adding/editing a service 150 | * 151 | * @param array $vars A list of input vars (optional) 152 | * @param stdClass $package A stdClass object representing the selected package (optional) 153 | * @param bool $edit True to get the edit rules, false for the add rules (optional) 154 | * @return array Service rules 155 | */ 156 | private function getServiceRules(array $vars = null, $package = null, $edit = false) 157 | { 158 | // Get the service helper 159 | $this->loadLib('pterodactyl_service'); 160 | $service_helper = new PterodactylService(); 161 | 162 | if ($package) { 163 | // Get and set the module row to use for API calls 164 | if ($package->module_group) { 165 | $this->setModuleRow($this->getModuleRow($this->selectModuleRow($package->module_group))); 166 | } else { 167 | $this->setModuleRow($this->getModuleRow($package->module_row)); 168 | } 169 | 170 | // Load egg 171 | $pterodactyl_egg = $this->apiRequest( 172 | 'Nests', 173 | 'eggsGet', 174 | ['nest_id' => $package->meta->nest_id, 'egg_id' => $package->meta->egg_id] 175 | ); 176 | 177 | $errors = $this->Input->errors(); 178 | if (!empty($errors)) { 179 | $pterodactyl_egg = null; 180 | } else { 181 | // Set egg variables from service, package, or config options 182 | $egg_variables = $service_helper->getEnvironmentVariables($vars, $package, $pterodactyl_egg); 183 | $vars = array_merge($vars, array_change_key_case($egg_variables)); 184 | } 185 | } 186 | 187 | $this->Input->setRules($service_helper->getServiceRules($vars, $package, $edit, $pterodactyl_egg)); 188 | return $this->Input->validates($vars); 189 | } 190 | 191 | /** 192 | * Adds the service to the remote server. Sets Input errors on failure, 193 | * preventing the service from being added. 194 | * 195 | * @param stdClass $package A stdClass object representing the selected package 196 | * @param array $vars An array of user supplied info to satisfy the request (optional) 197 | * @param stdClass $parent_package A stdClass object representing the parent 198 | * service's selected package (if the current service is an addon service) (optional) 199 | * @param stdClass $parent_service A stdClass object representing the parent 200 | * service of the service being added (if the current service is an addon service 201 | * service and parent service has already been provisioned) (optional) 202 | * @param string $status The status of the service being added. (optional) These include: 203 | * 204 | * - active 205 | * - canceled 206 | * - pending 207 | * - suspended 208 | * @return array A numerically indexed array of meta fields to be stored for this service containing: 209 | * 210 | * - key The key for this meta field 211 | * - value The value for this key 212 | * - encrypted Whether or not this field should be encrypted (default 0, not encrypted) 213 | * @see Module::getModule() 214 | * @see Module::getModuleRow() 215 | */ 216 | public function addService( 217 | $package, 218 | array $vars = null, 219 | $parent_package = null, 220 | $parent_service = null, 221 | $status = 'pending' 222 | ) { 223 | Loader::loadModels($this, ['ModuleClientMeta', 'Clients']); 224 | 225 | $meta = []; 226 | 227 | // Set configurable options 228 | $package = $this->getConfigurableOptions($vars, $package); 229 | 230 | // Load egg 231 | $pterodactyl_egg = $this->apiRequest( 232 | 'Nests', 233 | 'eggsGet', 234 | ['nest_id' => $package->meta->nest_id, 'egg_id' => $package->meta->egg_id] 235 | ); 236 | if ($this->Input->errors()) { 237 | return; 238 | } 239 | 240 | $this->validateService($package, $vars); 241 | if ($this->Input->errors()) { 242 | return; 243 | } 244 | 245 | // Get the service helper 246 | $this->loadLib('pterodactyl_service'); 247 | $service_helper = new PterodactylService(); 248 | if ($vars['use_module'] == 'true') { 249 | // Load module 250 | $module = $this->getModule(); 251 | 252 | // Fetch client 253 | $client = $this->Clients->get($vars['client_id'] ?? null); 254 | 255 | // Check if a user already exists with the client's email 256 | $pterodactyl_user = $this->apiRequest('Users', 'getByEmail', [$client->email]); 257 | $pterodactyl_user = !empty($pterodactyl_user->data) ? reset($pterodactyl_user->data) : null; 258 | 259 | if ($this->Input->errors()) { 260 | $this->Input->setErrors([]); 261 | } 262 | 263 | // Check if a user already exists with the current external id 264 | if (empty($pterodactyl_user)) { 265 | $pterodactyl_user = $this->apiRequest('Users', 'getByExternalID', ['bl-' . $vars['client_id']]); 266 | } 267 | 268 | if ($this->Input->errors()) { 269 | $this->Input->setErrors([]); 270 | } 271 | 272 | // If an account does not exists, create a new one and keep track of the credentials 273 | if (empty($pterodactyl_user)) { 274 | $addParameters = $service_helper->addUserParameters($vars); 275 | $pterodactyl_user = $this->apiRequest('Users', 'add', [$addParameters]); 276 | 277 | if ($this->Input->errors()) { 278 | return; 279 | } 280 | 281 | // Keep track of the username and password used for this client 282 | $this->ModuleClientMeta->set( 283 | $vars['client_id'], 284 | $module->id, 285 | 0, 286 | [ 287 | ['key' => 'pterodactyl_username', 'value' => $addParameters['username'], 'encrypted' => 0], 288 | ['key' => 'pterodactyl_password', 'value' => $addParameters['password'], 'encrypted' => 1] 289 | ] 290 | ); 291 | } 292 | 293 | // Get Pterodactyl credentials 294 | $server_username = $this->ModuleClientMeta->get( 295 | $vars['client_id'], 296 | 'pterodactyl_username', 297 | $module->id 298 | ); 299 | $server_password = $this->ModuleClientMeta->get( 300 | $vars['client_id'], 301 | 'pterodactyl_password', 302 | $module->id 303 | ); 304 | 305 | $vars['server_username'] = isset($server_username->value) ? $server_username->value : null; 306 | $vars['server_password'] = isset($server_password->value) ? $server_password->value : null; 307 | 308 | // Create server 309 | $pterodactyl_server = $this->apiRequest( 310 | 'Servers', 311 | 'add', 312 | [$service_helper->addServerParameters($vars, $package, $pterodactyl_user, $pterodactyl_egg)] 313 | ); 314 | if ($this->Input->errors()) { 315 | // No need to roll back user creation, we'll just use that user for future requests 316 | return; 317 | } 318 | 319 | $meta['server_id'] = $pterodactyl_server->attributes->id; 320 | $meta['external_id'] = $pterodactyl_server->attributes->external_id; 321 | if (isset($pterodactyl_server->attributes->relationships) 322 | && isset($pterodactyl_server->attributes->relationships->allocations) 323 | && isset($pterodactyl_server->attributes->relationships->allocations->data[0]) 324 | ) { 325 | $allocation = $pterodactyl_server->attributes->relationships->allocations->data[0]; 326 | $meta['server_ip'] = isset($allocation->attributes->ip) ? $allocation->attributes->ip : null; 327 | $meta['server_port'] = isset($allocation->attributes->port) ? $allocation->attributes->port : null; 328 | } 329 | } 330 | 331 | $return = [ 332 | [ 333 | 'key' => 'server_id', 334 | 'value' => isset($meta['server_id']) 335 | ? $meta['server_id'] : 336 | (isset($vars['server_id']) ? $vars['server_id'] : null), 337 | 'encrypted' => 0 338 | ], 339 | [ 340 | 'key' => 'external_id', 341 | 'value' => !empty($meta['external_id']) 342 | ? $meta['external_id'] 343 | : (isset($vars['external_id']) ? $vars['external_id'] : null), 344 | 'encrypted' => 0 345 | ], 346 | [ 347 | 'key' => 'server_ip', 348 | 'value' => isset($meta['server_ip']) 349 | ? $meta['server_ip'] : 350 | (isset($vars['server_ip']) ? $vars['server_ip'] : null), 351 | 'encrypted' => 0 352 | ], 353 | [ 354 | 'key' => 'server_port', 355 | 'value' => isset($meta['server_port']) 356 | ? $meta['server_port'] : 357 | (isset($vars['server_port']) ? $vars['server_port'] : null), 358 | 'encrypted' => 0 359 | ], 360 | [ 361 | 'key' => 'server_name', 362 | 'value' => isset($vars['server_name']) ? $vars['server_name'] : '', 363 | 'encrypted' => 0 364 | ], 365 | [ 366 | 'key' => 'server_description', 367 | 'value' => isset($vars['server_description']) ? $vars['server_description'] : '', 368 | 'encrypted' => 0 369 | ], 370 | [ 371 | 'key' => 'server_username', 372 | 'value' => isset($vars['server_username']) ? $vars['server_username'] : '', 373 | 'encrypted' => 0 374 | ], 375 | [ 376 | 'key' => 'server_password', 377 | 'value' => isset($vars['server_password']) ? $vars['server_password'] : '', 378 | 'encrypted' => 1 379 | ], 380 | ]; 381 | 382 | $environment_variables = $service_helper->getEnvironmentVariables($vars, $package, $pterodactyl_egg); 383 | foreach ($environment_variables as $environment_variable => $value) { 384 | $return[] = [ 385 | 'key' => strtolower($environment_variable), 386 | 'value' => $value, 387 | 'encrypted' => 0 388 | ]; 389 | } 390 | 391 | return $return; 392 | } 393 | 394 | /** 395 | * Edits the service on the remote server. Sets Input errors on failure, 396 | * preventing the service from being edited. 397 | * 398 | * @param stdClass $package A stdClass object representing the current package 399 | * @param stdClass $service A stdClass object representing the current service 400 | * @param array $vars An array of user supplied info to satisfy the request (optional) 401 | * @param stdClass $parent_package A stdClass object representing the parent 402 | * service's selected package (if the current service is an addon service) (optional) 403 | * @param stdClass $parent_service A stdClass object representing the parent 404 | * service of the service being edited (if the current service is an addon service) (optional) 405 | * @return array A numerically indexed array of meta fields to be stored for this service containing: 406 | * 407 | * - key The key for this meta field 408 | * - value The value for this key 409 | * - encrypted Whether or not this field should be encrypted (default 0, not encrypted) 410 | * @see Module::getModule() 411 | * @see Module::getModuleRow() 412 | */ 413 | public function editService( 414 | $package, 415 | $service, 416 | array $vars = null, 417 | $parent_package = null, 418 | $parent_service = null 419 | ) { 420 | $service_fields = $this->serviceFieldsToObject($service->fields); 421 | 422 | $this->validateServiceEdit($service, $vars); 423 | if ($this->Input->errors()) { 424 | return; 425 | } 426 | 427 | // Get the service helper 428 | $this->loadLib('pterodactyl_service'); 429 | $service_helper = new PterodactylService(); 430 | 431 | // Set configurable options 432 | $package = $this->getConfigurableOptions($vars, $package); 433 | 434 | // Load egg 435 | $pterodactyl_egg = $this->apiRequest( 436 | 'Nests', 437 | 'eggsGet', 438 | ['nest_id' => $package->meta->nest_id, 'egg_id' => $package->meta->egg_id] 439 | ); 440 | if ($this->Input->errors()) { 441 | return; 442 | } 443 | 444 | if ($vars['use_module'] == 'true') { 445 | // Load user account 446 | $pterodactyl_user = $this->apiRequest('Users', 'getByExternalID', ['bl-' . $service->client_id]); 447 | 448 | // Load the server 449 | $pterodactyl_server = $this->getServer($service); 450 | 451 | if ($this->Input->errors()) { 452 | return; 453 | } 454 | 455 | // Edit server details 456 | $vars['service_id'] = $service->id; 457 | $vars['client_id'] = $service->client_id; 458 | $pterodactyl_server_edited = $this->apiRequest( 459 | 'Servers', 460 | 'editDetails', 461 | [$pterodactyl_server->attributes->id, $service_helper->editServerParameters($vars, $pterodactyl_user)] 462 | ); 463 | if ($this->Input->errors()) { 464 | return; 465 | } 466 | 467 | // Set service fields 468 | $vars['server_id'] = $pterodactyl_server_edited->attributes->id; 469 | $vars['external_id'] = $pterodactyl_server_edited->attributes->external_id; 470 | if (isset($pterodactyl_server_edited->attributes->relationships) 471 | && isset($pterodactyl_server_edited->attributes->relationships->allocations) 472 | && isset($pterodactyl_server_edited->attributes->relationships->allocations->data[0]) 473 | ) { 474 | $allocation = $pterodactyl_server_edited->attributes->relationships->allocations->data[0]; 475 | $vars['server_ip'] = isset($allocation->attributes->ip) ? $allocation->attributes->ip : null; 476 | $vars['server_port'] = isset($allocation->attributes->port) ? $allocation->attributes->port : null; 477 | } 478 | 479 | // Set package fields 480 | $vars['allocation'] = $pterodactyl_server->attributes->allocation; 481 | $this->apiRequest( 482 | 'Servers', 483 | 'editBuild', 484 | [$pterodactyl_server->attributes->id, $service_helper->editServerBuildParameters($vars, $package)] 485 | ); 486 | 487 | // Edit startup parameters 488 | $this->apiRequest( 489 | 'Servers', 490 | 'editStartup', 491 | [ 492 | $pterodactyl_server->attributes->id, 493 | $service_helper->editServerStartupParameters($vars, $package, $pterodactyl_egg, $service_fields) 494 | ] 495 | ); 496 | if ($this->Input->errors()) { 497 | return; 498 | } 499 | } 500 | 501 | $return = [ 502 | [ 503 | 'key' => 'server_id', 504 | 'value' => !empty($vars['server_id']) ? $vars['server_id'] : $service_fields->server_id, 505 | 'encrypted' => 0 506 | ], 507 | [ 508 | 'key' => 'external_id', 509 | 'value' => !empty($vars['external_id']) 510 | ? $vars['external_id'] 511 | : (isset($service_fields->external_id) ? $service_fields->external_id : null), 512 | 'encrypted' => 0 513 | ], 514 | [ 515 | 'key' => 'server_ip', 516 | 'value' => !empty($vars['server_ip']) ? $vars['server_ip'] : $service_fields->server_ip, 517 | 'encrypted' => 0 518 | ], 519 | [ 520 | 'key' => 'server_port', 521 | 'value' => !empty($vars['server_port']) ? $vars['server_port'] : $service_fields->server_port, 522 | 'encrypted' => 0 523 | ], 524 | [ 525 | 'key' => 'server_name', 526 | 'value' => isset($vars['server_name']) ? $vars['server_name'] : $service_fields->server_name, 527 | 'encrypted' => 0 528 | ], 529 | [ 530 | 'key' => 'server_description', 531 | 'value' => isset($vars['server_description']) 532 | ? $vars['server_description'] 533 | : $service_fields->server_description, 534 | 'encrypted' => 0 535 | ], 536 | [ 537 | 'key' => 'server_username', 538 | 'value' => isset($vars['server_username']) ? $vars['server_username'] : $service_fields->server_username, 539 | 'encrypted' => 0 540 | ], 541 | [ 542 | 'key' => 'server_password', 543 | 'value' => isset($vars['server_password']) ? $vars['server_password'] : $service_fields->server_password, 544 | 'encrypted' => 1 545 | ], 546 | ]; 547 | 548 | // Add egg variables 549 | $environment_variables = $service_helper->getEnvironmentVariables( 550 | $vars, 551 | $package, 552 | $pterodactyl_egg, 553 | $service_fields 554 | ); 555 | foreach ($environment_variables as $environment_variable => $value) { 556 | $return[] = [ 557 | 'key' => strtolower($environment_variable), 558 | 'value' => $value, 559 | 'encrypted' => 0 560 | ]; 561 | } 562 | 563 | return $return; 564 | } 565 | 566 | /** 567 | * Cancels the service on the remote server. Sets Input errors on failure, 568 | * preventing the service from being canceled. 569 | * 570 | * @param stdClass $package A stdClass object representing the current package 571 | * @param stdClass $service A stdClass object representing the current service 572 | * @param stdClass $parent_package A stdClass object representing the parent 573 | * service's selected package (if the current service is an addon service) (optional) 574 | * @param stdClass $parent_service A stdClass object representing the parent 575 | * service of the service being canceled (if the current service is an addon service) (optional) 576 | * @return mixed null to maintain the existing meta fields or a numerically 577 | * indexed array of meta fields to be stored for this service containing: 578 | * 579 | * - key The key for this meta field 580 | * - value The value for this key 581 | * - encrypted Whether or not this field should be encrypted (default 0, not encrypted) 582 | * @see Module::getModule() 583 | * @see Module::getModuleRow() 584 | */ 585 | public function cancelService($package, $service, $parent_package = null, $parent_service = null) 586 | { 587 | // Load the server 588 | $pterodactyl_server = $this->getServer($service); 589 | 590 | // Delete the server 591 | $this->apiRequest( 592 | 'Servers', 593 | 'delete', 594 | ['server_id' => $pterodactyl_server ? $pterodactyl_server->attributes->id : null] 595 | ); 596 | 597 | // We do not delete the user, but rather leave it around to be used for any current or future services 598 | 599 | return null; 600 | } 601 | 602 | /** 603 | * Suspends the service on the remote server. Sets Input errors on failure, 604 | * preventing the service from being suspended. 605 | * 606 | * @param stdClass $package A stdClass object representing the current package 607 | * @param stdClass $service A stdClass object representing the current service 608 | * @param stdClass $parent_package A stdClass object representing the parent 609 | * service's selected package (if the current service is an addon service) 610 | * @param stdClass $parent_service A stdClass object representing the parent 611 | * service of the service being suspended (if the current service is an addon service) 612 | * @return mixed null to maintain the existing meta fields or a numerically 613 | * indexed array of meta fields to be stored for this service containing: 614 | * - key The key for this meta field 615 | * - value The value for this key 616 | * - encrypted Whether or not this field should be encrypted (default 0, not encrypted) 617 | * @see Module::getModule() 618 | * @see Module::getModuleRow() 619 | */ 620 | public function suspendService($package, $service, $parent_package = null, $parent_service = null) 621 | { 622 | // Load the server 623 | $pterodactyl_server = $this->getServer($service); 624 | 625 | // Suspend the server 626 | $this->apiRequest( 627 | 'Servers', 628 | 'suspend', 629 | ['server_id' => $pterodactyl_server ? $pterodactyl_server->attributes->id : null] 630 | ); 631 | 632 | return null; 633 | } 634 | 635 | /** 636 | * Unsuspends the service on the remote server. Sets Input errors on failure, 637 | * preventing the service from being unsuspended. 638 | * 639 | * @param stdClass $package A stdClass object representing the current package 640 | * @param stdClass $service A stdClass object representing the current service 641 | * @param stdClass $parent_package A stdClass object representing the parent 642 | * service's selected package (if the current service is an addon service) 643 | * @param stdClass $parent_service A stdClass object representing the parent 644 | * service of the service being unsuspended (if the current service is an addon service) 645 | * @return mixed null to maintain the existing meta fields or a numerically 646 | * indexed array of meta fields to be stored for this service containing: 647 | * - key The key for this meta field 648 | * - value The value for this key 649 | * - encrypted Whether or not this field should be encrypted (default 0, not encrypted) 650 | * @see Module::getModule() 651 | * @see Module::getModuleRow() 652 | */ 653 | public function unsuspendService($package, $service, $parent_package = null, $parent_service = null) 654 | { 655 | // Load the server 656 | $pterodactyl_server = $this->getServer($service); 657 | 658 | // Unsuspend the server 659 | $this->apiRequest( 660 | 'Servers', 661 | 'unsuspend', 662 | ['server_id' => $pterodactyl_server ? $pterodactyl_server->attributes->id : null] 663 | ); 664 | 665 | return null; 666 | } 667 | 668 | /** 669 | * Gets a Pterodactyl server for the given service 670 | * 671 | * @param stdClass $service A stdClass object representing the current service 672 | * @return PterodactylResponse An object representing the Pterodactyl server 673 | */ 674 | private function getServer($service) 675 | { 676 | $service_fields = $this->serviceFieldsToObject($service->fields); 677 | 678 | // Load server 679 | if (!empty($service_fields->server_id)) { 680 | $pterodactyl_server = $this->apiRequest('Servers', 'get', [$service_fields->server_id]); 681 | } else { 682 | $pterodactyl_server = $this->apiRequest( 683 | 'Servers', 684 | 'getByExternalID', 685 | [!empty($service_fields->external_id) ? $service_fields->external_id : null] 686 | ); 687 | } 688 | 689 | return $pterodactyl_server; 690 | } 691 | 692 | /** 693 | * Fetches the HTML content to display when viewing the service info in the 694 | * admin interface. 695 | * 696 | * @param stdClass $service A stdClass object representing the service 697 | * @param stdClass $package A stdClass object representing the service's package 698 | * @return string HTML content containing information to display when viewing the service info 699 | */ 700 | public function getAdminServiceInfo($service, $package) 701 | { 702 | Loader::loadModels($this, ['ModuleClientMeta']); 703 | $row = $this->getModuleRow(); 704 | 705 | // Load the view into this object, so helpers can be automatically added to the view 706 | $this->view = new View('admin_service_info', 'default'); 707 | $this->view->base_uri = $this->base_uri; 708 | $this->view->setDefaultView('components' . DS . 'modules' . DS . 'pterodactyl' . DS); 709 | 710 | // Load the helpers required for this view 711 | Loader::loadHelpers($this, ['Form', 'Html']); 712 | 713 | // Get username and password for the account 714 | $module = $this->getModule(); 715 | $username = $this->ModuleClientMeta->get($service->client_id, 'pterodactyl_username', $module->id); 716 | $password = $this->ModuleClientMeta->get($service->client_id, 'pterodactyl_password', $module->id); 717 | 718 | // Set view data 719 | $this->view->set('module_row', $row); 720 | $this->view->set('username', $username ? $username->value : ''); 721 | $this->view->set('password', $password ? $password->value : ''); 722 | 723 | return $this->view->fetch(); 724 | } 725 | 726 | /** 727 | * Fetches the HTML content to display when viewing the service info in the 728 | * client interface. 729 | * 730 | * @param stdClass $service A stdClass object representing the service 731 | * @param stdClass $package A stdClass object representing the service's package 732 | * @return string HTML content containing information to display when viewing the service info 733 | */ 734 | public function getClientServiceInfo($service, $package) 735 | { 736 | Loader::loadModels($this, ['ModuleClientMeta']); 737 | $row = $this->getModuleRow(); 738 | 739 | // Load the view into this object, so helpers can be automatically added to the view 740 | $this->view = new View('client_service_info', 'default'); 741 | $this->view->base_uri = $this->base_uri; 742 | $this->view->setDefaultView('components' . DS . 'modules' . DS . 'pterodactyl' . DS); 743 | 744 | // Load the helpers required for this view 745 | Loader::loadHelpers($this, ['Form', 'Html']); 746 | 747 | // Get username and password for the account 748 | $module = $this->getModule(); 749 | $username = $this->ModuleClientMeta->get($service->client_id, 'pterodactyl_username', $module->id); 750 | $password = $this->ModuleClientMeta->get($service->client_id, 'pterodactyl_password', $module->id); 751 | 752 | // Set view data 753 | $this->view->set('module_row', $row); 754 | $this->view->set('username', $username ? $username->value : ''); 755 | $this->view->set('password', $password ? $password->value : ''); 756 | 757 | return $this->view->fetch(); 758 | } 759 | 760 | /** 761 | * Returns all tabs to display to an admin when managing a service whose 762 | * package uses this module 763 | * 764 | * @param stdClass $package A stdClass object representing the selected package 765 | * @return array An array of tabs in the format of method => title. 766 | * Example: array('methodName' => "Title", 'methodName2' => "Title2") 767 | */ 768 | public function getAdminTabs($package) 769 | { 770 | return [ 771 | 'tabActions' => Language::_('Pterodactyl.tab_actions', true) 772 | ]; 773 | } 774 | 775 | /** 776 | * Returns all tabs to display to a client when managing a service whose 777 | * package uses this module 778 | * 779 | * @param stdClass $package A stdClass object representing the selected package 780 | * @return array An array of tabs in the format of method => title. 781 | * Example: array('methodName' => "Title", 'methodName2' => "Title2") 782 | */ 783 | public function getClientTabs($package) 784 | { 785 | return [ 786 | 'tabClientActions' => Language::_('Pterodactyl.tab_client_actions', true) 787 | ]; 788 | } 789 | 790 | /** 791 | * Actions tab (start, stop, restart) 792 | * 793 | * @param stdClass $package A stdClass object representing the current package 794 | * @param stdClass $service A stdClass object representing the current service 795 | * @param array $get Any GET parameters 796 | * @param array $post Any POST parameters 797 | * @param array $files Any FILES parameters 798 | * @return string The string representing the contents of this tab 799 | */ 800 | public function tabActions($package, $service, array $get = null, array $post = null, array $files = null) 801 | { 802 | $this->view = new View('tab_actions', 'default'); 803 | 804 | return $this->actionsTab($package, $service, false, $get, $post); 805 | } 806 | 807 | /** 808 | * Client Actions tab (start, stop, restart) 809 | * 810 | * @param stdClass $package A stdClass object representing the current package 811 | * @param stdClass $service A stdClass object representing the current service 812 | * @param array $get Any GET parameters 813 | * @param array $post Any POST parameters 814 | * @param array $files Any FILES parameters 815 | * @return string The string representing the contents of this tab 816 | */ 817 | public function tabClientActions($package, $service, array $get = null, array $post = null, array $files = null) 818 | { 819 | $this->view = new View('tab_client_actions', 'default'); 820 | 821 | return $this->actionsTab($package, $service, true, $get, $post); 822 | } 823 | /** 824 | * Handles data for the actions tab in the client and admin interfaces 825 | * @see Pterodactyl::tabActions() and Pterodactyl::tabClientActions() 826 | * 827 | * @param stdClass $package A stdClass object representing the current package 828 | * @param stdClass $service A stdClass object representing the current service 829 | * @param bool $client True if the action is being performed by the client, false otherwise 830 | * @param array $get Any GET parameters 831 | * @param array $post Any POST parameters 832 | * @param array $files Any FILES parameters 833 | */ 834 | private function actionsTab($package, $service, $client = false, array $get = null, array $post = null) 835 | { 836 | $this->view->base_uri = $this->base_uri; 837 | // Load the helpers required for this view 838 | Loader::loadHelpers($this, ['Form', 'Html']); 839 | 840 | // Get the service fields 841 | $service_fields = $this->serviceFieldsToObject($service->fields); 842 | 843 | // Get server information from the application API 844 | $server = $this->getServer($service); 845 | $server_id = isset($server->attributes->identifier) ? $server->attributes->identifier : null; 846 | 847 | // Get the service fields 848 | $get_key = '3'; 849 | if ($client) { 850 | $get_key = '2'; 851 | } 852 | 853 | // Perform actions 854 | if (array_key_exists($get_key, (array)$get) 855 | && in_array($get[$get_key], ['start', 'stop', 'restart']) 856 | && isset($server->attributes->identifier) 857 | ) { 858 | // Send a power signal 859 | $signal_response = $this->apiRequest( 860 | 'Client', 861 | 'serverPowerSignal', 862 | [$server->attributes->identifier, $get[$get_key]], 863 | true 864 | ); 865 | $errors = $this->Input->errors(); 866 | if (empty($errors)) { 867 | $this->setMessage('success', Language::_('Pterodactyl.!success.' . $get[$get_key], true)); 868 | } 869 | } 870 | 871 | // Fetch the server status from the account API 872 | $this->view->set('server', $this->apiRequest('Client', 'getServerUtilization', [$server_id], true)); 873 | 874 | $this->view->set('client_id', $service->client_id); 875 | $this->view->set('service_id', $service->id); 876 | 877 | $this->view->set('view', $this->view->view); 878 | $this->view->setDefaultView('components' . DS . 'modules' . DS . 'pterodactyl' . DS); 879 | 880 | return $this->view->fetch(); 881 | } 882 | 883 | /** 884 | * Returns an array of package fields, overriding the configurable options 885 | * 886 | * @param array $vars An array of key/value input pairs 887 | * @param stdClass $package A stdClass object representing the package for the service 888 | * @return stdClass The modified package object 889 | */ 890 | private function getConfigurableOptions(array $vars, $package) 891 | { 892 | $fields = [ 893 | 'location_id', 'egg_id', 'nest_id', 'port_range', 894 | 'pack_id', 'memory', 'swap', 'cpu', 'disk', 'io', 895 | 'startup', 'image', 'databases', 'allocations', 'backups' 896 | ]; 897 | 898 | // Override package fields, if an equivalent configurable option exists 899 | if (!empty($vars['configoptions'])) { 900 | foreach ($vars['configoptions'] as $field => $value) { 901 | if (in_array($field, $fields)) { 902 | $package->meta->{$field} = $value; 903 | } 904 | } 905 | } 906 | 907 | return $package; 908 | } 909 | 910 | /** 911 | * Runs a particaluar API requestor method, logs, and reports errors 912 | * 913 | * @param string $requestor The name of the requestor class to use 914 | * @param string $action The name of the requestor method to use 915 | * @param array $data The parameters to submit to the method (optional) 916 | * @return mixed The response from Pterodactyl 917 | */ 918 | private function apiRequest($requestor, $action, array $data = [], $client_api = false) 919 | { 920 | // Fetch the module row 921 | $row = $this->getModuleRow(); 922 | if (!$row) { 923 | $this->Input->setErrors( 924 | ['module_row' => ['missing' => Language::_('Pterodactyl.!error.module_row.missing', true)]] 925 | ); 926 | return; 927 | } 928 | 929 | // Fetch the API 930 | $api = $this->getApi( 931 | $row->meta->host_name, 932 | $client_api ? $row->meta->account_api_key : $row->meta->application_api_key, 933 | $row->meta->use_ssl == 'true' 934 | ); 935 | 936 | // Perform the request 937 | try { 938 | $response = call_user_func_array([$api->{$requestor}, $action], $data); 939 | } catch (Throwable $e) { 940 | // Try executing the request again, without array keys 941 | $response = call_user_func_array([$api->{$requestor}, $action], array_values($data)); 942 | } 943 | 944 | $errors = $response->errors(); 945 | $this->log($requestor . '.' . $action, json_encode($data), 'input', true); 946 | $this->log($requestor . '.' . $action, $response->raw(), 'output', empty($errors)); 947 | 948 | // Check for request errors 949 | if (!empty($errors)) { 950 | $this->Input->setErrors([$requestor => $errors]); 951 | return; 952 | } 953 | 954 | return $response->response(); 955 | } 956 | 957 | /** 958 | * Validates input data when attempting to add a package, returns the meta 959 | * data to save when adding a package. Performs any action required to add 960 | * the package on the remote server. Sets Input errors on failure, 961 | * preventing the package from being added. 962 | * 963 | * @param array $vars An array of key/value pairs used to add the package (optional) 964 | * @return array A numerically indexed array of meta fields to be stored for this package containing: 965 | * 966 | * - key The key for this meta field 967 | * - value The value for this key 968 | * - encrypted Whether or not this field should be encrypted (default 0, not encrypted) 969 | * @see Module::getModule() 970 | * @see Module::getModuleRow() 971 | */ 972 | public function addPackage(array $vars = null) 973 | { 974 | // Load the package helper 975 | $this->loadLib('pterodactyl_package'); 976 | $package_helper = new PterodactylPackage(); 977 | 978 | // Get package field lists from API 979 | $package_lists = $this->getPackageLists((object)$vars); 980 | 981 | // Validate and gather information using the package helper 982 | $meta = $package_helper->add($package_lists, $vars); 983 | if ($package_helper->errors()) { 984 | $this->Input->setErrors($package_helper->errors()); 985 | } 986 | 987 | return $meta; 988 | } 989 | 990 | /** 991 | * Validates input data when attempting to edit a package, returns the meta 992 | * data to save when editing a package. Performs any action required to edit 993 | * the package on the remote server. Sets Input errors on failure, 994 | * preventing the package from being edited. 995 | * 996 | * @param stdClass $package A stdClass object representing the selected package 997 | * @param array $vars An array of key/value pairs used to edit the package (optional) 998 | * @return array A numerically indexed array of meta fields to be stored for this package containing: 999 | * 1000 | * - key The key for this meta field 1001 | * - value The value for this key 1002 | * - encrypted Whether or not this field should be encrypted (default 0, not encrypted) 1003 | * @see Module::getModule() 1004 | * @see Module::getModuleRow() 1005 | */ 1006 | public function editPackage($package, array $vars = null) 1007 | { 1008 | // Adding and editing are the same 1009 | return $this->addPackage($vars); 1010 | } 1011 | 1012 | /** 1013 | * Returns the rendered view of the manage module page 1014 | * 1015 | * @param mixed $module A stdClass object representing the module and its rows 1016 | * @param array $vars An array of post data submitted to or on the manage module 1017 | * page (used to repopulate fields after an error) 1018 | * @return string HTML content containing information to display when viewing the manager module page 1019 | */ 1020 | public function manageModule($module, array &$vars) 1021 | { 1022 | // Load the view into this object, so helpers can be automatically added to the view 1023 | $this->view = new View('manage', 'default'); 1024 | $this->view->base_uri = $this->base_uri; 1025 | $this->view->setDefaultView('components' . DS . 'modules' . DS . 'pterodactyl' . DS); 1026 | 1027 | // Load the helpers required for this view 1028 | Loader::loadHelpers($this, ['Form', 'Html', 'Widget']); 1029 | 1030 | $this->view->set('module', $module); 1031 | 1032 | return $this->view->fetch(); 1033 | } 1034 | 1035 | /** 1036 | * Returns the rendered view of the add module row page 1037 | * 1038 | * @param array $vars An array of post data submitted to or on the add module 1039 | * row page (used to repopulate fields after an error) 1040 | * @return string HTML content containing information to display when viewing the add module row page 1041 | */ 1042 | public function manageAddRow(array &$vars) 1043 | { 1044 | // Load the view into this object, so helpers can be automatically added to the view 1045 | $this->view = new View('add_row', 'default'); 1046 | $this->view->base_uri = $this->base_uri; 1047 | $this->view->setDefaultView('components' . DS . 'modules' . DS . 'pterodactyl' . DS); 1048 | 1049 | // Load the helpers required for this view 1050 | Loader::loadHelpers($this, ['Form', 'Html', 'Widget']); 1051 | 1052 | // Set unspecified checkboxes 1053 | if (!empty($vars)) { 1054 | if (empty($vars['use_ssl'])) { 1055 | $vars['use_ssl'] = 'false'; 1056 | } 1057 | } 1058 | 1059 | $this->view->set('vars', (object)$vars); 1060 | return $this->view->fetch(); 1061 | } 1062 | 1063 | /** 1064 | * Returns the rendered view of the edit module row page 1065 | * 1066 | * @param stdClass $module_row The stdClass representation of the existing module row 1067 | * @param array $vars An array of post data submitted to or on the edit module 1068 | * row page (used to repopulate fields after an error) 1069 | * @return string HTML content containing information to display when viewing the edit module row page 1070 | */ 1071 | public function manageEditRow($module_row, array &$vars) 1072 | { 1073 | // Load the view into this object, so helpers can be automatically added to the view 1074 | $this->view = new View('edit_row', 'default'); 1075 | $this->view->base_uri = $this->base_uri; 1076 | $this->view->setDefaultView('components' . DS . 'modules' . DS . 'pterodactyl' . DS); 1077 | 1078 | // Load the helpers required for this view 1079 | Loader::loadHelpers($this, ['Form', 'Html', 'Widget']); 1080 | 1081 | if (empty($vars)) { 1082 | $vars = $module_row->meta; 1083 | } else { 1084 | // Set unspecified checkboxes 1085 | if (empty($vars['use_ssl'])) { 1086 | $vars['use_ssl'] = 'false'; 1087 | } 1088 | } 1089 | 1090 | $this->view->set('vars', (object)$vars); 1091 | return $this->view->fetch(); 1092 | } 1093 | 1094 | /** 1095 | * Adds the module row on the remote server. Sets Input errors on failure, 1096 | * preventing the row from being added. 1097 | * 1098 | * @param array $vars An array of module info to add 1099 | * @return array A numerically indexed array of meta fields for the module row containing: 1100 | * 1101 | * - key The key for this meta field 1102 | * - value The value for this key 1103 | * - encrypted Whether or not this field should be encrypted (default 0, not encrypted) 1104 | */ 1105 | public function addModuleRow(array &$vars) 1106 | { 1107 | $meta_fields = ['server_name', 'host_name', 'account_api_key', 'application_api_key', 'use_ssl']; 1108 | $encrypted_fields = ['account_api_key', 'application_api_key']; 1109 | 1110 | // Set unspecified checkboxes 1111 | if (empty($vars['use_ssl'])) { 1112 | $vars['use_ssl'] = 'false'; 1113 | } 1114 | 1115 | $this->Input->setRules($this->getRowRules($vars)); 1116 | 1117 | // Validate module row 1118 | if ($this->Input->validates($vars)) { 1119 | // Build the meta data for this row 1120 | $meta = []; 1121 | foreach ($vars as $key => $value) { 1122 | if (in_array($key, $meta_fields)) { 1123 | $meta[] = [ 1124 | 'key' => $key, 1125 | 'value' => $value, 1126 | 'encrypted' => in_array($key, $encrypted_fields) ? 1 : 0 1127 | ]; 1128 | } 1129 | } 1130 | 1131 | return $meta; 1132 | } 1133 | } 1134 | 1135 | /** 1136 | * Edits the module row on the remote server. Sets Input errors on failure, 1137 | * preventing the row from being updated. 1138 | * 1139 | * @param stdClass $module_row The stdClass representation of the existing module row 1140 | * @param array $vars An array of module info to update 1141 | * @return array A numerically indexed array of meta fields for the module row containing: 1142 | * 1143 | * - key The key for this meta field 1144 | * - value The value for this key 1145 | * - encrypted Whether or not this field should be encrypted (default 0, not encrypted) 1146 | */ 1147 | public function editModuleRow($module_row, array &$vars) 1148 | { 1149 | // Same as adding 1150 | return $this->addModuleRow($vars); 1151 | } 1152 | 1153 | /** 1154 | * Returns all fields used when adding/editing a package, including any 1155 | * javascript to execute when the page is rendered with these fields. 1156 | * 1157 | * @param $vars stdClass A stdClass object representing a set of post fields (optional) 1158 | * @return ModuleFields A ModuleFields object, containing the fields to render 1159 | * as well as any additional HTML markup to include 1160 | */ 1161 | public function getPackageFields($vars = null) 1162 | { 1163 | // Fetch the package fields 1164 | $this->loadLib('pterodactyl_package'); 1165 | $package = new PterodactylPackage(); 1166 | 1167 | $package_lists = $this->getPackageLists($vars); 1168 | 1169 | return $package->getFields($package_lists, $vars); 1170 | } 1171 | 1172 | /** 1173 | * Get package field lists from API 1174 | * 1175 | * @param $vars stdClass A stdClass object representing a set of post fields 1176 | */ 1177 | public function getPackageLists($vars) 1178 | { 1179 | // Fetch all packages available for the given server or server group 1180 | $row = null; 1181 | if (!isset($vars->module_group) || $vars->module_group == '') { 1182 | if (isset($vars->module_row) && $vars->module_row > 0) { 1183 | $row = $this->getModuleRow($vars->module_row); 1184 | } else { 1185 | $rows = $this->getModuleRows(); 1186 | if (isset($rows[0])) { 1187 | $row = $rows[0]; 1188 | } 1189 | unset($rows); 1190 | } 1191 | } elseif (isset($vars->module_group)) { 1192 | // Fetch the 1st server from the list of servers in the selected group 1193 | $rows = $this->getModuleRows($vars->module_group); 1194 | 1195 | if (isset($rows[0])) { 1196 | $row = $rows[0]; 1197 | } 1198 | unset($rows); 1199 | } 1200 | 1201 | $api = null; 1202 | $package_lists = []; 1203 | if ($row) { 1204 | $api = $this->getApi($row->meta->host_name, $row->meta->application_api_key, $row->meta->use_ssl == 'true'); 1205 | 1206 | // API request for locations 1207 | $locations_response = $api->Locations->getAll(); 1208 | $this->log('Locations.getAll', json_encode([]), 'input', true); 1209 | $this->log('Locations.getAll', $locations_response->raw(), 'output', $locations_response->status() == 200); 1210 | 1211 | // API request for nests 1212 | $nests_response = $api->Nests->getAll(); 1213 | $this->log('Nests.getAll', json_encode([]), 'input', true); 1214 | $this->log('Nests.getAll', $nests_response->raw(), 'output', $nests_response->status() == 200); 1215 | 1216 | // Gather a list of locations from the API response 1217 | if ($locations_response->status() == 200) { 1218 | $package_lists['locations'] = ['' => Language::_('AppController.select.please', true)]; 1219 | foreach ($locations_response->response()->data as $location) { 1220 | $package_lists['locations'][$location->attributes->id] = $location->attributes->long; 1221 | } 1222 | } 1223 | 1224 | // Gather a list of nests from the API response 1225 | if ($nests_response->status() == 200) { 1226 | $package_lists['nests'] = ['' => Language::_('AppController.select.please', true)]; 1227 | foreach ($nests_response->response()->data as $nest) { 1228 | $package_lists['nests'][$nest->attributes->id] = $nest->attributes->name; 1229 | } 1230 | } 1231 | 1232 | // Once we select a nest, gather a list of eggs from that belong to it 1233 | if (!empty($vars->meta['nest_id'])) { 1234 | // API request for eggs 1235 | $eggs_response = $api->Nests->eggsGetAll($vars->meta['nest_id']); 1236 | if ($eggs_response->status() == 200) { 1237 | $package_lists['eggs'] = ['' => Language::_('AppController.select.please', true)]; 1238 | foreach ($eggs_response->response()->data as $egg) { 1239 | // TODO This lists egg IDs, but eggs have name, for some reason they are just not fetched by 1240 | // the API. We should probably look into that. 1241 | $package_lists['eggs'][$egg->attributes->id] = $egg; 1242 | } 1243 | } 1244 | 1245 | // Log request data 1246 | $this->log('Nests.eggsGetAll', json_encode(['nest_id' => $vars->meta['nest_id']]), 'input', true); 1247 | $this->log('Nests.eggsGetAll', $eggs_response->raw(), 'output', $eggs_response->status() == 200); 1248 | } 1249 | } 1250 | 1251 | return $package_lists; 1252 | } 1253 | 1254 | /** 1255 | * Returns all fields to display to an admin attempting to add a service with the module 1256 | * 1257 | * @param stdClass $package A stdClass object representing the selected package 1258 | * @param stdClass $vars A stdClass object representing a set of post fields (optional) 1259 | * @return ModuleFields A ModuleFields object, containing the fields to render 1260 | * as well as any additional HTML markup to include 1261 | */ 1262 | public function getAdminAddFields($package, $vars = null) 1263 | { 1264 | // Get and set the module row to use for API calls 1265 | if ($package->module_group) { 1266 | $this->setModuleRow($this->getModuleRow($this->selectModuleRow($package->module_group))); 1267 | } else { 1268 | $this->setModuleRow($this->getModuleRow($package->module_row)); 1269 | } 1270 | 1271 | // Load the service helper 1272 | $this->loadLib('pterodactyl_service'); 1273 | $service_helper = new PterodactylService(); 1274 | 1275 | // Get configurable options 1276 | if (!isset($this->Record)) { 1277 | Loader::loadComponents($this, ['Record']); 1278 | } 1279 | if (!isset($this->Form)) { 1280 | Loader::loadHelpers($this, ['Form']); 1281 | } 1282 | 1283 | $nest_id = $package->meta->nest_id; 1284 | $egg_id = $package->meta->egg_id; 1285 | 1286 | $option_groups = []; 1287 | foreach ($package->option_groups as $option_group) { 1288 | $option_groups[] = $option_group->id; 1289 | } 1290 | 1291 | if (!empty($option_groups)) { 1292 | $package->configurable_options = $this->Form->collapseObjectArray( 1293 | $this->Record->select(['package_options.id', 'package_options.name']) 1294 | ->from('package_options') 1295 | ->innerJoin( 1296 | 'package_option_group', 1297 | 'package_option_group.option_id', 1298 | '=', 1299 | 'package_options.id', 1300 | false 1301 | ) 1302 | ->where('package_options.company_id', '=', Configure::get('Blesta.company_id')) 1303 | ->where('package_option_group.option_group_id', 'IN', $option_groups) 1304 | ->fetchAll(), 1305 | 'id', 1306 | 'name' 1307 | ); 1308 | 1309 | // Load nest/egg from config option if submitted 1310 | if (isset($vars->configoptions)) { 1311 | $config_options = $package->configurable_options; 1312 | if (isset($config_options['nest_id'], $vars->configoptions[$config_options['nest_id']])) { 1313 | $nest_id = $vars->configoptions[$config_options['nest_id']]; 1314 | } 1315 | 1316 | if (isset($config_options['egg_id'], $vars->configoptions[$config_options['egg_id']])) { 1317 | $egg_id = $vars->configoptions[$config_options['egg_id']]; 1318 | } 1319 | } 1320 | } 1321 | 1322 | $pterodactyl_egg = $this->apiRequest( 1323 | 'Nests', 1324 | 'eggsGet', 1325 | ['nest_id' => $nest_id, 'egg_id' => $egg_id] 1326 | ); 1327 | 1328 | // Fetch the service fields 1329 | return $service_helper->getFields($pterodactyl_egg, $package, $vars, true); 1330 | } 1331 | 1332 | /** 1333 | * Returns all fields to display to a client attempting to add a service with the module 1334 | * 1335 | * @param stdClass $package A stdClass object representing the selected package 1336 | * @param $vars stdClass A stdClass object representing a set of post fields (optional) 1337 | * @return ModuleFields A ModuleFields object, containing the fields to render 1338 | * as well as any additional HTML markup to include 1339 | */ 1340 | public function getClientAddFields($package, $vars = null) 1341 | { 1342 | // Get and set the module row to use for API calls 1343 | if ($package->module_group) { 1344 | $this->setModuleRow($this->getModuleRow($this->selectModuleRow($package->module_group))); 1345 | } else { 1346 | $this->setModuleRow($this->getModuleRow($package->module_row)); 1347 | } 1348 | 1349 | // Load the service helper 1350 | $this->loadLib('pterodactyl_service'); 1351 | $service_helper = new PterodactylService(); 1352 | 1353 | // Get configurable options 1354 | if (!isset($this->Record)) { 1355 | Loader::loadComponents($this, ['Record']); 1356 | } 1357 | if (!isset($this->Form)) { 1358 | Loader::loadHelpers($this, ['Form']); 1359 | } 1360 | 1361 | $nest_id = $package->meta->nest_id; 1362 | $egg_id = $package->meta->egg_id; 1363 | 1364 | $option_groups = []; 1365 | foreach ($package->option_groups as $option_group) { 1366 | $option_groups[] = $option_group->id; 1367 | } 1368 | 1369 | if (!empty($option_groups)) { 1370 | $package->configurable_options = $this->Form->collapseObjectArray( 1371 | $this->Record->select(['package_options.id', 'package_options.name']) 1372 | ->from('package_options') 1373 | ->innerJoin( 1374 | 'package_option_group', 1375 | 'package_option_group.option_id', 1376 | '=', 1377 | 'package_options.id', 1378 | false 1379 | ) 1380 | ->where('package_options.company_id', '=', Configure::get('Blesta.company_id')) 1381 | ->where('package_option_group.option_group_id', 'IN', $option_groups) 1382 | ->fetchAll(), 1383 | 'id', 1384 | 'name' 1385 | ); 1386 | 1387 | // Load nest/egg from config option if submitted 1388 | if (isset($vars->configoptions)) { 1389 | $config_options = $package->configurable_options; 1390 | if (isset($config_options['nest_id'], $vars->configoptions[$config_options['nest_id']])) { 1391 | $nest_id = $vars->configoptions[$config_options['nest_id']]; 1392 | } 1393 | 1394 | if (isset($config_options['egg_id'], $vars->configoptions[$config_options['egg_id']])) { 1395 | $egg_id = $vars->configoptions[$config_options['egg_id']]; 1396 | } 1397 | } 1398 | } 1399 | 1400 | $pterodactyl_egg = $this->apiRequest( 1401 | 'Nests', 1402 | 'eggsGet', 1403 | ['nest_id' => $nest_id, 'egg_id' => $egg_id] 1404 | ); 1405 | 1406 | // Fetch the service fields 1407 | return $service_helper->getFields($pterodactyl_egg, $package, $vars); 1408 | } 1409 | 1410 | /** 1411 | * Returns all fields to display to an admin attempting to edit a service with the module 1412 | * 1413 | * @param stdClass $package A stdClass object representing the selected package 1414 | * @param $vars stdClass A stdClass object representing a set of post fields (optional) 1415 | * @return ModuleFields A ModuleFields object, containing the fields to render 1416 | * as well as any additional HTML markup to include 1417 | */ 1418 | public function getAdminEditFields($package, $vars = null) 1419 | { 1420 | return $this->getAdminAddFields($package, $vars); 1421 | } 1422 | 1423 | /** 1424 | * Returns all fields to display to a client attempting to edit a service with the module 1425 | * 1426 | * @param stdClass $package A stdClass object representing the selected package 1427 | * @param $vars stdClass A stdClass object representing a set of post fields (optional) 1428 | * @return ModuleFields A ModuleFields object, containing the fields to render 1429 | * as well as any additional HTML markup to include 1430 | */ 1431 | public function getClientEditFields($package, $vars = null) 1432 | { 1433 | return $this->getClientAddFields($package, $vars); 1434 | } 1435 | 1436 | /** 1437 | * Initializes the PterodactylApi and returns an instance of that object with the given $host and $api_key set 1438 | * 1439 | * @param string $host The hostname of the Pterodactyl server 1440 | * @param string $api_key The user to connect as 1441 | * @param bool $use_ssl Whether to connect over ssl 1442 | * @return PterodactylApi The PterodactylApi instance 1443 | */ 1444 | private function getApi($host, $api_key, $use_ssl) 1445 | { 1446 | Loader::load( 1447 | dirname(__FILE__) . DS . 'components' . DS . 'modules' . DS . 'pterodactyl-sdk' . DS . 'PterodactylApi.php' 1448 | ); 1449 | 1450 | return new PterodactylApi($api_key, $host, $use_ssl); 1451 | } 1452 | 1453 | /** 1454 | * Builds and returns the rules required to add/edit a module row (e.g. server) 1455 | * 1456 | * @param array $vars An array of key/value data pairs 1457 | * @return array An array of Input rules suitable for Input::setRules() 1458 | */ 1459 | private function getRowRules(array &$vars) 1460 | { 1461 | return [ 1462 | 'server_name' => [ 1463 | 'empty' => [ 1464 | 'rule' => 'isEmpty', 1465 | 'negate' => true, 1466 | 'message' => Language::_('Pterodactyl.!error.server_name.empty', true) 1467 | ] 1468 | ], 1469 | 'host_name' => [ 1470 | 'valid' => [ 1471 | 'rule' => function ($host_name) { 1472 | $validator = new Server(); 1473 | $parts = explode(':', $host_name, 2); 1474 | return ($validator->isDomain($parts[0]) || $validator->isIp($parts[0])) 1475 | && (!isset($parts[1]) || is_numeric($parts[1])); 1476 | }, 1477 | 'message' => Language::_('Pterodactyl.!error.host_name.valid', true) 1478 | ] 1479 | ], 1480 | 'account_api_key' => [ 1481 | 'empty' => [ 1482 | 'rule' => 'isEmpty', 1483 | 'negate' => true, 1484 | 'message' => Language::_('Pterodactyl.!error.account_api_key.empty', true) 1485 | ], 1486 | 'valid' => [ 1487 | 'rule' => function ($api_key) use ($vars) { 1488 | try { 1489 | $api = $this->getApi( 1490 | isset($vars['host_name']) ? $vars['host_name'] : '', 1491 | $api_key, 1492 | (isset($vars['use_ssl']) ? $vars['use_ssl'] : 'true') == 'true' 1493 | ); 1494 | $servers_response = $api->Client->getServers(); 1495 | 1496 | return $servers_response->status() == 200; 1497 | } catch (Exception $e) { 1498 | return false; 1499 | } 1500 | }, 1501 | 'message' => Language::_('Pterodactyl.!error.account_api_key.valid', true) 1502 | ] 1503 | ], 1504 | 'application_api_key' => [ 1505 | 'empty' => [ 1506 | 'rule' => 'isEmpty', 1507 | 'negate' => true, 1508 | 'message' => Language::_('Pterodactyl.!error.application_api_key.empty', true) 1509 | ], 1510 | 'valid' => [ 1511 | 'rule' => function ($api_key) use ($vars) { 1512 | try { 1513 | $api = $this->getApi( 1514 | isset($vars['host_name']) ? $vars['host_name'] : '', 1515 | $api_key, 1516 | (isset($vars['use_ssl']) ? $vars['use_ssl'] : 'true') == 'true' 1517 | ); 1518 | $locations_response = $api->Locations->getAll(); 1519 | 1520 | return $locations_response->status() == 200; 1521 | } catch (Exception $e) { 1522 | return false; 1523 | } 1524 | }, 1525 | 'message' => Language::_('Pterodactyl.!error.application_api_key.valid', true) 1526 | ] 1527 | ] 1528 | ]; 1529 | } 1530 | } 1531 | -------------------------------------------------------------------------------- /views/default/add_row.pdt: -------------------------------------------------------------------------------- 1 | Widget->clear(); 3 | $this->Widget->setLinkButtons([]); 4 | $this->Widget->create($this->_('Pterodactyl.add_row.box_title', true)); 5 | ?> 6 |4 | | _('Pterodactyl.service_info.username');?> | 5 |_('Pterodactyl.service_info.password');?> | 6 |_('Pterodactyl.service_info.login');?> | 7 |
10 | | Html->safe($username) : null);?> | 11 |Html->safe($password) : null);?> | 12 |13 | meta->host_name) ? $this->Html->safe($module_row->meta->host_name) : null);?>/auth/login 14 | | 15 |
6 | | _('Pterodactyl.service_info.username');?> | 7 |_('Pterodactyl.service_info.password');?> | 8 |_('Pterodactyl.service_info.login');?> | 9 |
---|---|---|---|
14 | | Html->safe($username) : null);?> | 15 |Html->safe($password) : null);?> | 16 |17 | meta->host_name) ? $this->Html->safe($module_row->meta->host_name) : null);?>/auth/login 18 | | 19 |
_('Pterodactyl.manage.module_rows_heading.name');?> | 23 |_('Pterodactyl.manage.module_rows_heading.host_name');?> | 24 |_('Pterodactyl.manage.module_rows_heading.options');?> | 25 |
rows[$i]->meta->server_name) ? $this->Html->safe($module->rows[$i]->meta->server_name) : null);?> | 31 |rows[$i]->meta->host_name) ? $this->Html->safe($module->rows[$i]->meta->host_name) : null);?> | 32 |33 | _('Pterodactyl.manage.module_rows.edit');?> 34 | Form->create($this->base_uri . 'settings/company/modules/deleterow/'); 36 | $this->Form->fieldHidden('id', (isset($module->id) ? $module->id : null)); 37 | $this->Form->fieldHidden('row_id', (isset($module->rows[$i]->id) ? $module->rows[$i]->id : null)); 38 | ?> 39 | _('Pterodactyl.manage.module_rows.delete');?> 40 | Form->end(); 42 | ?> 43 | | 44 |
_('Pterodactyl.manage.module_groups_heading.name');?> | 71 |_('Pterodactyl.manage.module_groups_heading.servers');?> | 72 |_('Pterodactyl.manage.module_groups_heading.options');?> | 73 |
groups[$i]->name) ? $this->Html->safe($module->groups[$i]->name) : null);?> | 79 |groups[$i]->rows) ? $module->groups[$i]->rows : []));?> | 80 |81 | _('Pterodactyl.manage.module_groups.edit');?> 82 | Form->create($this->base_uri . 'settings/company/modules/deletegroup/'); 84 | $this->Form->fieldHidden('id', (isset($module->id) ? $module->id : null)); 85 | $this->Form->fieldHidden('group_id', (isset($module->groups[$i]->id) ? $module->groups[$i]->id : null)); 86 | ?> 87 | _('Pterodactyl.manage.module_groups.delete');?> 88 | Form->end(); 90 | ?> 91 | | 92 |