Completed
Push — master ( d6ca0a...e6b28a )
by Adam
03:23
created

Gravatar::isSizeValid()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 3

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 8
ccs 1
cts 1
cp 1
rs 9.4285
cc 3
eloc 4
nc 2
nop 1
crap 3
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 extends \Nette\Object
37
{
38
	/**
39
	 * Define class name
40
	 */
41
	const CLASS_NAME = __CLASS__;
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
	public function setSize(int $size)
145
	{
146
		if ($this->isSizeValid($size)) {
147
			$this->size = $size;
148 1
		}
149
	}
150
151
	/**
152 1
	 * Get the currently set avatar size
153 1
	 *
154
	 * @return int
155
	 */
156
	public function getSize() : int
157
	{
158
		return $this->size;
159
	}
160
161
	/**
162
	 * @param int $size
163
	 *
164 1
	 * @return bool
165
	 *
166
	 * @throws Exceptions\InvalidArgumentException
167
	 */
168 1
	public function isSizeValid(int $size) : bool
169
	{
170
		if ($size > 512 || $size < 0) {
171
			throw new Exceptions\InvalidArgumentException('Size must be within 0 pixels and 512 pixels');
172
		}
173
174
		return TRUE;
175
	}
176
177
	/**
178 1
	 * Set image cache expiration
179 1
	 *
180
	 * @param int $expiration
181
	 */
182
	public function setExpiration(int $expiration)
183
	{
184
		$this->expiration = $expiration;
185
	}
186
187
	/**
188
	 * Set the default image to use for avatars
189
	 *
190
	 * @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".
191 1
	 *
192
	 * @throws Exceptions\InvalidArgumentException
193
	 */
194
	public function setDefaultImage($image)
195
	{
196
		// Quick check against boolean FALSE.
197 1
		if ($image === FALSE) {
198
			$this->defaultImage = FALSE;
199 1
200 1
		} else {
201
			// Check $image against recognized gravatar "defaults"
202
			// and if it doesn't match any of those we need to see if it is a valid URL.
203
			$_image = strtolower($image);
204
205
			if (in_array($_image, ['404', 'mm', 'identicon', 'monsterid', 'wavatar', 'retro'])) {
206
				$this->defaultImage = $_image;
207
208
			} else {
209
				if (filter_var($image, FILTER_VALIDATE_URL)) {
210
					$this->defaultImage = rawurlencode($image);
211 1
212
				} else {
213
					throw new Exceptions\InvalidArgumentException('The default image is not a valid gravatar "default" and is not a valid URL');
214
				}
215
			}
216
		}
217
	}
218
219
	/**
220
	 * Get the current default image setting
221
	 *
222 1
	 * @param string|NULL $defaultImage
223 1
	 *
224
	 * @return mixed - False if no default image set, string if one is set
225
	 */
226 1
	public function getDefaultImage(string $defaultImage = NULL)
227
	{
228
		if ($defaultImage !== NULL && in_array($defaultImage, ['404', 'mm', 'identicon', 'monsterid', 'wavatar', 'retro'])) {
229
			return $defaultImage;
230 1
		}
231
232
		if (filter_var($defaultImage, FILTER_VALIDATE_URL)) {
233
			return rawurldecode($defaultImage);
234
		}
235
236
		return $this->defaultImage;
237
	}
238
239
	/**
240
	 * Set the maximum allowed rating for avatars.
241
	 *
242 1
	 * @param string $rating - The maximum rating to use for avatars ('g', 'pg', 'r', 'x').
243
	 *
244 1
	 * @throws Exceptions\InvalidArgumentException
245
	 */
246
	public function setMaxRating(string $rating)
247
	{
248 1
		$rating = strtolower($rating);
249 1
250
		if (!in_array($rating, ['g', 'pg', 'r', 'x'])) {
251
			throw new Exceptions\InvalidArgumentException(sprintf('Invalid rating "%s" specified, only "g", "pg", "r", or "x" are allowed to be used.', $rating));
252
		}
253
254
		$this->maxRating = $rating;
255
	}
256
257
	/**
258
	 * Get the current maximum allowed rating for avatars
259
	 *
260 1
	 * @param string|NULL $maxRating
261
	 *
262
	 * @return string - The string representing the current maximum allowed rating ('g', 'pg', 'r', 'x').
263
	 */
264 1
	public function getMaxRating(string $maxRating = NULL) : string
265
	{
266
		if ($maxRating !== NULL && in_array($maxRating, ['g', 'pg', 'r', 'x'])) {
267
			return $maxRating;
268
		}
269
270
		return $this->maxRating;
271
	}
272
273
	/**
274
	 * Returns the Nette\Image instance
275
	 *
276
	 * @return Utils\Image
277
	 */
278
	public function getImage() : Utils\Image
279
	{
280
		return $this->image;
281
	}
282
283
	/**
284
	 * Returns the type of a image
285
	 *
286
	 * @return string
287
	 */
288
	public function getImageType() : string
289
	{
290
		return $this->type;
291
	}
292
293
	/**
294
	 * Check if we are using the secure protocol for the image URLs
295
	 *
296
	 * @return bool - Are we supposed to use the secure protocol?
297
	 */
298
	public function usingSecureImages() : bool
299
	{
300
		return $this->useSecureUrl;
301
	}
302 1
303 1
	/**
304
	 * Enable the use of the secure protocol for image URLs
305
	 */
306
	public function enableSecureImages()
307
	{
308
		$this->useSecureUrl = TRUE;
309
	}
310
311
	/**
312
	 * Disable the use of the secure protocol for image URLs
313
	 */
314
	public function disableSecureImages()
315
	{
316
		$this->useSecureUrl = FALSE;
317
	}
318
319
	/**
320
	 * Create gravatar image
321
	 *
322
	 * @param string|NULL $email
323
	 * @param int|NULL $size
324
	 *
325
	 * @return Utils\Image
326
	 *
327
	 * @throws Exceptions\InvalidArgumentException
328
	 */
329
	public function get(string $email = NULL, int $size = NULL) : Utils\Image
330
	{
331
		// Set user email address
332
		if ($email !== NULL && !Utils\Validators::isEmail($email)) {
333
			throw new Exceptions\InvalidArgumentException('Inserted email is not valid email address');
334
		}
335
336
		if (!$size || !$this->isSizeValid($size)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $size of type null|integer is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
337
			$size = NULL;
338
		}
339
340
		// Check if avatar is in cache
341
		if (!$gravatar = $this->cache->load($this->getEmailHash($email) . ($size ? '.' . $size : ''))) {
342
			// Get gravatar content
343
			$gravatar = @file_get_contents($this->buildUrl($email, $size));
344
345
			// Store facebook avatar url into cache
346
			$this->cache->save($this->getEmailHash($email) . ($size ? '.' . $size : ''), $gravatar, [
347
				Caching\Cache::EXPIRE => '7 days',
348
			]);
349
		}
350
351
		$this->image = Utils\Image::fromString($gravatar);
352
		$this->type = Utils\Image::JPEG;
353
354
		return $this->image;
355
	}
356
357
	/**
358
	 * Build the avatar URL based on the provided email address
359
	 *
360
	 * @param string|NULL $email
361
	 * @param int|NULL $size
362 1
	 * @param string|NULL $maxRating
363
	 * @param string|NULL $defaultImage
364
	 *
365
	 * @return string
366
	 *
367 1
	 * @throws Exceptions\InvalidArgumentException
368
	 */
369
	public function buildUrl(string $email = NULL, int $size = NULL, string $maxRating = NULL, string $defaultImage = NULL) : string
370 1
	{
371
		// Set user email address
372
		if ($email !== NULL && !Utils\Validators::isEmail($email)) {
373
			throw new Exceptions\InvalidArgumentException('Inserted email is not valid email address');
374
		}
375
376
		// Create base url
377
		$url = $this->createUrl($email, $size, $maxRating, $defaultImage);
378
379
		// And we're done.
380
		return $url->getAbsoluteUrl();
381
	}
382
383 1
	/**
384
	 * Checks if a gravatar exists for the email. It does this by checking for the presence of 404 in the header
385 1
	 * returned. Will return null if fsockopen fails, for example when the hostname cannot be resolved.
386
	 *
387
	 * @param string $email
388
	 *
389 1
	 * @return bool|NULL Boolean if we could connect, null if no connection to gravatar.com
390 1
	 */
391 1
	public function exists(string $email)
392
	{
393 1
		$path = $this->buildUrl($email, NULL, NULL, '404');
394
395
		if (!$sock = @fsockopen('gravatar.com', 80, $errorNo, $error)) {
396
			return NULL;
397
		}
398
399
		fputs($sock, "HEAD " . $path . " HTTP/1.0\r\n\r\n");
400
		$header = fgets($sock, 128);
401
		fclose($sock);
402
403
		return strpos($header, '404') ? FALSE : TRUE;
404
	}
405
406
	/**
407
	 * @return Templating\Helpers
408
	 */
409
	public function createTemplateHelpers() : Templating\Helpers
410
	{
411
		return new Templating\Helpers($this);
412
	}
413
414
	/**
415 1
	 * @param string $email
416
	 * @param int|NULL $size
417
	 * @param string|NULL $maxRating
418 1
	 * @param string|NULL $defaultImage
419
	 *
420
	 * @return Http\Url
421
	 */
422 1
	private function createUrl(string $email, int $size = NULL, string $maxRating = NULL, string $defaultImage = NULL) : Http\Url
423 1
	{
424 1
		// Tack the email hash onto the end.
425 1
		$emailHash = $this->getEmailHash($email);
426
427
		// Start building the URL, and deciding if we're doing this via HTTPS or HTTP.
428
		$url = new Nette\Http\Url(($this->useSecureUrl ? static::HTTPS_URL : static::HTTP_URL) . $emailHash);
429 1
430
		if (!$size || !$this->isSizeValid($size)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $size of type null|integer is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
431 1
			$size = NULL;
432
		}
433 1
434
		// Time to figure out our request params
435
		$params = [
436
			's' => $size,
437
			'r' => $this->getMaxRating($maxRating),
438
			'd' => $this->getDefaultImage($defaultImage),
439
			'f' => is_null($email) ? 'y' : NULL,
440
		];
441
442
		// Add query params
443
		$url->appendQuery($params);
444
445
		return $url;
446
	}
447
}
448