Completed
Pull Request — master (#27)
by Michael
05:42 queued 03:55
created

StatsJsonView::buildResponseData()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 27
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 27
ccs 12
cts 12
cp 1
rs 8.439
c 0
b 0
f 0
cc 5
eloc 12
nc 8
nop 1
crap 5
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\Views\Stats;
10
11
use Joomla\StatsServer\Models\StatsModel;
12
use Joomla\View\BaseJsonView;
13
14
/**
15
 * JSON response for requesting the stats data.
16
 */
17
class StatsJsonView extends BaseJsonView
18
{
19
	/**
20
	 * Flag if the response should return the raw data.
21
	 *
22
	 * @var  boolean
23
	 */
24
	private $authorizedRaw = false;
25
26
	/**
27
	 * Array holding the valid data sources.
28
	 *
29
	 * @var  array
30
	 */
31
	private $dataSources = ['php_version', 'db_type', 'db_version', 'cms_version', 'server_os'];
32
33
	/**
34
	 * The data source to return.
35
	 *
36
	 * @var  string
37
	 */
38
	private $source = '';
39
40
	/**
41
	 * Count of the number of items.
42
	 *
43
	 * @var  integer
44
	 */
45
	private $totalItems = 0;
46
47
	/**
48
	 * Instantiate the view.
49
	 *
50
	 * @param   StatsModel  $model  The model object.
51
	 */
52
	public function __construct(StatsModel $model)
53
	{
54
		$this->model = $model;
0 ignored issues
show
Bug introduced by
The property model does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
55
	}
56
57
	/**
58
	 * Set whether the raw data should be returned.
59
	 *
60
	 * @param   bool  $authorizedRaw  Flag if the response should return the raw data.
61
	 *
62
	 * @return  void
63
	 */
64 1
	public function isAuthorizedRaw(bool $authorizedRaw)
65
	{
66 1
		$this->authorizedRaw = $authorizedRaw;
67 1
	}
68
69
	/**
70
	 * Method to render the view.
71
	 *
72
	 * @return  string  The rendered view.
73
	 */
74 4
	public function render()
75
	{
76 4
		$items = $this->model->getItems($this->source);
77
78
		// Null out the model now to free some memory
79 4
		$this->model = null;
80
81 4
		if ($this->source)
82
		{
83 2
			return $this->processSingleSource($items);
84
		}
85
86 2
		$php_version = [];
87 2
		$db_type     = [];
88 2
		$db_version  = [];
89 2
		$cms_version = [];
90 2
		$server_os   = [];
91
92
		// If we have the entire database, we have to loop within each group to put it all together
93 2
		foreach ($items as $group)
94
		{
95 2
			$this->totalItems = 0;
96
97 2
			foreach ($group as $item)
98
			{
99 2
				foreach ($this->dataSources as $source)
100
				{
101 2
					if (isset($item[$source]) && !is_null($item[$source]))
102
					{
103
						// Special case, if the server is empty then change the key to "unknown"
104 2
						if ($source === 'server_os' && empty($item[$source]))
105
						{
106 2
							$item[$source] = 'unknown';
107
						}
108
109 2
						${$source}[$item[$source]] = $item['count'];
110
111 2
						$this->totalItems += $item['count'];
112
					}
113
				}
114
			}
115
		}
116
117
		$data = [
118 2
			'php_version' => $php_version,
119 2
			'db_type'     => $db_type,
120 2
			'db_version'  => $db_version,
121 2
			'cms_version' => $cms_version,
122 2
			'server_os'   => $server_os,
123
		];
124
125 2
		$responseData = $this->buildResponseData($data);
126
127 2
		$responseData['total'] = $this->totalItems;
128
129 2
		$this->addData('data', $responseData);
130
131 2
		return parent::render();
132
	}
133
134
	/**
135
	 * Set the data source.
136
	 *
137
	 * @param   string  $source  Data source to return.
138
	 *
139
	 * @return  void
140
	 */
141 1
	public function setSource(string $source)
142
	{
143 1
		$this->source = $source;
144 1
	}
145
146
	/**
147
	 * Process the raw data into the response data format.
148
	 *
149
	 * @param   array  $data  The raw data array.
150
	 *
151
	 * @return  array
152
	 */
153 4
	private function buildResponseData(array $data) : array
154
	{
155 4
		$responseData = [];
156
157 4
		foreach ($data as $key => $value)
158
		{
159 4
			foreach ($value as $name => $count)
160
			{
161 4
				if ($name)
162
				{
163 4
					$responseData[$key][] = [
164 4
						'name'  => $name,
165 4
						'count' => $count
166
					];
167
				}
168
			}
169
		}
170
171 4
		unset($data);
172
173 4
		if (!$this->authorizedRaw)
174
		{
175 3
			$responseData = $this->sanitizeData($responseData);
176
		}
177
178 4
		return $responseData;
179
	}
180
181
	/**
182
	 * Process the response for a single data source.
183
	 *
184
	 * @param   array  $items  The source items to process.
185
	 *
186
	 * @return  string  The rendered view.
187
	 */
188 2
	private function processSingleSource(array $items) : string
189
	{
190
		$data = [
191 2
			${$this->source} = [],
192
		];
193
194 2
		$this->totalItems = 0;
195
196 2
		foreach ($items as $item)
197
		{
198
			// Special case, if the server is empty then change the key to "unknown"
199 2
			if ($this->source === 'server_os' && empty(trim($item[$this->source])))
200
			{
201 1
				$item[$this->source] = 'unknown';
202
			}
203
204 2
			$data[$this->source][$item[$this->source]] = $item['count'];
205 2
			$this->totalItems += $item['count'];
206
		}
207
208 2
		unset($generator);
209
210 2
		$responseData = $this->buildResponseData($data);
211
212 2
		$responseData['total'] = $this->totalItems;
213
214 2
		$this->addData('data', $responseData);
215
216 2
		return parent::render();
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (render() instead of processSingleSource()). Are you sure this is correct? If so, you might want to change this to $this->render().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
217
	}
218
219
	/**
220
	 * Sanitize the response data into summarized groups.
221
	 *
222
	 * @param   array  $responseData  The response data to sanitize.
223
	 *
224
	 * @return  array
225
	 */
226 3
	private function sanitizeData(array $responseData) : array
227
	{
228 3
		foreach ($responseData as $key => $dataGroup)
229
		{
230
			switch ($key)
231
			{
232 3
				case 'php_version':
233 2
				case 'db_version':
234 2
				case 'cms_version':
235
					// We're going to group by minor version branch here and convert to a percentage
236 2
					$counts = [];
237
238 2
					foreach ($dataGroup as $row)
239
					{
240 2
						$exploded = explode('.', $row['name']);
241 2
						$version  = $exploded[0] . '.' . (isset($exploded[1]) ? $exploded[1] : '0');
242
243
						// If the container does not exist, add it
244 2
						if (!isset($counts[$version]))
245
						{
246 2
							$counts[$version] = 0;
247
						}
248
249 2
						$counts[$version] += $row['count'];
250
					}
251
252 2
					$sanitizedData = [];
253
254 2 View Code Duplication
					foreach ($counts as $version => $count)
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...
255
					{
256 2
						$sanitizedData[$version] = round(($count / $this->totalItems) * 100, 2);
257
					}
258
259 2
					$responseData[$key] = $sanitizedData;
260
261 2
					break;
262
263 2
				case 'server_os':
264
					// We're going to group by operating system here
265 2
					$counts = [];
266
267 2
					foreach ($dataGroup as $row)
268
					{
269 2
						$fullOs = explode(' ', $row['name']);
270 2
						$os     = $fullOs[0];
271
272
						// If the container does not exist, add it
273 2
						if (!isset($counts[$os]))
274
						{
275 2
							$counts[$os] = 0;
276
						}
277
278 2
						$counts[$os] += $row['count'];
279
					}
280
281 2
					$sanitizedData = [];
282
283 2 View Code Duplication
					foreach ($counts as $os => $count)
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...
284
					{
285 2
						$sanitizedData[$os] = round(($count / $this->totalItems) * 100, 2);
286
					}
287
288 2
					$responseData[$key] = $sanitizedData;
289
290 2
					break;
291
292 1
				case 'db_type':
293
				default:
294
					// For now, group by the object name and figure out the percentages
295 1
					$sanitizedData = [];
296
297 1
					foreach ($dataGroup as $row)
298
					{
299 1
						$sanitizedData[$row['name']] = round(($row['count'] / $this->totalItems) * 100, 2);
300
					}
301
302 1
					$responseData[$key] = $sanitizedData;
303
304 3
					break;
305
			}
306
		}
307
308 3
		return $responseData;
309
	}
310
}
311