]> git.mxchange.org Git - friendica-addons.git/blob - libravatar/Services/Libravatar.php
Libravatar Addon
[friendica-addons.git] / libravatar / Services / Libravatar.php
1 <?php
2
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5 /**
6  * PHP support for the Libravatar.org service.
7  *
8  * PHP version 5
9  *
10  * The MIT License
11  *
12  * Copyright (c) 2011 Services_Libravatar committers.
13  *
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:
20  *
21  * The above copyright notice and this permission notice shall be included in
22  * all copies or substantial portions of the Software.
23  *
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
30  * THE SOFTWARE.
31  *
32  * @category  Services
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
40  */
41
42 /**
43  * PHP support for the Libravatar.org service.
44  *
45  * Using this class is easy. After including or requiring
46  * PEAR/libravatar.php simply do:
47  *  <code>
48  *   $libravatar = new Services_Libravatar();
49  *   $url = $libravatar->url('melissa@meldraweb.com');
50  *  </code>
51  *
52  *  This would populate $url with the string:
53  *  http://cdn.libravatar.org/avatar/4db84629c121f2d443d33bdb9fd149bc
54  *
55  * A complicated lookup using all the options is:
56  *  <code>
57  *   $libravatar = new Services_Libravatar();
58  *   $options = array();
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);
64  *  </code>
65  *
66  * @category  Services
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
74  */
75 class Services_Libravatar
76 {
77
78     /**
79      *  Composes a URL for the identifier and options passed in
80      *
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.
83      *
84      *  @param string $identifier a string of either an email address
85      *                            or an openid url
86      *  @param array  $options    an array of (bool) https, (string) algorithm
87      *                            (string) s or size, (string) d or default
88      *
89      *  @return  string  A string of a full URL for an avatar image
90      *
91      *  @since Method available since Release 0.1.0
92      */
93     public function url($identifier, $options = array())
94     {
95
96         // If no identifier has been passed, set it to a null.
97         // This way, there'll always be something returned.
98         if (!$identifier) {
99             $identifier = null;
100         }
101
102         $https = null;
103         if (isset($options['https']) && $options['https'] === true) {
104             $https = true;
105         }
106
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(
111                 $identifier,
112                 $https,
113                 $options['algorithm']
114             );
115         } else {
116             $identiferHash = $this->identiferHash($identifier, $https);
117         }
118
119         // Get the domain so we can determine the SRV stuff for federation
120         $domain = $this->domainGet($identifier, $https);
121
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);
126             $protocol = 'https';
127         } else {
128             $service  = $this->srvGet($domain);
129             $protocol = 'http';
130         }
131
132         // We no longer need these, and they will pollute our query string
133         unset($options['algorithm']);
134         unset($options['https']);
135
136         // If there are any $options left, we want to make those into a query
137         $params = null;
138         if (count($options) > 0) {
139             $params = '?' . http_build_query($options);
140         }
141
142         // Compose the URL from the pieces we generated
143         $url = $protocol . '://' . $service . '/avatar/' . $identiferHash . $params;
144
145         // Return the URL string
146         return $url;
147
148     }
149
150     /**
151      *  Create a hash of the identifier.
152      *
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.
156      *
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
160      *
161      *  @return string  A string hash of the identifier.
162      *
163      *  @since Method available since Release 0.1.0
164      */
165     protected function identiferHash($identifier, $https = false, $hash = 'md5')
166     {
167
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);
172         } else {
173
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://';
179                 } else {
180                     $protocol = 'http://';
181                 }
182                 $identifier = $protocol . $identifier;
183             }
184
185             // Is this an email address or an OpenID account
186             $filter = filter_var(
187                 $identifier,
188                 FILTER_VALIDATE_URL,
189                 FILTER_FLAG_PATH_REQUIRED
190             );
191
192             if ($filter) {
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
203                 ) {
204                     $hashurl .= ':' . $url['port'];
205                 }
206                 $hashurl .= $url['path'];
207                 return hash('sha256', $hashurl);
208             }
209         }
210     }
211
212     /**
213      *  Grab the domain from the identifier.
214      *
215      *  Extract the domain from the Email or OpenID.
216      *
217      *  @param string  $identifier A string of the email address or openid URL
218      *  @param boolean $https      If this is https, true.
219      *
220      *  @return string  A string of the domain to use
221      *
222      *  @since Method available since Release 0.1.0
223      */
224     protected function domainGet($identifier, $https = false)
225     {
226
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);
231             return $email[1];
232         } else {
233
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://';
239                 } else {
240                     $protocol = 'http://';
241                 }
242                 $identifier = $protocol . $identifier;
243             }
244
245             $filter = filter_var(
246                 $identifier,
247                 FILTER_VALIDATE_URL,
248                 FILTER_FLAG_PATH_REQUIRED
249             );
250
251             if ($filter) {
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
258                 ) {
259                     $domain .= ':' . $url['port'];
260                 }
261
262                 return $domain;
263             }
264         }
265     }
266
267     /**
268      *  Get the target to use.
269      *
270      *  Get the SRV record, filtered by priority and weight. If our domain
271      *  has no SRV records, fall back to Libravatar.org
272      *
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
276      *
277      *  @return string  The target URL.
278      *
279      *  @since Method available since Release 0.1.0
280      */
281     protected function srvGet($domain, $https = false)
282     {
283
284         // Are we going secure? Set up a fallback too.
285         if (isset($https) && $https === true) {
286             $subdomain = '_avatars-sec._tcp.';
287             $fallback  = 'seccdn.';
288         } else {
289             $subdomain = '_avatars._tcp.';
290             $fallback  = 'cdn.';
291         }
292
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);
296
297         // Did we get anything? No?
298         if (count($srv) == 0) {
299             // Then let's try Libravatar.org.
300             return $fallback . 'libravatar.org';
301         }
302
303         // Sort by the priority. We must get the lowest.
304         usort($srv, array($this, 'comparePriority'));
305
306         $top = $srv[0];
307         $sum = 0;
308
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
312         // the list."
313         shuffle($srv);
314         $srvs = array();
315         foreach ($srv as $s) {
316             if ($s['weight'] == 0) {
317                 array_unshift($srvs, $s);
318             } else {
319                 array_push($srvs, $s);
320             }
321         }
322
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
328                 // order."
329                 $pri[$sum] = $s;
330             }
331         }
332
333         // "Then choose a uniform random number between 0 and the sum computed
334         // (inclusive)"
335         $random = rand(0, $sum);
336
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) {
340             if ($k >= $random) {
341                 return $v['target'];
342             }
343         }
344     }
345
346     /**
347      *  Sorting function for record priorities.
348      *
349      *  @param mixed $a A mixed value passed by usort()
350      *  @param mixed $b A mixed value passed by usort()
351      *
352      *  @return mixed  The result of the comparison
353      *
354      *  @since Method available since Release 0.1.0
355      */
356     protected function comparePriority($a, $b)
357     {
358         return $a['pri'] - $b['pri'];
359     }
360
361 }
362
363 /*
364  * Local variables:
365  * tab-width: 4
366  * c-basic-offset: 4
367  * c-hanging-comment-ender-p: nil
368  * End:
369  */
370
371 ?>
372