Completed
Push — master ( d5cb5b...48be88 )
by Adam
02:24
created

Gravatar   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 424
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 64.2%

Importance

Changes 7
Bugs 0 Features 0
Metric Value
wmc 50
c 7
b 0
f 0
lcom 1
cbo 2
dl 0
loc 424
rs 8.6206
ccs 52
cts 81
cp 0.642

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