Completed
Push — master ( 9d99f5...e10429 )
by Adam
11:21
created

Gravatar   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 421
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 64.63%

Importance

Changes 7
Bugs 0 Features 0
Metric Value
wmc 50
c 7
b 0
f 0
lcom 1
cbo 2
dl 0
loc 421
rs 8.6206
ccs 53
cts 82
cp 0.6463

20 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
A getEmailHash() 0 14 4
A setSize() 0 6 2
A getSize() 0 4 1
A isSizeValid() 0 8 3
A setExpiration() 0 4 1
B setDefaultImage() 0 24 4
A getDefaultImage() 0 12 4
A setMaxRating() 0 10 2
A getMaxRating() 0 8 3
A getImage() 0 4 1
A getImageType() 0 4 1
A usingSecureImages() 0 4 1
A enableSecureImages() 0 4 1
A disableSecureImages() 0 4 1
C get() 0 27 8
A buildUrl() 0 13 3
A exists() 0 14 3
A createTemplateHelpers() 0 4 1
B createUrl() 0 25 5

How to fix   Complexity   

Complex Class

Complex classes like Gravatar 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 Gravatar, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Gravatar.php
4
 *
5
 * @copyright      More in license.md
6
 * @license        http://www.ipublikuj.eu
7
 * @author         Adam Kadlec http://www.ipublikuj.eu
8
 * @package        iPublikuj:Gravatar!
9
 * @subpackage     common
10
 * @since          1.0.0
11
 *
12
 * @date           05.04.14
13
 */
14
15
declare(strict_types = 1);
16
17
namespace IPub\Gravatar;
18
19
use Nette;
20
use Nette\Http;
21
use Nette\Utils;
22
23
use IPub;
24
use IPub\Gravatar\Caching;
25
use IPub\Gravatar\Exceptions;
26
use IPub\Gravatar\Templating;
27
28
/**
29
 * Gravatar service
30
 *
31
 * @package        iPublikuj:Gravatar!
32
 * @subpackage     common
33
 *
34
 * @author         Adam Kadlec <[email protected]>
35
 */
