Completed
Push — master ( 4eed4e...7b7e84 )
by Michael
01:53
created

StatsJsonView::isRecent()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 3
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
crap 2
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
	 * Flag if the response should return the recently updated data.
35
	 *
36
	 * @var  boolean
37
	 */
38
	private $recent = false;
39
40
	/**
41
	 * The data source to return.
42
	 *
43
	 * @var  string
44
	 */
45
	private $source = '';
46
47
	/**
48
	 * Count of the number of items.
49
	 *
50
	 * @var  integer
51
	 */
52
	private $totalItems = 0;
53
54
	/**
55
	 * Instantiate the view.
56
	 *
57
	 * @param   StatsModel  $model  The model object.
58
	 */
59
	public function __construct(StatsModel $model)
60
	{
61
		$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...
62
	}
63
64
	/**
65
	 * Set whether the raw data should be returned.
66
	 *
67
	 * @param   bool  $authorizedRaw  Flag if the response should return the raw data.
68
	 *
69
	 * @return  void
70
	 */
71 1
	public function isAuthorizedRaw(bool $authorizedRaw)
72
	{
73 1
		$this->authorizedRaw = $authorizedRaw;
74 1
	}
75
76
	/**
77
	 * Set whether the recently updated data should be returned.
78
	 *
79
	 * @param   bool  $recent  Flag if the response should return the recently updated data.
80
	 *
81
	 * @return  void
82
	 */
83
	public function isRecent(bool $recent)
84
	{
85
		$this->recent = $recent;
86
	}
87
88
	/**
89
	 * Method to render the view.
90
	 *
91
	 * @return  string  The rendered view.
92
	 */
93 4
	public function render()
94
	{
95 4
		if ($this->recent)
96
		{
97
			$items = $this->model->getRecentlyUpdatedItems();
98
		}
99
		else
100
		{
101 4
			$items = $this->model->getItems($this->source);
102
		}
103
104
		// Null out the model now to free some memory
105 4
		$this->model = null;
106
107 4
		if ($this->source)
108
		{
109 2
			return $this->processSingleSource($items);
110
		}
111
112 2
		$php_version = [];
113 2
		$db_type     = [];
114 2
		$db_version  = [];
115 2
		$cms_version = [];
116 2
		$server_os   = [];
117
118
		// If we have the entire database, we have to loop within each group to put it all together
119 2
		foreach ($items as $group)
120
		{
121 2
			$this->totalItems = 0;
122
123 2
			foreach ($group as $item)
124
			{
125 2
				foreach ($this->dataSources as $source)
126
				{
127 2
					if (isset($item[$source]) && !is_null($item[$source]))
128
					{
129
						// Special case, if the server is empty then change the key to "unknown"
130 2
						if ($source === 'server_os' && empty($item[$source]))
131
						{
132 2
							$item[$source] = 'unknown';
133
						}
134
135 2
						${$source}[$item[$source]] = $item['count'];
136
137 2
						$this->totalItems += $item['count'];
138
					}
139
				}
140
			}
141
		}
142
143
		$data = [
144 2
			'php_version' => $php_version,
145 2
			'db_type'     => $db_type,
146 2
			'db_version'  => $db_version,
147 2
			'cms_version' => $cms_version,
148 2
			'server_os'   => $server_os,
149
		];
150
151 2
		$responseData = $this->buildResponseData($data);
152
153 2
		$responseData['total'] = $this->totalItems;
154
155 2
		$this->addData('data', $responseData);
156
157 2
		return parent::render();
158
	}
159
160
	/**
161
	 * Set the data source.
162
	 *
163
	 * @param   string  $source  Data source to return.
164
	 *
165
	 * @return  void
166
	 */
167 1
	public function setSource(string $source)
168
	{
169 1
		$this->source = $source;
170 1
	}
171
172
	/**
173
	 * Process the raw data into the response data format.
174
	 *
175
	 * @param   array  $data  The raw data array.
176
	 *
177
	 * @return  array
178
	 */
179 4
	private function buildResponseData(array $data) : array
