Skip to content

Commit c522286

Browse files
authored
Merge pull request #231 from dshanske/clients
Clients
2 parents f27e8b8 + 4602edb commit c522286

9 files changed

+491
-29
lines changed

includes/class-indieauth-authorization-endpoint.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -380,9 +380,14 @@ public function authorize() {
380380
$current_user = wp_get_current_user();
381381
// phpcs:disable
382382
$client_id = esc_url_raw( wp_unslash( $_GET['client_id'] ) ); // WPCS: CSRF OK
383-
$info = new IndieAuth_Client_Discovery( $client_id );
384-
$client_name = $info->get_name();
385-
$client_icon = $info->get_icon();
383+
$client_term = IndieAuth_Client_Taxonomy::add_client_with_discovery( $client_id );
384+
if ( ! is_wp_error( $client_term ) ) {
385+
$client_name = $client_term['name'];
386+
$client_icon = $client_term['icon'];
387+
} else {
388+
$client_name = $client_term->get_error_message();
389+
$client_icon = null;
390+
}
386391
if ( ! empty( $client_name ) ) {
387392
$client = sprintf( '<a href="%1$s">%2$s</a>', $client_id, $client_name );
388393
} else {
Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
<?php
2+
/**
3+
* IndieAuth Client Taxonomy Class
4+
*
5+
* Registers the taxonomy and sets its behavior.
6+
*
7+
*/
8+
9+
add_action( 'init', array( 'IndieAuth_Client_Taxonomy', 'init' ) );
10+
11+
/**
12+
* Class that handles the client taxonomy functions.
13+
*/
14+
final class IndieAuth_Client_Taxonomy {
15+
16+
public static function init() {
17+
self::register();
18+
19+
add_filter( 'terms_clauses', array( __CLASS__, 'terms_clauses' ), 11, 3 );
20+
21+
}
22+
23+
public static function terms_clauses( $clauses, $taxonomies, $args ) {
24+
global $wpdb;
25+
26+
// This allows for using an exact search instead of a LIKE search by adding a straight description argument.
27+
if ( array_key_exists( 'description', $args ) ) {
28+
$clauses['where'] .= $wpdb->prepare( ' AND tt.description = %s', $args['description'] );
29+
}
30+
return $clauses;
31+
}
32+
33+
/**
34+
* Register the custom taxonomy for location.
35+
*/
36+
public static function register() {
37+
$labels = array(
38+
'name' => _x( 'IndieAuth Applications', 'taxonomy general name', 'indieauth' ),
39+
'singular_name' => _x( 'IndieAuth Applications', 'taxonomy singular name', 'indieauth' ),
40+
'search_items' => _x( 'Search IndieAuth Applications', 'search locations', 'indieauth' ),
41+
'popular_items' => _x( 'Popular Applications', 'popular locations', 'indieauth' ),
42+
'all_items' => _x( 'All Applications', 'all taxonomy items', 'indieauth' ),
43+
'edit_item' => _x( 'Edit Application', 'edit taxonomy item', 'indieauth' ),
44+
'view_item' => _x( 'View Application Archive', 'view taxonomy item', 'indieauth' ),
45+
'update_item' => _x( 'Update Application', 'update taxonomy item', 'indieauth' ),
46+
'add_new_item' => _x( 'Add New Application', 'add taxonomy item', 'indieauth' ),
47+
'new_item_name' => _x( 'New Application', 'new taxonomy item', 'indieauth' ),
48+
'not found' => _x( 'No applications found', 'no clients found', 'indieauth' ),
49+
'no_terms' => _x( 'No applications', 'no locations', 'indieauth' ),
50+
);
51+
52+
$args = array(
53+
'labels' => $labels,
54+
'public' => true,
55+
'publicly_queryable' => true,
56+
'hierarchical' => false,
57+
'show_ui' => false,
58+
'show_in_menu' => false,
59+
'show_in_nav_menus' => true,
60+
'show_in_rest' => false,
61+
'show_tagcloud' => false,
62+
'show_in_quick_edit' => false,
63+
'show_admin_column' => false,
64+
'description' => __( 'Stores information in IndieAuth Client Applications', 'indieauth' ),
65+
);
66+
67+
$object_types = apply_filters( 'indieauth_client_taxonomy_object_types', array( 'post', 'page', 'attachment' ) );
68+
69+
register_taxonomy( 'indieauth_client', $object_types, $args );
70+
71+
register_meta(
72+
'term',
73+
'icon',
74+
array(
75+
'object_subtype' => 'indieauth_client',
76+
'type' => 'string',
77+
'description' => __( 'IndieAuth Client Application Icon', 'indieauth' ),
78+
'single' => true,
79+
'sanitize_callback' => 'esc_url_raw',
80+
'show_in_rest' => true,
81+
)
82+
);
83+
}
84+
85+
/**
86+
* Add Client from Discovery
87+
*/
88+
public static function add_client_with_discovery( $url ) {
89+
$client = new IndieAuth_Client_Discovery( $url );
90+
return self::add_client( $url, $client->get_name(), $client->get_icon() );
91+
}
92+
93+
/**
94+
* Update Client Icon from Discovery
95+
*/
96+
public static function update_client_icon_from_discovery( $url ) {
97+
$current = self::get_client( $url );
98+
if ( ! $current ) {
99+
return false;
100+
}
101+
102+
$client = new IndieAuth_Client_Discovery( $url );
103+
if ( ! $client->get_icon() ) {
104+
return false;
105+
}
106+
107+
return self::sideload_icon( $client->get_icon(), $url );
108+
109+
}
110+
111+
112+
113+
/**
114+
* Add a client as a term and return.
115+
*/
116+
public static function add_client( $url, $name = null, $icon = null ) {
117+
$exists = self::get_client( $url );
118+
119+
// Do Not Overwrite if Already Exists.
120+
if ( ! is_wp_error( $exists ) ) {
121+
return $exists;
122+
}
123+
124+
if ( empty( $name ) ) {
125+
$client = new IndieAuth_Client_Discovery( $url );
126+
if ( defined( 'INDIEAUTH_UNIT_TESTS' ) ) {
127+
return array(
128+
'client_id' => $url,
129+
);
130+
}
131+
return self::add_client( $url, $client->get_name(), $client->get_icon() );
132+
}
133+
134+
$icon = self::sideload_icon( $icon, $url );
135+
136+
$term = wp_insert_term(
137+
$name,
138+
'indieauth_client',
139+
array(
140+
'slug' => sanitize_title( $name ),
141+
'description' => esc_url_raw( $url ),
142+
)
143+
);
144+
if ( is_wp_error( $term ) ) {
145+
return $term;
146+
}
147+
add_term_meta( $term['term_id'], 'icon', $icon );
148+
return array_filter(
149+
array(
150+
'url' => $url,
151+
'name' => $name,
152+
'id' => $term['term_id'],
153+
'icon' => $icon,
154+
)
155+
);
156+
}
157+
158+
/**
159+
* Get Client
160+
*/
161+
public static function get_client( $url = null ) {
162+
// If url is null retrieve all clients.
163+
if ( is_null( $url ) ) {
164+
$terms = get_terms(
165+
array(
166+
'taxonomy' => 'indieauth_client',
167+
'hide_empty' => false,
168+
)
169+
);
170+
$clients = array();
171+
foreach ( $terms as $term ) {
172+
$clients[] = array(
173+
'url' => $term->description,
174+
'name' => $term->name,
175+
'id' => $term->term_id,
176+
'icon' => get_term_meta( $term->term_id, 'icon', true ),
177+
);
178+
}
179+
return $clients;
180+
}
181+
182+
if ( is_numeric( $url ) ) {
183+
$terms = array( get_term( $url, 'indieauth_client' ) );
184+
return $terms;
185+
} else {
186+
$terms = get_terms(
187+
array(
188+
'taxonomy' => 'indieauth_client',
189+
'description' => $url,
190+
'hide_empty' => false,
191+
)
192+
);
193+
}
194+
if ( empty( $terms ) ) {
195+
return new WP_Error( 'not_found', __( 'No Term Found', 'indieauth' ) );
196+
}
197+
198+
if ( 1 !== count( $terms ) ) {
199+
return new WP_Error( 'multiples', __( 'Multiple Terms Found', 'indieauth' ), $terms );
200+
}
201+
202+
$term = $terms[0];
203+
204+
return array(
205+
'url' => $term->description,
206+
'name' => $term->name,
207+
'id' => $term->term_id,
208+
'icon' => get_term_meta( $term->term_id, 'icon', true ),
209+
);
210+
}
211+
212+
/**
213+
* Delete a client
214+
*/
215+
public static function delete_client( $url ) {
216+
$client = self::get_client( $url );
217+
if ( ! $client ) {
218+
return false;
219+
}
220+
221+
self::delete_icon_file( $client['icon'] );
222+
223+
return wp_delete_term(
224+
$client['id'],
225+
'indieauth_client'
226+
);
227+
}
228+
229+
/**
230+
* Return upload directory.
231+
*
232+
* @param string $filepath File Path. Optional
233+
* @param boolean $url Return a URL if true, otherwise the directory.
234+
* @return string URL of upload directory.
235+
*/
236+
public static function upload_directory( $filepath = '', $url = false ) {
237+
$upload_dir = wp_get_upload_dir();
238+
$upload_dir = $url ? $upload_dir['baseurl'] : $upload_dir['basedir'];
239+
$upload_dir .= '/indieauth/icons/';
240+
$upload_dir = apply_filters( 'indieauth_client_icon_directory', $upload_dir, $url );
241+
return $upload_dir . $filepath;
242+
}
243+
244+
/**
245+
* Given an Icon URL return the filepath.
246+
*
247+
* @param string $url URL.
248+
* @return string Filepath.
249+
*/
250+
public static function icon_url_to_filepath( $url ) {
251+
if ( ! str_contains( self::upload_directory( '', true ), $url ) ) {
252+
return false;
253+
}
254+
$path = str_replace( self::upload_directory( '', true ), '', $url );
255+
return self::upload_directory( $path );
256+
}
257+
258+
/**
259+
* Delete Icon File.
260+
*
261+
* @param string $url Icon to Delete.
262+
* @return boolean True if successful. False if not.
263+
*
264+
*/
265+
public static function delete_icon_file( $url ) {
266+
$filepath = self::icon_url_to_filepath( $url );
267+
if ( empty( $filepath ) ) {
268+
return false;
269+
}
270+
if ( file_exists( $filepath ) ) {
271+
wp_delete_file( $filepath );
272+
return true;
273+
}
274+
return false;
275+
}
276+
277+
278+
/**
279+
* Sideload Icon
280+
*
281+
* @param string $url URL for the client icon.
282+
* @param string $client_id Client ID
283+
* @return string URL to Downloaded Image.
284+
*
285+
*/
286+
public static function sideload_icon( $url, $client_id ) {
287+
// If the URL is inside the upload directory.
288+
if ( str_contains( self::upload_directory( '', true ), $url ) ) {
289+
return $url;
290+
}
291+
292+
// Load dependencies.
293+
require_once ABSPATH . 'wp-admin/includes/file.php';
294+
require_once ABSPATH . WPINC . '/media.php';
295+
296+
$filehandle = md5( $client_id ) . '.jpg';
297+
$filepath = self::upload_directory( $filehandle );
298+
299+
// Allow for common query parameters in image APIs to get a better quality image.
300+
$query = array();
301+
wp_parse_str( wp_parse_url( $url, PHP_URL_QUERY ), $query );
302+
if ( array_key_exists( 's', $query ) && is_numeric( $query['s'] ) ) {
303+
$url = str_replace( 's=' . $query['s'], 's=' . INDIEAUTH_ICON_SIZE, $url );
304+
}
305+
if ( array_key_exists( 'width', $query ) && array_key_exists( 'height', $query ) ) {
306+
$url = str_replace( 'width=' . $query['width'], 'width=' . INDIEAUTH_ICON_SIZE, $url );
307+
$url = str_replace( 'height=' . $query['height'], 'height=' . INDIEAUTH_ICON_SIZE, $url );
308+
}
309+
310+
// Download Profile Picture and add as attachment
311+
$file = wp_get_image_editor( download_url( $url, 300 ) );
312+
if ( is_wp_error( $file ) ) {
313+
return false;
314+
}
315+
$file->resize( null, INDIEAUTH_ICON_SIZE, true );
316+
$file->set_quality( INDIEAUTH_ICON_QUALITY );
317+
$file->save( $filepath, 'image/jpg' );
318+
319+
return self::upload_directory( $filehandle, true );
320+
}
321+
322+
323+
324+
} // End Class
325+
326+

includes/class-indieauth-token-endpoint.php

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,11 @@ public function generate_token_response( $response ) {
209209

210210
// Issue a token
211211
if ( ! empty( $scopes ) ) {
212-
$info = new IndieAuth_Client_Discovery( $response['client_id'] );
212+
$client = IndieAuth_Client_Taxonomy::add_client_with_discovery( $response['client_id'] );
213+
if ( is_wp_error( $client ) ) {
214+
$client = array( 'id' => $client->get_error_message() );
215+
}
216+
213217
$return['token_type'] = 'Bearer';
214218

215219
if ( ! array_key_exists( 'uuid', $response ) ) {
@@ -222,12 +226,14 @@ public function generate_token_response( $response ) {
222226
$return['uuid'] = $response['uuid'];
223227
}
224228

225-
$return['scope'] = $response['scope'];
226-
$return['issued_by'] = rest_url( 'indieauth/1.0/token' );
227-
$return['client_id'] = $response['client_id'];
228-
$return['client_name'] = $info->get_name();
229-
$return['client_icon'] = $info->get_icon();
230-
$return['iat'] = time();
229+
$return['scope'] = $response['scope'];
230+
$return['issued_by'] = rest_url( 'indieauth/1.0/token' );
231+
$return['client_id'] = $response['client_id'];
232+
if ( array_key_exists( 'id', $client ) ) {
233+
$return['client_uid'] = $client['id'];
234+
}
235+
236+
$return['iat'] = time();
231237

232238
$expires = (int) get_option( 'indieauth_expires_in' );
233239

0 commit comments

Comments
 (0)