GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — develop (#286)
by Dane
08:45 queued 05:44
created

ServerRepository   D

Complexity

Total Complexity 138

Size/Duplication

Total Lines 925
Duplicated Lines 22.92 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 0
Metric Value
wmc 138
c 0
b 0
f 0
lcom 1
cbo 8
dl 212
loc 925
rs 4.4444

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
B generateSFTPUsername() 0 18 5
F create() 17 260 38
C updateDetails() 0 76 12
B updateContainer() 41 41 4
F changeBuild() 20 163 33
D updateStartup() 19 108 18
A deleteServer() 19 19 4
B deleteNow() 0 61 7
A cancelDeletion() 0 8 1
A toggleInstall() 0 10 3
B suspend() 30 30 4
B unsuspend() 30 30 4
B updateSFTPPassword() 36 36 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like ServerRepository often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ServerRepository, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Pterodactyl - Panel
4
 * Copyright (c) 2015 - 2017 Dane Everitt <[email protected]>.
5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a copy
7
 * of this software and associated documentation files (the "Software"), to deal
8
 * in the Software without restriction, including without limitation the rights
9
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
 * copies of the Software, and to permit persons to whom the Software is
11
 * furnished to do so, subject to the following conditions:
12
 *
13
 * The above copyright notice and this permission notice shall be included in all
14
 * copies or substantial portions of the Software.
15
 *
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
 * SOFTWARE.
23
 */
24
25
namespace Pterodactyl\Repositories;
26
27
use DB;
28
use Log;
29
use Crypt;
30
use Validator;
31
use Pterodactyl\Models;
32
use Pterodactyl\Services\UuidService;
33
use GuzzleHttp\Exception\TransferException;
34
use Pterodactyl\Services\DeploymentService;
35
use Pterodactyl\Exceptions\DisplayException;
36
use Pterodactyl\Exceptions\DisplayValidationException;
37
38
class ServerRepository
39
{
40
    protected $daemonPermissions = [
41
        's:*',
42
    ];
43
44
    public function __construct()
45
    {
46
        //
47
    }
48
49
    /**
50
     * Generates a SFTP username for a server given a server name.
51
     * format: mumble_67c7a4b0.
52
     *
53
     * @param  string $name
54
     * @param  string $identifier
55
     * @return string
56
     */
57
    protected function generateSFTPUsername($name, $identifier = null)
58
    {
59
        if (is_null($identifier) || ! ctype_alnum($identifier)) {
60
            $unique = str_random(8);
61
        } else {
62
            if (strlen($identifier) < 8) {
63
                $unique = $identifier . str_random((8 - strlen($identifier)));
64
            } else {
65
                $unique = substr($identifier, 0, 8);
66
            }
67
        }
68
69
        // Filter the Server Name
70
        $name = trim(preg_replace('/[^\w]+/', '', $name), '_');
71
        $name = (strlen($name) < 1) ? str_random(6) : $name;
72
73
        return strtolower(substr($name, 0, 6) . '_' . $unique);
74
    }
75
76
    /**
77
     * Adds a new server to the system.
78
     * @param   array  $data  An array of data descriptors for creating the server. These should align to the columns in the database.
79
     * @return  int
80
     */
81
    public function create(array $data)
82
    {
83
84
        // Validate Fields
85
        $validator = Validator::make($data, [
86
            'owner' => 'bail|required',
87
            'name' => 'required|regex:/^([\w .-]{1,200})$/',
88
            'memory' => 'required|numeric|min:0',
89
            'swap' => 'required|numeric|min:-1',
90
            'io' => 'required|numeric|min:10|max:1000',
91
            'cpu' => 'required|numeric|min:0',
92
            'disk' => 'required|numeric|min:0',
93
            'service_id' => 'required|numeric|min:1|exists:services,id',
94
            'option_id' => 'required|numeric|min:1|exists:service_options,id',
95
            'location_id' => 'required|numeric|min:1|exists:locations,id',
96
            'pack_id' => 'sometimes|nullable|numeric|min:0',
97
            'startup' => 'string',
98
            'custom_image_name' => 'required_if:use_custom_image,on',
99
            'auto_deploy' => 'sometimes|boolean',
100
            'custom_id' => 'sometimes|required|numeric|unique:servers,id',
101
        ]);
102
103
        $validator->sometimes('node_id', 'bail|required|numeric|min:1|exists:nodes,id', function ($input) {
104
            return ! ($input->auto_deploy);
105
        });
106
107
        $validator->sometimes('ip', 'required|ip', function ($input) {
108
            return ! $input->auto_deploy && ! $input->allocation;
109
        });
110
111
        $validator->sometimes('port', 'required|numeric|min:1|max:65535', function ($input) {
112
            return ! $input->auto_deploy && ! $input->allocation;
113
        });
114
115
        $validator->sometimes('allocation_id', 'numeric|exists:allocations,id', function ($input) {
116
            return ! ($input->auto_deploy || ($input->port && $input->ip));
117
        });
118
119
        // Run validator, throw catchable and displayable exception if it fails.
120
        // Exception includes a JSON result of failed validation rules.
121
        if ($validator->fails()) {
122
            throw new DisplayValidationException($validator->errors());
123
        }
124
125
        $user = Models\User::select('id', 'email')->where((is_int($data['owner'])) ? 'id' : 'email', $data['owner'])->first();
126
        if (! $user) {
127
            throw new DisplayException('The user id or email passed to the function was not found on the system.');
128
        }
129
130
        $autoDeployed = false;
131
        if (isset($data['auto_deploy']) && in_array($data['auto_deploy'], [true, 1, '1'])) {
132
            // This is an auto-deployment situation
133
            // Ignore any other passed node data
134
            unset($data['node_id'], $data['ip'], $data['port'], $data['allocation_id']);
135
136
            $autoDeployed = true;
137
            $node = DeploymentService::smartRandomNode($data['memory'], $data['disk'], $data['location_id']);
138
            $allocation = DeploymentService::randomAllocation($node->id);
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<Pterodactyl\Models\Node>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
139
        } else {
140
            $node = Models\Node::findOrFail($data['node_id']);
141
        }
142
143
        // Verify IP & Port are a.) free and b.) assigned to the node.
144
        // We know the node exists because of 'exists:nodes,id' in the validation
145
        if (! $autoDeployed) {
146
            if (! isset($data['allocation_id'])) {
147
                $allocation = Models\Allocation::where('ip', $data['ip'])->where('port', $data['port'])->where('node', $data['node'])->whereNull('assigned_to')->first();
148
            } else {
149
                $allocation = Models\Allocation::where('id', $data['allocation'])->where('node', $data['node'])->whereNull('assigned_to')->first();
150
            }
151
        }
152
153
        // Something failed in the query, either that combo doesn't exist, or it is in use.
154
        if (! $allocation) {
0 ignored issues
show
Bug introduced by
The variable $allocation does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
155
            throw new DisplayException('The selected IP/Port combination or Allocation ID is either already in use, or unavaliable for this node.');
156
        }
157
158
        // Validate those Service Option Variables
159
        // We know the service and option exists because of the validation.
160
        // We need to verify that the option exists for the service, and then check for
161
        // any required variable fields. (fields are labeled env_<env_variable>)
162
        $option = Models\ServiceOption::where('id', $data['option'])->where('service_id', $data['service'])->first();
163
        if (! $option) {
164
            throw new DisplayException('The requested service option does not exist for the specified service.');
165
        }
166
167
        // Validate the Pack
168
        if ($data['pack_id'] == 0) {
169
            $data['pack_id'] = null;
170
        }
171
172
        if (! is_null($data['pack_id'])) {
173
            $pack = Models\ServicePack::where('id', $data['pack_id'])->where('option', $data['option_id'])->first();
174
            if (! $pack) {
175
                throw new DisplayException('The requested service pack does not seem to exist for this combination.');
176
            }
177
        }
178
179
        // Load up the Service Information
180
        $service = Models\Service::find($option->service_id);
181
182
        // Check those Variables
183
        $variables = Models\ServiceVariable::where('option_id', $data['option_id'])->get();
184
        $variableList = [];
185
        if ($variables) {
186
            foreach ($variables as $variable) {
187
188
                // Is the variable required?
189
                if (! isset($data['env_' . $variable->env_variable])) {
190
                    if ($variable->required === 1) {
191
                        throw new DisplayException('A required service option variable field (env_' . $variable->env_variable . ') was missing from the request.');
192
                    }
193
                    $variableList[] = [
194
                        'id' => $variable->id,
195
                        'env' => $variable->env_variable,
196
                        'val' => $variable->default_value,
197
                    ];
198
                    continue;
199
                }
200
201
                // Check aganist Regex Pattern
202 View Code Duplication
                if (! is_null($variable->regex) && ! preg_match($variable->regex, $data['env_' . $variable->env_variable])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
203
                    throw new DisplayException('Failed to validate service option variable field (env_' . $variable->env_variable . ') aganist regex (' . $variable->regex . ').');
204
                }
205
206
                $variableList[] = [
207
                    'id' => $variable->id,
208
                    'env' => $variable->env_variable,
209
                    'val' => $data['env_' . $variable->env_variable],
210
                ];
211
                continue;
212
            }
213
        }
214
215
        // Check Overallocation
216
        if (! $autoDeployed) {
217
            if (is_numeric($node->memory_overallocate) || is_numeric($node->disk_overallocate)) {
218
                $totals = Models\Server::select(DB::raw('SUM(memory) as memory, SUM(disk) as disk'))->where('node', $node->id)->first();
219
220
                // Check memory limits
221 View Code Duplication
                if (is_numeric($node->memory_overallocate)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
222
                    $newMemory = $totals->memory + $data['memory'];
223
                    $memoryLimit = ($node->memory * (1 + ($node->memory_overallocate / 100)));
224
                    if ($newMemory > $memoryLimit) {
225
                        throw new DisplayException('The amount of memory allocated to this server would put the node over its allocation limits. This node is allowed ' . ($node->memory_overallocate + 100) . '% of its assigned ' . $node->memory . 'Mb of memory (' . $memoryLimit . 'Mb) of which ' . (($totals->memory / $node->memory) * 100) . '% (' . $totals->memory . 'Mb) is in use already. By allocating this server the node would be at ' . (($newMemory / $node->memory) * 100) . '% (' . $newMemory . 'Mb) usage.');
226
                    }
227
                }
228
229
                // Check Disk Limits
230 View Code Duplication
                if (is_numeric($node->disk_overallocate)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
231
                    $newDisk = $totals->disk + $data['disk'];
232
                    $diskLimit = ($node->disk * (1 + ($node->disk_overallocate / 100)));
233
                    if ($newDisk > $diskLimit) {
234
                        throw new DisplayException('The amount of disk allocated to this server would put the node over its allocation limits. This node is allowed ' . ($node->disk_overallocate + 100) . '% of its assigned ' . $node->disk . 'Mb of disk (' . $diskLimit . 'Mb) of which ' . (($totals->disk / $node->disk) * 100) . '% (' . $totals->disk . 'Mb) is in use already. By allocating this server the node would be at ' . (($newDisk / $node->disk) * 100) . '% (' . $newDisk . 'Mb) usage.');
235
                    }
236
                }
237
            }
238
        }
239
240
        DB::beginTransaction();
241
242
        try {
243
            $uuid = new UuidService;
244
245
            // Add Server to the Database
246
            $server = new Models\Server;
247
            $genUuid = $uuid->generate('servers', 'uuid');
248
            $genShortUuid = $uuid->generateShort('servers', 'uuidShort', $genUuid);
249
250
            if (isset($data['custom_id'])) {
251
                $server->id = $data['custom_id'];
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<Pterodactyl\Models\Server>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
252
            }
253
254
            $server->fill([
255
                'uuid' => $genUuid,
256
                'uuidShort' => $genShortUuid,
257
                'node_id' => $node->id,
258
                'name' => $data['name'],
259
                'suspended' => 0,
260
                'owner_id' => $user->id,
261
                'memory' => $data['memory'],
262
                'swap' => $data['swap'],
263
                'disk' => $data['disk'],
264
                'io' => $data['io'],
265
                'cpu' => $data['cpu'],
266
                'oom_disabled' => (isset($data['oom_disabled'])) ? true : false,
267
                'allocation' => $allocation->id,
268
                'service_id' => $data['service_id'],
269
                'option_id' => $data['option_id'],
270
                'pack_id' => $data['pack_id'],
271
                'startup' => $data['startup'],
272
                'daemonSecret' => $uuid->generate('servers', 'daemonSecret'),
273
                'image' => (isset($data['custom_image_name'])) ? $data['custom_image_name'] : $option->docker_image,
274
                'username' => $this->generateSFTPUsername($data['name'], $genShortUuid),
275
                'sftp_password' => Crypt::encrypt('not set'),
276
            ]);
277
            $server->save();
278
279
            // Mark Allocation in Use
280
            $allocation->server_id = $server->id;
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<Pterodactyl\Models\Server>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
281
            $allocation->save();
282
283
            // Add Variables
284
            $environmentVariables = [
285
                'STARTUP' => $data['startup'],
286
            ];
287
288
            foreach ($variableList as $item) {
289
                $environmentVariables[$item['env']] = $item['val'];
290
291
                Models\ServerVariable::create([
292
                    'server_id' => $server->id,
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<Pterodactyl\Models\Server>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
293
                    'variable_id' => $item['id'],
294
                    'variable_value' => $item['val'],
295
                ]);
296
            }
297
298
            $node->guzzleClient(['X-Access-Token' => $node->daemonSecret])->request('POST', '/servers', [
299
                'json' => [
300
                    'uuid' => (string) $server->uuid,
0 ignored issues
show
Documentation introduced by
The property uuid does not exist on object<Pterodactyl\Models\Server>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
301
                    'user' => $server->username,
0 ignored issues
show
Documentation introduced by
The property username does not exist on object<Pterodactyl\Models\Server>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
302
                    'build' => [
303
                        'default' => [
304
                            'ip' => $allocation->ip,
305
                            'port' => (int) $allocation->port,
306
                        ],
307
                        'ports' => [
308
                            (string) $allocation->ip => [(int) $allocation->port],
309
                        ],
310
                        'env' => $environmentVariables,
311
                        'memory' => (int) $server->memory,
0 ignored issues
show
Documentation introduced by
The property memory does not exist on object<Pterodactyl\Models\Server>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
312
                        'swap' => (int) $server->swap,
0 ignored issues
show
Documentation introduced by
The property swap does not exist on object<Pterodactyl\Models\Server>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
313
                        'io' => (int) $server->io,
0 ignored issues
show
Documentation introduced by
The property io does not exist on object<Pterodactyl\Models\Server>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
314
                        'cpu' => (int) $server->cpu,
0 ignored issues
show
Documentation introduced by
The property cpu does not exist on object<Pterodactyl\Models\Server>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
315
                        'disk' => (int) $server->disk,
0 ignored issues
show
Documentation introduced by
The property disk does not exist on object<Pterodactyl\Models\Server>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
316
                        'image' => (isset($data['custom_image_name'])) ? $data['custom_image_name'] : $option->docker_image,
317
                    ],
318
                    'service' => [
319
                        'type' => $service->file,
320
                        'option' => $option->tag,
321
                        'pack' => (isset($pack)) ? $pack->uuid : null,
322
                    ],
323
                    'keys' => [
324
                        (string) $server->daemonSecret => $this->daemonPermissions,
0 ignored issues
show
Documentation introduced by
The property daemonSecret does not exist on object<Pterodactyl\Models\Server>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
325
                    ],
326
                    'rebuild' => false,
327
                ],
328
            ]);
329
330
            DB::commit();
331
332
            return $server;
333
        } catch (TransferException $ex) {
334
            DB::rollBack();
335
            throw new DisplayException('There was an error while attempting to connect to the daemon to add this server.', $ex);
336
        } catch (\Exception $ex) {
337
            DB::rollBack();
338
            throw $ex;
339
        }
340
    }
341
342
    /**
343
     * [updateDetails description].
344
     * @param  int  $id
345
     * @param  array    $data
346
     * @return bool
347
     */
348
    public function updateDetails($id, array $data)
349
    {
350
        $uuid = new UuidService;
351
        $resetDaemonKey = false;
352
353
        // Validate Fields
354
        $validator = Validator::make($data, [
355
            'owner' => 'email|exists:users,email',
356
            'name' => 'regex:([\w .-]{1,200})',
357
        ]);
358
359
        // Run validator, throw catchable and displayable exception if it fails.
360
        // Exception includes a JSON result of failed validation rules.
361
        if ($validator->fails()) {
362
            throw new DisplayValidationException($validator->errors());
363
        }
364
365
        DB::beginTransaction();
366
367
        try {
368
            $server = Models\Server::with('user')->findOrFail($id);
0 ignored issues
show
Bug introduced by
The method findOrFail does only exist in Illuminate\Database\Eloquent\Builder, but not in Illuminate\Database\Eloquent\Model.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
369
370
            // Update daemon secret if it was passed.
371
            if ((isset($data['reset_token']) && $data['reset_token'] === true) || (isset($data['owner']) && $data['owner'] !== $server->user->email)) {
372
                $oldDaemonKey = $server->daemonSecret;
373
                $server->daemonSecret = $uuid->generate('servers', 'daemonSecret');
374
                $resetDaemonKey = true;
375
            }
376
377
            // Update Server Owner if it was passed.
378
            if (isset($data['owner']) && $data['owner'] !== $owner->email) {
0 ignored issues
show
Bug introduced by
The variable $owner does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
379
                $newOwner = Models\User::select('id')->where('email', $data['owner'])->first();
380
                $server->owner_id = $newOwner->id;
381
            }
382
383
            // Update Server Name if it was passed.
384
            if (isset($data['name'])) {
385
                $server->name = $data['name'];
386
            }
387
388
            // Save our changes
389
            $server->save();
390
391
            // Do we need to update? If not, return successful.
392
            if (! $resetDaemonKey) {
393
                DB::commit();
394
395
                return true;
396
            }
397
398
            $res = $server->node->guzzleClient([
399
                'X-Access-Server' => $server->uuid,
400
                'X-Access-Token' => $server->node->daemonSecret,
401
            ])->request('PATCH', '/server', [
402
                'exceptions' => false,
403
                'json' => [
404
                    'keys' => [
405
                        (string) $oldDaemonKey => [],
0 ignored issues
show
Bug introduced by
The variable $oldDaemonKey does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
406
                        (string) $server->daemonSecret => $this->daemonPermissions,
407
                    ],
408
                ],
409
            ]);
410
411
            if ($res->getStatusCode() === 204) {
412
                DB::commit();
413
414
                return true;
415
            } else {
416
                throw new DisplayException('Daemon returned a a non HTTP/204 error code. HTTP/' + $res->getStatusCode());
417
            }
418
        } catch (\Exception $ex) {
419
            DB::rollBack();
420
            Log::error($ex);
421
            throw new DisplayException('An error occured while attempting to update this server\'s information.');
422
        }
423
    }
424
425
    /**
426
     * [updateContainer description].
427
     * @param  int      $id
428
     * @param  array    $data
429
     * @return bool
430
     */
431 View Code Duplication
    public function updateContainer($id, array $data)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
432
    {
433
        $validator = Validator::make($data, [
434
            'image' => 'required|string',
435
        ]);
436
437
        // Run validator, throw catchable and displayable exception if it fails.
438
        // Exception includes a JSON result of failed validation rules.
439
        if ($validator->fails()) {
440
            throw new DisplayValidationException($validator->errors());
441
        }
442
443
        DB::beginTransaction();
444
        try {
445
            $server = Models\Server::findOrFail($id);
446
447
            $server->image = $data['image'];
448
            $server->save();
449
450
            $server->node->guzzleClient([
451
                'X-Access-Server' => $server->uuid,
452
                'X-Access-Token' => $server->node->daemonSecret,
453
            ])->request('PATCH', '/server', [
454
                'json' => [
455
                    'build' => [
456
                        'image' => $server->image,
457
                    ],
458
                ],
459
            ]);
460
461
            DB::commit();
462
463
            return true;
464
        } catch (TransferException $ex) {
465
            DB::rollBack();
466
            throw new DisplayException('An error occured while attempting to update the container image.', $ex);
467
        } catch (\Exception $ex) {
468
            DB::rollBack();
469
            throw $ex;
470
        }
471
    }
472
473
    /**
474
     * [changeBuild description].
475
     * @param  int  $id
476
     * @param  array    $data
477
     * @return bool
478
     */
479
    public function changeBuild($id, array $data)
480
    {
481
        $validator = Validator::make($data, [
482
            'default' => [
483
                'string',
484
                'regex:/^(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5])):(\d{1,5})$/',
485
            ],
486
            'add_additional' => 'nullable|array',
487
            'remove_additional' => 'nullable|array',
488
            'memory' => 'integer|min:0',
489
            'swap' => 'integer|min:-1',
490
            'io' => 'integer|min:10|max:1000',
491
            'cpu' => 'integer|min:0',
492
            'disk' => 'integer|min:0',
493
        ]);
494
495
        // Run validator, throw catchable and displayable exception if it fails.
496
        // Exception includes a JSON result of failed validation rules.
497
        if ($validator->fails()) {
498
            throw new DisplayValidationException($validator->errors());
499
        }
500
501
        DB::beginTransaction();
502
503
        try {
504
            $server = Models\Server::with('allocation', 'allocations')->findOrFail($id);
0 ignored issues
show
Bug introduced by
The method findOrFail does only exist in Illuminate\Database\Eloquent\Builder, but not in Illuminate\Database\Eloquent\Model.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
505
            $newBuild = [];
506
507
            if (isset($data['default'])) {
508
                list($ip, $port) = explode(':', $data['default']);
509
                if ($ip !== $server->allocation->ip || (int) $port !== $server->allocation->port) {
510
                    $selection = $server->allocations->where('ip', $ip)->where('port', $port)->first();
511
                    if (! $selection) {
512
                        throw new DisplayException('The requested default connection (' . $ip . ':' . $port . ') is not allocated to this server.');
513
                    }
514
515
                    $server->allocation_id = $selection->id;
516
                    $newBuild['default'] = [
517
                        'ip' => $ip,
518
                        'port' => (int) $port,
519
                    ];
520
521
                    // Re-Run to keep updated for rest of function
522
                    $server->load('allocation');
523
                }
524
            }
525
526
            $newPorts = false;
527
            // Remove Assignments
528
            if (isset($data['remove_additional'])) {
529
                foreach ($data['remove_additional'] as $id => $combo) {
530
                    list($ip, $port) = explode(':', $combo);
531
                    // Invalid, not worth killing the whole thing, we'll just skip over it.
532
                    if (! filter_var($ip, FILTER_VALIDATE_IP) || ! preg_match('/^(\d{1,5})$/', $port)) {
533
                        break;
534
                    }
535
536
                    // Can't remove the assigned IP/Port combo
537
                    if ($ip === $server->allocation->ip && (int) $port === (int) $server->allocation->port) {
538
                        break;
539
                    }
540
541
                    $newPorts = true;
542
                    $server->allocations->where('ip', $ip)->where('port', $port)->update([
543
                        'assigned_to' => null,
544
                    ]);
545
                }
546
547
                $server->load('allocations');
548
            }
549
550
            // Add Assignments
551
            if (isset($data['add_additional'])) {
552
                foreach ($data['add_additional'] as $id => $combo) {
553
                    list($ip, $port) = explode(':', $combo);
554
                    // Invalid, not worth killing the whole thing, we'll just skip over it.
555
                    if (! filter_var($ip, FILTER_VALIDATE_IP) || ! preg_match('/^(\d{1,5})$/', $port)) {
556
                        break;
557
                    }
558
559
                    // Don't allow double port assignments
560
                    if ($server->allocations->where('port', $port)->count() !== 0) {
561
                        break;
562
                    }
563
564
                    $newPorts = true;
565
                    Models\Allocation::where('ip', $ip)->where('port', $port)->whereNull('assigned_to')->update([
566
                        'assigned_to' => $server->id,
567
                    ]);
568
                }
569
570
                $server->load('allocations');
571
            }
572
573
            // Loop All Assignments
574
            $additionalAssignments = [];
575
            foreach ($server->allocations as &$assignment) {
576
                if (array_key_exists((string) $assignment->ip, $additionalAssignments)) {
577
                    array_push($additionalAssignments[(string) $assignment->ip], (int) $assignment->port);
578
                } else {
579
                    $additionalAssignments[(string) $assignment->ip] = [(int) $assignment->port];
580
                }
581
            }
582
583
            if ($newPorts === true) {
584
                $newBuild['ports|overwrite'] = $additionalAssignments;
585
            }
586
587
            // @TODO: verify that server can be set to this much memory without
588
            // going over node limits.
589 View Code Duplication
            if (isset($data['memory']) && $server->memory !== (int) $data['memory']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
590
                $server->memory = $data['memory'];
591
                $newBuild['memory'] = (int) $server->memory;
592
            }
593
594 View Code Duplication
            if (isset($data['swap']) && $server->swap !== (int) $data['swap']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
595
                $server->swap = $data['swap'];
596
                $newBuild['swap'] = (int) $server->swap;
597
            }
598
599
            // @TODO: verify that server can be set to this much disk without
600
            // going over node limits.
601 View Code Duplication
            if (isset($data['disk']) && $server->disk !== (int) $data['disk']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
602
                $server->disk = $data['disk'];
603
                $newBuild['disk'] = (int) $server->disk;
604
            }
605
606 View Code Duplication
            if (isset($data['cpu']) && $server->cpu !== (int) $data['cpu']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
607
                $server->cpu = $data['cpu'];
608
                $newBuild['cpu'] = (int) $server->cpu;
609
            }
610
611 View Code Duplication
            if (isset($data['io']) && $server->io !== (int) $data['io']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
612
                $server->io = $data['io'];
613
                $newBuild['io'] = (int) $server->io;
614
            }
615
616
            // Try save() here so if it fails we haven't contacted the daemon
617
            // This won't be committed unless the HTTP request succeedes anyways
618
            $server->save();
619
620
            if (! empty($newBuild)) {
621
                $server->node->guzzleClient([
622
                    'X-Access-Server' => $server->uuid,
623
                    'X-Access-Token' => $server->node->daemonSecret,
624
                ])->request('PATCH', '/server', [
625
                    'json' => [
626
                        'build' => $newBuild,
627
                    ],
628
                ]);
629
            }
630
631
            DB::commit();
632
633
            return true;
634
        } catch (TransferException $ex) {
635
            DB::rollBack();
636
            throw new DisplayException('An error occured while attempting to update the configuration.', $ex);
637
        } catch (\Exception $ex) {
638
            DB::rollBack();
639
            throw $ex;
640
        }
641
    }
642
643
    public function updateStartup($id, array $data, $admin = false)
644
    {
645
        $server = Models\Server::with('variables', 'option.variables')->findOrFail($id);
0 ignored issues
show
Bug introduced by
The method findOrFail does only exist in Illuminate\Database\Eloquent\Builder, but not in Illuminate\Database\Eloquent\Model.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
646
647
        DB::beginTransaction();
648
649
        try {
650
            // Check the startup
651
            if (isset($data['startup'])) {
652
                $server->startup = $data['startup'];
653
                $server->save();
654
            }
655
656
            // Check those Variables
657
            $server->option->variables->transform(function ($item, $key) use ($server) {
0 ignored issues
show
Unused Code introduced by
The parameter $key is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
658
                $displayValue = $server->variables->where('variable_id', $item->id)->pluck('variable_value')->first();
659
                $item->server_value = (! is_null($displayValue)) ? $displayValue : $item->default_value;
660
661
                return $item;
662
            });
663
664
            $variableList = [];
665
            if ($server->option->variables) {
666
                foreach ($server->option->variables as &$variable) {
667
                    // Move on if the new data wasn't even sent
668 View Code Duplication
                    if (! isset($data[$variable->env_variable])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
669
                        $variableList[] = [
670
                            'id' => $variable->id,
671
                            'env' => $variable->env_variable,
672
                            'val' => $variable->server_value,
673
                        ];
674
                        continue;
675
                    }
676
677
                    // Update Empty but skip validation
678 View Code Duplication
                    if (empty($data[$variable->env_variable])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
679
                        $variableList[] = [
680
                            'id' => $variable->id,
681
                            'env' => $variable->env_variable,
682
                            'val' => null,
683
                        ];
684
                        continue;
685
                    }
686
687
                    // Is the variable required?
688
                    // @TODO: is this even logical to perform this check?
689
                    if (isset($data[$variable->env_variable]) && empty($data[$variable->env_variable])) {
690
                        if ($variable->required) {
691
                            throw new DisplayException('A required service option variable field (' . $variable->env_variable . ') was included in this request but was left blank.');
692
                        }
693
                    }
694
695
                    // Variable hidden and/or not user editable
696
                    if ((! $variable->user_viewable || ! $variable->user_editable) && ! $admin) {
697
                        throw new DisplayException('A service option variable field (' . $variable->env_variable . ') does not exist or you do not have permission to edit it.');
698
                    }
699
700
                    // Check aganist Regex Pattern
701 View Code Duplication
                    if (! is_null($variable->regex) && ! preg_match($variable->regex, $data[$variable->env_variable])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
702
                        throw new DisplayException('Failed to validate service option variable field (' . $variable->env_variable . ') aganist regex (' . $variable->regex . ').');
703
                    }
704
705
                    $variableList[] = [
706
                        'id' => $variable->id,
707
                        'env' => $variable->env_variable,
708
                        'val' => $data[$variable->env_variable],
709
                    ];
710
                }
711
            }
712
713
            // Add Variables
714
            $environmentVariables = [
715
                'STARTUP' => $server->startup,
716
            ];
717
            foreach ($variableList as $item) {
718
                $environmentVariables[$item['env']] = $item['val'];
719
720
                // Update model or make a new record if it doesn't exist.
721
                $model = Models\ServerVariable::firstOrNew([
722
                    'variable_id' => $item['id'],
723
                    'server_id' => $server->id,
724
                ]);
725
                $model->variable_value = $item['val'];
726
                $model->save();
727
            }
728
729
            $server->node->guzzleClient([
730
                'X-Access-Server' => $server->uuid,
731
                'X-Access-Token' => $server->node->daemonSecret,
732
            ])->request('PATCH', '/server', [
733
                'json' => [
734
                    'build' => [
735
                        'env|overwrite' => $environmentVariables,
736
                    ],
737
                ],
738
            ]);
739
740
            DB::commit();
741
742
            return true;
743
        } catch (TransferException $ex) {
744
            DB::rollBack();
745
            throw new DisplayException('An error occured while attempting to update the server configuration.', $ex);
746
        } catch (\Exception $ex) {
747
            DB::rollBack();
748
            throw $ex;
749
        }
750
    }
751
752 View Code Duplication
    public function deleteServer($id, $force)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
753
    {
754
        $server = Models\Server::findOrFail($id);
755
        DB::beginTransaction();
756
757
        try {
758
            if ($force === 'force' || $force) {
759
                $server->installed = 3;
760
                $server->save();
761
            }
762
763
            $server->delete();
764
765
            return DB::commit();
766
        } catch (\Exception $ex) {
767
            DB::rollBack();
768
            throw $ex;
769
        }
770
    }
771
772
    public function deleteNow($id, $force = false)
773
    {
774
        $server = Models\Server::withTrashed()->with('node')->findOrFail($id);
0 ignored issues
show
Bug introduced by
The method withTrashed() does not exist on Pterodactyl\Models\Server. Did you maybe mean trashed()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
775
776
        // Handle server being restored previously or
777
        // an accidental queue.
778
        if (! $server->trashed()) {
779
            return;
780
        }
781
782
        DB::beginTransaction();
783
        try {
784
            // Unassign Allocations
785
            Models\Allocation::where('server_id', $server->id)->update([
786
                'server_id' => null,
787
            ]);
788
789
            // Remove Variables
790
            Models\ServerVariable::where('server_id', $server->id)->delete();
791
792
            // Remove Permissions (Foreign Key requires before Subusers)
793
            Models\Permission::where('server_id', $server->id)->delete();
794
795
            // Remove SubUsers
796
            Models\Subuser::where('server_id', $server->id)->delete();
797
798
            // Remove Downloads
799
            Models\Download::where('server', $server->uuid)->delete();
800
801
            // Clear Tasks
802
            Models\Task::where('server', $server->id)->delete();
803
804
            // Delete Databases
805
            // This is the one un-recoverable point where
806
            // transactions will not save us.
807
            $repository = new DatabaseRepository;
808
            foreach (Models\Database::select('id')->where('server_id', $server->id)->get() as &$database) {
0 ignored issues
show
Bug introduced by
The expression \Pterodactyl\Models\Data...d', $server->id)->get() cannot be used as a reference.

Let?s assume that you have the following foreach statement:

foreach ($array as &$itemValue) { }

$itemValue is assigned by reference. This is possible because the expression (in the example $array) can be used as a reference target.

However, if we were to replace $array with something different like the result of a function call as in

foreach (getArray() as &$itemValue) { }

then assigning by reference is not possible anymore as there is no target that could be modified.

Available Fixes

1. Do not assign by reference
foreach (getArray() as $itemValue) { }
2. Assign to a local variable first
$array = getArray();
foreach ($array as &$itemValue) {}
3. Return a reference
function &getArray() { $array = array(); return $array; }

foreach (getArray() as &$itemValue) { }
Loading history...
809
                $repository->drop($database->id);
810
            }
811
812
            $server->node->guzzleRequest([
813
                'X-Access-Token' => $server->node->daemonSecret,
814
                'X-Access-Server' => $server->uuid,
815
            ])->request('DELETE', '/servers');
816
817
            $server->forceDelete();
818
            DB::commit();
819
        } catch (TransferException $ex) {
820
            // Set installed is set to 3 when force deleting.
821
            if ($server->installed === 3 || $force) {
822
                $server->forceDelete();
823
                DB::commit();
824
            } else {
825
                DB::rollBack();
826
                throw $ex;
827
            }
828
        } catch (\Exception $ex) {
829
            DB::rollBack();
830
            throw $ex;
831
        }
832
    }
833
834
    public function cancelDeletion($id)
835
    {
836
        $server = Models\Server::withTrashed()->findOrFail($id);
0 ignored issues
show
Bug introduced by
The method withTrashed() does not exist on Pterodactyl\Models\Server. Did you maybe mean trashed()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
837
        $server->restore();
838
839
        $server->installed = 1;
840
        $server->save();
841
    }
842
843
    public function toggleInstall($id)
844
    {
845
        $server = Models\Server::findOrFail($id);
846
        if ($server->installed === 2) {
847
            throw new DisplayException('This server was marked as having a failed install, you cannot override this.');
848
        }
849
        $server->installed = ($server->installed) ? 0 : 1;
850
851
        return $server->save();
852
    }
853
854
    /**
855
     * Suspends a server instance making it unable to be booted or used by a user.
856
     * @param  int $id
857
     * @return bool
858
     */
859 View Code Duplication
    public function suspend($id, $deleted = false)
0 ignored issues
show
Unused Code introduced by
The parameter $deleted is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
860
    {
861
        $server = Models\Server::withTrashed()->with('node')->findOrFail($id);
0 ignored issues
show
Bug introduced by
The method withTrashed() does not exist on Pterodactyl\Models\Server. Did you maybe mean trashed()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
862
863
        DB::beginTransaction();
864
865
        try {
866
867
            // Already suspended, no need to make more requests.
868
            if ($server->suspended) {
869
                return true;
870
            }
871
872
            $server->suspended = 1;
873
            $server->save();
874
875
            $server->node->guzzleClient([
876
                'X-Access-Token' => $server->node->daemonSecret,
877
                'X-Access-Server' => $server->uuid,
878
            ])->request('POST', '/server/suspend');
879
880
            return DB::commit();
881
        } catch (TransferException $ex) {
882
            DB::rollBack();
883
            throw new DisplayException('An error occured while attempting to contact the remote daemon to suspend this server.', $ex);
884
        } catch (\Exception $ex) {
885
            DB::rollBack();
886
            throw $ex;
887
        }
888
    }
889
890
    /**
891
     * Unsuspends a server instance.
892
     * @param  int $id
893
     * @return bool
894
     */
895 View Code Duplication
    public function unsuspend($id)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
896
    {
897
        $server = Models\Server::with('node')->findOrFail($id);
0 ignored issues
show
Bug introduced by
The method findOrFail does only exist in Illuminate\Database\Eloquent\Builder, but not in Illuminate\Database\Eloquent\Model.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
898
899
        DB::beginTransaction();
900
901
        try {
902
903
            // Already unsuspended, no need to make more requests.
904
            if ($server->suspended === 0) {
905
                return true;
906
            }
907
908
            $server->suspended = 0;
909
            $server->save();
910
911
            $server->node->guzzleClient([
912
                'X-Access-Token' => $server->node->daemonSecret,
913
                'X-Access-Server' => $server->uuid,
914
            ])->request('POST', '/server/unsuspend');
915
916
            return DB::commit();
917
        } catch (TransferException $ex) {
918
            DB::rollBack();
919
            throw new DisplayException('An error occured while attempting to contact the remote daemon to un-suspend this server.', $ex);
920
        } catch (\Exception $ex) {
921
            DB::rollBack();
922
            throw $ex;
923
        }
924
    }
925
926 View Code Duplication
    public function updateSFTPPassword($id, $password)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
927
    {
928
        $server = Models\Server::with('node')->findOrFail($id);
0 ignored issues
show
Bug introduced by
The method findOrFail does only exist in Illuminate\Database\Eloquent\Builder, but not in Illuminate\Database\Eloquent\Model.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
929
930
        $validator = Validator::make(['password' => $password], [
931
            'password' => 'required|regex:/^((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})$/',
932
        ]);
933
934
        if ($validator->fails()) {
935
            throw new DisplayValidationException(json_encode($validator->errors()));
936
        }
937
938
        DB::beginTransaction();
939
        $server->sftp_password = Crypt::encrypt($password);
940
941
        try {
942
            $server->save();
943
944
            $server->node->guzzleClient([
945
                'X-Access-Token' => $server->node->daemonSecret,
946
                'X-Access-Server' => $server->uuid,
947
            ])->request('POST', '/server/password', [
948
                'json' => ['password' => $password],
949
            ]);
950
951
            DB::commit();
952
953
            return true;
954
        } catch (TransferException $ex) {
955
            DB::rollBack();
956
            throw new DisplayException('There was an error while attmping to contact the remote service to change the password.', $ex);
957
        } catch (\Exception $ex) {
958
            DB::rollBack();
959
            throw $ex;
960
        }
961
    }
962
}
963