WordPress.org

Make WordPress Core

Ticket #17902: 17902.patch

File 17902.patch, 30.0 KB (added by kurtpayne, 6 years ago)

Display local information about plugins from readme.txt and plugin headers

  • 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}
  • wp-admin/includes/class-wp-plugins-list-table.php

    Property changes on: wp-admin\includes\class-wp-plugin-readme-parser.php
    ___________________________________________________________________
    Added: svn:eol-style
       + LF
    
     
    427427                                                        $author = '<a href="' . $plugin_data['AuthorURI'] . '" title="' . esc_attr__( 'Visit author homepage' ) . '">' . $plugin_data['Author'] . '</a>';
    428428                                                $plugin_meta[] = sprintf( __( 'By %s' ), $author );
    429429                                        }
     430                                        $slug = basename( $plugin_file, '.php' );
     431                                        $plugin_meta[] = '<a href="' . self_admin_url( 'plugin-install.php?tab=plugin-readme&amp;readme=true&amp;plugin=' . $slug .
     432                                                                '&amp;TB_iframe=true&amp;width=600&amp;height=550' ) . '" class="thickbox" title="' .
     433                                                                esc_attr( sprintf( __( 'More information about %s' ), "{$plugin_data['Name']} {$plugin_data['Version']}" ) ) . '">' . __( 'Details' ) . '</a>';
    430434                                        if ( ! empty( $plugin_data['PluginURI'] ) )
    431435                                                $plugin_meta[] = '<a href="' . $plugin_data['PluginURI'] . '" title="' . esc_attr__( 'Visit plugin site' ) . '">' . __( 'Visit plugin site' ) . '</a>';
    432436
  • wp-admin/css/colors-classic.dev.css

     
    583583}
    584584
    585585div#media-upload-header,
    586 div#plugin-information-header {
     586div#plugin-information-header,
     587div#plugin-readme-header {
    587588        background-color: #f9f9f9;
    588589        border-bottom-color: #dfdfdf;
    589590}
     
    17251726        border-right: 1px solid #fff;
    17261727}
    17271728
    1728 #plugin-information .fyi ul {
     1729#plugin-information .fyi ul,
     1730#plugin-readme .fyi ul {
    17291731        background-color: #eaf3fa;
    17301732}
    17311733
    1732 #plugin-information .fyi h2.mainheader {
     1734#plugin-information .fyi h2.mainheader,
     1735#plugin-readme .fyi h2.mainheader {
    17331736        background-color: #cee1ef;
    17341737}
    17351738
    17361739#plugin-information pre,
    1737 #plugin-information code {
     1740#plugin-information code,
     1741#plugin-readme pre,
     1742#plugin-readme code {
    17381743        background-color: #ededff;
    17391744}
    17401745
    1741 #plugin-information pre {
     1746#plugin-information pre,
     1747#plugin-readme pre {
    17421748        border: 1px solid #ccc;
    17431749}
    17441750
  • wp-admin/css/colors-fresh.dev.css

     
    591591}
    592592
    593593div#media-upload-header,
    594 div#plugin-information-header {
     594div#plugin-information-header,
     595div#plugin-readme-header {
    595596        background-color: #f9f9f9;
    596597        border-bottom-color: #dfdfdf;
    597598}
     
    13601361        border-right: 1px solid #f9f9f9;
    13611362}
    13621363
    1363 #plugin-information .fyi ul {
     1364#plugin-information .fyi ul,
     1365#plugin-readme .fyi ul {
    13641366        background-color: #eaf3fa;
    13651367}
    13661368
    1367 #plugin-information .fyi h2.mainheader {
     1369#plugin-information .fyi h2.mainheader,
     1370#plugin-readme .fyi h2.mainheader {
    13681371        background-color: #cee1ef;
    13691372}
    13701373
    13711374#plugin-information pre,
    1372 #plugin-information code {
     1375#plugin-information code,
     1376#plugin-readme pre,
     1377#plugin-readme code {
    13731378        background-color: #ededff;
    13741379}
    13751380
    1376 #plugin-information pre {
     1381#plugin-information pre,
     1382#plugin-readme pre {
    13771383        border: 1px solid #ccc;
    13781384}
    13791385
  • 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/plugin-install.php

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

     
    21482148        left: 0;
    21492149}
    21502150
    2151 #plugin-information ul#sidemenu {
     2151#plugin-information ul#sidemenu,
     2152#plugin-readme ul#sidemenu {
    21522153        left: auto;
    21532154        right: 0;
    21542155}
    21552156
    2156 #plugin-information h2 {
     2157#plugin-information h2,
     2158#plugin-readme h2 {
    21572159        margin-right: 0;
    21582160        margin-left: 200px;
    21592161}
    21602162
    2161 #plugin-information .fyi {
     2163#plugin-information .fyi,
     2164#plugin-readme .fyi {
    21622165        margin-left: 5px;
    21632166        margin-right: 20px;
    21642167}
    21652168
    2166 #plugin-information .fyi h2 {
     2169#plugin-information .fyi h2,
     2170#plugin-readme .fyi h2 {
    21672171        margin-left: 0;
    21682172}
    21692173
    2170 #plugin-information .fyi ul {
     2174#plugin-information .fyi ul,
     2175#plugin-readme .fyi ul {
    21712176        padding: 10px 7px 10px 5px;
    21722177}
    21732178
     
    21782183
    21792184#plugin-information #section-screenshots ol,
    21802185#plugin-information .updated,
    2181 #plugin-information pre {
     2186#plugin-information pre,
     2187#plugin-readme .updated,
     2188#plugin-readme pre {
    21822189        margin-right: 0;
    21832190        margin-left: 215px;
    21842191}
    21852192
    21862193#plugin-information .updated,
    2187 #plugin-information .error {
     2194#plugin-information .error,
     2195#plugin-readme .updated,
     2196#plugin-readme .error {
    21882197        clear: none;
    21892198        direction: rtl;
    21902199}
  • wp-admin/css/wp-admin.dev.css

     
    67776777}
    67786778
    67796779/* Header on thickbox */
    6780 #plugin-information-header {
     6780#plugin-information-header,
     6781#plugin-readme-header {
    67816782        margin: 0;
    67826783        padding: 0 5px;
    67836784        font-weight: bold;
     
    67866787        border-bottom-style: solid;
    67876788        height: 2.5em;
    67886789}
    6789 #plugin-information ul#sidemenu {
     6790#plugin-information ul#sidemenu,
     6791#plugin-readme ul#sidemenu {
    67906792        font-weight: normal;
    67916793        margin: 0 5px;
    67926794        position: absolute;
     
    68146816        line-height: 2em;
    68156817}
    68166818
    6817 #plugin-information h2 {
     6819#plugin-information h2,
     6820#plugin-readme h2 {
    68186821        clear: none !important;
    68196822        margin-right: 200px;
    68206823}
    68216824
    6822 #plugin-information .fyi {
     6825#plugin-information .fyi,
     6826#plugin-readme .fyi {
    68236827        margin: 0 10px 50px;
    68246828        width: 210px;
    68256829}
    68266830
    6827 #plugin-information .fyi h2 {
     6831#plugin-information .fyi h2,
     6832#plugin-readme .fyi h2 {
    68286833        font-size: 0.9em;
    68296834        margin-bottom: 0;
    68306835        margin-right: 0;
    68316836}
    68326837
    6833 #plugin-information .fyi h2.mainheader {
     6838#plugin-information .fyi h2.mainheader,
     6839#plugin-readme .fyi h2.mainheader {
    68346840        padding: 5px;
    68356841        -webkit-border-top-left-radius: 3px;
    68366842        border-top-left-radius: 3px;
    68376843}
    68386844
     6845#plugin-readme .fyi ul,
    68396846#plugin-information .fyi ul {
    68406847        padding: 10px 5px 10px 7px;
    68416848        margin: 0;
     
    68446851        border-bottom-left-radius: 3px;
    68456852}
    68466853
    6847 #plugin-information .fyi li {
     6854#plugin-information .fyi li,
     6855#plugin-readme .fyi li {
    68486856        margin-right: 0;
    68496857}
    68506858
    6851 #plugin-information #section-holder {
     6859#plugin-information #section-holder,
     6860#plugin-readme #section-holder {
    68526861        padding: 10px;
    68536862}
    68546863
    68556864#plugin-information .section ul,
    6856 #plugin-information .section ol {
     6865#plugin-information .section ol,
     6866#plugin-readme .section ul,
     6867#plugin-readme .section ol {
    68576868        margin-left: 16px;
    68586869        list-style-type: square;
    68596870        list-style-image: none;
     
    68796890
    68806891#plugin-information #section-screenshots ol,
    68816892#plugin-information .updated,
    6882 #plugin-information pre {
     6893#plugin-information pre,
     6894#plugin-readme .updated,
     6895#plugin-readme pre {
    68836896        margin-right: 215px;
    68846897}
    68856898
    6886 #plugin-information pre {
     6899#plugin-information pre,
     6900#plugin-readme pre {
    68876901        padding: 7px;
    68886902        overflow: auto;
    68896903}