WordPress.org

Make WordPress Core

Ticket #17902: 17902.patch

File 17902.patch, 30.0 KB (added by kurtpayne, 4 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}