180
	{
181 4
		$responseData = [];
182
183 4
		foreach ($data as $key => $value)
184
		{
185 4
			foreach ($value as $name => $count)
186
			{
187 4
				if ($name)
188
				{
189 4
					$responseData[$key][] = [
190 4
						'name'  => $name,
191 4
						'count' => $count
192
					];
193
				}
194
			}
195
		}
196
197 4
		unset($data);
198
199 4
		if (!$this->authorizedRaw)
200
		{
201 3
			$responseData = $this->sanitizeData($responseData);
202
		}
203
204 4
		return $responseData;
205
	}
206
207
	/**
208
	 * Process the response for a single data source.
209
	 *
210
	 * @param   array  $items  The source items to process.
211
	 *
212
	 * @return  string  The rendered view.
213
	 */
214 2
	private function processSingleSource(array $items) : string
215
	{
216
		$data = [
217 2
			${$this->source} = [],
218
		];
219
220 2
		$this->totalItems = 0;
221
222 2
		foreach ($items as $item)
223
		{
224
			// Special case, if the server is empty then change the key to "unknown"
225 2
			if ($this->source === 'server_os' && empty(trim($item[$this->source])))
226
			{
227 1
				$item[$this->source] = 'unknown';
228
			}
229
230 2
			$data[$this->source][$item[$this->source]] = $item['count'];
231 2
			$this->totalItems += $item['count'];
232
		}
233
234 2
		unset($generator);
235
236 2
		$responseData = $this->buildResponseData($data);
237
238 2
		$responseData['total'] = $this->totalItems;
239
240 2
		$this->addData('data', $responseData);
241
242 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...
243
	}
244
245
	/**
246
	 * Sanitize the response data into summarized groups.
247
	 *
248
	 * @param   array  $responseData  The response data to sanitize.
249
	 *
250
	 * @return  array
251
	 */
252 3
	private function sanitizeData(array $responseData) : array
253
	{
254 3
		foreach ($responseData as $key => $dataGroup)
255
		{
256
			switch ($key)
257
			{
258 3
				case 'php_version':
259 2
				case 'db_version':
260 2
				case 'cms_version':
261
					// We're going to group by minor version branch here and convert to a percentage
262 2
					$counts = [];
263
264 2
					foreach ($dataGroup as $row)
265
					{
266 2
						$exploded = explode('.', $row['name']);
267 2
						$version  = $exploded[0] . '.' . (isset($exploded[1]) ? $exploded[1] : '0');
268
269
						// If the container does not exist, add it
270 2
						if (!isset($counts[$version]))
271
						{
272 2
							$counts[$version] = 0;
273
						}
274
275 2
						$counts[$version] += $row['count'];
276
					}
277
278 2
					$sanitizedData = [];
279
280 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...
281
					{
282 2
						$sanitizedData[$version] = round(($count / $this->totalItems) * 100, 2);
283
					}
284
285 2
					$responseData[$key] = $sanitizedData;
286
287 2
					break;
288
289 2
				case 'server_os':
290
					// We're going to group by operating system here
291 2
					$counts = [];
292
293 2
					foreach ($dataGroup as $row)
294
					{
295 2
						$fullOs = explode(' ', $row['name']);
296 2
						$os     = $fullOs[0];
297
298
						// If the container does not exist, add it
299 2
						if (!isset($counts[$os]))
300
						{
301 2
							$counts[$os] = 0;
302
						}
303
304 2
						$counts[$os] += $row['count'];
305
					}
306
307 2
					$sanitizedData = [];
308
309 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...
310
					{
311 2
						$sanitizedData[$os] = round(($count / $this->totalItems) * 100, 2);
312
					}
313
314 2
					$responseData[$key] = $sanitizedData;
315
316 2
					break;
317
318 1
				case 'db_type':
319
				default:
320
					// For now, group by the object name and figure out the percentages
321 1
					$sanitizedData = [];
322
323 1
					foreach ($dataGroup as $row)
324
					{
325 1
						$sanitizedData[$row['name']] = round(($row['count'] / $this->totalItems) * 100, 2);
326
					}
327
328 1
					$responseData[$key] = $sanitizedData;
329
330 3
					break;
331
			}
332
		}
333
334 3
		return $responseData;
335
	}
336
}
337