Services_Libravatar
[ class tree: Services_Libravatar ] [ index: Services_Libravatar ] [ all elements ]

Source for file Libravatar.php

Documentation is available at Libravatar.php

  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
  3. /**
  4.  * PHP support for the Libravatar.org service.
  5.  *
  6.  * PHP version 5
  7.  *
  8.  * The MIT License
  9.  *
  10.  * Copyright (c) 2011 Services_Libravatar committers.
  11.  *
  12.  * Permission is hereby granted, free of charge, to any person obtaining a copy
  13.  * of this software and associated documentation files (the "Software"), to deal
  14.  * in the Software without restriction, including without limitation the rights
  15.  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  16.  * copies of the Software, and to permit persons to whom the Software is
  17.  * furnished to do so, subject to the following conditions:
  18.  *
  19.  * The above copyright notice and this permission notice shall be included in
  20.  * all copies or substantial portions of the Software.
  21.  *
  22.  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  23.  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  24.  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  25.  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  26.  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  27.  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  28.  * THE SOFTWARE.
  29.  *
  30.  * @category  Services
  31.  * @package   Services_Libravatar
  32.  * @author    Melissa Draper <melissa@meldraweb.com>
  33.  * @copyright 2011 Services_Libravatar committers.
  34.  * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
  35.  * @link      http://pear.php.net/package/Services_Libravatar
  36.  * @since     File available since Release 0.1.0
  37.  */
  38.  
  39. /**
  40.  * PHP support for the Libravatar.org service.
  41.  *
  42.  * Using this class is easy. After including or requiring
  43.  * Services/Libravatar.php simply do:
  44.  * <code>
  45.  * $libravatar = new Services_Libravatar();
  46.  * $url = $libravatar->getUrl('melissa@meldraweb.com');
  47.  * </code>
  48.  *
  49.  * This would populate $url with the string:
  50.  * <code>
  51.  * http://cdn.libravatar.org/avatar/4db84629c121f2d443d33bdb9fd149bc
  52.  * </code>
  53.  *
  54.  * A complicated lookup using all the options is:
  55.  * <code>
  56.  * $libravatar = new Services_Libravatar();
  57.  * $libravatar->setSize(40);
  58.  * $libravatar->setAlgorithm('sha256');
  59.  * $libravatar->setHttps(true);
  60.  * $libravatar->setDefault(
  61.  *     'http://upload.wikimedia.org/wikipedia/commons/a/af/Tux.png'
  62.  * );
  63.  * $url = $libravatar->getUrl('melissa@meldraweb.com');
  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: 0.2.3
  72.  * @link      http://pear.php.net/package/Services_Libravatar
  73.  * @since     Class available since Release 0.1.0
  74.  */
  75. {
  76.     /**
  77.      * Hashing algorithm to use
  78.      *
  79.      * @var string 
  80.      * @see processAlgorithm()
  81.      * @see setAlgorithm()
  82.      */
  83.     protected $algorithm = 'md5';
  84.  
  85.     /**
  86.      * Default image URL to use
  87.      *
  88.      * @var string 
  89.      * @see processDefault()
  90.      * @see setDefault()
  91.      */
  92.     protected $default;
  93.  
  94.     /**
  95.      * If HTTPS URLs should be used
  96.      *
  97.      * @var boolean 
  98.      * @see detectHttps()
  99.      * @see setHttps()
  100.      */
  101.     protected $https;
  102.  
  103.     /**
  104.      * Image size in pixels
  105.      *
  106.      * @var integer 
  107.      * @see processSize()
  108.      * @see setSize()
  109.      */
  110.     protected $size;
  111.  
  112.  
  113.     /**
  114.      * Composes a URL for the identifier and options passed in
  115.      *
  116.      * Compose a full URL as specified by the Libravatar API, based on the
  117.      * email address or openid URL passed in, and the options specified.
  118.      *
  119.      * @param string $identifier a string of either an email address
  120.      *                            or an openid url
  121.      * @param array  $options    an array of (bool) https, (string) algorithm
  122.      *                            (string) size, (string) default.
  123.      *                            See the set* methods.
  124.      *
  125.      * @return string A string of a full URL for an avatar image
  126.      *
  127.      * @since Method available since Release 0.2.0
  128.      * @deprecated Use getUrl() instead
  129.      */
  130.     public function url($identifier$options = array())
  131.     {
  132.         return $this->getUrl($identifier$options);
  133.     }
  134.  
  135.     /**
  136.      * Composes a URL for the identifier and options passed in
  137.      *
  138.      * Compose a full URL as specified by the Libravatar API, based on the
  139.      * email address or openid URL passed in, and the options specified.
  140.      *
  141.      * @param string $identifier a string of either an email address
  142.      *                            or an openid url
  143.      * @param array  $options    an array of (bool) https, (string) algorithm
  144.      *                            (string) size, (string) default.
  145.      *                            See the set* methods.
  146.      *
  147.      * @return string A string of a full URL for an avatar image
  148.      *
  149.      * @since  Method available since Release 0.2.0
  150.      * @throws InvalidArgumentException When an invalid option is passed
  151.      */
  152.     public function getUrl($identifier$options = array())
  153.     {
  154.         // If no identifier has been passed, set it to a null.
  155.         // This way, there'll always be something returned.
  156.         if (!$identifier{
  157.             $identifier = null;
  158.         else {
  159.             $identifier $this->normalizeIdentifier($identifier);
  160.         }
  161.  
  162.         // Load all options
  163.         $options $this->checkOptionsArray($options);
  164.         $https $this->https;
  165.         if (isset($options['https'])) {
  166.             $https = (bool)$options['https'];
  167.         }
  168.  
  169.         $algorithm $this->algorithm;
  170.         if (isset($options['algorithm'])) {
  171.             $algorithm $this->processAlgorithm($options['algorithm']);
  172.         }
  173.  
  174.         $default $this->default;
  175.         if (isset($options['default'])) {
  176.             $default $this->processDefault($options['default']);
  177.         }
  178.         $size $this->size;
  179.         if (isset($options['size'])) {
  180.             $size $this->processSize($options['size']);
  181.         }
  182.  
  183.  
  184.         $identifierHash $this->identifierHash($identifier$algorithm);
  185.  
  186.         // Get the domain so we can determine the SRV stuff for federation
  187.         $domain $this->domainGet($identifier);
  188.  
  189.         // If https has been specified in $options, make sure we make the
  190.         // correct SRV lookup
  191.         $service  $this->srvGet($domain$https);
  192.         $protocol $https 'https' 'http';
  193.  
  194.         $params = array();
  195.         if ($size !== null{
  196.             $params['size'$size;
  197.         }
  198.         if ($default !== null{
  199.             $params['default'$default;
  200.         }
  201.         $paramString '';
  202.         if (count($params> 0{
  203.             $paramString '?' http_build_query($params);
  204.         }
  205.  
  206.         // Compose the URL from the pieces we generated
  207.         $url $protocol '://' $service '/avatar/' $identifierHash
  208.             . $paramString;
  209.  
  210.         // Return the URL string
  211.         return $url;
  212.     }
  213.  
  214.     /**
  215.      * Checks the options array and verify that only allowed options are in it.
  216.      *
  217.      * @param array $options Array of options for getUrl()
  218.      *
  219.      * @return void 
  220.      * @throws Exception When an invalid option is used
  221.      */
  222.     protected function checkOptionsArray($options)
  223.     {
  224.         //this short options are deprecated!
  225.         if (isset($options['s'])) {
  226.             $options['size'$options['s'];
  227.             unset($options['s']);
  228.         }
  229.         if (isset($options['d'])) {
  230.             $options['default'$options['d'];
  231.             unset($options['d']);
  232.         }
  233.  
  234.         $allowedOptions = array(
  235.             'algorithm' => true,
  236.             'default'   => true,
  237.             'https'     => true,
  238.             'size'      => true,
  239.         );
  240.         foreach ($options as $key => $value{
  241.             if (!isset($allowedOptions[$key])) {
  242.                 throw new InvalidArgumentException(
  243.                     'Invalid option in array: ' $key
  244.                 );
  245.             }
  246.         }
  247.  
  248.         return $options;
  249.     }
  250.  
  251.     /**
  252.      * Normalizes the identifier (E-mail address or OpenID)
  253.      *
  254.      * @param string $identifier E-Mail address or OpenID
  255.      *
  256.      * @return string Normalized identifier
  257.      */
  258.     protected function normalizeIdentifier($identifier)
  259.     {
  260.         if (filter_var($identifierFILTER_VALIDATE_EMAIL)) {
  261.             return strtolower($identifier);
  262.         else {
  263.             return self::normalizeOpenId($identifier);
  264.         }
  265.     }
  266.  
  267.     /**
  268.      * Create a hash of the identifier.
  269.      *
  270.      * Create a hash of the email address or openid passed in. Algorithm
  271.      * used for email address ONLY can be varied. Either md5 or sha256
  272.      * are supported by the Libravatar API. Will be ignored for openid.
  273.      *
  274.      * @param string $identifier A string of the email address or openid URL
  275.      * @param string $hash       A string of the hash algorithm type to make
  276.      *                            Uses the php implementation of hash()
  277.      *                            MD5 preferred for Gravatar fallback
  278.      *
  279.      * @return string A string hash of the identifier.
  280.      *
  281.      * @since Method available since Release 0.1.0
  282.      */
  283.     protected function identifierHash($identifier$hash 'md5')
  284.     {
  285.         if (filter_var($identifierFILTER_VALIDATE_EMAIL|| $identifier === null{
  286.             // If email, we can select our algorithm. Default to md5 for
  287.             // gravatar fallback.
  288.             return hash($hash$identifier);
  289.         }
  290.  
  291.         //no email, so the identifier has to be an OpenID
  292.         return hash('sha256'$identifier);
  293.     }
  294.  
  295.     /**
  296.      * Normalizes an identifier (URI or XRI)
  297.      *
  298.      * @param mixed $identifier URI or XRI to be normalized
  299.      *
  300.      * @return string Normalized Identifier.
  301.      *                 Empty string when the OpenID is invalid.
  302.      *
  303.      * @internal Adapted from OpenID::normalizeIdentifier()
  304.      */
  305.     public static function normalizeOpenId($identifier)
  306.     {
  307.         // XRI
  308.         if (preg_match('@^xri://@i'$identifier)) {
  309.             return preg_replace('@^xri://@i'''$identifier);
  310.         }
  311.  
  312.         if (in_array($identifier[0]array('=''@''+''$''!'))) {
  313.             return $identifier;
  314.         }
  315.  
  316.         // URL
  317.         if (!preg_match('@^http[s]?://@i'$identifier)) {
  318.             $identifier 'http://' $identifier;
  319.         }
  320.         if (strpos($identifier'/'8=== false{
  321.             $identifier .= '/';
  322.         }
  323.         if (!filter_var($identifierFILTER_VALIDATE_URL)) {
  324.             return '';
  325.         }
  326.  
  327.         $parts parse_url($identifier);
  328.         $parts['scheme'strtolower($parts['scheme']);
  329.         $parts['host']   strtolower($parts['host']);
  330.  
  331.         //http://openid.net/specs/openid-authentication-2_0.html#normalization
  332.         return $parts['scheme''://'
  333.             . (isset($parts['user']$parts['user''')
  334.             . (isset($parts['pass']':' $parts['pass''')
  335.             . (isset($parts['user']|| isset($parts['pass']'@' '')
  336.             . $parts['host']
  337.             . (
  338.                 (isset($parts['port'])
  339.                 && $parts['scheme'=== 'http' && $parts['port'!= 80)
  340.                 || (isset($parts['port'])
  341.                 && $parts['scheme'=== 'https' && $parts['port'!= 443)
  342.                 ? ':' $parts['port'''
  343.             )
  344.             . $parts['path']
  345.             . (isset($parts['query']'?' $parts['query''');
  346.             //leave out fragment as requested by the spec
  347.     }
  348.  
  349.     /**
  350.      * Grab the domain from the identifier.
  351.      *
  352.      * Extract the domain from the Email or OpenID.
  353.      *
  354.      * @param string $identifier A string of the email address or openid URL
  355.      *
  356.      * @return string A string of the domain to use
  357.      *
  358.      * @since Method available since Release 0.1.0
  359.      */
  360.     protected function domainGet($identifier)
  361.     {
  362.         if ($identifier === null{
  363.             return null;
  364.         }
  365.  
  366.         // What are we, email or openid? Split ourself up and get the
  367.         // important bit out.
  368.         if (filter_var($identifierFILTER_VALIDATE_EMAIL)) {
  369.             $email explode('@'$identifier);
  370.             return $email[1];
  371.         }
  372.  
  373.         //OpenID
  374.         $url parse_url($identifier);
  375.         if (!isset($url['host'])) {
  376.             //invalid URL
  377.             return null;
  378.         }
  379.  
  380.         $domain $url['host'];
  381.         if (isset($url['port']&& $url['scheme'=== 'http'
  382.             && $url['port'!= 80
  383.             || isset($url['port']&& $url['scheme'=== 'https'
  384.             && $url['port'!= 443
  385.         {
  386.             $domain .= ':' $url['port'];
  387.         }
  388.  
  389.         return $domain;
  390.     }
  391.  
  392.     /**
  393.      * Get the target to use.
  394.      *
  395.      * Get the SRV record, filtered by priority and weight. If our domain
  396.      * has no SRV records, fall back to Libravatar.org
  397.      *
  398.      * @param string  $domain A string of the domain we extracted from the
  399.      *                         provided identifier with domainGet()
  400.      * @param boolean $https  Whether or not to look for https records
  401.      *
  402.      * @return string The target URL.
  403.      *
  404.      * @since Method available since Release 0.1.0
  405.      */
  406.     protected function srvGet($domain$https = false)
  407.     {
  408.         // Are we going secure? Set up a fallback too.
  409.         if (isset($https&& $https === true{
  410.             $subdomain '_avatars-sec._tcp.';
  411.             $fallback  'seccdn.';
  412.             $port      = 443;
  413.         else {
  414.             $subdomain '_avatars._tcp.';
  415.             $fallback  'cdn.';
  416.             $port      = 80;
  417.         }
  418.  
  419.         if ($domain === null{
  420.             // No domain means invalid email address/openid
  421.             return $fallback 'libravatar.org';
  422.         }
  423.  
  424.         // Lets try get us some records based on the choice of subdomain
  425.         // and the domain we had passed in.
  426.         $srv dns_get_record($subdomain $domainDNS_SRV);
  427.  
  428.         // Did we get anything? No?
  429.         if (count($srv== 0{
  430.             // Then let's try Libravatar.org.
  431.             return $fallback 'libravatar.org';
  432.         }
  433.  
  434.         // Sort by the priority. We must get the lowest.
  435.         usort($srvarray($this'comparePriority'));
  436.  
  437.         $top $srv[0];
  438.         $sum = 0;
  439.  
  440.         // Try to adhere to RFC2782's weighting algorithm, page 3
  441.         // "arrange all SRV RRs (that have not been ordered yet) in any order,
  442.         // except that all those with weight 0 are placed at the beginning of
  443.         // the list."
  444.         shuffle($srv);
  445.         $srvs = array();
  446.         foreach ($srv as $s{
  447.             if ($s['weight'== 0{
  448.                 array_unshift($srvs$s);
  449.             else {
  450.                 array_push($srvs$s);
  451.             }
  452.         }
  453.  
  454.         foreach ($srvs as $s{
  455.             if ($s['pri'== $top['pri']{
  456.                 // "Compute the sum of the weights of those RRs"
  457.                 $sum += (int) $s['weight'];
  458.                 // "and with each RR associate the running sum in the selected
  459.                 // order."
  460.                 $pri[$sum$s;
  461.             }
  462.         }
  463.  
  464.         // "Then choose a uniform random number between 0 and the sum computed
  465.         // (inclusive)"
  466.         $random rand(0$sum);
  467.  
  468.         // "and select the RR whose running sum value is the first in the selected
  469.         // order which is greater than or equal to the random number selected"
  470.         foreach ($pri as $k => $v{
  471.             if ($k >= $random{
  472.                 $target $v['target'];
  473.                 if ($v['port'!== $port{
  474.                     $target.= ':'$v['port'];
  475.                 }
  476.                 return $target;
  477.             }
  478.         }
  479.     }
  480.  
  481.     /**
  482.      * Sorting function for record priorities.
  483.      *
  484.      * @param mixed $a A mixed value passed by usort()
  485.      * @param mixed $b A mixed value passed by usort()
  486.      *
  487.      * @return mixed The result of the comparison
  488.      *
  489.      * @since Method available since Release 0.1.0
  490.      */
  491.     protected function comparePriority($a$b)
  492.     {
  493.         return $a['pri'$b['pri'];
  494.     }
  495.  
  496.     /**
  497.      * Automatically set the https option depending on the current connection
  498.      * value.
  499.      *
  500.      * If the current connection is HTTPS, the https options is activated.
  501.      * If it is not HTTPS, the https option is deactivated.
  502.      *
  503.      * @return self 
  504.      */
  505.     public function detectHttps()
  506.     {
  507.         $this->setHttps(
  508.             isset($_SERVER['HTTPS']&& $_SERVER['HTTPS']
  509.         );
  510.  
  511.         return $this;
  512.     }
  513.  
  514.     /**
  515.      * Verify and cast the email address hashing algorithm to use.
  516.      *
  517.      * @param string $algorithm Algorithm to use, "sha256" or "md5".
  518.      *
  519.      * @return string Algorithm
  520.      *
  521.      * @throws InvalidArgumentException When an unsupported algorithm is given
  522.      */
  523.     protected function processAlgorithm($algorithm)
  524.     {
  525.         $algorithm = (string)$algorithm;
  526.         if ($algorithm !== 'md5' && $algorithm !== 'sha256'{
  527.             throw new InvalidArgumentException(
  528.                 'Only md5 and sha256 hashing supported'
  529.             );
  530.         }
  531.  
  532.         return $algorithm;
  533.     }
  534.  
  535.     /**
  536.      * Verify and cast the default URL to use when no avatar image can be found.
  537.      * If none is set, the libravatar logo is returned.
  538.      *
  539.      * @param string $url Full URL to use OR one of the following:
  540.      *                     - "404" - give a "404 File not found" instead of an image
  541.      *                     - "mm"
  542.      *                     - "identicon"
  543.      *                     - "monsterid"
  544.      *                     - "wavatar"
  545.      *                     - "retro"
  546.      *
  547.      * @return string Default URL
  548.      *
  549.      * @throws InvalidArgumentException When an invalid URL is given
  550.      */
  551.     protected function processDefault($url)
  552.     {
  553.         if ($url === null{
  554.             return $url;
  555.         }
  556.  
  557.         $url = (string)$url;
  558.  
  559.         switch ($url{
  560.         case '404':
  561.         case 'mm':
  562.         case 'identicon':
  563.         case 'monsterid':
  564.         case 'wavatar':
  565.         case 'retro':
  566.             break;
  567.         default:
  568.             $valid filter_var($urlFILTER_VALIDATE_URL);
  569.             if (!$valid{
  570.                 throw new InvalidArgumentException('Invalid default avatar URL');
  571.             }
  572.             break;
  573.         }
  574.  
  575.         return $url;
  576.     }
  577.  
  578.     /**
  579.      * Verify and cast the required size of the images.
  580.      *
  581.      * @param integer $size Size (width and height in pixels) of the image.
  582.      *                       NULL for the default width.
  583.      *
  584.      * @return integer Size
  585.      *
  586.      * @throws InvalidArgumentException When a size <= 0 is given
  587.      */
  588.     protected function processSize($size)
  589.     {
  590.         if ($size === null{
  591.             return $size;
  592.         }
  593.  
  594.         $size = (int)$size;
  595.         if ($size <= 0{
  596.             throw new InvalidArgumentException('Size has to be larger than 0');
  597.         }
  598.  
  599.         return (int)$size;
  600.     }
  601.  
  602.  
  603.     /**
  604.      * Set the email address hashing algorithm to use.
  605.      * To keep gravatar compatibility, use "md5".
  606.      *
  607.      * @param string $algorithm Algorithm to use, "sha256" or "md5".
  608.      *
  609.      * @return self 
  610.      * @throws InvalidArgumentException When an unsupported algorithm is given
  611.      */
  612.     public function setAlgorithm($algorithm)
  613.     {
  614.         $this->algorithm = $this->processAlgorithm($algorithm);
  615.  
  616.         return $this;
  617.     }
  618.  
  619.     /**
  620.      * Set the default URL to use when no avatar image can be found.
  621.      * If none is set, the gravatar logo is returned.
  622.      *
  623.      * @param string $url Full URL to use OR one of the following:
  624.      *                     - "404" - give a "404 File not found" instead of an image
  625.      *                     - "mm"
  626.      *                     - "identicon"
  627.      *                     - "monsterid"
  628.      *                     - "wavatar"
  629.      *                     - "retro"
  630.      *
  631.      * @return self 
  632.      * @throws InvalidArgumentException When an invalid URL is given
  633.      */
  634.     public function setDefault($url)
  635.     {
  636.         $this->default = $this->processDefault($url);
  637.  
  638.         return $this;
  639.     }
  640.  
  641.     /**
  642.      * Set if HTTPS URLs shall be returned.
  643.      *
  644.      * @param boolean $useHttps If HTTPS url shall be returned
  645.      *
  646.      * @return self 
  647.      *
  648.      * @see detectHttps()
  649.      */
  650.     public function setHttps($useHttps)
  651.     {
  652.         $this->https = (bool)$useHttps;
  653.  
  654.         return $this;
  655.     }
  656.  
  657.     /**
  658.      * Set the required size of the images.
  659.      * Every avatar image is square sized, which means you need to set only number.
  660.      *
  661.      * @param integer $size Size (width and height) of the image
  662.      *
  663.      * @return self 
  664.      * @throws InvalidArgumentException When a size <= 0 is given
  665.      */
  666.     public function setSize($size)
  667.     {
  668.         $this->size = $this->processSize($size);
  669.  
  670.         return $this;
  671.     }
  672.  
  673. }
  674.  
  675. /*
  676.  * Local variables:
  677.  * tab-width: 4
  678.  * c-basic-offset: 4
  679.  * c-hanging-comment-ender-p: nil
  680.  * End:
  681.  */
  682.  
  683. ?>

Documentation generated on Fri, 20 Sep 2013 11:00:03 +0000 by phpDocumentor 1.4.3. PEAR Logo Copyright © PHP Group 2004.