WordPress.org

Make WordPress Core

Ticket #17902: 17902.3.patch

File 17902.3.patch, 30.6 KB (added by kurtpayne, 12 months ago)

Updated for 3.5

  • wp-admin/css/colors-classic.dev.css

     
    619619} 
    620620 
    621621div#media-upload-header, 
    622 div#plugin-information-header { 
     622div#plugin-information-header, 
     623div#plugin-readme-header { 
    623624        background-color: #f9f9f9; 
    624625        border-bottom-color: #dfdfdf; 
    625626} 
     
    19261927} 
    19271928 
    19281929/* Install Plugins */ 
    1929 #plugin-information .fyi ul { 
     1930#plugin-information .fyi ul, 
     1931#plugin-readme .fyi ul { 
    19301932        background-color: #eaf3fa; 
    19311933} 
    19321934 
    1933 #plugin-information .fyi h2.mainheader { 
     1935#plugin-information .fyi h2.mainheader, 
     1936#plugin-readme .fyi h2.mainheader { 
    19341937        background-color: #cee1ef; 
    19351938} 
    19361939 
    19371940#plugin-information pre, 
    1938 #plugin-information code { 
     1941#plugin-information code, 
     1942#plugin-readme pre, 
     1943#plugin-readme code { 
    19391944        background-color: #ededff; 
    19401945} 
    19411946 
    1942 #plugin-information pre { 
     1947#plugin-information pre, 
     1948#plugin-readme pre { 
    19431949        border: 1px solid #ccc; 
    19441950} 
    19451951 
  • wp-admin/css/colors-fresh.dev.css

     
    610610} 
    611611 
    612612div#media-upload-header, 
    613 div#plugin-information-header { 
     613div#plugin-information-header, 
     614div#plugin-readme-header { 
    614615        background-color: #f9f9f9; 
    615616        border-bottom-color: #dfdfdf; 
    616617} 
     
    15421543} 
    15431544 
    15441545/* Install Plugins */ 
    1545 #plugin-information .fyi ul { 
     1546#plugin-information .fyi ul, 
     1547#plugin-readme .fyi ul { 
    15461548        background-color: #eaf3fa; 
    15471549} 
    15481550 
    1549 #plugin-information .fyi h2.mainheader { 
     1551#plugin-information .fyi h2.mainheader, 
     1552#plugin-readme .fyi h2.mainheader { 
    15501553        background-color: #cee1ef; 
    15511554} 
    15521555 
    15531556#plugin-information pre, 
    1554 #plugin-information code { 
     1557#plugin-information code, 
     1558#plugin-readme pre, 
     1559#plugin-readme code { 
    15551560        background-color: #ededff; 
    15561561} 
    15571562 
    1558 #plugin-information pre { 
     1563#plugin-information pre, 
     1564#plugin-readme pre { 
    15591565        border: 1px solid #ccc; 
    15601566} 
    15611567 
  • wp-admin/css/wp-admin.dev.css

     
    71647164} 
    71657165 
    71667166/* Header on thickbox */ 
    7167 #plugin-information-header { 
     7167#plugin-information-header, 
     7168#plugin-readme-header { 
    71687169        margin: 0; 
    71697170        padding: 0 5px; 
    71707171        font-weight: bold; 
     
    71737174        border-bottom-style: solid; 
    71747175        height: 2.5em; 
    71757176} 
    7176 #plugin-information ul#sidemenu { 
     7177#plugin-information ul#sidemenu, 
     7178#plugin-readme ul#sidemenu { 
    71777179        font-weight: normal; 
    71787180        margin: 0 5px; 
    71797181        position: absolute; 
     
    72017203        line-height: 2em; 
    72027204} 
    72037205 
    7204 #plugin-information h2 { 
     7206#plugin-information h2, 
     7207#plugin-readme h2 { 
    72057208        clear: none !important; 
    72067209        margin-right: 200px; 
    72077210} 
    72087211 
    7209 #plugin-information .fyi { 
     7212#plugin-information .fyi, 
     7213#plugin-readme .fyi { 
    72107214        margin: 0 10px 50px; 
    72117215        width: 210px; 
    72127216} 
    72137217 
    7214 #plugin-information .fyi h2 { 
     7218#plugin-information .fyi h2, 
     7219#plugin-readme .fyi h2 { 
    72157220        font-size: 0.9em; 
    72167221        margin-bottom: 0; 
    72177222        margin-right: 0; 
    72187223} 
    72197224 
    7220 #plugin-information .fyi h2.mainheader { 
     7225#plugin-information .fyi h2.mainheader, 
     7226#plugin-readme .fyi h2.mainheader { 
    72217227        padding: 5px; 
    72227228        -webkit-border-top-left-radius: 3px; 
    72237229        border-top-left-radius: 3px; 
    72247230} 
    72257231 
     7232#plugin-readme .fyi ul, 
    72267233#plugin-information .fyi ul { 
    72277234        padding: 10px 5px 10px 7px; 
    72287235        margin: 0; 
     
    72317238        border-bottom-left-radius: 3px; 
    72327239} 
    72337240 
    7234 #plugin-information .fyi li { 
     7241#plugin-information .fyi li, 
     7242#plugin-readme .fyi li { 
    72357243        margin-right: 0; 
    72367244} 
    72377245 
    7238 #plugin-information #section-holder { 
     7246#plugin-information #section-holder, 
     7247#plugin-readme #section-holder { 
    72397248        padding: 10px; 
    72407249} 
    72417250 
    72427251#plugin-information .section ul, 
    7243 #plugin-information .section ol { 
     7252#plugin-information .section ol, 
     7253#plugin-readme .section ul, 
     7254#plugin-readme .section ol { 
    72447255        margin-left: 16px; 
    72457256        list-style-type: square; 
    72467257        list-style-image: none; 
     
    72667277 
    72677278#plugin-information #section-screenshots ol, 
    72687279#plugin-information .updated, 
    7269 #plugin-information pre { 
     7280#plugin-information pre, 
     7281#plugin-readme .updated, 
     7282#plugin-readme pre { 
    72707283        margin-right: 215px; 
    72717284} 
    72727285 
    7273 #plugin-information pre { 
     7286#plugin-information pre, 
     7287#plugin-readme pre { 
    72747288        padding: 7px; 
    72757289        overflow: auto; 
    72767290} 
  • wp-admin/css/wp-admin-rtl.dev.css

     
    22392239        float: right; 
    22402240} 
    22412241 
    2242 #plugin-information ul#sidemenu { 
     2242#plugin-information ul#sidemenu, 
     2243#plugin-readme ul#sidemenu { 
    22432244        left: auto; 
    22442245        right: 0; 
    22452246} 
    22462247 
    2247 #plugin-information h2 { 
     2248#plugin-information h2, 
     2249#plugin-readme h2 { 
    22482250        margin-right: 0; 
    22492251        margin-left: 200px; 
    22502252} 
    22512253 
    2252 #plugin-information .fyi { 
     2254#plugin-information .fyi, 
     2255#plugin-readme .fyi { 
    22532256        margin-left: 5px; 
    22542257        margin-right: 20px; 
    22552258} 
    22562259 
    2257 #plugin-information .fyi h2 { 
     2260#plugin-information .fyi h2, 
     2261#plugin-readme .fyi h2 { 
    22582262        margin-left: 0; 
    22592263} 
    22602264 
    2261 #plugin-information .fyi ul { 
     2265#plugin-information .fyi ul, 
     2266#plugin-readme .fyi ul { 
    22622267        padding: 10px 7px 10px 5px; 
    22632268} 
    22642269 
     
    22692274 
    22702275#plugin-information #section-screenshots ol, 
    22712276#plugin-information .updated, 
    2272 #plugin-information pre { 
     2277#plugin-information pre, 
     2278#plugin-readme .updated, 
     2279#plugin-readme pre { 
    22732280        margin-right: 0; 
    22742281        margin-left: 215px; 
    22752282} 
    22762283 
    22772284#plugin-information .updated, 
    2278 #plugin-information .error { 
     2285#plugin-information .error, 
     2286#plugin-readme .updated, 
     2287#plugin-readme .error { 
    22792288        clear: none; 
    22802289        direction: rtl; 
    22812290} 
  • wp-admin/plugin-install.php

     
    66 * @subpackage Administration 
    77 */ 
    88// TODO route this pages via a specific iframe handler instead of the do_action below 
    9 if ( !defined( 'IFRAME_REQUEST' ) && isset( $_GET['tab'] ) && ( 'plugin-information' == $_GET['tab'] ) ) 
     9if ( !defined( 'IFRAME_REQUEST' ) && isset( $_GET['tab'] ) && ( in_array( $_GET['tab'], array( 'plugin-information', 'plugin-readme' ) ) ) ) 
    1010        define( 'IFRAME_REQUEST', true ); 
    1111 
    1212/** WordPress Administration Bootstrap */ 
     
    2828$parent_file = 'plugins.php'; 
    2929 
    3030wp_enqueue_script( 'plugin-install' ); 
    31 if ( 'plugin-information' != $tab ) 
     31if ( !in_array( $tab, array('plugin-information', 'plugin-readme') ) ) 
    3232        add_thickbox(); 
    3333 
    3434$body_id = $tab; 
  • wp-admin/includes/class-wp-plugins-list-table.php

     
    420420                                                        $author = '<a href="' . $plugin_data['AuthorURI'] . '" title="' . esc_attr__( 'Visit author homepage' ) . '">' . $plugin_data['Author'] . '</a>'; 
    421421                                                $plugin_meta[] = sprintf( __( 'By %s' ), $author ); 
    422422                                        } 
     423                                        $slug = basename( $plugin_file, '.php' ); 
     424                                        $plugin_meta[] = '<a href="' . self_admin_url( 'plugin-install.php?tab=plugin-readme&amp;readme=true&amp;plugin=' . $slug . 
     425                                                                '&amp;TB_iframe=true&amp;width=600&amp;height=550' ) . '" class="thickbox" title="' . 
     426                                                                esc_attr( sprintf( __( 'More information about %s' ), "{$plugin_data['Name']} {$plugin_data['Version']}" ) ) . '">' . __( 'Details' ) . '</a>'; 
    423427                                        if ( ! empty( $plugin_data['PluginURI'] ) ) 
    424428                                                $plugin_meta[] = '<a href="' . $plugin_data['PluginURI'] . '" title="' . esc_attr__( 'Visit plugin site' ) . '">' . __( 'Visit plugin site' ) . '</a>'; 
    425429 
  • wp-admin/includes/plugin-install.php

     
    380380        exit; 
    381381} 
    382382add_action('install_plugins_pre_plugin-information', 'install_plugin_information'); 
     383 
     384/** 
     385 * Get information from a plugin locally 
     386 * @param string $slug 
     387 * @since 3.4.0 
     388 */ 
     389function local_plugin_api( $slug ) { 
     390 
     391        // Try to find the plugin file from the slug 
     392        $plugin_data = array(); 
     393        if ( file_exists( ABSPATH . PLUGINDIR . "/$slug.php" ) ) { 
     394                $plugin_data = get_plugin_data( ABSPATH . PLUGINDIR . "/$slug.php" ); 
     395        } elseif ( file_exists( ABSPATH . PLUGINDIR . "/$slug/$slug.php" ) ) { 
     396                $plugin_data = get_plugin_data( ABSPATH . PLUGINDIR . "/$slug/$slug.php" ); 
     397        } 
     398         
     399        // Try to load data from readme.txt 
     400        $readme_data = false; 
     401        if ( file_exists( ABSPATH . PLUGINDIR . "/$slug/readme.txt" ) ) { 
     402 
     403                // Get markddown library, turn off plugin functionality 
     404                if ( !defined( 'MARKDOWN_WP_POSTS' ) ) 
     405                        define( 'MARKDOWN_WP_POSTS', false ); 
     406                if ( !defined( 'MARKDOWN_WP_COMMENTS' ) ) 
     407                        define( 'MARKDOWN_WP_COMMENTS', false ); 
     408                if ( !class_exists( 'Markdown_Parser' ) ) 
     409                        include_once( ABSPATH . 'wp-admin/includes/markdown.php' ); 
     410                 
     411                include_once( ABSPATH . 'wp-admin/includes/class-wp-plugin-readme-parser.php' ); 
     412                $readme_parser = new wp_plugin_readme_parser(); 
     413                $readme_data = $readme_parser->parse_readme_file( ABSPATH . PLUGINDIR . "/$slug/readme.txt" ); 
     414        } 
     415         
     416        // If there's no readme (e.g. hello.php) create a fake structure 
     417        if ( empty( $readme_data ) ) { 
     418                $readme_data = array( 
     419                        'contributors'    => '', 
     420                        'requires_at_least' => '', 
     421                        'tested_up_to'    => '', 
     422                        'tags'                    => array(), 
     423                        'sections'                => array( 
     424                                'description'   => !empty( $plugin_data['Description'] ) ? $plugin_data['Description'] : '' 
     425                        ) 
     426                ); 
     427        } 
     428 
     429        // Convert to an API-style response 
     430        $api = array( 
     431                'name'             => !empty( $plugin_data['Name'] ) ? $plugin_data['Name'] : $slug, 
     432                'slug'             => $slug, 
     433                'version'               => !empty( $plugin_data['Version'] ) ? $plugin_data['Version'] : '', 
     434                'author'                 => !empty( $plugin_data['AuthorName'] ) ? $plugin_data['AuthorName'] : '', 
     435                'author_profile' => null, 
     436                'contributors'   => $readme_data['contributors'], 
     437                'requires'         => $readme_data['requires_at_least'], 
     438                'tested'                 => $readme_data['tested_up_to'], 
     439                'compatibility'  => null, 
     440                'rating'                 => null, 
     441                'num_ratings'   => null, 
     442                'downloaded'     => null, 
     443                'last_updated'   => null, 
     444                'added'           => null, 
     445                'homepage'         => !empty( $plugin_data['PluginURI'] ) ? $plugin_data['PluginURI'] : '', 
     446                'sections'         => $readme_data['sections'], 
     447                'download_link'  => null, 
     448                'tags'             => $readme_data['tags'] 
     449        ); 
     450 
     451        // Done 
     452        return (object) $api; 
     453} 
     454 
     455/** 
     456 * Display local plugin information in dialog box form. 
     457 * Pull from the readme.txt file and the plugin header 
     458 * @since 3.4.0 
     459 */ 
     460function plugin_readme_information() { 
     461        global $tab; 
     462 
     463        $api = local_plugin_api( stripslashes( $_REQUEST['plugin'] ) ); 
     464 
     465        if ( is_wp_error($api) ) 
     466                wp_die($api); 
     467 
     468        $plugins_allowedtags = array( 
     469                'a'     => array( 
     470                        'href'   => array(), 
     471                        'title'  => array(), 
     472                        'target' => array() 
     473                ), 
     474                'abbr' => array( 
     475                        'title' => array() 
     476                ), 
     477                'acronym' => array( 
     478                        'title' => array() 
     479                ), 
     480                'code'   => array(), 
     481                'pre'   => array(), 
     482                'em'     => array(), 
     483                'strong' => array(), 
     484                'div'   => array(), 
     485                'p'       => array(), 
     486                'ul'     => array(), 
     487                'ol'     => array(), 
     488                'li'     => array(), 
     489                'h1'     => array(), 
     490                'h2'     => array(), 
     491                'h3'     => array(), 
     492                'h4'     => array(), 
     493                'h5'     => array(), 
     494                'h6'     => array(), 
     495                'img'   => array( 
     496                        'src'   => array(), 
     497                        'class' => array(), 
     498                        'alt'   => array() 
     499                ) 
     500        ); 
     501 
     502        $plugins_section_titles = array( 
     503                'description'  => _x('Description',  'Plugin installer section title'), 
     504                'installation' => _x('Installation', 'Plugin installer section title'), 
     505                'faq'             => _x('FAQ',            'Plugin installer section title'), 
     506                'changelog'     => _x('Changelog',      'Plugin installer section title'), 
     507                'other_notes'  => _x('Other Notes',  'Plugin installer section title') 
     508        ); 
     509 
     510        // No screenshots at this time 
     511        if ( !empty( $api->sections['screenshots'] ) ) 
     512                unset( $api->sections['screenshots'] ); 
     513         
     514        // Sanitize HTML 
     515        foreach ( (array)$api->sections as $section_name => $content ) 
     516                $api->sections[$section_name] = wp_kses( $content, $plugins_allowedtags ); 
     517        foreach ( array( 'version', 'author', 'requires', 'tested', 'homepage', 'downloaded', 'slug' ) as $key ) { 
     518                if ( isset( $api->$key ) ) 
     519                        $api->$key = wp_kses( $api->$key, $plugins_allowedtags ); 
     520        } 
     521 
     522        // Default to the Description tab, Do not translate, API returns English. 
     523        $section = isset( $_REQUEST['section'] ) ? stripslashes( $_REQUEST['section'] ) : 'description'; 
     524        if ( empty($section) || ! isset($api->sections[ $section ]) ) 
     525                $section = array_shift( $section_titles = array_keys((array)$api->sections) ); 
     526 
     527        iframe_header( __( 'Plugin Details' ) ); 
     528        ?> 
     529        <div id="<?php echo $tab; ?>-header"> 
     530                <ul id="sidemenu"> 
     531                        <?php foreach ( (array) $api->sections as $section_name => $content ) : ?> 
     532                                <?php                            
     533                                        if ( isset( $plugins_section_titles[ $section_name ] ) ) 
     534                                                $title = $plugins_section_titles[ $section_name ]; 
     535                                        else 
     536                                                $title = ucwords( str_replace( '_', ' ', $section_name ) ); 
     537 
     538                                        $class = ( $section_name == $section ) ? ' class="current"' : ''; 
     539                                        $href = add_query_arg( array('tab' => $tab, 'section' => $section_name) ); 
     540                                        $href = esc_url($href); 
     541                                        $san_section = esc_attr( $section_name ); 
     542                                ?> 
     543                                <li><a name="<?php echo $san_section; ?>" href="<?php echo $href; ?>" <?php echo $class; ?>><?php echo $title; ?></a></li> 
     544                        <?php endforeach ; ?> 
     545                </ul> 
     546        </div> 
     547        <div class="alignright fyi"> 
     548                <h2 class="mainheader"><?php /* translators: For Your Information */ _e('FYI') ?></h2> 
     549                <ul> 
     550                        <?php if ( !empty( $api->version ) ) : ?> 
     551                                <li><strong><?php _e('Version:') ?></strong> <?php echo $api->version ?></li> 
     552                        <?php endif; ?> 
     553                        <?php if ( !empty( $api->author ) ) : ?> 
     554                                <li><strong><?php _e('Author:') ?></strong> <?php echo $api->author ?></li> 
     555                        <?php endif; ?> 
     556                        <?php if ( !empty( $api->requires ) ) : ?> 
     557                                <li><strong><?php _e('Requires WordPress Version:') ?></strong> <?php printf(__('%s or higher'), $api->requires) ?></li> 
     558                        <?php endif; ?> 
     559                        <?php if ( !empty( $api->tested ) ) : ?> 
     560                                <li><strong><?php _e('Compatible up to:') ?></strong> <?php echo $api->tested ?></li> 
     561                        <?php endif; ?>          
     562                        <?php if ( !empty( $api->homepage ) ) : ?> 
     563                                <li><a target="_blank" href="<?php echo $api->homepage ?>"><?php _e('Plugin Homepage &#187;') ?></a></li> 
     564                        <?php endif; ?> 
     565                </ul> 
     566        </div> 
     567        <div id="section-holder" class="wrap"> 
     568        <?php 
     569                if ( !empty( $api->tested ) && version_compare( substr( $GLOBALS['wp_version'], 0, strlen( $api->tested ) ), $api->tested, '>' ) ) 
     570                        echo '<div class="updated"><p>' . __('<strong>Warning:</strong> This plugin has <strong>not been tested</strong> with your current version of WordPress.') . '</p></div>'; 
     571 
     572                elseif ( !empty( $api->requires ) && version_compare( substr( $GLOBALS['wp_version'], 0, strlen( $api->requires ) ), $api->requires, '<' ) ) 
     573                        echo '<div class="updated"><p>' . __('<strong>Warning:</strong> This plugin has <strong>not been marked as compatible</strong> with your version of WordPress.') . '</p></div>'; 
     574 
     575                foreach ( (array) $api->sections as $section_name => $content ) { 
     576                        if ( isset( $plugins_section_titles[ $section_name ] ) ) 
     577                                $title = $plugins_section_titles[ $section_name ]; 
     578                        else 
     579                                $title = ucwords( str_replace( '_', ' ', $section_name ) ); 
     580 
     581                        $content = links_add_target($content, '_blank'); 
     582                        $san_section = esc_attr( $section_name ); 
     583                        $display = ( $section_name == $section ) ? 'block' : 'none'; 
     584                        ?> 
     585                        <div id="section-<?php echo $san_section; ?>" class="section" style="display: <?php echo $display; ?>;"> 
     586                                <h2 class="long-header"><?php echo $title; ?></h2> 
     587                                <?php echo $content; ?> 
     588                        </div> 
     589                        <?php 
     590                } 
     591        echo "</div>\n"; 
     592        iframe_footer(); 
     593        exit; 
     594} 
     595add_action('install_plugins_pre_plugin-readme', 'plugin_readme_information'); 
  • wp-admin/includes/class-wp-plugin-install-list-table.php

     
    3434                $tabs['popular']  = _x( 'Popular','Plugin Installer' ); 
    3535                $tabs['new']      = _x( 'Newest','Plugin Installer' ); 
    3636 
    37                 $nonmenu_tabs = array( 'plugin-information' ); //Valid actions to perform which do not have a Menu item. 
     37                $nonmenu_tabs = array( 'plugin-information', 'plugin-readme' ); //Valid actions to perform which do not have a Menu item. 
    3838 
    3939                $tabs = apply_filters( 'install_plugins_tabs', $tabs ); 
    4040                $nonmenu_tabs = apply_filters( 'install_plugins_nonmenu_tabs', $nonmenu_tabs ); 
  • wp-admin/includes/class-wp-plugin-readme-parser.php

     
     1<?php 
     2 
     3/** 
     4 * Parse a plugin's readme.txt file 
     5 * Based on http://code.svn.wordpress.org/plugin-readme-parser/parse-readme.php 
     6 * @link http://wordpress.org/extend/plugins/about/readme.txt 
     7 * @link http://wordpress.org/extend/plugins/about/validator/ 
     8 * @pacakge WordPress 
     9 * @version 1.0 
     10 */ 
     11class wp_plugin_readme_parser { 
     12 
     13                /** 
     14                 * Readme.txt file contents 
     15                 * This string will change with each pass in the process 
     16                 * @var string 
     17                 */ 
     18                protected $_readme_contents = ''; 
     19                 
     20                /** 
     21                 * Special section names 
     22                 * @var array 
     23                 */ 
     24                protected $_special_sections = array( 
     25                                'description', 
     26                                'installation', 
     27                                'frequently_asked_questions', 
     28                                'screenshots', 
     29                                'changelog', 
     30                                'change_log', 
     31                                'upgrade_notice' 
     32                ); 
     33 
     34                /** 
     35                 * Minimum version of WordPress 
     36                 * @var string 
     37                 */ 
     38                protected $_requires_at_least = ''; 
     39 
     40                /** 
     41                 * Maximum compatible version of WordPress 
     42                 * @var string 
     43                 */ 
     44                protected $_tested_up_to = ''; 
     45 
     46                /** 
     47                 * Stable tag for the plugin 
     48                 * @var string 
     49                 */ 
     50                protected $_stable_tag = ''; 
     51 
     52                /** 
     53                 * Keywords for the plugin directory search 
     54                 * @var array 
     55                 */ 
     56                protected $_tags = array(); 
     57 
     58                /** 
     59                 * Authors who wrote the plugin 
     60                 * @var array 
     61                 */ 
     62                protected $_contributors = array(); 
     63 
     64                /** 
     65                 * Where to donate 
     66                 * @var string 
     67                 */ 
     68                protected $_donate_link = ''; 
     69 
     70                /** 
     71                 * License type 
     72                 * @var string 
     73                 */ 
     74                protected $_license = ''; 
     75 
     76                /** 
     77                 * Recognized keys for records 
     78                 * @var array 
     79                 */ 
     80                protected $_keys = array( 
     81                                'requires_at_least' => '/Requires at least:[ \t]*(.+)/i', 
     82                                'tested_up_to'    => '/Tested up to:[ \t]*(.+)/i', 
     83                                'stable_tag'            => '/Stable tag:[ \t]*(.+)/i', 
     84                                'tags'                    => '/Tags:[ \t]*(.+)/i', 
     85                                'contributors'    => '/Contributors:[ \t]*(.+)/i', 
     86                                'donate_link'      => '/Donate link:[ \t]*(.+)/i', 
     87                                'license'                  => '/License:[ \t]*(.+)/i' 
     88                ); 
     89                 
     90                /** 
     91                 * Is the description a copy of the short description? 
     92                 * @var bool 
     93                 */ 
     94                protected $_is_excerpt = false; 
     95 
     96                /** 
     97                 * Was the short description truncated to 150 characters? 
     98                 * @var bool 
     99                 */ 
     100                protected $_is_truncated = false; 
     101 
     102                /** 
     103                 * Short description of the plugin 
     104                 * @var string 
     105                 */ 
     106                protected $_short_description = ''; 
     107 
     108                /** 
     109                 * List of the plugin's screenshots 
     110                 * @var array 
     111                 */ 
     112                protected $_screenshots = array(); 
     113                 
     114                /** 
     115                 * Content outside of the standard sections 
     116                 * @var string 
     117                 */ 
     118                protected $_remaining_content = ''; 
     119                 
     120                /** 
     121                 * Upgrade notices for users 
     122                 * Key = version, value = message.  Example: 
     123                 * 1.0 => Please upgrade 
     124                 * 1.1 => Dire security bug 
     125                 * 1.2 => Minor UI issue 
     126                 * @var array 
     127                 */ 
     128                protected $_upgrade_notice = array(); 
     129                 
     130                /** 
     131                 * Allowed tags in sections 
     132                 * @var array 
     133                 */ 
     134                private $_allowed_tags = array( 
     135                                'a'               => array( 
     136                                                'href'     => array(), 
     137                                                'title'   => array(), 
     138                                                'rel'           => array() 
     139                                ), 
     140                                'blockquote' => array( 'cite' => array() ), 
     141                                'br'             => array(), 
     142                                'cite'     => array(), 
     143                                'p'               => array(), 
     144                                'code'     => array(), 
     145                                'pre'           => array(), 
     146                                'em'             => array(), 
     147                                'strong'         => array(), 
     148                                'ul'             => array(), 
     149                                'ol'             => array(), 
     150                                'li'             => array(), 
     151                                'h3'             => array(), 
     152                                'h4'             => array() 
     153                ); 
     154 
     155                /** 
     156                 * Parse a plugin's readme.txt file.  Expects a path to the file. 
     157                 * @param string $file 
     158                 * @return string 
     159                 */ 
     160                public function parse_readme_file ( $file ) { 
     161                                if ( !file_exists( $file ) ) { 
     162                                                throw new Exception('File not found'); 
     163                                } 
     164                                $this->_readme_contents = file_get_contents( $file ); 
     165                                return $this->_parse_readme(); 
     166                } 
     167 
     168                /** 
     169                 * Parse a plugin's readme.txt.  Expect's the file contents as string. 
     170                 * @param type $file_contents 
     171                 * @return type  
     172                 */ 
     173                public function parse_readme_contents( $file_contents ) { 
     174                                $this->_readme_contents = $file_contents; 
     175                                return $this->_parse_readme(); 
     176                } 
     177                 
     178                /** 
     179                 * Extract sections 
     180                 * @return array 
     181                 */ 
     182                private function _exract_sections( ) { 
     183                                $ret = array(); 
     184 
     185                                $sections = preg_split( '/^[\s]*==[\s]*(.+?)[\s]*==/m', $this->_readme_contents, -1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY ); 
     186 
     187                                // Check if the first element is a short description 
     188                                $this->_short_description = ''; 
     189                                $this->_is_truncated      = false; 
     190                                $this->_is_excerpt              = true; 
     191                                if ( '==' != substr($sections[0][0], 0, 2) ) { 
     192                                                $short_description = array_shift( $sections ); 
     193                                                 
     194                                                // If the user doesn't include a full description, this will be used, not truncated, and converted to markdown.  If they 
     195                                                // do include a full description, this field will be overwritten 
     196                                                $ret['description'] = $this->_markdown( $short_description ); 
     197 
     198                                                $short_description = $this->_sanitize_text( $short_description ); 
     199                                                if ( strlen( $short_description ) > 150 ) 
     200                                                                $this->_is_truncated = true; 
     201                                                $this->_short_description = substr( $short_description, 0, 150 ); 
     202                                } 
     203 
     204                                // Sanitize titles / sections 
     205                                for ( $i = 0 ; $i < count( $sections ) ; $i += 2 ) { 
     206                                                $title  = $this->_sanitize_text( $sections[$i] ); 
     207                                                $contents = preg_replace( '/^[\s]*=[\s]+(.+?)[\s]+=/m', '<h4>$1</h4>', $sections[$i+1] ); 
     208                                                $contents = $this->_markdown( $contents ); 
     209                                                $ret[$title] = $contents;                                           
     210                                                if ( $this->_is_excerpt && 'description' == strtolower( strip_tags( $title ) ) ) 
     211                                                                $this->_is_excerpt = false; 
     212                                } 
     213 
     214                                // Remove the sections from the readme file 
     215                                $this->_readme_contents = trim( str_replace( $sections, '', $this->_readme_contents ) ); 
     216                                 
     217                                // Done 
     218                                return $this->_process_sections( $ret ); 
     219                } 
     220 
     221                /** 
     222                 * Post-process sections 
     223                 * Any special business logic (e.g. change_log -> changelog) is done here 
     224                 * @param array $sections 
     225                 * @return array 
     226                 */ 
     227                private function _process_sections( $sections ) { 
     228                                 
     229                                // Rename sections to lower-case-underscore notation relegate non-special 
     230                                // content to the "remaining content" section 
     231                                $_sections = array(); 
     232                                $this->_remaining_content = ''; 
     233                                foreach ( (array) $sections as $k => $v ) { 
     234                                                $name = strtolower( preg_replace( '/[^a-zA-Z_]/', '_', $k ) ); 
     235                                                $_sections[$name] = $v; 
     236 
     237                                                // Is this "remaining content" ? 
     238                                                if ( !in_array( $name, $this->_special_sections ) ) { 
     239                                                                $title_id = esc_attr( $k ); 
     240                                                                $title_id = str_replace( ' ', '-', $title_id ); 
     241                                                                $this->_remaining_content .= sprintf("\n<h3 id=\"%s\">%s</h3>\n%s", $name, $k, $v); 
     242                                                } 
     243                                } 
     244                                $sections = $_sections; 
     245 
     246                                // Upgrade notice section 
     247                                $this->_upgrade_notice = array(); 
     248                                if ( array_key_exists( 'upgrade_notice', $sections ) ) { 
     249                                                $upgrade_notice = array(); 
     250                                                $notices = preg_split( '/<h4>(.*?)<\/h4>/', $sections['upgrade_notice'], -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY ); 
     251                                                if ( empty( $notices ) && count( $notices ) > 1 ) { 
     252                                                                for ( $i = 0 ; $i < count( $notices ) ; $i += 2 ) 
     253                                                                                $upgrade_notice[$notices[$i]] = substr( $this->_sanitize_text( @$notices[$i+1] ), 0, 300 ); 
     254                                                } elseif ( is_array( $notices ) && 1 == count( $notices ) ) { 
     255                                                                $upgrade_notice[ $this->_stable_tag ] = $notices[0]; 
     256                                                } 
     257                                                $this->_upgrade_notice = $upgrade_notice; 
     258                                                unset( $sections['upgrade_notice'] ); 
     259                                } 
     260 
     261                                // Screenshots 
     262                                $this->_screenshots = array(); 
     263                                if ( array_key_exists( 'screenshots', $sections ) ) { 
     264                                                preg_match_all( '/<li>(.*?)<\/li>/s', $sections['screenshots'], $screenshots, PREG_SET_ORDER); 
     265                                                if ( $screenshots ) { 
     266                                                                foreach ( (array) $screenshots as $screenshot ) 
     267                                                                                $this->_screenshots[] = $screenshot[1]; 
     268                                                } 
     269                                } 
     270 
     271                                // Fix change_log -> changelog 
     272                                if ( array_key_exists( 'change_log', $sections ) ) 
     273                                                $sections['changelog'] = $sections['change_log']; 
     274 
     275                                return $sections; 
     276                } 
     277 
     278                /** 
     279                 * Post-process records 
     280                 * Any special business logic (e.g. splitting tags, contributors) is done here 
     281                 * @param array $records 
     282                 * @return array 
     283                 */ 
     284                private function _process_records( $records ) { 
     285                                 
     286                                // Escape donate link URL 
     287                                if ( !empty( $records['donate_link'] ) ) 
     288                                                $records['donate_link'] = esc_url( $records['donate_link'] ); 
     289                                 
     290                                // Split up tags 
     291                                $tags = preg_split('/,[\s]*?/', trim( $records['tags'] ) ); 
     292                                foreach ( (array) $tags as $k => $v ) { 
     293                                                $v = $this->_sanitize_text( $v ); 
     294                                                if ( !empty( $v ) ) 
     295                                                                $tags[$k] = $this->_sanitize_text( $v ); 
     296                                                else 
     297                                                                unset( $tags[$k] ); 
     298                                } 
     299                                $records['tags'] = $tags; 
     300 
     301                                // Split up contributors 
     302                                $contributors = preg_split( '/,[\s]*/', trim( $records['contributors'] ) ); 
     303                                foreach ( (array) $contributors as $k => $v ) { 
     304                                                $v = $this->_sanitize_text( $v ); 
     305                                                if ( !empty( $v ) ) 
     306                                                                $contributors[$k] = sanitize_user( $v ); 
     307                                                else 
     308                                                                unset( $contributors[$k] ); 
     309                                } 
     310                                $records['contributors'] = $contributors; 
     311 
     312                                // Assign records to object properties 
     313                                foreach ( (array) $records as $k => $v ) 
     314                                                if (in_array( $k , array_keys( $this->_keys ) ) ) 
     315                                                                $this->{"_$k"} = $v; 
     316 
     317                                // Done 
     318                                return $records; 
     319                } 
     320 
     321                /** 
     322                 * Extract key-value pairs 
     323                 * @return array 
     324                 */ 
     325                private function _extract_records( ) { 
     326                                $ret = array(); 
     327                                foreach ( (array) $this->_keys as $k => $v ) { 
     328                                                if ( preg_match( $v, $this->_readme_contents, $matches ) ) { 
     329                                                                $ret[$k] = $this->_sanitize_text( $matches[1] ); 
     330                                                                $this->_readme_contents = trim( str_replace( $matches[0], '', $this->_readme_contents ) ); 
     331                                                } 
     332                                } 
     333                                return $this->_process_records( $ret ); 
     334                } 
     335                 
     336                /** 
     337                 * Parse a readme file into an associative array 
     338                 * @return string 
     339                 */ 
     340                protected function _parse_readme() { 
     341 
     342                                // Normalize white-space 
     343                                $file_contents = str_replace( array( "\r\n", "\r" ), "\n", $this->_readme_contents ); 
     344                                $this->_readme_contents = trim( $this->_readme_contents ); 
     345 
     346                                // Remove UTF-8 byte-order-mark 
     347                                if ( 0 === strpos( $this->_readme_contents, "\xEF\xBB\xBF" ) ) 
     348                                                $this->_readme_contents = substr( $this->_readme_contents, 3 ); 
     349 
     350                                // === Plugin Name === 
     351                                // Must be the very first thing.                                 
     352                                if ( !preg_match( '/^===(.*)===/', $this->_readme_contents, $_name ) ) 
     353                                                return array(); // require a name 
     354                                $name = trim( $_name[1], '=' ); 
     355                                $name = $this->_sanitize_text( $name ); 
     356                                $this->_readme_contents = str_replace( $_name[0], '', $this->_readme_contents ); 
     357                                 
     358                                // Extract the "key: value" records to the local scope 
     359                                extract( $this->_extract_records() ); 
     360                                 
     361                                // Extract the sections (e.g. == HEADING == .... text ...) 
     362                                $sections = $this->_exract_sections(); 
     363                                extract( $sections ); 
     364                                 
     365                                // Compile the final array 
     366                                return array( 
     367                                                'name'                    => isset( $name )                       ? $name                         : '', 
     368                                                'tags'                    => isset( $tags )                       ? $tags                         : array(), 
     369                                                'requires_at_least' => isset( $requires_at_least ) ? $requires_at_least : '', 
     370                                                'tested_up_to'    => isset( $tested_up_to )       ? $tested_up_to         : '', 
     371                                                'stable_tag'            => isset( $stable_tag )         ? $stable_tag           : 'trunk', 
     372                                                'contributors'    => isset( $contributors )       ? $contributors         : array(), 
     373                                                'donate_link'      => isset( $donate_link )        ? $donate_link          : '', 
     374                                                'short_description' => $this->_short_description, 
     375                                                'screenshots'      => $this->_screenshots, 
     376                                                'is_excerpt'            => $this->_is_excerpt, 
     377                                                'is_truncated'    => $this->_is_truncated, 
     378                                                'sections'                => $sections, 
     379                                                'remaining_content' => $this->_remaining_content, 
     380                                                'upgrade_notice'        => $this->_upgrade_notice 
     381                                ); 
     382                } 
     383 
     384                /** 
     385                 * Make the text safe for use in a browser 
     386                 * @param string $text 
     387                 * @return string 
     388                 */ 
     389                private function _sanitize_text( $text ) { 
     390                                $text = strip_tags( $text ); 
     391                                $text = esc_html( $text ); 
     392                                $text = trim( $text ); 
     393                                return $text; 
     394                } 
     395 
     396                /** 
     397                 * Format text.  Use markdown, with some extra code -> backtick formatting. 
     398                 * @param type $text 
     399                 * @return type  
     400                 */ 
     401                private function _markdown( $text ) { 
     402                                $text = trim( $text ); 
     403                                $text = $this->_convert_code( $text ); 
     404                                $text = Markdown( $text );                         
     405                                $text = balanceTags( $text ); 
     406                                $text = wp_kses( $text, $this->_allowed_tags ); 
     407                                $text = trim( $text ); 
     408                                return $text; 
     409                } 
     410 
     411                /** 
     412                 * First take any user formatted code blocks and turn them into backticks so that 
     413                 * markdown will preserve things like underscores in code blocks 
     414                 * @param type $text 
     415                 * @param type $markdown 
     416                 * @return type  
     417                 */ 
     418                private function _convert_code( $text ) { 
     419                                $text = preg_replace_callback( '/(<pre><code>|<code>)(.*?)(<\/code>\/pre>|<\/code>)/s', array( $this, '_decodeit' ), $text ); 
     420                                $text = str_replace( array("\r\n", "\r"), "\n", $text ); 
     421                                // Markdown seems to be choking on block level stuff too.  Let's just encode it and be done with it. 
     422                                $text = preg_replace_callback( '/(^|\n)`(.*?)`/s', array( $this, '_encodeit' ), $text ); 
     423                                return $text; 
     424                } 
     425                 
     426                /** 
     427                 * Encode Markdown to HTML 
     428                 * @param array $matches Output from preg_replace_callback 
     429                 * @return string  
     430                 */ 
     431                private function _encodeit( $matches ) { 
     432                                $text = trim( $matches[2] ); 
     433                                $text = htmlspecialchars( $text, ENT_QUOTES ); 
     434                                $text = str_replace( array( "\r\n", "\r" ), "\n", $text ); 
     435                                $text = preg_replace( "|\n\n\n+|", "\n\n", $text ); 
     436                                $text = str_replace( 
     437                                                array( '&amp;lt;', '&amp;&gt; ' ), 
     438                                                array( '&lt;'   , '&gt;'           ), 
     439                                                $text 
     440                                ); 
     441                                $text = "<code>$text</code>"; 
     442                                if ( "`" != $matches[1] ) 
     443                                                $text = "<pre>$text</pre>"; 
     444                                return $text; 
     445                } 
     446 
     447                /** 
     448                 * De-code HTML, turn it back into markdown 
     449                 * @param array $matches Output from preg_replace_callback 
     450                 * @return string  
     451                 */ 
     452                private function _decodeit( $matches ) { 
     453                                $text = $matches[2]; 
     454                                $text = html_entity_decode( $text ); 
     455                                $text = str_replace( 
     456                                                array( '<br />', '&#38;', '&#39;'), 
     457                                                array( ''         , '&' , "'"   ), 
     458                                                $text 
     459                                );                         
     460                                if ( '<pre><code>' == $matches[1] ) 
     461                                                $text = "\n$text\n"; 
     462                                return "`$text`"; 
     463                } 
     464}