Ticket #17902: 17902.3.patch
File 17902.3.patch, 30.6 KB (added by , 13 years ago) |
---|
-
wp-admin/css/colors-classic.dev.css
619 619 } 620 620 621 621 div#media-upload-header, 622 div#plugin-information-header { 622 div#plugin-information-header, 623 div#plugin-readme-header { 623 624 background-color: #f9f9f9; 624 625 border-bottom-color: #dfdfdf; 625 626 } … … 1926 1927 } 1927 1928 1928 1929 /* Install Plugins */ 1929 #plugin-information .fyi ul { 1930 #plugin-information .fyi ul, 1931 #plugin-readme .fyi ul { 1930 1932 background-color: #eaf3fa; 1931 1933 } 1932 1934 1933 #plugin-information .fyi h2.mainheader { 1935 #plugin-information .fyi h2.mainheader, 1936 #plugin-readme .fyi h2.mainheader { 1934 1937 background-color: #cee1ef; 1935 1938 } 1936 1939 1937 1940 #plugin-information pre, 1938 #plugin-information code { 1941 #plugin-information code, 1942 #plugin-readme pre, 1943 #plugin-readme code { 1939 1944 background-color: #ededff; 1940 1945 } 1941 1946 1942 #plugin-information pre { 1947 #plugin-information pre, 1948 #plugin-readme pre { 1943 1949 border: 1px solid #ccc; 1944 1950 } 1945 1951 -
wp-admin/css/colors-fresh.dev.css
610 610 } 611 611 612 612 div#media-upload-header, 613 div#plugin-information-header { 613 div#plugin-information-header, 614 div#plugin-readme-header { 614 615 background-color: #f9f9f9; 615 616 border-bottom-color: #dfdfdf; 616 617 } … … 1542 1543 } 1543 1544 1544 1545 /* Install Plugins */ 1545 #plugin-information .fyi ul { 1546 #plugin-information .fyi ul, 1547 #plugin-readme .fyi ul { 1546 1548 background-color: #eaf3fa; 1547 1549 } 1548 1550 1549 #plugin-information .fyi h2.mainheader { 1551 #plugin-information .fyi h2.mainheader, 1552 #plugin-readme .fyi h2.mainheader { 1550 1553 background-color: #cee1ef; 1551 1554 } 1552 1555 1553 1556 #plugin-information pre, 1554 #plugin-information code { 1557 #plugin-information code, 1558 #plugin-readme pre, 1559 #plugin-readme code { 1555 1560 background-color: #ededff; 1556 1561 } 1557 1562 1558 #plugin-information pre { 1563 #plugin-information pre, 1564 #plugin-readme pre { 1559 1565 border: 1px solid #ccc; 1560 1566 } 1561 1567 -
wp-admin/css/wp-admin.dev.css
7164 7164 } 7165 7165 7166 7166 /* Header on thickbox */ 7167 #plugin-information-header { 7167 #plugin-information-header, 7168 #plugin-readme-header { 7168 7169 margin: 0; 7169 7170 padding: 0 5px; 7170 7171 font-weight: bold; … … 7173 7174 border-bottom-style: solid; 7174 7175 height: 2.5em; 7175 7176 } 7176 #plugin-information ul#sidemenu { 7177 #plugin-information ul#sidemenu, 7178 #plugin-readme ul#sidemenu { 7177 7179 font-weight: normal; 7178 7180 margin: 0 5px; 7179 7181 position: absolute; … … 7201 7203 line-height: 2em; 7202 7204 } 7203 7205 7204 #plugin-information h2 { 7206 #plugin-information h2, 7207 #plugin-readme h2 { 7205 7208 clear: none !important; 7206 7209 margin-right: 200px; 7207 7210 } 7208 7211 7209 #plugin-information .fyi { 7212 #plugin-information .fyi, 7213 #plugin-readme .fyi { 7210 7214 margin: 0 10px 50px; 7211 7215 width: 210px; 7212 7216 } 7213 7217 7214 #plugin-information .fyi h2 { 7218 #plugin-information .fyi h2, 7219 #plugin-readme .fyi h2 { 7215 7220 font-size: 0.9em; 7216 7221 margin-bottom: 0; 7217 7222 margin-right: 0; 7218 7223 } 7219 7224 7220 #plugin-information .fyi h2.mainheader { 7225 #plugin-information .fyi h2.mainheader, 7226 #plugin-readme .fyi h2.mainheader { 7221 7227 padding: 5px; 7222 7228 -webkit-border-top-left-radius: 3px; 7223 7229 border-top-left-radius: 3px; 7224 7230 } 7225 7231 7232 #plugin-readme .fyi ul, 7226 7233 #plugin-information .fyi ul { 7227 7234 padding: 10px 5px 10px 7px; 7228 7235 margin: 0; … … 7231 7238 border-bottom-left-radius: 3px; 7232 7239 } 7233 7240 7234 #plugin-information .fyi li { 7241 #plugin-information .fyi li, 7242 #plugin-readme .fyi li { 7235 7243 margin-right: 0; 7236 7244 } 7237 7245 7238 #plugin-information #section-holder { 7246 #plugin-information #section-holder, 7247 #plugin-readme #section-holder { 7239 7248 padding: 10px; 7240 7249 } 7241 7250 7242 7251 #plugin-information .section ul, 7243 #plugin-information .section ol { 7252 #plugin-information .section ol, 7253 #plugin-readme .section ul, 7254 #plugin-readme .section ol { 7244 7255 margin-left: 16px; 7245 7256 list-style-type: square; 7246 7257 list-style-image: none; … … 7266 7277 7267 7278 #plugin-information #section-screenshots ol, 7268 7279 #plugin-information .updated, 7269 #plugin-information pre { 7280 #plugin-information pre, 7281 #plugin-readme .updated, 7282 #plugin-readme pre { 7270 7283 margin-right: 215px; 7271 7284 } 7272 7285 7273 #plugin-information pre { 7286 #plugin-information pre, 7287 #plugin-readme pre { 7274 7288 padding: 7px; 7275 7289 overflow: auto; 7276 7290 } -
wp-admin/css/wp-admin-rtl.dev.css
2239 2239 float: right; 2240 2240 } 2241 2241 2242 #plugin-information ul#sidemenu { 2242 #plugin-information ul#sidemenu, 2243 #plugin-readme ul#sidemenu { 2243 2244 left: auto; 2244 2245 right: 0; 2245 2246 } 2246 2247 2247 #plugin-information h2 { 2248 #plugin-information h2, 2249 #plugin-readme h2 { 2248 2250 margin-right: 0; 2249 2251 margin-left: 200px; 2250 2252 } 2251 2253 2252 #plugin-information .fyi { 2254 #plugin-information .fyi, 2255 #plugin-readme .fyi { 2253 2256 margin-left: 5px; 2254 2257 margin-right: 20px; 2255 2258 } 2256 2259 2257 #plugin-information .fyi h2 { 2260 #plugin-information .fyi h2, 2261 #plugin-readme .fyi h2 { 2258 2262 margin-left: 0; 2259 2263 } 2260 2264 2261 #plugin-information .fyi ul { 2265 #plugin-information .fyi ul, 2266 #plugin-readme .fyi ul { 2262 2267 padding: 10px 7px 10px 5px; 2263 2268 } 2264 2269 … … 2269 2274 2270 2275 #plugin-information #section-screenshots ol, 2271 2276 #plugin-information .updated, 2272 #plugin-information pre { 2277 #plugin-information pre, 2278 #plugin-readme .updated, 2279 #plugin-readme pre { 2273 2280 margin-right: 0; 2274 2281 margin-left: 215px; 2275 2282 } 2276 2283 2277 2284 #plugin-information .updated, 2278 #plugin-information .error { 2285 #plugin-information .error, 2286 #plugin-readme .updated, 2287 #plugin-readme .error { 2279 2288 clear: none; 2280 2289 direction: rtl; 2281 2290 } -
wp-admin/plugin-install.php
6 6 * @subpackage Administration 7 7 */ 8 8 // 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']) )9 if ( !defined( 'IFRAME_REQUEST' ) && isset( $_GET['tab'] ) && ( in_array( $_GET['tab'], array( 'plugin-information', 'plugin-readme' ) ) ) ) 10 10 define( 'IFRAME_REQUEST', true ); 11 11 12 12 /** WordPress Administration Bootstrap */ … … 28 28 $parent_file = 'plugins.php'; 29 29 30 30 wp_enqueue_script( 'plugin-install' ); 31 if ( 'plugin-information' != $tab)31 if ( !in_array( $tab, array('plugin-information', 'plugin-readme') ) ) 32 32 add_thickbox(); 33 33 34 34 $body_id = $tab; -
wp-admin/includes/class-wp-plugins-list-table.php
420 420 $author = '<a href="' . $plugin_data['AuthorURI'] . '" title="' . esc_attr__( 'Visit author homepage' ) . '">' . $plugin_data['Author'] . '</a>'; 421 421 $plugin_meta[] = sprintf( __( 'By %s' ), $author ); 422 422 } 423 $slug = basename( $plugin_file, '.php' ); 424 $plugin_meta[] = '<a href="' . self_admin_url( 'plugin-install.php?tab=plugin-readme&readme=true&plugin=' . $slug . 425 '&TB_iframe=true&width=600&height=550' ) . '" class="thickbox" title="' . 426 esc_attr( sprintf( __( 'More information about %s' ), "{$plugin_data['Name']} {$plugin_data['Version']}" ) ) . '">' . __( 'Details' ) . '</a>'; 423 427 if ( ! empty( $plugin_data['PluginURI'] ) ) 424 428 $plugin_meta[] = '<a href="' . $plugin_data['PluginURI'] . '" title="' . esc_attr__( 'Visit plugin site' ) . '">' . __( 'Visit plugin site' ) . '</a>'; 425 429 -
wp-admin/includes/plugin-install.php
380 380 exit; 381 381 } 382 382 add_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 */ 389 function 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 */ 460 function 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 »') ?></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 } 595 add_action('install_plugins_pre_plugin-readme', 'plugin_readme_information'); -
wp-admin/includes/class-wp-plugin-install-list-table.php
34 34 $tabs['popular'] = _x( 'Popular','Plugin Installer' ); 35 35 $tabs['new'] = _x( 'Newest','Plugin Installer' ); 36 36 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. 38 38 39 39 $tabs = apply_filters( 'install_plugins_tabs', $tabs ); 40 40 $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 */ 11 class 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( '&lt;', '&> ' ), 438 array( '<' , '>' ), 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 />', '&', '''), 457 array( '' , '&' , "'" ), 458 $text 459 ); 460 if ( '<pre><code>' == $matches[1] ) 461 $text = "\n$text\n"; 462 return "`$text`"; 463 } 464 }