diff --git a/gravatar.tgz b/gravatar.tgz index 38e942f89..25106f265 100644 Binary files a/gravatar.tgz and b/gravatar.tgz differ diff --git a/gravatar/gravatar.php b/gravatar/gravatar.php index b9435a317..fc5358eb4 100644 --- a/gravatar/gravatar.php +++ b/gravatar/gravatar.php @@ -2,7 +2,7 @@ /** * Name: Gravatar Support * Description: If there is no avatar image for a new user or contact this plugin will look for one at Gravatar. - * Version: 1.0 + * Version: 1.1 * Author: Klaus Weidenbach */ @@ -12,7 +12,7 @@ function gravatar_install() { register_hook('avatar_lookup', 'addon/gravatar/gravatar.php', 'gravatar_lookup'); - logger("installed gravatar"); + logger("registered gravatar in avatar_lookup hook"); } /** @@ -21,7 +21,7 @@ function gravatar_install() { function gravatar_uninstall() { unregister_hook('avatar_lookup', 'addon/gravatar/gravatar.php', 'gravatar_lookup'); - logger("uninstalled gravatar"); + logger("unregistered gravatar in avatar_lookup hook"); } /** @@ -81,7 +81,16 @@ function gravatar_plugin_admin (&$a, &$o) { 'x' => 'x' ); - $o = ''; + // Check if Libravatar is enabled and show warning + $r = q("SELECT * FROM `addon` WHERE `name` = '%s' and `installed` = 1", + dbesc('libravatar') + ); + if (count($r)) { + $o = '
' .t('Information') .'

' .t('Libravatar addon is installed, too. Please disable Libravatar addon or this Gravatar addon.
The Libravatar addon will fall back to Gravatar if nothing was found at Libravatar.') .'



'; + } + + // output Gravatar settings + $o .= ''; $o .= replace_macros( $t, array( '$submit' => t('Submit'), '$default_avatar' => array('avatar', t('Default avatar image'), $default_avatar, t('Select default avatar image if none was found at Gravatar. See README'), $default_avatars), diff --git a/libravatar.tgz b/libravatar.tgz new file mode 100644 index 000000000..0405af9bd Binary files /dev/null and b/libravatar.tgz differ diff --git a/libravatar/README.md b/libravatar/README.md new file mode 100644 index 000000000..16fbd85a2 --- /dev/null +++ b/libravatar/README.md @@ -0,0 +1,36 @@ +# Libravatar Plugin +by [Klaus Weidenbach](http://friendica.dszdw.net/profile/klaus) + +This addon allows you to look up an avatar image for new users and contacts at [Libravatar](http://www.libravatar.com). It will be used if there have not been found any other avatar images yet for example through OpenID. + +Libravatar is a free and open replacement for Gravatar. It is a service where people can store an avatar image for their email-addresses. These avatar images can get looked up for example in comment functions, profile pages, etc. on other sites. There exists a central installation at [www.libravatar.com](http://www.libravatar.com), but you can also host it on your own server. If no avatar was found Libravatar will look up at Gravatar as a fallback. +There is no rating available, as it is on Gravatar, so all avatar lookups are g-rated. (Suitable for all audiences.) + +PHP >= 5.3 is required for this plugin! + +You can not use the Libravatar and Gravatar addon at the same time. You need to choose one. If you need other ratings than g you better stay with Gravatar, otherwise it is safe to use Libravatar, because it will fall back to Gravatar if nothing was found at Libravatar. + +* * * + +# Configuration +## Default Avatar Image +If no avatar was found for an email Libravatar can create some pseudo-random generated avatars based on an email hash. You can choose between these presets: + +* __MM__: (mystery-man) a static image +* __Identicon__: a generated geometric pattern based on email hash +* __Monsterid__: a generated 'monster' with different colors, faces, etc. based on email hash +* __Wavatar__: faces with different features and backgrounds based on email hash +* __Retro__: 8-bit arcade-styled pixelated faces based on email hash + +See examples at [Libravatar][1]. + +## Alternative Configuration +Open the .htconfig.php file and add "libravatar" to the list of activated addons: + + $a->config['system']['addon'] = "..., libravatar"; + +You can add one configuration variable for the addon: + + $a->config['libravatar']['default_avatar'] = "identicon"; + +[1]: http://wiki.libravatar.org/api/ "See API documentation at Libravatar for more information" diff --git a/libravatar/Services/Libravatar.php b/libravatar/Services/Libravatar.php new file mode 100644 index 000000000..1b6d022e3 --- /dev/null +++ b/libravatar/Services/Libravatar.php @@ -0,0 +1,669 @@ + + * @copyright 2011 Services_Libravatar committers. + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Services_Libravatar + * @since File available since Release 0.1.0 + */ + +/** + * PHP support for the Libravatar.org service. + * + * Using this class is easy. After including or requiring + * Services/Libravatar.php simply do: + * + * $libravatar = new Services_Libravatar(); + * $url = $libravatar->getUrl('melissa@meldraweb.com'); + * + * + * This would populate $url with the string: + * + * http://cdn.libravatar.org/avatar/4db84629c121f2d443d33bdb9fd149bc + * + * + * A complicated lookup using all the options is: + * + * $libravatar = new Services_Libravatar(); + * $libravatar->setSize(40); + * $libravatar->setAlgorithm('sha256'); + * $libravatar->setHttps(true); + * $libravatar->setDefault( + * 'http://upload.wikimedia.org/wikipedia/commons/a/af/Tux.png' + * ); + * $url = $libravatar->getUrl('melissa@meldraweb.com'); + * + * + * @category Services + * @package Services_Libravatar + * @author Melissa Draper + * @copyright 2011 Services_Libravatar committers. + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version Release: 0.2.1 + * @link http://pear.php.net/package/Services_Libravatar + * @since Class available since Release 0.1.0 + */ +class Services_Libravatar +{ + /** + * Hashing algorithm to use + * + * @var string + * @see processAlgorithm() + * @see setAlgorithm() + */ + protected $algorithm = 'md5'; + + /** + * Default image URL to use + * + * @var string + * @see processDefault() + * @see setDefault() + */ + protected $default; + + /** + * If HTTPS URLs should be used + * + * @var boolean + * @see detectHttps() + * @see setHttps() + */ + protected $https; + + /** + * Image size in pixels + * + * @var integer + * @see processSize() + * @see setSize() + */ + protected $size; + + + /** + * Composes a URL for the identifier and options passed in + * + * Compose a full URL as specified by the Libravatar API, based on the + * email address or openid URL passed in, and the options specified. + * + * @param string $identifier a string of either an email address + * or an openid url + * @param array $options an array of (bool) https, (string) algorithm + * (string) size, (string) default. + * See the set* methods. + * + * @return string A string of a full URL for an avatar image + * + * @since Method available since Release 0.2.0 + * @deprecated Use getUrl() instead + */ + public function url($identifier, $options = array()) + { + return $this->getUrl($identifier, $options); + } + + /** + * Composes a URL for the identifier and options passed in + * + * Compose a full URL as specified by the Libravatar API, based on the + * email address or openid URL passed in, and the options specified. + * + * @param string $identifier a string of either an email address + * or an openid url + * @param array $options an array of (bool) https, (string) algorithm + * (string) size, (string) default. + * See the set* methods. + * + * @return string A string of a full URL for an avatar image + * + * @since Method available since Release 0.2.0 + * @throws InvalidArgumentException When an invalid option is passed + */ + public function getUrl($identifier, $options = array()) + { + // If no identifier has been passed, set it to a null. + // This way, there'll always be something returned. + if (!$identifier) { + $identifier = null; + } else { + $identifier = $this->normalizeIdentifier($identifier); + } + + // Load all options + $options = $this->checkOptionsArray($options); + $https = $this->https; + if (isset($options['https'])) { + $https = (bool)$options['https']; + } + + $algorithm = $this->algorithm; + if (isset($options['algorithm'])) { + $algorithm = $this->processAlgorithm($options['algorithm']); + } + + $default = $this->default; + if (isset($options['default'])) { + $default = $this->processDefault($options['default']); + } + $size = $this->size; + if (isset($options['size'])) { + $size = $this->processSize($options['size']); + } + + + $identifierHash = $this->identifierHash($identifier, $algorithm); + + // Get the domain so we can determine the SRV stuff for federation + $domain = $this->domainGet($identifier); + + // If https has been specified in $options, make sure we make the + // correct SRV lookup + $service = $this->srvGet($domain, $https); + $protocol = $https ? 'https' : 'http'; + + $params = array(); + if ($size !== null) { + $params['size'] = $size; + } + if ($default !== null) { + $params['default'] = $default; + } + $paramString = ''; + if (count($params) > 0) { + $paramString = '?' . http_build_query($params); + } + + // Compose the URL from the pieces we generated + $url = $protocol . '://' . $service . '/avatar/' . $identifierHash + . $paramString; + + // Return the URL string + return $url; + } + + /** + * Checks the options array and verify that only allowed options are in it. + * + * @param array $options Array of options for getUrl() + * + * @return void + * @throws Exception When an invalid option is used + */ + protected function checkOptionsArray($options) + { + //this short options are deprecated! + if (isset($options['s'])) { + $options['size'] = $options['s']; + unset($options['s']); + } + if (isset($options['d'])) { + $options['default'] = $options['d']; + unset($options['d']); + } + + $allowedOptions = array( + 'algorithm' => true, + 'default' => true, + 'https' => true, + 'size' => true, + ); + foreach ($options as $key => $value) { + if (!isset($allowedOptions[$key])) { + throw new InvalidArgumentException( + 'Invalid option in array: ' . $key + ); + } + } + + return $options; + } + + /** + * Normalizes the identifier (E-mail address or OpenID) + * + * @param string $identifier E-Mail address or OpenID + * + * @return string Normalized identifier + */ + protected function normalizeIdentifier($identifier) + { + if (filter_var($identifier, FILTER_VALIDATE_EMAIL)) { + return strtolower($identifier); + } else { + return self::normalizeOpenId($identifier); + } + } + + /** + * Create a hash of the identifier. + * + * Create a hash of the email address or openid passed in. Algorithm + * used for email address ONLY can be varied. Either md5 or sha256 + * are supported by the Libravatar API. Will be ignored for openid. + * + * @param string $identifier A string of the email address or openid URL + * @param string $hash A string of the hash algorithm type to make + * Uses the php implementation of hash() + * MD5 preferred for Gravatar fallback + * + * @return string A string hash of the identifier. + * + * @since Method available since Release 0.1.0 + */ + protected function identifierHash($identifier, $hash = 'md5') + { + if (filter_var($identifier, FILTER_VALIDATE_EMAIL) || $identifier === null) { + // If email, we can select our algorithm. Default to md5 for + // gravatar fallback. + return hash($hash, $identifier); + } + + //no email, so the identifier has to be an OpenID + return hash('sha256', $identifier); + } + + /** + * Normalizes an identifier (URI or XRI) + * + * @param mixed $identifier URI or XRI to be normalized + * + * @return string Normalized Identifier. + * Empty string when the OpenID is invalid. + * + * @internal Adapted from OpenID::normalizeIdentifier() + */ + public static function normalizeOpenId($identifier) + { + // XRI + if (preg_match('@^xri://@i', $identifier)) { + return preg_replace('@^xri://@i', '', $identifier); + } + + if (in_array($identifier[0], array('=', '@', '+', '$', '!'))) { + return $identifier; + } + + // URL + if (!preg_match('@^http[s]?://@i', $identifier)) { + $identifier = 'http://' . $identifier; + } + if (strpos($identifier, '/', 8) === false) { + $identifier .= '/'; + } + if (!filter_var($identifier, FILTER_VALIDATE_URL)) { + return ''; + } + + $parts = parse_url($identifier); + $parts['scheme'] = strtolower($parts['scheme']); + $parts['host'] = strtolower($parts['host']); + + //http://openid.net/specs/openid-authentication-2_0.html#normalization + return $parts['scheme'] . '://' + . (isset($parts['user']) ? $parts['user'] : '') + . (isset($parts['pass']) ? ':' . $parts['pass'] : '') + . (isset($parts['user']) || isset($parts['pass']) ? '@' : '') + . $parts['host'] + . ( + (isset($parts['port']) + && $parts['scheme'] === 'http' && $parts['port'] != 80) + || (isset($parts['port']) + && $parts['scheme'] === 'https' && $parts['port'] != 443) + ? ':' . $parts['port'] : '' + ) + . $parts['path'] + . (isset($parts['query']) ? '?' . $parts['query'] : ''); + //leave out fragment as requested by the spec + } + + /** + * Grab the domain from the identifier. + * + * Extract the domain from the Email or OpenID. + * + * @param string $identifier A string of the email address or openid URL + * + * @return string A string of the domain to use + * + * @since Method available since Release 0.1.0 + */ + protected function domainGet($identifier) + { + if ($identifier === null) { + return null; + } + + // What are we, email or openid? Split ourself up and get the + // important bit out. + if (filter_var($identifier, FILTER_VALIDATE_EMAIL)) { + $email = explode('@', $identifier); + return $email[1]; + } + + //OpenID + $url = parse_url($identifier); + $domain = $url['host']; + if (isset($url['port']) && $url['scheme'] === 'http' + && $url['port'] != 80 + || isset($url['port']) && $url['scheme'] === 'https' + && $url['port'] != 443 + ) { + $domain .= ':' . $url['port']; + } + + return $domain; + } + + /** + * Get the target to use. + * + * Get the SRV record, filtered by priority and weight. If our domain + * has no SRV records, fall back to Libravatar.org + * + * @param string $domain A string of the domain we extracted from the + * provided identifier with domainGet() + * @param boolean $https Whether or not to look for https records + * + * @return string The target URL. + * + * @since Method available since Release 0.1.0 + */ + protected function srvGet($domain, $https = false) + { + + // Are we going secure? Set up a fallback too. + if (isset($https) && $https === true) { + $subdomain = '_avatars-sec._tcp.'; + $fallback = 'seccdn.'; + } else { + $subdomain = '_avatars._tcp.'; + $fallback = 'cdn.'; + } + + // Lets try get us some records based on the choice of subdomain + // and the domain we had passed in. + $srv = dns_get_record($subdomain . $domain, DNS_SRV); + + // Did we get anything? No? + if (count($srv) == 0) { + // Then let's try Libravatar.org. + return $fallback . 'libravatar.org'; + } + + // Sort by the priority. We must get the lowest. + usort($srv, array($this, 'comparePriority')); + + $top = $srv[0]; + $sum = 0; + + // Try to adhere to RFC2782's weighting algorithm, page 3 + // "arrange all SRV RRs (that have not been ordered yet) in any order, + // except that all those with weight 0 are placed at the beginning of + // the list." + shuffle($srv); + $srvs = array(); + foreach ($srv as $s) { + if ($s['weight'] == 0) { + array_unshift($srvs, $s); + } else { + array_push($srvs, $s); + } + } + + foreach ($srvs as $s) { + if ($s['pri'] == $top['pri']) { + // "Compute the sum of the weights of those RRs" + $sum += (int) $s['weight']; + // "and with each RR associate the running sum in the selected + // order." + $pri[$sum] = $s; + } + } + + // "Then choose a uniform random number between 0 and the sum computed + // (inclusive)" + $random = rand(0, $sum); + + // "and select the RR whose running sum value is the first in the selected + // order which is greater than or equal to the random number selected" + foreach ($pri as $k => $v) { + if ($k >= $random) { + return $v['target']; + } + } + } + + /** + * Sorting function for record priorities. + * + * @param mixed $a A mixed value passed by usort() + * @param mixed $b A mixed value passed by usort() + * + * @return mixed The result of the comparison + * + * @since Method available since Release 0.1.0 + */ + protected function comparePriority($a, $b) + { + return $a['pri'] - $b['pri']; + } + + /** + * Automatically set the https option depending on the current connection + * value. + * + * If the current connection is HTTPS, the https options is activated. + * If it is not HTTPS, the https option is deactivated. + * + * @return self + */ + public function detectHttps() + { + $this->setHttps( + isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] + ); + + return $this; + } + + /** + * Verify and cast the email address hashing algorithm to use. + * + * @param string $algorithm Algorithm to use, "sha256" or "md5". + * + * @return string Algorithm + * + * @throws InvalidArgumentException When an unsupported algorithm is given + */ + protected function processAlgorithm($algorithm) + { + $algorithm = (string)$algorithm; + if ($algorithm !== 'md5' && $algorithm !== 'sha256') { + throw new InvalidArgumentException( + 'Only md5 and sha256 hashing supported' + ); + } + + return $algorithm; + } + + /** + * Verify and cast the default URL to use when no avatar image can be found. + * If none is set, the libravatar logo is returned. + * + * @param string $url Full URL to use OR one of the following: + * - "404" - give a "404 File not found" instead of an image + * - "mm" + * - "identicon" + * - "monsterid" + * - "wavatar" + * - "retro" + * + * @return string Default URL + * + * @throws InvalidArgumentException When an invalid URL is given + */ + protected function processDefault($url) + { + if ($url === null) { + return $url; + } + + $url = (string)$url; + + switch ($url) { + case '404': + case 'mm': + case 'identicon': + case 'monsterid': + case 'wavatar': + case 'retro': + break; + default: + $valid = filter_var($url, FILTER_VALIDATE_URL); + if (!$valid) { + throw new InvalidArgumentException('Invalid default avatar URL'); + } + break; + } + + return $url; + } + + /** + * Verify and cast the required size of the images. + * + * @param integer $size Size (width and height in pixels) of the image. + * NULL for the default width. + * + * @return integer Size + * + * @throws InvalidArgumentException When a size <= 0 is given + */ + protected function processSize($size) + { + if ($size === null) { + return $size; + } + + $size = (int)$size; + if ($size <= 0) { + throw new InvalidArgumentException('Size has to be larger than 0'); + } + + return (int)$size; + } + + + /** + * Set the email address hashing algorithm to use. + * To keep gravatar compatibility, use "md5". + * + * @param string $algorithm Algorithm to use, "sha256" or "md5". + * + * @return self + * @throws InvalidArgumentException When an unsupported algorithm is given + */ + public function setAlgorithm($algorithm) + { + $this->algorithm = $this->processAlgorithm($algorithm); + + return $this; + } + + /** + * Set the default URL to use when no avatar image can be found. + * If none is set, the gravatar logo is returned. + * + * @param string $url Full URL to use OR one of the following: + * - "404" - give a "404 File not found" instead of an image + * - "mm" + * - "identicon" + * - "monsterid" + * - "wavatar" + * - "retro" + * + * @return self + * @throws InvalidArgumentException When an invalid URL is given + */ + public function setDefault($url) + { + $this->default = $this->processDefault($url); + + return $this; + } + + /** + * Set if HTTPS URLs shall be returned. + * + * @param boolean $useHttps If HTTPS url shall be returned + * + * @return self + * + * @see detectHttps() + */ + public function setHttps($useHttps) + { + $this->https = (bool)$useHttps; + + return $this; + } + + /** + * Set the required size of the images. + * Every avatar image is square sized, which means you need to set only number. + * + * @param integer $size Size (width and height) of the image + * + * @return self + * @throws InvalidArgumentException When a size <= 0 is given + */ + public function setSize($size) + { + $this->size = $this->processSize($size); + + return $this; + } + +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ + +?> diff --git a/libravatar/admin.tpl b/libravatar/admin.tpl new file mode 100644 index 000000000..814f4a44a --- /dev/null +++ b/libravatar/admin.tpl @@ -0,0 +1,2 @@ +{{ inc field_select.tpl with $field=$default_avatar}}{{ endinc }} +
diff --git a/libravatar/libravatar.php b/libravatar/libravatar.php new file mode 100644 index 000000000..08ed6d00b --- /dev/null +++ b/libravatar/libravatar.php @@ -0,0 +1,114 @@ += 5.3) + * Version: 1.1 + * Author: Klaus Weidenbach + */ + +/** + * Installs the plugin hook + */ +function libravatar_install() { + if (! version_compare(PHP_VERSION, '5.3.0', '>=')) { + info(t('Could NOT install Libravatar successfully.
It requires PHP >= 5.3') .EOL); + // avoid registering the hook + return false; + } + register_hook('avatar_lookup', 'addon/libravatar/libravatar.php', 'libravatar_lookup'); + + logger("registered libravatar in avatar_lookup hook"); +} + +/** + * Removes the plugin hook + */ +function libravatar_uninstall() { + unregister_hook('avatar_lookup', 'addon/libravatar/libravatar.php', 'libravatar_lookup'); + + logger("unregistered libravatar in avatar_lookup hook"); +} + +/** + * Looks up the avatar at Libravatar and returns the URL. + * + * @param $a array + * @param &$b array + */ +function libravatar_lookup($a, &$b) { + $default_avatar = get_config('libravatar', 'default_img'); + + if (! $default_avatar) { + // if not set, look up if there was one from the gravatar addon + $default_avatar = get_config('gravatar', 'default_img'); + // setting default avatar if nothing configured + if (! $default_avatar) + $default_avatar = 'identicon'; // default image will be a random pattern + } + + require_once 'Services/Libravatar.php'; + $libravatar = new Services_Libravatar(); + $libravatar->setSize($b['size']); + $libravatar->setDefault($default_avatar); + $avatar_url = $libravatar->getUrl($b['email']); + + $b['url'] = $avatar_url; + $b['success'] = true; +} + +/** + * Display admin settings for this addon + */ +function libravatar_plugin_admin (&$a, &$o) { + $t = file_get_contents( dirname(__file__)."/admin.tpl"); + + $default_avatar = get_config('libravatar', 'default_img'); + + // set default values for first configuration + if(! $default_avatar) + $default_avatar = 'identicon'; // pseudo-random geometric pattern based on email hash + + // Available options for the select boxes + $default_avatars = array( + 'mm' => t('generic profile image'), + 'identicon' => t('random geometric pattern'), + 'monsterid' => t('monster face'), + 'wavatar' => t('computer generated face'), + 'retro' => t('retro arcade style face'), + ); + + // Show warning if PHP version is too old + if (! version_compare(PHP_VERSION, '5.3.0', '>=')) { + $o = '
' .t('Warning') .'

'; + $o .= sprintf(t('Your PHP version %s is lower than the required PHP >= 5.3.'), PHP_VERSION); + $o .= '
' .t('This addon is not functional on your server.') .'


'; + return; + } + + // Libravatar falls back to gravatar, so show warning about gravatar addon if enabled + $r = q("SELECT * FROM `addon` WHERE `name` = '%s' and `installed` = 1", + dbesc('gravatar') + ); + if (count($r)) { + $o = '

' .t('Information') .'

' .t('Gravatar addon is installed. Please disable the Gravatar addon.
The Libravatar addon will fall back to Gravatar if nothing was found at Libravatar.') .'



'; + } + + // output Libravatar settings + $o .= ''; + $o .= replace_macros( $t, array( + '$submit' => t('Submit'), + '$default_avatar' => array('avatar', t('Default avatar image'), $default_avatar, t('Select default avatar image if none was found. See README'), $default_avatars), + )); +} + +/** + * Save admin settings + */ +function libravatar_plugin_admin_post (&$a) { + check_form_security_token('libravatarrsave'); + + $default_avatar = ((x($_POST, 'avatar')) ? notags(trim($_POST['avatar'])) : 'identicon'); + set_config('libravatar', 'default_img', $default_avatar); + info(t('Libravatar settings updated.') .EOL); +} +?>