├── LICENSE ├── build ├── index.asset.php └── index.js ├── hum.php └── readme.md /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Google and contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /build/index.asset.php: -------------------------------------------------------------------------------- 1 | array('react-jsx-runtime', 'wp-components', 'wp-editor', 'wp-element', 'wp-i18n', 'wp-plugins'), 'version' => '616645f563721e226fea'); 2 | -------------------------------------------------------------------------------- /build/index.js: -------------------------------------------------------------------------------- 1 | (()=>{"use strict";const n=window.wp.plugins,e=window.wp.editor,i=window.wp.components,t=window.wp.element,o=window.wp.i18n,l=window.ReactJSXRuntime;(0,n.registerPlugin)("hum-gutenberg-shortlink-panel",{render:()=>{const{shortlink:n}=window._humEditorObject,[r,s]=(0,t.useState)(!1);return(0,l.jsxs)(e.PluginDocumentSettingPanel,{name:"shortlink-panel",title:"Shortlink",className:"shortlink-panel",children:[(0,l.jsx)(i.TextControl,{label:(0,o.__)("Shortlink","hum"),hideLabelFromVision:"true",value:n,disabled:!0}),(0,l.jsx)(i.ClipboardButton,{isPrimary:!0,text:n,onCopy:()=>s(!0),onFinishCopy:()=>s(!1),children:r?(0,o.__)("Copied!","hum"):(0,o.__)("Copy link","hum")})]})}})})(); -------------------------------------------------------------------------------- /hum.php: -------------------------------------------------------------------------------- 1 | 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 | 452 | class="regular-text code" /> 453 |
454 | 455 |
456 | 457 | 461 | to Atom-Entry. 466 | */ 467 | public function shortlink_atom_entry() { 468 | $shortlink = wp_get_shortlink(); 469 | if ( $shortlink ) { 470 | echo "\t\t" . '' . 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( '%s', 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 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Hum 2 | 3 | - Contributors: willnorris, pfefferle 4 | - Tags: shortlink, whistle, diso 5 | - Requires at least: 3.0 6 | - Tested up to: 6.7 7 | - Stable tag: 1.3.6 8 | - Requires PHP: 5.6 9 | - License: MIT 10 | - License URI: http://opensource.org/licenses/MIT 11 | 12 | Personal URL shortener for WordPress 13 | 14 | 15 | ## Description 16 | 17 | Hum is a personal URL shortener for WordPress, designed to provide short URLs to your personal content, both hosted on WordPress and elsewhere. For example, rather than a long URL for a WordPress post such as