PluginProbe
Hum / 1.3.1
Hum v1.3.1
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.3.1, at hum.php

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