Gravatar::buildUrl()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 3.1406

Importance

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