36 1
final class Gravatar
37
{
38 1
	use Nette\SmartObject;
39
	
40
	/**
41
	 * @var string - URL constants for the avatar images
42
	 */
43
	const HTTP_URL = 'http://www.gravatar.com/avatar/';
44
	const HTTPS_URL = 'https://secure.gravatar.com/avatar/';
45
46
	/**
47
	 * @var int
48
	 */
49
	private $expiration = 172800; // two days
50
51
	/**
52
	 * The size to use for avatars.
53
	 *
54
	 * @var int
55
	 */
56
	private $size = 80;
57
58
	/**
59
	 * The default image to use
60
	 * Either a string of the gravatar-recognized default image "type" to use, a URL, or FALSE if using the...default gravatar default image (hah)
61
	 *
62
	 * @var mixed
63
	 */
64
	private $defaultImage = FALSE;
65
66
	/**
67
	 * The maximum rating to allow for the avatar.
68
	 *
69
	 * @var string
70
	 */
71
	private $maxRating = 'g';
72
73
	/**
74
	 * Should we use the secure (HTTPS) URL base?
75
	 *
76
	 * @var bool
77
	 */
78
	private $useSecureUrl = FALSE;
79
80
	/**
81
	 * @var Utils\Image
82
	 */
83
	private $image;
84
85
	/**
86
	 * @var string
87
	 */
88
	private $type;
89
90
	/**
91
	 * @var bool
92
	 */
93
	private $hashEmail = TRUE;
94
95
	/**
96
	 * @var Caching\Cache
97
	 */
98
	private $cache;
99
100
	/**
101
	 * @param Http\Request $httpRequest
102
	 * @param Caching\Cache $cache
103
	 */
104
	public function __construct(
105
		Http\Request $httpRequest,
106
		Caching\Cache $cache
107
	) {
108 1
		$this->useSecureUrl = $httpRequest->isSecured();
109
110
		// Init cache
111 1
		$this->cache = $cache;
112 1
	}
113
114
	/**
115
	 * Get the email hash to use (after cleaning the string)
116
	 *
117
	 * @param string|NULL $email
118
	 *
119
	 * @return string - The hashed form of the email, post cleaning
120
	 */
121
	public function getEmailHash(string $email = NULL) : string
122
	{
123
		// Tack the email hash onto the end.
124 1
		if ($this->hashEmail === TRUE && $email !== NULL) {
125
			// Using md5 as per gravatar docs
126 1
			return hash('md5', strtolower(trim($email)));
127
128
		} elseif ($email !== NULL) {
129
			return $email;
130
131
		} else {
132
			return str_repeat('0', 32);
133
		}
134
	}
135
136
	/**
137
	 * Set the avatar size to use
138
	 *
139
	 * @param int $size - The avatar size to use, must be less than 512 and greater than 0
140
	 *
141
	 * @return void
142
	 */
143
	public function setSize(int $size)
144
	{
145 1
		if ($this->isSizeValid($size)) {
146 1
			$this->size = $size;
147
		}
148 1
	}
149
150
	/**
151
	 * Get the currently set avatar size
152
	 *
153
	 * @return int
154
	 */
155
	public function getSize() : int
156
	{
157 1
		return $this->size;
158
	}
159
160
	/**
161
	 * @param int $size
162
	 *
163
	 * @return bool
164
	 *
165
	 * @throws Exceptions\InvalidArgumentException
166
	 */
167
	public function isSizeValid(int $size) : bool
168
	{
169 1
		if ($size > 512 || $size < 0) {
170
			throw new Exceptions\InvalidArgumentException('Size must be within 0 pixels and 512 pixels');
171
		}
172
173 1
		return TRUE;
174
	}
175
176
	/**
177
	 * Set image cache expiration
178
	 *
179
	 * @param int $expiration
180
	 *
181
	 * @return void
182
	 */
183
	public function setExpiration(int $expiration)
184
	{
185 1
		$this->expiration = $expiration;
186 1
	}
187
188
	/**
189
	 * Set the default image to use for avatars
190
	 *
191
	 * @param mixed $image - The default image to use. Use boolean FALSE for the gravatar default, a string containing a valid image URL, or a string specifying a recognized gravatar "default".
192
	 *
193
	 * @return void
194
	 *
195
	 * @throws Exceptions\InvalidArgumentException
196
	 */
197
	public function setDefaultImage($image)
198
	{
199
		// Quick check against boolean FALSE.
200 1
		if ($image === FALSE) {
201
			$this->defaultImage = FALSE;
202
203
		} else {
204
			// Check $image against recognized gravatar "defaults"
205
			// and if it doesn't match any of those we need to see if it is a valid URL.
206 1
			$_image = strtolower($image);
207
208 1
			if (in_array($_image, ['404', 'mm', 'identicon', 'monsterid', 'wavatar', 'retro'])) {
209 1
				$this->defaultImage = $_image;
210
211
			} else {
212
				if (filter_var($image, FILTER_VALIDATE_URL)) {
213
					$this->defaultImage = rawurlencode($image);
214
215
				} else {
216
					throw new Exceptions\InvalidArgumentException('The default image is not a valid gravatar "default" and is not a valid URL');
217
				}
218
			}
219
		}
220 1
	}
221
222
	/**
223
	 * Get the current default image setting
224
	 *
225
	 * @param string|NULL $defaultImage
226
	 *
227
	 * @return mixed - False if no default image set, string if one is set
228
	 */
229
	public function getDefaultImage(string $defaultImage = NULL)
230
	{
231 1
		if ($defaultImage !== NULL && in_array($defaultImage, ['404', 'mm', 'identicon', 'monsterid', 'wavatar', 'retro'])) {
232 1
			return $defaultImage;
233
		}
234
235 1
		if (filter_var($defaultImage, FILTER_VALIDATE_URL)) {
236
			return rawurldecode($defaultImage);
237
		}
238
239 1
		return $this->defaultImage;
240
	}
241
242
	/**
243
	 * Set the maximum allowed rating for avatars.
244
	 *
245
	 * @param string $rating - The maximum rating to use for avatars ('g', 'pg', 'r', 'x')
246
	 *
247
	 * @return void
248
	 *
249
	 * @throws Exceptions\InvalidArgumentException
250
	 */
251
	public function setMaxRating(string $rating)
252
	{
253 1
		$rating = strtolower($rating);
254
255 1
		if (!in_array($rating, ['g', 'pg', 'r', 'x'])) {
256
			throw new Exceptions\InvalidArgumentException(sprintf('Invalid rating "%s" specified, only "g", "pg", "r", or "x" are allowed to be used.', $rating));
257
		}
258
259 1
		$this->maxRating = $rating;
260 1
	}
261
262
	/**
263
	 * Get the current maximum allowed rating for avatars
264
	 *
265
	 * @param string|NULL $maxRating
266
	 *
267
	 * @return string - The string representing the current maximum allowed rating ('g', 'pg', 'r', 'x').
268
	 */
269
	public function getMaxRating(string $maxRating = NULL) : string
270
	{
271 1
		if ($maxRating !== NULL && in_array($maxRating, ['g', 'pg', 'r', 'x'])) {
272
			return $maxRating;
273
		}
274
275 1
		return $this->maxRating;
276
	}
277
278
	/**
279
	 * Returns the Nette\Image instance
280
	 *
281
	 * @return Utils\Image
282
	 */
283
	public function getImage() : Utils\Image
284
	{
285
		return $this->image;
286
	}
287
288
	/**
289
	 * Returns the type of a image
290
	 *
291
	 * @return string
292
	 */
293
	public function getImageType() : string
294
	{
295
		return $this->type;
296
	}
297
298
	/**
299
	 * Check if we are using the secure protocol for the image URLs
300
	 *
301
	 * @return bool - Are we supposed to use the secure protocol?
302
	 */
303
	public function usingSecureImages() : bool
304
	{
305
		return $this->useSecureUrl;
306
	}
307
308
	/**
309
	 * Enable the use of the secure protocol for image URLs
310
	 *
311
	 * @return void
312
	 */
313
	public function enableSecureImages()
314
	{
315 1
		$this->useSecureUrl = TRUE;
316 1
	}
317
318
	/**
319
	 * Disable the use of the secure protocol for image URLs
320
	 *
321
	 * @return void
322
	 */
323
	public function disableSecureImages()
324
	{
325
		$this->useSecureUrl = FALSE;
326
	}
327
328
	/**
329
	 * Create gravatar image
330
	 *
331
	 * @param string|NULL $email
332
	 * @param int|NULL $size
333
	 *
334
	 * @return Utils\Image
335
	 *
336
	 * @throws Exceptions\InvalidArgumentException
337
	 */
338
	public function get(string $email = NULL, int $size = NULL) : Utils\Image
339
	{
340
		// Set user email address
341
		if ($email !== NULL && !Utils\Validators::isEmail($email)) {
342
			throw new Exceptions\InvalidArgumentException('Inserted email is not valid email address');
343
		}
344
345
		if ($size === NULL || !$this->isSizeValid($size)) {
346
			$size = $this->getSize();
347
		}
348
349
		// Check if avatar is in cache
350
		if (!$gravatar = $this->cache->load($this->getEmailHash($email) . ($size ? '.' . $size : ''))) {
351
			// Get gravatar content
352
			$gravatar = @file_get_contents($this->buildUrl($email, $size));
353
354
			// Store facebook avatar url into cache
355
			$this->cache->save($this->getEmailHash($email) . ($size ? '.' . $size : ''), $gravatar, [
356
				Caching\Cache::EXPIRE => '7 days',
357
			]);
358
		}
359
360
		$this->image = Utils\Image::fromString($gravatar);
361
		$this->type = Utils\Image::JPEG;
362
363
		return $this->image;
364
	}
365
366
	/**
367
	 * Build the avatar URL based on the provided email address
368
	 *
369
	 * @param string|NULL $email
370
	 * @param int|NULL $size
371
	 * @param string|NULL $maxRating
372
	 * @param string|NULL $defaultImage
373
	 *
374
	 * @return string
375
	 *
376
	 * @throws Exceptions\InvalidArgumentException
377
	 */
378
	public function buildUrl(string $email = NULL, int $size = NULL, string $maxRating = NULL, string $defaultImage = NULL) : string
379
	{
380
		// Set user email address
381 1
		if ($email !== NULL && !Utils\Validators::isEmail($email)) {
382
			throw new Exceptions\InvalidArgumentException('Inserted email is not valid email address');
383
		}
384
385
		// Create base url
386 1
		$url = $this->createUrl($email, $size, $maxRating, $defaultImage);
387
388
		// And we're done.
389 1
		return $url->getAbsoluteUrl();
390
	}
391
392
	/**
393
	 * Checks if a gravatar exists for the email. It does this by checking for the presence of 404 in the header
394
	 * returned. Will return null if fsockopen fails, for example when the hostname cannot be resolved.
395
	 *
396
	 * @param string $email
397
	 *
398
	 * @return bool|NULL Boolean if we could connect, null if no connection to gravatar.com
399
	 */
400
	public function exists(string $email)
401
	{
402 1
		$path = $this->buildUrl($email, NULL, NULL, '404');
403
404 1
		if (!$sock = @fsockopen('gravatar.com', 80, $errorNo, $error)) {
405
			return NULL;
406
		}
407
408 1
		fputs($sock, "HEAD " . $path . " HTTP/1.0\r\n\r\n");
409 1
		$header = fgets($sock, 128);
410 1
		fclose($sock);
411
412 1
		return strpos($header, '404') ? FALSE : TRUE;
413
	}
414
415
	/**
416
	 * @return Templating\Helpers
417
	 */
418
	public function createTemplateHelpers() : Templating\Helpers
419
	{
420 1
		return new Templating\Helpers($this);
421
	}
422
423
	/**
424
	 * @param string $email
425
	 * @param int|NULL $size
426
	 * @param string|NULL $maxRating
427
	 * @param string|NULL $defaultImage
428
	 *
429
	 * @return Http\Url
430
	 */
431
	private function createUrl(string $email, int $size = NULL, string $maxRating = NULL, string $defaultImage = NULL) : Http\Url
432
	{
433
		// Tack the email hash onto the end.
434 1
		$emailHash = $this->getEmailHash($email);
435
436
		// Start building the URL, and deciding if we're doing this via HTTPS or HTTP.
437 1
		$url = new Nette\Http\Url(($this->useSecureUrl ? static::HTTPS_URL : static::HTTP_URL) . $emailHash);
438
439 1
		if ($size === NULL || !$this->isSizeValid($size)) {
440 1
			$size = $this->getSize();
441
		}
442
443
		// Time to figure out our request params
444
		$params = [
445 1
			's' => $size,
446 1
			'r' => $this->getMaxRating($maxRating),
447 1
			'd' => $this->getDefaultImage($defaultImage),
448 1
			'f' => is_null($email) ? 'y' : NULL,
449
		];
450
451
		// Add query params
452 1
		$url->appendQuery($params);
453
454 1
		return $url;
455
	}
456
}
457