WordPress.org

Making WordPress.org

Changeset 2814


Ignore:
Timestamp:
03/25/16 18:22:12 (21 months ago)
Author:
coffee2code
Message:

developer.wordpress.org: Auto-link plaintext references to classes, functions, and methods.

  • Ignores references made in <code> blocks.
  • Does not attempt to recognize references to hooks. Such references must use {@see 'hook_name'} syntax.

Fixes #1483.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-developer/inc/formatting.php

    r2452 r2814  
    2727        add_filter( 'the_excerpt', array( __CLASS__, 'remove_inline_internal' ) ); 
    2828        add_filter( 'the_content', array( __CLASS__, 'remove_inline_internal' ) ); 
     29 
     30        add_filter( 'the_excerpt', array( __CLASS__, 'autolink_references' ), 11 ); 
     31        add_filter( 'the_content', array( __CLASS__, 'autolink_references' ), 11 ); 
    2932 
    3033        add_action( 'the_content', array( __CLASS__, 'fix_unintended_markdown' ) ); 
     
    237240    } 
    238241 
     242    /** 
     243     * Automatically detects inline references to parsed resources and links to them. 
     244     * 
     245     * Examples: 
     246     * - Functions: get_the_ID() 
     247     * - Classes:   WP_Query 
     248     * - Methods:   WP_Query::is_single() 
     249     * 
     250     * Note: currently there is not a reliable way to infer references to hooks. Recommend 
     251     * using the {@}see 'hook_name'} notation as used in the inline docs. 
     252     * 
     253     * @param  string $text The text. 
     254     * @return string 
     255     */ 
     256    public function autolink_references( $text ) { 
     257        $r = ''; 
     258        $textarr = preg_split( '/(<[^<>]+>)/', $text, -1, PREG_SPLIT_DELIM_CAPTURE ); // split out HTML tags 
     259        $nested_code_pre = 0; // Keep track of how many levels link is nested inside <pre> or <code> 
     260        foreach ( $textarr as $piece ) { 
     261 
     262            if ( preg_match( '|^<code[\s>]|i', $piece ) || preg_match( '|^<pre[\s>]|i', $piece ) || preg_match( '|^<script[\s>]|i', $piece ) || preg_match( '|^<style[\s>]|i', $piece ) ) 
     263                $nested_code_pre++; 
     264            elseif ( $nested_code_pre && ( '</code>' === strtolower( $piece ) || '</pre>' === strtolower( $piece ) || '</script>' === strtolower( $piece ) || '</style>' === strtolower( $piece ) ) ) 
     265                $nested_code_pre--; 
     266 
     267            if ( $nested_code_pre || empty( $piece ) || ( $piece[0] === '<' && ! preg_match( '|^<\s*[\w]{1,20}+://|', $piece ) ) ) { 
     268                $r .= $piece; 
     269                continue; 
     270            } 
     271 
     272            // Long strings might contain expensive edge cases ... 
     273            if ( 10000 < strlen( $piece ) ) { 
     274                // ... break it up 
     275                foreach ( _split_str_by_whitespace( $piece, 2100 ) as $chunk ) { // 2100: Extra room for scheme and leading and trailing paretheses 
     276                    if ( 2101 < strlen( $chunk ) ) { 
     277                        $r .= $chunk; // Too big, no whitespace: bail. 
     278                    } else { 
     279                        $r .= make_clickable( $chunk ); 
     280                    } 
     281                } 
     282            } else { 
     283                /* 
     284                 * Everthing outside of this conditional block was copied from core's 
     285                 *`make_clickable()`. 
     286                 */ 
     287 
     288                $content = " $piece "; // Pad with whitespace to simplify the regexes 
     289 
     290                // Only if the text contains something that might be a function. 
     291                if ( false !== strpos( $content, '()' ) ) { 
     292 
     293                    // Detect references to class methods, e.g. WP_Query::query() 
     294                    // or functions, e.g. register_post_type(). 
     295                    $content = preg_replace_callback( 
     296                        '~ 
     297                            (?!<.*?)       # Non-capturing check to ensure not matching what looks like the inside of an HTML tag. 
     298                            (              # 1: The full method or function name. 
     299                                ((\w+)::)? # 2: The class prefix, if a method reference. 
     300                                (\w+)      # 3: The method or function name. 
     301                            ) 
     302                            \(\)           # The () that signifies either a method or function. 
     303                            (?![^<>]*?>)   # Non-capturing check to ensure not matching what looks like the inside of an HTML tag. 
     304                        ~x', 
     305                        function ( $matches ) { 
     306                            // Reference to a class method. 
     307                            if ( $matches[2] ) { 
     308                                // Only link actually parsed methods. 
     309                                if ( $post = get_page_by_title( $matches[1], OBJECT, 'wp-parser-method' ) ) { 
     310                                    return sprintf( 
     311                                        '<a href="%s">%s</a>', 
     312                                        get_permalink( $post->ID ), 
     313                                        $matches[0] 
     314                                    ); 
     315                                } 
     316 
     317                            // Reference to a function. 
     318                            } else { 
     319                                // Only link actually parsed functions. 
     320                                if ( $post = get_page_by_title( $matches[1], OBJECT, 'wp-parser-function' ) ) { 
     321                                    return sprintf( 
     322                                        '<a href="%s">%s</a>', 
     323                                        get_permalink( $post->ID ), 
     324                                        $matches[0] 
     325                                    ); 
     326                                } 
     327                            } 
     328 
     329                            // It's not a reference to an actual thing, so restore original text. 
     330                            return $matches[0]; 
     331                        }, 
     332                        $content 
     333                    ); 
     334 
     335                } 
     336 
     337                // Detect references to classes, e.g. WP_Query 
     338                $content = preg_replace_callback( 
     339                    // Most class names start with an uppercase letter and have an underscore. 
     340                    // The exceptions are explicitly listed since future classes likely won't violate previous statement. 
     341                    '~' 
     342                        . '(?<!/)' 
     343                        . '\b'                // Word boundary 
     344                        . '('                 // Primary match grouping 
     345                            . 'wpdb|wp_atom_server|wp_xmlrpc_server extends IXR_Server'               // Exceptions that start with lowercase letter 
     346                            . '|AtomFeed|AtomEntry|AtomParser|MagpieRSS|RSSCache|Translations|Walker' // Exceptions that lack an underscore 
     347                            . '|[A-Z][a-zA-Z]+_\w+'                                                   // Most start with uppercase, has underscore 
     348                        . ')'                 // End primary match grouping 
     349                        . '\b'                // Word boundary 
     350                        . '(?!([<:]|"|\'>))'  // Does not appear within a tag 
     351                    . '~', 
     352                    function ( $matches ) { 
     353                        // If match is all caps, it's not a possible class name. 
     354                        // We'll chalk the sole exception, WP, as merely being an abbreviation (the regex won't match it anyhow). 
     355                        if ( strtoupper( $matches[0] ) === $matches[0] ) { 
     356                            return $matches[0]; 
     357                        } 
     358 
     359                        // Only link actually parsed classes. 
     360                        if ( $post = get_page_by_title( $matches[0], OBJECT, 'wp-parser-class' ) ) { 
     361                            return sprintf( 
     362                                '<a href="%s">%s</a>', 
     363                                get_permalink( $post->ID ), 
     364                                $matches[0] 
     365                            ); 
     366                        } 
     367 
     368                        // Not a class reference, so put the original reference back in. 
     369                        return $matches[0]; 
     370                    }, 
     371                    $content 
     372                ); 
     373 
     374                // Maybelater: Detect references to hooks, Currently not deemed reliably possible. 
     375 
     376                $content = substr( $content, 1, -1 ); // Remove our whitespace padding. 
     377                $r .= $content; 
     378 
     379            } // end else 
     380 
     381        } // end foreach 
     382 
     383        // Cleanup of accidental links within links 
     384        return preg_replace( '#(<a([ \r\n\t]+[^>]+?>|>))<a [^>]+?>([^>]+?)</a></a>#i', "$1$3</a>", $r ); 
     385    } 
     386 
    239387} // DevHub_Formatting 
    240388 
Note: See TracChangeset for help on using the changeset viewer.