PluginProbe
WebFinger / 3.0.0
WebFinger v3.0.0
4.1.0 trunk 0.5 0.7 0.9 0.9.1 1.0 1.0.1 1.1 1.2 1.3 1.3.1 2.0.0 2.0.1 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.1.5 All 36 releases
webfinger / webfinger.php

webfinger.php in WebFinger 3.0.0, at webfinger.php

490 lines 13.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 Plugin Name: WebFinger
4 Plugin URI: http://wordpress.org/extend/plugins/webfinger/
5 Description: WebFinger for WordPress
6 Version: 3.0.0
7 Author: pfefferle
8 Author URI: http://notizblog.org/
9 */
10
11 // initialize plugin
12 add_action('init', array( 'WebFingerPlugin', 'init' ));
13
14 /**
15 * webfinger
16 *
17 * @author Matthias Pfefferle
18 * @author Will Norris
19 */
20 class WebFingerPlugin {
21
22 /**
23 * Initialize the plugin, registering WordPress hooks.
24 */
25 public static function init() {
26 register_activation_hook(__FILE__, 'flush_rewrite_rules');
27 register_deactivation_hook(__FILE__, 'flush_rewrite_rules');
28
29 add_action('query_vars', array('WebFingerPlugin', 'query_vars'));
30 add_action('parse_request', array('WebFingerPlugin', 'parse_request'));
31 add_action('generate_rewrite_rules', array('WebFingerPlugin', 'rewrite_rules'));
32
33 add_filter('webfinger_data', array('WebFingerPlugin', 'generate_user_data'), 10, 3);
34 add_filter('webfinger_data', array('WebFingerPlugin', 'filter_by_rel'), 99, 1);
35
36 // default output
37 add_action('webfinger_render', array('WebFingerPlugin', 'render_jrd'));
38
39 // support plugins pre 3.0.0
40 add_filter('webfinger_user_data', array('WebFingerPlugin', 'legacy_filter'), 10, 3);
41 }
42
43 /**
44 * add query vars
45 *
46 * @param array $vars
47 * @return array
48 */
49 public static function query_vars($vars) {
50 $vars[] = 'well-known';
51 $vars[] = 'resource';
52 $vars[] = 'rel';
53
54 return $vars;
55 }
56
57 /**
58 * add rewrite rules
59 *
60 * @param WP_Rewrite
61 */
62 public static function rewrite_rules( $wp_rewrite ) {
63 $webfinger_rules = array(
64 '.well-known/webfinger' => 'index.php?well-known=webfinger'
65 );
66
67 $wp_rewrite->rules = $webfinger_rules + $wp_rewrite->rules;
68 }
69
70 /**
71 * parse the webfinger request and render the document.
72 *
73 * @param WP $wp WordPress request context
74 *
75 * @uses apply_filters() Calls 'webfinger' on webfinger data array
76 * @uses do_action() Calls 'webfinger_render' to render webfinger data
77 */
78 public static function parse_request($wp) {
79 // check if it is a webfinger request or not
80 if (!array_key_exists('well-known', $wp->query_vars) ||
81 $wp->query_vars['well-known'] != 'webfinger') {
82 return;
83 }
84
85 header('Access-Control-Allow-Origin: *');
86
87 // check if "resource" param exists
88 if (!array_key_exists('resource', $wp->query_vars) ||
89 empty($wp->query_vars['resource'])) {
90 status_header(400);
91 header('Content-Type: text/plain; charset=' . get_bloginfo('charset'), true);
92 echo "missing 'resource' parameter";
93 exit;
94 }
95
96 // filter webfinger array
97 $webfinger = apply_filters('webfinger_data', array(), $wp->query_vars['resource']);
98
99 // check if "user" exists
100 if (empty($webfinger)) {
101 status_header(404);
102 header('Content-Type: text/plain; charset=' . get_bloginfo('charset'), true);
103 echo "no data for resource '{$wp->query_vars['resource']}' found";
104 exit;
105 }
106
107 do_action('webfinger_render', $webfinger);
108
109 // stop exactly here!
110 exit;
111 }
112
113 /**
114 * render the JRD representation of the webfinger resource.
115 *
116 * @param array $webfinger the webfinger data-array
117 */
118 public static function render_jrd($webfinger) {
119 header('Content-Type: application/jrd+json; charset=' . get_bloginfo('charset'), true);
120
121 echo json_encode($webfinger);
122 exit;
123 }
124
125 /**
126 * generates the webfinger base array
127 *
128 * @param array $webfinger the webfinger data-array
129 * @param stdClass $user the WordPress user
130 * @param string $resource the resource param
131 * @return array the enriched webfinger data-array
132 */
133 public static function generate_user_data($webfinger, $resource) {
134 // find matching user
135 $user = self::get_user_by_uri($resource);
136
137 if (!$user) {
138 return $webfinger;
139 }
140
141 // generate "profile" url
142 $url = get_author_posts_url($user->ID, $user->user_nicename);
143 // generate default photo-url
144 $photo = get_user_meta($user->ID, 'photo', true);
145 if(!$photo) $photo = 'http://www.gravatar.com/avatar/'.md5($user->user_email);
146
147 // generate default array
148 $webfinger = array('subject' => self::get_user_resource($user->ID),
149 'aliases' => self::get_user_resources($user->ID),
150 'links' => array(
151 array('rel' => 'http://webfinger.net/rel/profile-page', 'type' => 'text/html', 'href' => $url),
152 array('rel' => 'http://webfinger.net/rel/avatar', 'href' => $photo)
153 ));
154
155 // add user_url if set
156 if (isset($user->user_url) && !empty($user->user_url)) {
157 $webfinger['links'][] = array('rel' => 'http://webfinger.net/rel/profile-page', 'type' => 'text/html', 'href' => $user->user_url);
158 }
159
160 return apply_filters('webfinger_user_data', $webfinger, $resource, $user);
161 }
162
163 /**
164 * filters the webfinger array by request params like "rel"
165 *
166 * @link http://tools.ietf.org/html/rfc7033#section-4.3
167 * @param array $array
168 * @param stdClass $user
169 * @param array $queries
170 * @return array
171 */
172 public static function filter_by_rel($webfinger) {
173 // check if webfinger is empty or if "rel"
174 // is set or if array has any "links"
175 if (empty($webfinger) ||
176 !array_key_exists('rel', $_GET) ||
177 !isset($webfinger['links'])) {
178 return $webfinger;
179 }
180
181 // explode the query-string by hand because php does not
182 // support multiple queries with the same name
183 $query = explode("&", $_SERVER['QUERY_STRING']);
184 $rels = array();
185
186 foreach($query as $param) {
187 $param = explode('=', $param);
188
189 // check if query-string is valid and if it is a 'rel'
190 if (isset($param[0], $param[1]) &&
191 $param[0] == 'rel' &&
192 !empty($param[1])) {
193 $rels[] = urldecode(trim($param[1]));
194 }
195 }
196
197 // check if there is something to filter
198 if (empty($rels)) {
199 return $webfinger;
200 }
201
202 // filter webfinger-array
203 $links = array();
204 foreach ($webfinger['links'] as $link) {
205 if (in_array($link["rel"], $rels)) {
206 $links[] = $link;
207 }
208 }
209 $webfinger['links'] = $links;
210
211 // return only "links" with the matching rel-values
212 return $webfinger;
213 }
214
215 /**
216 * returns a Userobject
217 *
218 * @param string $uri
219 * @return WP_User
220 * @uses apply_filters() uses 'webfinger_user' to filter the
221 * user and 'webfinger_user_query' to add custom query-params
222 */
223 private static function get_user_by_uri($uri) {
224 $uri = urldecode($uri);
225
226 if (!preg_match("/^([a-zA-Z^:]+):(.*)$/i", $uri, $match)) {
227 // no valid scheme provided
228 return null;
229 }
230
231 // extract the scheme
232 $scheme = $match[1];
233 // extract the "host"
234 $host = $match[2];
235
236 switch ($scheme) {
237 // check urls
238 case "http":
239 case "https":
240 // check if is the author url
241 if ($author_id = url_to_authorid($uri)) {
242 $args = array(
243 'search' => $author_id,
244 'search_columns' => array('ID'),
245 'meta_compare' => '=',
246 );
247 // check other urls
248 } else {
249 // search url in user_url
250 $args = array(
251 'search' => $uri,
252 'search_columns' => array('user_url'),
253 'meta_compare' => '=',
254 );
255 }
256
257 break;
258 // check acct scheme
259 case "acct":
260 // get the identifier at the left of the '@'
261 $parts = explode("@", $host);
262
263 $args = array(
264 'search' => $parts[0],
265 'search_columns' => array('user_name', 'display_name', 'user_login'),
266 'meta_compare' => '=',
267 );
268 break;
269 // check mailto scheme
270 case "mailto":
271 $args = array(
272 'search' => $host,
273 'search_columns' => array('user_email'),
274 'meta_compare' => '=',
275 );
276 break;
277 // check xmpp/jabber schemes
278 case "xmpp":
279 case "urn:xmpp":
280 $args = array(
281 'meta_key' => 'jabber',
282 'meta_value' => $host,
283 'meta_compare' => '=',
284 );
285 break;
286 // check Yahoo messenger schemes
287 case "ymsgr":
288 $args = array(
289 'meta_key' => 'yim',
290 'meta_value' => $host,
291 'meta_compare' => '=',
292 );
293 break;
294 // check AOL messenger schemes
295 case "aim":
296 $args = array(
297 'meta_key' => 'aim',
298 'meta_value' => $host,
299 'meta_compare' => '=',
300 );
301 break;
302 // check instant messaging schemes
303 case "im":
304 $args = array(
305 'meta_query' => array(
306 'relation' => 'OR',
307 array(
308 'key' => 'jabber',
309 'value' => $host,
310 'compare' => '='
311 ),
312 array(
313 'key' => 'yim',
314 'value' => $host,
315 'compare' => '='
316 ),
317 array(
318 'key' => 'aim',
319 'value' => $host,
320 'compare' => '='
321 )
322 )
323 );
324 break;
325 default:
326 $args = array();
327 break;
328 }
329
330 $args = apply_filters("webfinger_user_query", $args, $uri, $scheme);
331
332 // get user query
333 $user_query = new WP_User_Query($args);
334
335 // check result
336 if (!empty($user_query->results)) {
337 $user = $user_query->results[0];
338 } else {
339 $user = null;
340 }
341
342 return $user;
343 }
344
345 /**
346 * returns a users default webfinger
347 *
348 * @param mixed $id_or_name_or_object
349 * @return string|null
350 */
351 public static function get_user_resource($id_or_name_or_object) {
352 $user = self::get_user_by_various($id_or_name_or_object);
353
354 if ($user) {
355 $resource = $user->user_login."@".parse_url(home_url(), PHP_URL_HOST);
356
357 return apply_filters('webfinger_user_resource', "acct:".$resource, $user);
358 } else {
359 return null;
360 }
361 }
362
363 /**
364 * returns all webfinger "resources"
365 *
366 * @param mixed $id_or_name_or_object
367 * @return array
368 */
369 public static function get_user_resources($id_or_name_or_object) {
370 $user = self::get_user_by_various($id_or_name_or_object);
371
372 if (!$user) {
373 return array();
374 }
375
376 // generate account idenitfier (acct: uri)
377 $resources[] = self::get_user_resource($user, true);
378 $resources[] = get_author_posts_url($user->ID, $user->user_nicename);
379
380 if ($user->user_email) {
381 $resources[] = "mailto:".$user->user_email;
382 }
383
384 /*
385 * the IM schemes are based on the "vCard Extensions for Instant Messaging (IM)".
386 * that means that the YahooID for example is represented by ymsgr:identifier
387 * and not by the ymsgr:SendIM?identifier pseudo uri
388 *
389 * @link http://tools.ietf.org/html/rfc4770#section-1
390 */
391 if (get_user_meta($user->ID, "yim", true)) {
392 $resources[] = "ymsgr:".get_user_meta($user->ID, "yim", true);
393 }
394
395 // aim:identifier instead of aim:goim?screenname=identifier
396 if (get_user_meta($user->ID, "aim", true)) {
397 $resources[] = "aim:".get_user_meta($user->ID, "aim", true);
398 }
399
400 if (get_user_meta($user->ID, "jabber", true)) {
401 $resources[] = "xmpp:".get_user_meta($user->ID, "jabber", true);
402 }
403
404 return array_unique(apply_filters('webfinger_user_resources', $resources, $user));
405 }
406
407 /**
408 * convenience method to get user data by ID, username, object or from current user.
409 *
410 * @param mixed $id_or_name_or_object the username, ID or object. If not provided, the current user will be used.
411 * @return bool|object False on failure, User DB row object
412 *
413 * @author Will Norris
414 * @see get_userdata_by_various() # DiSo OpenID-Plugin
415 */
416 public static function get_user_by_various($id_or_name_or_object = null) {
417 if ( $id_or_name_or_object === null ) {
418 $user = wp_get_current_user();
419 if ($user == null) return false;
420 return $user->data;
421 } else if ( is_object($id_or_name_or_object) ) {
422 return $id_or_name_or_object;
423 } else if ( is_numeric($id_or_name_or_object) ) {
424 return get_userdata($id_or_name_or_object);
425 } else {
426 return get_userdatabylogin($id_or_name_or_object);
427 }
428 }
429
430 /**
431 * backwards compatibility for old versions. please don't use!
432 *
433 * @deprecated
434 *
435 * @param array $webfinger
436 * @param string $resource
437 * @param WP_User $user
438 * @return array
439 */
440 public static function legacy_filter($webfinger, $resource, $user) {
441 // filter webfinger array
442 return apply_filters('webfinger', $webfinger, $user, $resource, $_GET);
443 }
444 }
445
446 if (!function_exists('url_to_authorid')) {
447 /**
448 * Examine a url and try to determine the author ID it represents.
449 *
450 * Checks are supposedly from the hosted site blog.
451 *
452 * @param string $url Permalink to check.
453 * @return int User ID, or 0 on failure.
454 */
455 function url_to_authorid($url) {
456 global $wp_rewrite;
457
458 // check if url hase the same host
459 if (parse_url(site_url(), PHP_URL_HOST) != parse_url($url, PHP_URL_HOST)) {
460 return 0;
461 }
462
463 // First, check to see if there is a 'author=N' to match against
464 if ( preg_match('/[?&]author=(\d+)/i', $url, $values) ) {
465 $id = absint($values[1]);
466 if ( $id )
467 return $id;
468 }
469
470 // Check to see if we are using rewrite rules
471 $rewrite = $wp_rewrite->wp_rewrite_rules();
472
473 // Not using rewrite rules, and 'author=N' method failed, so we're out of options
474 if ( empty($rewrite) )
475 return 0;
476
477 // generate rewrite rule for the author url
478 $author_rewrite = $wp_rewrite->get_author_permastruct();
479 $author_regexp = str_replace("%author%", "", $author_rewrite);
480
481 // match the rewrite rule with the passed url
482 if (preg_match("/https?:\/\/(.+)".preg_quote($author_regexp, '/')."([^\/]+)/i", $url, $match)) {
483 if ($user = get_user_by("slug", $match[2])) {
484 return $user->ID;
485 }
486 }
487
488 return 0;
489 }
490 }