PluginProbe
Hum / 1.2.3
Hum v1.2.3
trunk 1.0 1.1 1.2 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6
hum / hum.php

hum.php in Hum 1.2.3, at hum.php

475 lines 13.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Plugin Name: Hum
4 * Plugin URI: https://github.com/willnorris/wordpress-hum
5 * Description: Personal URL shortener for WordPress
6 * Author: Will Norris
7 * Author URI: https://willnorris.com/
8 * Version: 1.2.3
9 * License: MIT
10 * License URI: http://opensource.org/licenses/MIT
11 * Text Domain: hum
12 */
13
14 class Hum {
15
16 public function __construct() {
17 add_action( 'init', array( $this, 'init' ) );
18
19 register_activation_hook( __FILE__, array( $this, 'flush_rewrite_rules' ) );
20 register_deactivation_hook( __FILE__, 'flush_rewrite_rules' );
21 }
22
23 /**
24 * Initialize the plugin, registering WordPress hooks.
25 */
26 public function init() {
27 load_plugin_textdomain( 'hum', null, basename( dirname( __FILE__ ) ) );
28
29 // if you have hum installed, then you probably actually care about short
30 // links, so we'll add it to the admin menu bar.
31 add_action( 'admin_bar_menu', 'wp_admin_bar_shortlink_menu', 90 );
32
33 add_action( 'query_vars', array( $this, 'query_vars' ) );
34 add_action( 'parse_request', array( $this, 'parse_request' ) );
35 add_filter( 'hum_redirect', array( $this, 'redirect_request' ), 10, 3 );
36 add_filter( 'hum_redirect_i', array( $this, 'redirect_request_i' ), 10, 2 );
37 add_filter( 'hum_process_redirect', array( $this, 'process_redirect' ), 10, 2 );
38 add_action( 'init', array( $this, 'rewrite_rules' ) );
39 add_filter( 'pre_option_hum_shortlink_base', array( $this, 'config_shortlink_base' ) );
40 add_filter( 'pre_get_shortlink', array( $this, 'get_shortlink' ), 10, 4 );
41 add_filter( 'template_redirect', array( $this, 'legacy_redirect' ) );
42 add_filter( 'hum_legacy_id', array( $this, 'legacy_ftl_id' ), 10, 2 );
43 add_action( 'atom_entry', array( $this, 'shortlink_atom_entry' ) );
44
45 // Admin Settings
46 add_action( 'admin_init', array( $this, 'admin_init' ) );
47 add_action( 'admin_menu', array( $this, 'admin_menu' ) );
48 }
49
50 /**
51 * Accept hum query variables.
52 */
53 public function query_vars( $vars ) {
54 $vars[] = 'hum';
55 return $vars;
56 }
57
58 /**
59 * Parse request for shortlink. This is the main entry point for handling
60 * short URLs.
61 *
62 * @uses apply_filters() Calls 'hum_redirect' filter
63 * @uses apply_filters() Calls 'hum_process_redirect' filter
64 *
65 * @param WP $wp the WordPress environment for the request
66 */
67 public function parse_request( $wp ) {
68 if ( array_key_exists( 'hum', $wp->query_vars ) ) {
69 $hum_path = $wp->query_vars['hum'];
70 if ( strpos( $hum_path, '/' ) !== false ) {
71 list($type, $id) = explode( '/', $hum_path, 2 );
72 } else {
73 $type = $hum_path;
74 $id = null;
75 }
76
77 $url = apply_filters( 'hum_redirect', null, $type, $id );
78
79 // hum hasn't handled the request yet, so try again but strip common
80 // punctuation that might appear after a URL in written text: . , )
81 if ( ! $url ) {
82 $clean_id = preg_replace( '/[\.,\)]+$/', '', $id );
83 if ( $id != $clean_id ) {
84 $url = apply_filters( 'hum_redirect', null, $type, $clean_id );
85 }
86 }
87
88 if ( $url ) {
89 do_action( 'hum_process_redirect', $url, $id );
90 }
91
92 // hum didn't handle request, so issue 404.
93 // manually setting query vars like this feels very fragile, but
94 // $wp_query->set_404() doesn't do what we need here.
95 $wp->query_vars['error'] = '404';
96 }
97 }
98
99 /**
100 * Process the redirect.
101 *
102 * @param string $url the permalink of the post
103 * @param string $id the requested post ID
104 */
105 public function process_redirect( $url, $id ) {
106 wp_redirect( $url, 301 );
107 exit;
108 }
109
110 /**
111 * Get the short URL types that are handled locally by WordPress.
112 *
113 * @uses apply_filters() Calls 'hum_local_types' with array of local types
114 *
115 * @return array local types
116 */
117 public function local_types() {
118 $local_types = array( 'b', 't', 'a', 'p' );
119 return apply_filters( 'hum_local_types', $local_types );
120 }
121
122 /**
123 * Attempt to handle redirect for the current shortlink.
124 *
125 * This redirects shortlinks that are for content hosted directly within
126 * WordPress. The 'id' portion of these URLs is expected to be the
127 * sexagesimal post ID.
128 *
129 * This also allows for simple redirect rules for shortlink prefixes. Users
130 * can provide a filter to perform simple URL redirect for a given type
131 * prefix. For example, to redirect all /w/ shortlinks to your personal
132 * PBworks wiki, you could use:
133 *
134 * add_filter('hum_redirect_base_w',
135 * create_function('', 'return "http://willnorris.pbworks.com/";'));
136 *
137 * @uses apply_filters() Calls 'hum_redirect_{$type}' action
138 * @uses apply_filters() Calls 'hum_redirect_base_{$type}' filter on redirect base URL
139 *
140 * @param string $url the short URL
141 * @param string $type the content-type prefix
142 * @param string $id the requested post ID
143 */
144 public function redirect_request( $url, $type, $id ) {
145 // locally hosted content
146 $local_types = $this->local_types();
147 if ( in_array( $type, $local_types ) ) {
148 $p = sxg_to_num( $id );
149 if ( $p ) {
150 $url = get_permalink( $p );
151 }
152 }
153
154 // simple redirects for entire base type
155 if ( ! $url ) {
156 $url = apply_filters( "hum_redirect_base_{$type}", false );
157 if ( $url ) {
158 $url = trailingslashit( $url ) . $id;
159 }
160 }
161
162 $url = apply_filters( "hum_redirect_{$type}", $url, $id );
163 return $url;
164 }
165
166 /**
167 * Handles /i/ URLs that have ISBN or ASIN subpaths by redirecting to Amazon.
168 *
169 * @uses apply_filters() Calls 'hum_redirect_i_{$subtype}' action
170 * @uses apply_filters() Calls 'amazon_domain' filter
171 * @uses apply_filters() Calls 'amazon_affiliate_id' filter
172 *
173 * @param string $url the short URL
174 * @param string $path subpath of URL (after /i/)
175 */
176 public function redirect_request_i( $url, $path ) {
177 list( $subtype, $id ) = explode( '/', $path, 2 );
178
179 if ( $subtype ) {
180 switch ( $subtype ) {
181 case 'a':
182 case 'asin':
183 case 'i':
184 case 'isbn':
185 $amazon_domain = apply_filters( 'amazon_domain', 'www.amazon.com' );
186 $amazon_id = apply_filters( 'amazon_affiliate_id', false );
187 if ( $amazon_id ) {
188 // valid partner shortlink, checked by
189 // https://partnernet.amazon.de/gp/associates/network/tools/link-checker/main.html
190 $url = 'http://' . $amazon_domain . '/dp/product/' . $id . '?tag=' . $amazon_id;
191 } else {
192 $url = 'http://' . $amazon_domain . '/dp/product/' . $id;
193 }
194 break;
195 }
196 $url = apply_filters( "hum_redirect_i_{$subtype}", $url, $id );
197 }
198 return $url;
199 }
200
201 /**
202 * Add rewrite rules for hum shortlinks.
203 */
204 public function rewrite_rules() {
205 add_rewrite_rule( '([a-z](/.*)?$)', 'index.php?hum=$matches[1]', 'top' );
206 }
207
208 /**
209 * Add rewrite rules for hum shortlinks.
210 */
211 public function flush_rewrite_rules() {
212 $this->rewrite_rules();
213 flush_rewrite_rules();
214 }
215
216 /**
217 * Get the base URL for hum shortlinks. Defaults to the WordPress home url.
218 * Users can define HUM_SHORTLINK_BASE or provide a filter to use a custom
219 * domain for shortlinks.
220 *
221 * @uses apply_filters() Calls 'hum_shortlink_base' filter on base URL
222 *
223 * @return string
224 */
225 public function shortlink_base() {
226 $base = get_option( 'hum_shortlink_base' );
227 if ( empty( $base ) ) {
228 $base = home_url();
229 }
230 return apply_filters( 'hum_shortlink_base', $base );
231 }
232
233 /**
234 * Allow the constant named 'HUM_SHORTLINK_BASE' to override the base URL for shortlinks.
235 *
236 * @param string $url the short URL
237 */
238 public function config_shortlink_base( $url = '' ) {
239 if ( defined( 'HUM_SHORTLINK_BASE' ) ) {
240 return untrailingslashit( HUM_SHORTLINK_BASE );
241 }
242 return $url;
243 }
244
245 /**
246 * Get the shortlink for a post, page, attachment, or blog.
247 *
248 * @param string $link the current shortlink for the post
249 * @param int $id post ID
250 * @param string $context
251 * @param boolean $allow_slugs
252 * @return string
253 */
254 public function get_shortlink( $link, $id, $context, $allow_slugs ) {
255 $post_id = 0;
256 if ( 'query' == $context ) {
257 if ( is_front_page() ) {
258 $link = trailingslashit( $this->shortlink_base() );
259 } elseif ( is_singular() ) {
260 $post_id = get_queried_object_id();
261 }
262 } elseif ( 'post' == $context ) {
263 $post = get_post( $id );
264 $post_id = $post->ID;
265 }
266
267 if ( ! empty( $post_id ) ) {
268 $type = $this->type_prefix( $post_id );
269 $sxg_id = num_to_sxg( $post_id );
270 $link = trailingslashit( $this->shortlink_base() ) . $type . '/' . $sxg_id;
271 }
272
273 return $link;
274 }
275
276 /**
277 * Get the content-type prefix for the specified post.
278 *
279 * @see http://ttk.me/w/Whistle#design
280 * @uses apply_filters() Calls 'hum_type_prefix' on the content type prefix
281 *
282 * @param int|object $post A post
283 * @return string the content type prefix for the post
284 */
285 public function type_prefix( $post ) {
286 $prefix = 'b';
287
288 $post_type = get_post_type( $post );
289
290 if ( 'attachment' == $post_type ) {
291 // check if $post is a WP_Post or an ID
292 if ( is_numeric( $post ) ) {
293 $post_id = $post;
294 } else {
295 $post_id = $post->ID;
296 }
297
298 $mime_type = get_post_mime_type( $post_id );
299 $media_type = preg_replace( '/(\/[a-zA-Z]+)/i', '', $mime_type );
300
301 switch ( $media_type ) {
302 case 'audio':
303 case 'video':
304 $prefix = 'a'; break;
305 case 'image':
306 $prefix = 'p'; break;
307 }
308
309 // @todo add support for slides
310 } else {
311 $post_format = get_post_format( $post );
312 switch ( $post_format ) {
313 case 'aside':
314 case 'status':
315 case 'link':
316 $prefix = 't'; break;
317 case 'audio':
318 case 'video':
319 $prefix = 'a'; break;
320 case 'photo':
321 case 'gallery':
322 case 'image':
323 $prefix = 'p'; break;
324 }
325 }
326
327 return apply_filters( 'hum_type_prefix', $prefix, $post );
328 }
329
330 /**
331 * Support redirects from legacy short URL schemes. This allows users to migrate from other
332 * shortlink generaters, but still have hum support the old URLs.
333 *
334 * @uses do_action() Calls 'hum_legacy_id' with the post ID and shortlink path.
335 */
336 public function legacy_redirect() {
337 if ( is_404() ) {
338 global $wp;
339 $post_id = apply_filters( 'hum_legacy_id', 0, $wp->request );
340 if ( $post_id ) {
341 $url = get_permalink( $post_id );
342 if ( $url ) {
343 $url = apply_filters( 'hum_legacy_redirect', $url );
344 wp_redirect( $url, 301 );
345 exit;
346 }
347 }
348 }
349 }
350
351 /**
352 * Handle shortlinks generated by Friendly Twitter Links, which take the form
353 * /{id}, where {id} can be the base10 or base32 post ID.
354 *
355 * @param int $id post ID to filter on
356 * @param string $path URL path (without preceding slash) of the request
357 *
358 * @return string ID of post to redirect to
359 */
360 public function legacy_ftl_id( $id, $path ) {
361 if ( is_numeric( $path ) ) {
362 $post = get_post( $path );
363 } else {
364 $post_id = base_convert( $path, 32, 10 );
365 $post = get_post( $post_id );
366 }
367
368 if ( $post ) {
369 $id = $post->ID;
370 }
371
372 return $id;
373 }
374
375
376 // Admin Settings
377
378 /**
379 * Register admin settings for Hum.
380 */
381 public function admin_init() {
382 register_setting( 'general', 'hum_shortlink_base' );
383 }
384
385 /**
386 * Add admin settings fields for Hum.
387 */
388 public function admin_menu() {
389 add_settings_field( 'hum_shortlink_base', __( 'Shortlink Base (URL)', 'hum' ), array( $this, 'admin_shortlink_base' ), 'general' );
390 }
391
392 /**
393 * Admin UI for setting the shortlink base URL.
394 */
395 public function admin_shortlink_base() {
396 ?>
397 <input name="hum_shortlink_base" type="text" id="hum_shortlink_base"
398 value="<?php form_option( 'hum_shortlink_base' ); ?>"
399 <?php disabled( defined( 'HUM_SHORTLINK_BASE' ) ); ?>
400 class="regular-text code<?php if ( defined( 'HUM_SHORTLINK_BASE' ) ) { echo ' disabled'; } ?>" />
401 <p class="description">
402 <?php _e( 'If you have a custom domain you want to use for shortlinks, enter the address here.', 'hum' ); ?>
403 </p>
404
405 <script type="text/javascript">
406 // move adjacent to other URL properties
407 jQuery('input#hum_shortlink_base').parents('tr').insertAfter( jQuery('input#home').parents('tr') );
408 </script>
409 <?php
410 }
411
412 /**
413 * Add shortlink <link /> to Atom-Entry.
414 */
415 public function shortlink_atom_entry() {
416 $shortlink = wp_get_shortlink();
417 if ( $shortlink ) {
418 echo "\t\t" . '<link rel="shortlink" href="' . esc_attr( $shortlink ) . '" />' . PHP_EOL;
419 }
420 }
421 }
422
423 new Hum;
424
425
426 // New Base 60 - see http://ttk.me/w/NewBase60
427 //
428 // slightly modified from Cassis Project (http://cassisproject.com/)
429 // Copyright 2010 Tantek Çelik, used with permission under CC0 license (http://git.io/tZ8fjw)
430 //
431 // @codingStandardsIgnoreStart
432 if ( ! function_exists( 'num_to_sxg' ) ) :
433 /**
434 * Convert base-10 number to sexagesimal.
435 */
436 function num_to_sxg($n) {
437 $s = "";
438 $m = "0123456789ABCDEFGHJKLMNPQRSTUVWXYZ_abcdefghijkmnopqrstuvwxyz";
439 if ($n===null || $n===0) { return 0; }
440 while ($n>0) {
441 $d = $n % 60;
442 $s = $m[$d] . $s;
443 $n = ($n-$d)/60;
444 }
445 return $s;
446 }
447 endif;
448
449
450 if ( ! function_exists( 'sxg_to_num' ) ) :
451 /**
452 * Convert sexagesimal to base-10 number.
453 */
454 function sxg_to_num( $s ) {
455 $n = 0;
456 $j = strlen($s);
457 for ($i=0;$i<$j;$i++) { // iterate from first to last char of $s
458 $c = ord($s[$i]); // put current ASCII of char into $c
459 if ($c>=48 && $c<=57) { $c=$c-48; }
460 else if ($c>=65 && $c<=72) { $c-=55; }
461 else if ($c==73 || $c==108) { $c=1; } // typo capital I, lowercase l to 1
462 else if ($c>=74 && $c<=78) { $c-=56; }
463 else if ($c==79) { $c=0; } // error correct typo capital O to 0
464 else if ($c>=80 && $c<=90) { $c-=57; }
465 else if ($c==95) { $c=34; } // underscore
466 else if ($c>=97 && $c<=107) { $c-=62; }
467 else if ($c>=109 && $c<=122) { $c-=63; }
468 else { $c = 0; } // treat all other noise as 0
469 $n = 60*$n + $c;
470 }
471 return $n;
472 }
473 endif;
474 // @codingStandardsIgnoreEnd
475