1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Joomla! Statistics Server |
4
|
|
|
* |
5
|
|
|
* @copyright Copyright (C) 2013 - 2017 Open Source Matters, Inc. All rights reserved. |
6
|
|
|
* @license http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License Version 2 or Later |
7
|
|
|
*/ |
8
|
|
|
|
9
|
|
|
namespace Joomla\StatsServer\Controllers; |
10
|
|
|
|
11
|
|
|
use Joomla\Controller\AbstractController; |
12
|
|
|
use Joomla\StatsServer\Decorators\ValidateVersion; |
13
|
|
|
use Joomla\StatsServer\Repositories\StatisticsRepository; |
14
|
|
|
use League\Flysystem\FileNotFoundException; |
15
|
|
|
use League\Flysystem\Filesystem; |
16
|
|
|
use Zend\Diactoros\Response\JsonResponse; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* Controller for processing submitted statistics data. |
20
|
|
|
* |
21
|
|
|
* @method \Joomla\Application\WebApplication getApplication() Get the application object. |
22
|
|
|
* @property-read \Joomla\Application\WebApplication $app Application object |
23
|
|
|
*/ |
24
|
|
|
class SubmitDataController extends AbstractController |
25
|
|
|
{ |
26
|
|
|
use ValidateVersion; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* Statistics repository. |
30
|
|
|
* |
31
|
|
|
* @var StatisticsRepository |
32
|
|
|
*/ |
33
|
|
|
private $repository; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* Filesystem adapter for the snapshots space. |
37
|
|
|
* |
38
|
|
|
* @var Filesystem |
39
|
|
|
*/ |
40
|
|
|
private $filesystem; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* Allowed Database Types. |
44
|
|
|
* |
45
|
|
|
* @var array |
46
|
|
|
*/ |
47
|
|
|
private $databaseTypes = [ |
48
|
|
|
'mysql', |
49
|
|
|
'mysqli', |
50
|
|
|
'pgsql', |
51
|
|
|
'pdomysql', |
52
|
|
|
'postgresql', |
53
|
|
|
'sqlazure', |
54
|
|
|
'sqlsrv', |
55
|
|
|
]; |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* Constructor. |
59
|
|
|
* |
60
|
|
|
* @param StatisticsRepository $repository Statistics repository. |
61
|
|
|
* @param Filesystem $filesystem Filesystem adapter for the versions space. |
62
|
|
|
*/ |
63
|
5 |
|
public function __construct(StatisticsRepository $repository, Filesystem $filesystem) |
64
|
|
|
{ |
65
|
5 |
|
$this->repository = $repository; |
66
|
5 |
|
$this->filesystem = $filesystem; |
67
|
5 |
|
} |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* Execute the controller. |
71
|
|
|
* |
72
|
|
|
* @return boolean |
73
|
|
|
*/ |
74
|
5 |
|
public function execute() |
75
|
|
|
{ |
76
|
5 |
|
$input = $this->getInput(); |
77
|
|
|
|
78
|
|
|
$data = [ |
79
|
5 |
|
'php_version' => $input->getRaw('php_version', ''), |
80
|
5 |
|
'db_version' => $input->getRaw('db_version', ''), |
81
|
5 |
|
'cms_version' => $input->getRaw('cms_version', ''), |
82
|
5 |
|
'unique_id' => $input->getString('unique_id'), |
83
|
5 |
|
'db_type' => $input->getString('db_type', ''), |
84
|
5 |
|
'server_os' => $input->getString('server_os'), |
85
|
|
|
]; |
86
|
|
|
|
87
|
|
|
// Backup the original POST before manipulating/validating data |
88
|
5 |
|
$originalData = $data; |
89
|
|
|
|
90
|
|
|
// Validate the submitted data |
91
|
5 |
|
$data['php_version'] = $this->checkPHPVersion($data['php_version']); |
92
|
5 |
|
$data['cms_version'] = $this->checkCMSVersion($data['cms_version']); |
93
|
5 |
|
$data['db_type'] = $this->checkDatabaseType($data['db_type']); |
94
|
5 |
|
$data['db_version'] = $this->validateVersionNumber($data['db_version']); |
95
|
|
|
|
96
|
|
|
// We require at a minimum a unique ID and the CMS version |
97
|
5 |
|
if (empty($data['unique_id']) || (empty($data['cms_version']) && $data['cms_version'] !== false)) |
98
|
|
|
{ |
99
|
1 |
|
$this->getApplication()->getLogger()->info( |
100
|
1 |
|
'Missing required data from request.', |
101
|
1 |
|
['postData' => $originalData] |
102
|
|
|
); |
103
|
|
|
|
104
|
|
|
/** @var JsonResponse $response */ |
105
|
1 |
|
$response = $this->getApplication()->getResponse(); |
106
|
1 |
|
$response = $response->withPayload( |
107
|
|
|
[ |
108
|
1 |
|
'error' => true, |
109
|
|
|
'message' => 'There was an error storing the data.', |
110
|
|
|
] |
111
|
|
|
); |
112
|
1 |
|
$response = $response->withStatus(500); |
113
|
|
|
|
114
|
1 |
|
$this->getApplication()->setResponse($response); |
115
|
|
|
|
116
|
1 |
|
return true; |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
// If the below data does not pass tests, we do not accept the POST |
120
|
4 |
|
if ($data['php_version'] === false || $data['cms_version'] === false || $data['db_type'] === false || $data['db_version'] === false) |
121
|
|
|
{ |
122
|
|
|
/** @var JsonResponse $response */ |
123
|
1 |
|
$response = $this->getApplication()->getResponse(); |
124
|
1 |
|
$response = $response->withPayload( |
125
|
|
|
[ |
126
|
1 |
|
'error' => true, |
127
|
|
|
'message' => 'Invalid data submission.', |
128
|
|
|
] |
129
|
|
|
); |
130
|
1 |
|
$response = $response->withStatus(500); |
131
|
|
|
|
132
|
1 |
|
$this->getApplication()->setResponse($response); |
133
|
|
|
|
134
|
1 |
|
return true; |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
// Account for configuration differences with 4.0 |
138
|
3 |
|
if (version_compare($data['cms_version'], '4.0', 'ge')) |
139
|
|
|
{ |
140
|
|
|
// For 4.0 and later, we map `mysql` to the `pdomysql` option to correctly track the database type |
141
|
1 |
|
if ($data['db_type'] === 'mysql') |
142
|
|
|
{ |
143
|
1 |
|
$data['db_type'] = 'pdomysql'; |
144
|
|
|
} |
145
|
|
|
} |
146
|
|
|
|
147
|
3 |
|
$this->repository->save((object) $data); |
148
|
|
|
|
149
|
|
|
/** @var JsonResponse $response */ |
150
|
3 |
|
$response = $this->getApplication()->getResponse(); |
151
|
3 |
|
$response = $response->withPayload( |
152
|
|
|
[ |
153
|
3 |
|
'error' => false, |
154
|
|
|
'message' => 'Data saved successfully', |
155
|
|
|
] |
156
|
|
|
); |
157
|
|
|
|
158
|
3 |
|
$this->getApplication()->setResponse($response); |
159
|
|
|
|
160
|
3 |
|
return true; |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
/** |
164
|
|
|
* Check the CMS version. |
165
|
|
|
* |
166
|
|
|
* @param string $version The version number to check. |
167
|
|
|
* |
168
|
|
|
* @return string|boolean The version number on success or boolean false on failure. |
169
|
|
|
*/ |
170
|
5 |
View Code Duplication |
private function checkCMSVersion(string $version) |
|
|
|
|
171
|
|
|
{ |
172
|
5 |
|
$version = $this->validateVersionNumber($version); |
173
|
|
|
|
174
|
|
|
// If the version number is invalid, don't go any further |
175
|
5 |
|
if ($version === false) |
176
|
|
|
{ |
177
|
1 |
|
return false; |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
// Joomla only uses major.minor.patch so everything else is invalid |
181
|
4 |
|
$explodedVersion = explode('.', $version); |
182
|
|
|
|
183
|
4 |
|
if (\count($explodedVersion) > 3) |
184
|
|
|
{ |
185
|
|
|
return false; |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
try |
189
|
|
|
{ |
190
|
4 |
|
$validVersions = json_decode($this->filesystem->read('joomla.json'), true); |
191
|
|
|
} |
192
|
|
|
catch (FileNotFoundException $exception) |
193
|
|
|
{ |
194
|
|
|
throw new \RuntimeException('Missing Joomla! release listing', 500, $exception); |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
// Check that the version is in our valid release list |
198
|
4 |
|
if (!\in_array($version, $validVersions)) |
199
|
|
|
{ |
200
|
1 |
|
return false; |
201
|
|
|
} |
202
|
|
|
|
203
|
3 |
|
return $version; |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
/** |
207
|
|
|
* Check the database type |
208
|
|
|
* |
209
|
|
|
* @param string $database The database type to check. |
210
|
|
|
* |
211
|
|
|
* @return string|boolean The database type on success or boolean false on failure. |
212
|
|
|
*/ |
213
|
5 |
|
private function checkDatabaseType(string $database) |
214
|
|
|
{ |
215
|
5 |
|
if (!\in_array($database, $this->databaseTypes)) |
216
|
|
|
{ |
217
|
|
|
return false; |
218
|
|
|
} |
219
|
|
|
|
220
|
5 |
|
return $database; |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
/** |
224
|
|
|
* Check the PHP version |
225
|
|
|
* |
226
|
|
|
* @param string $version The version number to check. |
227
|
|
|
* |
228
|
|
|
* @return string|boolean The version number on success or boolean false on failure. |
229
|
|
|
*/ |
230
|
5 |
View Code Duplication |
private function checkPHPVersion(string $version) |
|
|
|
|
231
|
|
|
{ |
232
|
5 |
|
$version = $this->validateVersionNumber($version); |
233
|
|
|
|
234
|
|
|
// If the version number is invalid, don't go any further |
235
|
5 |
|
if ($version === false) |
236
|
|
|
{ |
237
|
|
|
return false; |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
// We only track versions based on major.minor.patch so everything else is invalid |
241
|
5 |
|
$explodedVersion = explode('.', $version); |
242
|
|
|
|
243
|
5 |
|
if (\count($explodedVersion) > 3) |
244
|
|
|
{ |
245
|
|
|
return false; |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
try |
249
|
|
|
{ |
250
|
5 |
|
$validVersions = json_decode($this->filesystem->read('php.json'), true); |
251
|
|
|
} |
252
|
|
|
catch (FileNotFoundException $exception) |
253
|
|
|
{ |
254
|
|
|
throw new \RuntimeException('Missing PHP release listing', 500, $exception); |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
// Check that the version is in our valid release list |
258
|
5 |
|
if (!\in_array($version, $validVersions)) |
259
|
|
|
{ |
260
|
|
|
return false; |
261
|
|
|
} |
262
|
|
|
|
263
|
5 |
|
return $version; |
264
|
|
|
} |
265
|
|
|
} |
266
|
|
|
|
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.