PluginProbe
WebFinger / 3.0.1
WebFinger v3.0.1
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.1, at webfinger.php

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