3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
6 * PHP support for the Libravatar.org service.
12 * Copyright (c) 2011 Services_Libravatar committers.
14 * Permission is hereby granted, free of charge, to any person obtaining a copy
15 * of this software and associated documentation files (the "Software"), to deal
16 * in the Software without restriction, including without limitation the rights
17 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18 * copies of the Software, and to permit persons to whom the Software is
19 * furnished to do so, subject to the following conditions:
21 * The above copyright notice and this permission notice shall be included in
22 * all copies or substantial portions of the Software.
24 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
33 * @package Services_Libravatar
34 * @author Melissa Draper <melissa@meldraweb.com>
35 * @copyright 2011 Services_Libravatar committers.
36 * @license http://www.opensource.org/licenses/mit-license.html MIT License
37 * @version SVN: <package_version>
38 * @link http://pear.php.net/package/Services_Libravatar
39 * @since File available since Release 0.1.0
43 * PHP support for the Libravatar.org service.
45 * Using this class is easy. After including or requiring
46 * PEAR/libravatar.php simply do:
48 * $libravatar = new Services_Libravatar();
49 * $url = $libravatar->url('melissa@meldraweb.com');
52 * This would populate $url with the string:
53 * http://cdn.libravatar.org/avatar/4db84629c121f2d443d33bdb9fd149bc
55 * A complicated lookup using all the options is:
57 * $libravatar = new Services_Libravatar();
59 * $options['s'] = '40';
60 * $options['algorithm'] = 'sha256';
61 * $options['https'] = true;
62 * $options['d'] = 'http://upload.wikimedia.org/wikipedia/commons/a/af/Tux.png';
63 * $url = $libravatar->url('melissa@meldraweb.com', $options);
67 * @package Services_Libravatar
68 * @author Melissa Draper <melissa@meldraweb.com>
69 * @copyright 2011 Services_Libravatar committers.
70 * @license http://www.opensource.org/licenses/mit-license.html MIT License
71 * @version Release: <package_version>
72 * @link http://pear.php.net/package/Services_Libravatar
73 * @since Class available since Release 0.1.0
75 class Services_Libravatar
79 * Composes a URL for the identifier and options passed in
81 * Compose a full URL as specified by the Libravatar API, based on the
82 * email address or openid URL passed in, and the options specified.
84 * @param string $identifier a string of either an email address
86 * @param array $options an array of (bool) https, (string) algorithm
87 * (string) s or size, (string) d or default
89 * @return string A string of a full URL for an avatar image
91 * @since Method available since Release 0.1.0
93 public function url($identifier, $options = array())
96 // If no identifier has been passed, set it to a null.
97 // This way, there'll always be something returned.
103 if (isset($options['https']) && $options['https'] === true) {
107 // If the algorithm has been passed in $options, send it on.
108 // This will only affect email functionality.
109 if (isset($options['algorithm']) && is_string($options['algorithm'])) {
110 $identiferHash = $this->identiferHash(
113 $options['algorithm']
116 $identiferHash = $this->identiferHash($identifier, $https);
119 // Get the domain so we can determine the SRV stuff for federation
120 $domain = $this->domainGet($identifier, $https);
122 // If https has been specified in $options, make sure we make the
123 // correct SRV lookup
124 if (isset($options['https']) && $options['https'] === true) {
125 $service = $this->srvGet($domain, true);
128 $service = $this->srvGet($domain);
132 // We no longer need these, and they will pollute our query string
133 unset($options['algorithm']);
134 unset($options['https']);
136 // If there are any $options left, we want to make those into a query
138 if (count($options) > 0) {
139 $params = '?' . http_build_query($options);
142 // Compose the URL from the pieces we generated
143 $url = $protocol . '://' . $service . '/avatar/' . $identiferHash . $params;
145 // Return the URL string
151 * Create a hash of the identifier.
153 * Create a hash of the email address or openid passed in. Algorithm
154 * used for email address ONLY can be varied. Either md5 or sha256
155 * are supported by the Libravatar API. Will be ignored for openid.
157 * @param string $identifier A string of the email address or openid URL
158 * @param boolean $https If this is https, true.
159 * @param string $hash A string of the hash algorithm type to make
161 * @return string A string hash of the identifier.
163 * @since Method available since Release 0.1.0
165 protected function identiferHash($identifier, $https = false, $hash = 'md5')
168 if (filter_var($identifier, FILTER_VALIDATE_EMAIL)) {
169 // If email, we can select our algorithm. Default to md5 for
170 // gravatar fallback.
171 return hash($hash, $identifier);
174 // The protocol is important. If we're lacking it this will not be
175 // filtered. Add it per our preference in the options.
176 if (stripos($identifier, 'http') !== 0) {
177 if ($https === true) {
178 $protocol = 'https://';
180 $protocol = 'http://';
182 $identifier = $protocol . $identifier;
185 // Is this an email address or an OpenID account
186 $filter = filter_var(
189 FILTER_FLAG_PATH_REQUIRED
193 // If this is an OpenID, split the string and make sure the
194 // formatting is correct. See the Libravatar API for more info.
195 // http://wiki.libravatar.org/api/
196 $url = parse_url($identifier);
197 $hashurl = strtolower($url['scheme']) . '://' .
198 strtolower($url['host']);
199 if (isset($url['port']) && $url['scheme'] === 'http'
200 && $url['port'] != 80
201 || isset($url['port']) && $url['scheme'] === 'https'
202 && $url['port'] != 443
204 $hashurl .= ':' . $url['port'];
206 $hashurl .= $url['path'];
207 return hash('sha256', $hashurl);
213 * Grab the domain from the identifier.
215 * Extract the domain from the Email or OpenID.
217 * @param string $identifier A string of the email address or openid URL
218 * @param boolean $https If this is https, true.
220 * @return string A string of the domain to use
222 * @since Method available since Release 0.1.0
224 protected function domainGet($identifier, $https = false)
227 // What are we, email or openid? Split ourself up and get the
228 // important bit out.
229 if (filter_var($identifier, FILTER_VALIDATE_EMAIL)) {
230 $email = explode('@', $identifier);
234 // The protocol is important. If we're lacking it this will not be
235 // filtered. Add it per our preference in the options.
236 if ( ! strpos($identifier, 'http')) {
237 if ($https === true) {
238 $protocol = 'https://';
240 $protocol = 'http://';
242 $identifier = $protocol . $identifier;
245 $filter = filter_var(
248 FILTER_FLAG_PATH_REQUIRED
252 $url = parse_url($identifier);
253 $domain = $url['host'];
254 if (isset($url['port']) && $url['scheme'] === 'http'
255 && $url['port'] != 80
256 || isset($url['port']) && $url['scheme'] === 'https'
257 && $url['port'] != 443
259 $domain .= ':' . $url['port'];
268 * Get the target to use.
270 * Get the SRV record, filtered by priority and weight. If our domain
271 * has no SRV records, fall back to Libravatar.org
273 * @param string $domain A string of the domain we extracted from the
274 * provided identifer with domainGet()
275 * @param boolean $https Whether or not to look for https records
277 * @return string The target URL.
279 * @since Method available since Release 0.1.0
281 protected function srvGet($domain, $https = false)
284 // Are we going secure? Set up a fallback too.
285 if (isset($https) && $https === true) {
286 $subdomain = '_avatars-sec._tcp.';
287 $fallback = 'seccdn.';
289 $subdomain = '_avatars._tcp.';
293 // Lets try get us some records based on the choice of subdomain
294 // and the domain we had passed in.
295 $srv = dns_get_record($subdomain . $domain, DNS_SRV);
297 // Did we get anything? No?
298 if (count($srv) == 0) {
299 // Then let's try Libravatar.org.
300 return $fallback . 'libravatar.org';
303 // Sort by the priority. We must get the lowest.
304 usort($srv, array($this, 'comparePriority'));
309 // Try to adhere to RFC2782's weighting algorithm, page 3
310 // "arrange all SRV RRs (that have not been ordered yet) in any order,
311 // except that all those with weight 0 are placed at the beginning of
315 foreach ($srv as $s) {
316 if ($s['weight'] == 0) {
317 array_unshift($srvs, $s);
319 array_push($srvs, $s);
323 foreach ($srvs as $s) {
324 if ($s['pri'] == $top['pri']) {
325 // "Compute the sum of the weights of those RRs"
326 $sum += (int) $s['weight'];
327 // "and with each RR associate the running sum in the selected
333 // "Then choose a uniform random number between 0 and the sum computed
335 $random = rand(0, $sum);
337 // "and select the RR whose running sum value is the first in the selected
338 // order which is greater than or equal to the random number selected"
339 foreach ($pri as $k => $v) {
347 * Sorting function for record priorities.
349 * @param mixed $a A mixed value passed by usort()
350 * @param mixed $b A mixed value passed by usort()
352 * @return mixed The result of the comparison
354 * @since Method available since Release 0.1.0
356 protected function comparePriority($a, $b)
358 return $a['pri'] - $b['pri'];
367 * c-hanging-comment-ender-p: nil