PluginProbe
Hum / trunk
Hum vtrunk
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 trunk, at hum.php

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