Changeset 2638
- Timestamp:
- 02/26/2016 02:26:25 AM (9 years ago)
- Location:
- sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory
- Files:
-
- 10 added
- 1 deleted
- 2 edited
- 1 copied
Legend:
- Unmodified
- Added
- Removed
-
sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/class-readme-parser.php
r2602 r2638 1 1 <?php 2 namespace WordPressdotorg\Plugin_Directory; 2 3 /** 3 * Custom readme parser.4 * WordPress.org Plugin Readme Parser. 4 5 * 5 * Based on Automattic_Readme from http://code.google.com/p/wordpress-plugin-readme-parser/6 * Based on Baikonur_ReadmeParser from https://github.com/rmccue/WordPress-Readme-Parser 6 7 * 7 * Relies on Markdown_Extra8 * Relies on \Michaelf\Markdown_Extra 8 9 * 9 * @todo Handle screenshots section properly 10 * @todo Create validator for this based on http://code.google.com/p/wordpress-plugin-readme-parser/source/browse/trunk/validator.php 10 * @package WordPressdotorg\Plugin_Directory 11 11 */ 12 13 /** 14 * Class Baikonur_ReadmeParser 15 */ 16 class Baikonur_ReadmeParser { 17 public static function parse_readme( $file ) { 12 class Readme_Parser { 13 public $name = ''; 14 public $tags = array(); 15 public $requires = ''; 16 public $tested = ''; 17 public $contributors = array(); 18 public $stable_tag = ''; 19 public $donate_link = ''; 20 public $short_description = ''; 21 public $sections = array(); 22 public $upgrade_notice = array(); 23 public $screenshots = array(); 24 25 // These are the readme sections which we expect 26 private $expected_sections = array( 27 'description', 28 'installation', 29 'faq', 30 'screenshots', 31 'changelog', 32 'upgrade_notice', 33 'other_notes', 34 ); 35 36 // We alias these sections, from => to 37 private $alias_sections = array( 38 'frequently_asked_questions' => 'faq', 39 'change_log' => 'changelog', 40 'screenshot' => 'screenshots', 41 ); 42 43 public function __construct( $file ) { 44 $this->parse_readme( $file ); 45 } 46 47 protected function parse_readme( $file ) { 18 48 $contents = file( $file ); 19 49 20 return self::parse_readme_contents( $contents ); 21 } 22 23 public static function parse_readme_contents( $contents ) { 24 if ( is_string( $contents ) ) { 25 $contents = explode( "\n", $contents ); 26 } 27 28 $this_class = __CLASS__; 29 if ( function_exists( 'get_called_class' ) ) { 30 $this_class = get_called_class(); 31 } 32 33 $contents = array_map( array( $this_class, 'strip_newlines' ), $contents ); 50 $contents = array_map( array( $this, 'strip_newlines' ), $contents ); 34 51 35 52 // Strip BOM … … 38 55 } 39 56 40 $data = new stdClass; 41 42 // Defaults 43 $data->is_excerpt = false; 44 $data->is_truncated = false; 45 $data->tags = array(); 46 $data->requires = ''; 47 $data->tested = ''; 48 $data->contributors = array(); 49 $data->stable_tag = ''; 50 $data->version = ''; 51 $data->donate_link = ''; 52 $data->short_description = ''; 53 $data->sections = array(); 54 $data->changelog = array(); 55 $data->upgrade_notice = array(); 56 $data->screenshots = array(); 57 $data->remaining_content = array(); 58 59 $line = call_user_func_array( array( $this_class, 'get_first_nonwhitespace' ), array( &$contents ) ); 60 $data->name = $line; 61 $data->name = trim( $data->name, "#= " ); 57 $line = $this->get_first_nonwhitespace( $contents ); 58 $this->name = $this->sanitize_text( trim( $line, "#= " ) ); 59 60 // Strip Github style header\n==== underlines 61 if ( '' === trim( $contents[0], '=-' ) ) { 62 array_shift( $contents ); 63 } 62 64 63 65 // Parse headers 64 66 $headers = array(); 65 67 66 $line = call_user_func_array( array( $this_class, 'get_first_nonwhitespace' ), array( &$contents ));68 $line = $this->get_first_nonwhitespace( $contents ); 67 69 do { 68 70 $key = $value = null; … … 72 74 $bits = explode( ':', $line, 2 ); 73 75 list( $key, $value ) = $bits; 74 $key = strtolower( str_replace( array( ' ', "\t" ), '_', trim( $key ) ) ); 75 if ( $key === 'tags' && isset( $headers['tags'] ) ) { 76 $headers[ $key ] .= ',' . trim( $value ); 77 } else { 78 $headers[ $key ] = trim( $value ); 79 } 76 $key = strtolower( str_replace( ' ', '_', trim( $key, " \t*-" ) ) ); 77 $headers[ $key ] = trim( $value ); 80 78 } while ( ( $line = array_shift( $contents ) ) !== null && ( $line = trim( $line ) ) && ! empty( $line ) ); 81 79 array_unshift( $contents, $line ); 82 80 83 81 if ( ! empty( $headers['tags'] ) ) { 84 $data->tags = explode( ',', $headers['tags'] ); 85 $data->tags = array_map( 'trim', $data->tags ); 86 } 87 if ( ! empty( $headers['requires'] ) ) { 88 $data->requires = $headers['requires']; 82 $this->tags = explode( ',', $headers['tags'] ); 83 $this->tags = array_map( 'trim', $this->tags ); 89 84 } 90 85 if ( ! empty( $headers['requires_at_least'] ) ) { 91 $data->requires = $headers['requires_at_least']; 92 } 93 if ( ! empty( $headers['tested'] ) ) { 94 $data->tested = $headers['tested']; 86 $this->requires = $headers['requires_at_least']; 87 } elseif ( ! empty( $headers['requires'] ) ) { 88 $this->requires = $headers['requires']; 95 89 } 96 90 if ( ! empty( $headers['tested_up_to'] ) ) { 97 $data->tested = $headers['tested_up_to']; 91 $this->tested = $headers['tested_up_to']; 92 } elseif ( ! empty( $headers['tested'] ) ) { 93 $this->tested = $headers['tested']; 98 94 } 99 95 if ( ! empty( $headers['contributors'] ) ) { 100 $data->contributors = explode( ',', $headers['contributors'] ); 101 $data->contributors = array_map( 'trim', $data->contributors ); 96 $this->contributors = explode( ',', $headers['contributors'] ); 97 $this->contributors = array_map( 'trim', $this->contributors ); 98 $this->contributors = $this->sanitize_contributors( $this->contributors ); 102 99 } 103 100 if ( ! empty( $headers['stable_tag'] ) ) { 104 $ data->stable_tag = $headers['stable_tag'];101 $this->stable_tag = $headers['stable_tag']; 105 102 } 106 103 if ( ! empty( $headers['donate_link'] ) ) { 107 $data->donate_link = $headers['donate_link']; 108 } 109 if ( ! empty( $headers['version'] ) ) { 110 $data->version = $headers['version']; 111 } else { 112 $data->version = $data->stable_tag; 104 $this->donate_link = $headers['donate_link']; 113 105 } 114 106 … … 117 109 $trimmed = trim( $line ); 118 110 if ( empty( $trimmed ) ) { 119 $ data->short_description .= "\n";111 $this->short_description .= "\n"; 120 112 continue; 121 113 } 122 if ( $trimmed[0] === '=' && isset( $trimmed[1] ) && $trimmed[1] === '=' ) { 114 if ( ( '=' === $trimmed[0] && isset( $trimmed[1] ) && '=' === $trimmed[1] ) || 115 ( '#' === $trimmed[0] && isset( $trimmed[1] ) && '#' === $trimmed[1] ) ) { // Stop after any Markdown heading 123 116 array_unshift( $contents, $line ); 124 117 break; 125 118 } 126 119 127 $data->short_description .= $line . "\n"; 128 } 129 $data->short_description = trim( $data->short_description ); 130 if ( ! $data->short_description && ! empty( $headers['description'] ) ) { 131 $data->short_description = $headers['description']; 132 } 133 134 $data->is_truncated = call_user_func_array( array( 135 $this_class, 136 'trim_short_desc' 137 ), array( &$data->short_description ) ); 120 $this->short_description .= $line . "\n"; 121 } 122 $this->short_description = trim( $this->short_description ); 138 123 139 124 // Parse the rest of the body 140 $current = ''; 141 $special = array( 142 'description', 143 'installation', 144 'faq', 145 'frequently_asked_questions', 146 'screenshots', 147 'changelog', 148 'upgrade_notice' 149 ); 150 125 // Prefill the sections, we'll filter out empty sections later. 126 $this->sections = array_fill_keys( $this->expected_sections, '' ); 127 $current = $section_name = $section_title = ''; 151 128 while ( ( $line = array_shift( $contents ) ) !== null ) { 152 129 $trimmed = trim( $line ); … … 156 133 } 157 134 158 if ( $trimmed[0] === '=' && isset( $trimmed[1] ) && $trimmed[1] === '=' ) { 159 if ( ! empty( $title ) ) { 160 $data->sections[ $title ] = trim( $current ); 161 } 162 163 $current = ''; 164 $real_title = trim( $line, "#= \t" ); 165 $title = strtolower( str_replace( ' ', '_', $real_title ) ); 166 if ( $title === 'faq' ) { 167 $title = 'frequently_asked_questions'; 168 } elseif ( $title === 'change_log' ) { 169 $title = 'changelog'; 170 } 171 if ( ! in_array( $title, $special ) ) { 172 $current .= '<h3>' . $real_title . "</h3>"; 135 if ( ( '=' === $trimmed[0] && isset( $trimmed[1] ) && '=' === $trimmed[1] ) || 136 ( '#' === $trimmed[0] && isset( $trimmed[1] ) && '#' === $trimmed[1] && isset( $trimmed[2] ) && '#' !== $trimmed[2] ) ) { // Stop only after a ## Markdown header, not a ###. 137 if ( ! empty( $section_name ) ) { 138 $this->sections[ $section_name ] .= trim( $current ); 139 } 140 141 $current = ''; 142 $section_title = trim( $line, "#= \t" ); 143 $section_name = strtolower( str_replace( ' ', '_', $section_title ) ); 144 145 if ( isset( $this->alias_sections[ $section_name ] ) ) { 146 $section_name = $this->alias_sections[ $section_name ]; 147 } 148 149 // If we encounter an unknown section header, include the provided Title, we'll filter it to other_notes later. 150 if ( ! in_array( $section_name, $this->expected_sections ) ) { 151 $current .= '<h3>' . $section_title . '</h3>'; 152 $section_name = 'other_notes'; 173 153 } 174 154 continue; … … 178 158 } 179 159 180 if ( ! empty( $title ) ) { 181 $data->sections[ $title ] = trim( $current ); 182 } 183 $title = null; 184 $current = null; 185 186 if ( empty( $data->sections['description'] ) ) { 187 $data->sections['description'] = call_user_func( array( 188 $this_class, 189 'parse_markdown' 190 ), $data->short_description ); 191 } 192 193 // Parse changelog 194 if ( ! empty( $data->sections['changelog'] ) ) { 195 $lines = explode( "\n", $data->sections['changelog'] ); 160 if ( ! empty( $section_name ) ) { 161 $this->sections[ $section_name ] .= trim( $current ); 162 } 163 164 // Filter out any empty sections. 165 $this->sections = array_filter( $this->sections ); 166 167 // Use the description for the short description if not provided. 168 if ( empty( $this->short_description ) && ! empty( $this->sections['description'] ) ) { 169 $this->short_description = $this->sections['description']; 170 } 171 172 // Use the short description for the description section if not provided. 173 if ( empty( $this->sections['description'] ) ) { 174 $this->sections['description'] = $this->short_description; 175 } 176 177 // Sanitize and trim the short_description to match requirements 178 $this->short_description = $this->sanitize_text( $this->short_description ); 179 $this->short_description = $this->trim_length( $this->short_description, 150 ); 180 181 // Parse out the Upgrade Notice section into it's own data 182 if ( isset( $this->sections['upgrade_notice'] ) ) { 183 $lines = explode( "\n", $this->sections['upgrade_notice'] ); 184 $version = null; 196 185 while ( ( $line = array_shift( $lines ) ) !== null ) { 197 186 $trimmed = trim( $line ); … … 200 189 } 201 190 202 if ( $trimmed[0] === '=') {191 if ( '=' === $trimmed[0] || '#' === $trimmed[0] ) { 203 192 if ( ! empty( $current ) ) { 204 $ data->changelog[ $title ] = trim( $current);193 $this->upgrade_notice[ $version ] = $this->sanitize_text( trim( $current ) ); 205 194 } 206 195 207 196 $current = ''; 208 $ title= trim( $line, "#= \t" );197 $version = trim( $line, "#= \t" ); 209 198 continue; 210 199 } … … 212 201 $current .= $line . "\n"; 213 202 } 214 215 $data->changelog[ $title ] = trim( $current ); 216 } 217 $title = null; 218 $current = null; 219 220 if ( isset( $data->sections['upgrade_notice'] ) ) { 221 $lines = explode( "\n", $data->sections['upgrade_notice'] ); 222 while ( ( $line = array_shift( $lines ) ) !== null ) { 223 $trimmed = trim( $line ); 224 if ( empty( $trimmed ) ) { 225 continue; 226 } 227 228 if ( $trimmed[0] === '=' ) { 229 if ( ! empty( $current ) ) { 230 $data->upgrade_notice[ $title ] = trim( $current ); 231 } 232 233 $current = ''; 234 $title = trim( $line, "#= \t" ); 235 continue; 236 } 237 238 $current .= $line . "\n"; 239 } 240 241 if ( ! empty( $title ) && ! empty( $current ) ) { 242 $data->upgrade_notice[ $title ] = trim( $current ); 243 } 244 unset( $data->sections['upgrade_notice'] ); 203 if ( ! empty( $version ) && ! empty( $current ) ) { 204 $this->upgrade_notice[ $version ] = $this->sanitize_text( trim( $current ) ); 205 } 206 unset( $this->sections['upgrade_notice'] ); 245 207 } 246 208 247 209 // Markdownify! 248 $data->sections = array_map( array( $this_class, 'parse_markdown' ), $data->sections ); 249 $data->changelog = array_map( array( $this_class, 'parse_markdown' ), $data->changelog ); 250 $data->upgrade_notice = array_map( array( $this_class, 'parse_markdown' ), $data->upgrade_notice ); 251 252 if ( isset( $data->sections['screenshots'] ) ) { 253 preg_match_all( '#<li>(.*?)</li>#is', $data->sections['screenshots'], $screenshots, PREG_SET_ORDER ); 210 $this->sections = array_map( array( $this, 'parse_markdown' ), $this->sections ); 211 $this->upgrade_notice = array_map( array( $this, 'parse_markdown' ), $this->upgrade_notice ); 212 213 if ( isset( $this->sections['screenshots'] ) ) { 214 preg_match_all( '#<li>(.*?)</li>#is', $this->sections['screenshots'], $screenshots, PREG_SET_ORDER ); 254 215 if ( $screenshots ) { 255 foreach ( (array) $screenshots as $ss ) { 256 $data->screenshots[] = trim( $ss[1] ); 257 } 258 } 259 } 260 261 // Rearrange stuff. 262 $data->remaining_content = $data->sections; 263 $data->sections = array(); 264 265 foreach ( $special as $spec ) { 266 if ( isset( $data->remaining_content[ $spec ] ) ) { 267 $data->sections[ $spec ] = $data->remaining_content[ $spec ]; 268 unset( $data->remaining_content[ $spec ] ); 269 } 270 } 271 272 return $data; 273 } 274 275 protected static function get_first_nonwhitespace( &$contents ) { 216 $i = 1; // Screenshots start from 1 217 foreach ( $screenshots as $ss ) { 218 $this->screenshots[ $i++ ] = $this->filter_text( $ss[1] ); 219 } 220 } 221 unset( $this->sections['screenshots'] ); 222 } 223 224 // Filter the HTML 225 $this->sections = array_map( array( $this, 'filter_text' ), $this->sections ); 226 227 return true; 228 } 229 230 protected function get_first_nonwhitespace( &$contents ) { 276 231 while ( ( $line = array_shift( $contents ) ) !== null ) { 277 232 $trimmed = trim( $line ); … … 284 239 } 285 240 286 protected staticfunction strip_newlines( $line ) {241 protected function strip_newlines( $line ) { 287 242 return rtrim( $line, "\r\n" ); 288 243 } 289 244 290 protected static function trim_short_desc( &$desc) {245 protected function trim_length( $desc, $length = 150 ) { 291 246 if ( function_exists( 'mb_strlen' ) && function_exists( 'mb_substr' ) ) { 292 if ( mb_strlen( $desc ) > 150 ) { 293 $desc = mb_substr( $desc, 0, 150 ); 294 $desc = trim( $desc ); 295 296 return true; 247 if ( mb_strlen( $desc ) > $length ) { 248 $desc = mb_substr( $desc, 0, $length ); 297 249 } 298 250 } else { 299 if ( strlen( $desc ) > 150 ) { 300 $desc = substr( $desc, 0, 150 ); 301 $desc = trim( $desc ); 302 303 return true; 304 } 305 } 306 307 return false; 308 } 309 310 protected static function parse_markdown( $text ) { 311 $text = self::code_trick( $text ); 251 if ( strlen( $desc ) > $length ) { 252 $desc = substr( $desc, 0, $length ); 253 } 254 } 255 256 return trim( $desc ); 257 } 258 259 /** 260 * @access protected 261 * 262 * @param string $text 263 * @return string 264 */ 265 protected function filter_text( $text ) { 266 $text = trim( $text ); 267 268 $allowed = array( 269 'a' => array( 270 'href' => true, 271 'title' => true, 272 'rel' => true, 273 ), 274 'blockquote' => array( 275 'cite' => true 276 ), 277 'br' => true, 278 'p' => true, 279 'code' => true, 280 'pre' => true, 281 'em' => true, 282 'strong' => true, 283 'ul' => true, 284 'ol' => true, 285 'li' => true, 286 'h3' => true, 287 'h4' => true, 288 ); 289 290 $text = balanceTags( $text ); 291 $text = make_clickable( $text ); 292 293 $text = wp_kses( $text, $allowed ); 294 295 // wpautop() will eventually replace all \n's with <br>s, and that isn't what we want. 296 $text = preg_replace( "/(?<![> ])\n/", ' ', $text ); 297 298 $text = trim( $text ); 299 300 return $text; 301 } 302 303 /** 304 * @access protected 305 * 306 * @param string $text 307 * @return string 308 */ 309 protected function sanitize_text( $text ) { // not fancy 310 $text = strip_tags( $text ); 311 $text = esc_html( $text ); 312 $text = trim( $text ); 313 314 return $text; 315 } 316 317 /** 318 * Sanitize proided contributors to valid WordPress users 319 * 320 * @param array $users Array of user_login's or user_nicename's. 321 * @return array Array of user_logins. 322 */ 323 protected function sanitize_contributors( $users ) { 324 foreach ( $users as $i => $name ) { 325 if ( get_user_by( 'login', $name ) ) { 326 continue; 327 } elseif ( false !== ( $user = get_user_by( 'slug', $name ) ) ) { 328 // Overwrite the nicename with the user_login 329 $users[ $i ] = $user->user_login; 330 } else { 331 // Unknown user, we'll skip these entirely to encourage correct readmes 332 unset( $users[ $i ] ); 333 } 334 } 335 return $users; 336 } 337 338 protected function parse_markdown( $text ) { 339 static $markdown = null; 340 if ( ! class_exists( '\\Michelf\\MarkdownExtra' ) ) { 341 // TODO: Autoloader? 342 include __DIR__ . '/libs/michelf-php-markdown-1.6.0/Michelf/MarkdownExtra.inc.php'; 343 } 344 if ( is_null( $markdown ) ) { 345 $markdown = new \Michelf\MarkdownExtra(); 346 } 347 348 $text = $this->code_trick( $text ); 312 349 $text = preg_replace( '/^[\s]*=[\s]+(.+?)[\s]+=/m', "\n" . '<h4>$1</h4>' . "\n", $text ); 313 $text = Markdown( trim( $text ) );350 $text = $markdown->transform( trim( $text ) ); 314 351 315 352 return trim( $text ); 316 353 } 317 354 318 protected staticfunction code_trick( $text ) {355 protected function code_trick( $text ) { 319 356 // If doing markdown, first take any user formatted code blocks and turn them into backticks so that 320 357 // markdown will preserve things like underscores in code blocks 321 $text = preg_replace_callback( "!(<pre><code>|<code>)(.*?)(</code></pre>|</code>)!s", array( __CLASS__, 'decodeit' ), $text );358 $text = preg_replace_callback( "!(<pre><code>|<code>)(.*?)(</code></pre>|</code>)!s", array( $this, 'code_trick_decodeit_cb' ), $text ); 322 359 $text = str_replace( array( "\r\n", "\r" ), "\n", $text ); 323 360 324 361 // Markdown can do inline code, we convert bbPress style block level code to Markdown style 325 $text = preg_replace_callback( "!(^|\n)([ \t]*?)`(.*?)`!s", array( __CLASS__, 'indent' ), $text );362 $text = preg_replace_callback( "!(^|\n)([ \t]*?)`(.*?)`!s", array( $this, 'code_trick_indent_cb' ), $text ); 326 363 327 364 return $text; 328 365 } 329 366 330 protected static function indent( $matches ) {367 protected function code_trick_indent_cb( $matches ) { 331 368 $text = $matches[3]; 332 369 $text = preg_replace( '|^|m', $matches[2] . ' ', $text ); … … 335 372 } 336 373 337 protected static function decodeit( $matches ) {374 protected function code_trick_decodeit_cb( $matches ) { 338 375 $text = $matches[2]; 339 376 $trans_table = array_flip( get_html_translation_table( HTML_ENTITIES ) ); … … 343 380 $text = str_replace( ''', "'", $text ); 344 381 345 382 if ( '<pre><code>' == $matches[1] ) { 346 383 $text = "\n$text\n"; 347 384 } -
sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/class-tools.php
r2621 r2638 8 8 */ 9 9 class Tools { 10 11 /**12 * @param string $readme13 * @return object14 */15 static function get_readme_data( $readme ) {16 17 // Uses https://github.com/rmccue/WordPress-Readme-Parser (with modifications)18 include_once __DIR__ . '/readme-parser/markdown.php';19 include_once __DIR__ . '/readme-parser/compat.php';20 21 $data = (object) \WPorg_Readme::parse_readme( $readme );22 23 unset( $data->sections['screenshots'] ); // Useless.24 25 // Sanitize contributors.26 foreach ( $data->contributors as $i => $name ) {27 if ( get_user_by( 'login', $name ) ) {28 continue;29 } elseif ( false !== ( $user = get_user_by( 'slug', $name ) ) ) {30 $data->contributors[] = $user->user_login;31 unset( $data->contributors[ $i ] );32 } else {33 unset( $data->contributors[ $i ] );34 }35 }36 37 return $data;38 }39 10 40 11 /** -
sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/libs/CREDITS
r2555 r2638 1 1 GeoPattern-php - MIT Licensed, https://github.com/redeyeventures/geopattern-php 2 michelf-php-markdown - BSD-3-Clause, https://github.com/michelf/php-markdown
Note: See TracChangeset
for help on using the changeset viewer.