parse_readme_contents( $file_contents ); } function parse_readme_contents( $file_contents ) { $file_contents = str_replace(array("\r\n", "\r"), "\n", $file_contents); $file_contents = trim($file_contents); if ( 0 === strpos( $file_contents, "\xEF\xBB\xBF" ) ) $file_contents = substr( $file_contents, 3 ); // Markdown transformations $file_contents = preg_replace( "|^###([^#]+)#*?\s*?\n|im", '=$1='."\n", $file_contents ); $file_contents = preg_replace( "|^##([^#]+)#*?\s*?\n|im", '==$1=='."\n", $file_contents ); $file_contents = preg_replace( "|^#([^#]+)#*?\s*?\n|im", '===$1==='."\n", $file_contents ); // === Plugin Name === // Must be the very first thing. if ( !preg_match('|^===(.*)===|', $file_contents, $_name) ) return []; // require a name $name = trim($_name[1], '='); $name = $this->sanitize_text( $name ); $file_contents = $this->chop_string( $file_contents, $_name[0] ); // Requires at least: 1.5 if ( preg_match('|Requires at least:(.*)|i', $file_contents, $_requires_at_least) ) $requires_at_least = $this->sanitize_text($_requires_at_least[1]); else $requires_at_least = NULL; // Tested up to: 2.1 if ( preg_match('|Tested up to:(.*)|i', $file_contents, $_tested_up_to) ) $tested_up_to = $this->sanitize_text( $_tested_up_to[1] ); else $tested_up_to = NULL; // Requires PHP: 5.2.4 if ( preg_match('|Requires PHP:(.*)|i', $file_contents, $_requires_php) ) { $requires_php = $this->sanitize_text( $_requires_php[1] ); } else { $requires_php = null; } // Stable tag: 10.4-ride-the-fire-eagle-danger-day if ( preg_match('|Stable tag:(.*)|i', $file_contents, $_stable_tag) ) $stable_tag = $this->sanitize_text( $_stable_tag[1] ); else $stable_tag = NULL; // we assume trunk, but don't set it here to tell the difference between specified trunk and default trunk // Tags: some tag, another tag, we like tags if ( preg_match('|Tags:(.*)|i', $file_contents, $_tags) ) { $tags = preg_split('|,[\s]*?|', trim($_tags[1])); foreach ( array_keys($tags) as $t ) $tags[$t] = $this->sanitize_text( $tags[$t] ); } else { $tags = []; } // Contributors: markjaquith, mdawaffe, zefrank $contributors = []; if ( preg_match('|Contributors:(.*)|i', $file_contents, $_contributors) ) { $temp_contributors = preg_split('|,[\s]*|', trim($_contributors[1])); foreach ( array_keys($temp_contributors) as $c ) { $tmp_sanitized = $this->user_sanitize( $temp_contributors[$c] ); if ( strlen(trim($tmp_sanitized)) > 0 ) $contributors[$c] = $tmp_sanitized; unset($tmp_sanitized); } } // Donate Link: URL if ( preg_match('|Donate link:(.*)|i', $file_contents, $_donate_link) ) $donate_link = esc_url( $_donate_link[1] ); else $donate_link = NULL; // togs, conts, etc are optional and order shouldn't matter. So we chop them only after we've grabbed their values. foreach ( array('tags', 'contributors', 'requires_at_least', 'tested_up_to', 'stable_tag', 'donate_link') as $chop ) { if ( $$chop ) { $_chop = '_' . $chop; $file_contents = $this->chop_string( $file_contents, ${$_chop}[0] ); } } $file_contents = trim($file_contents); // short-description fu if ( !preg_match('/(^(.*?))^[\s]*=+?[\s]*.+?[\s]*=+?/ms', $file_contents, $_short_description) ) $_short_description = array( 1 => &$file_contents, 2 => &$file_contents ); $short_desc_filtered = $this->sanitize_text( $_short_description[2] ); $short_desc_length = strlen($short_desc_filtered); $short_description = substr($short_desc_filtered, 0, 150); if ( $short_desc_length > strlen($short_description) ) $truncated = true; else $truncated = false; if ( $_short_description[1] ) $file_contents = $this->chop_string( $file_contents, $_short_description[1] ); // yes, the [1] is intentional // == Section == // Break into sections // $_sections[0] will be the title of the first section, $_sections[1] will be the content of the first section // the array alternates from there: title2, content2, title3, content3... and so forth $_sections = preg_split('/^[\s]*==[\s]*(.+?)[\s]*==/m', $file_contents, -1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY); $sections = []; for ( $i=0; $i < count($_sections); $i +=2 ) { $title = $this->sanitize_text( $_sections[$i] ); if ( isset($_sections[$i+1]) ) { $content = preg_replace('/(^[\s]*)=[\s]+(.+?)[\s]+=/m', '$1

$2

', $_sections[$i+1]); $content = $this->filter_text( $content, true ); } else { $content = ''; } $sections[str_replace(' ', '_', strtolower($title))] = array('title' => $title, 'content' => $content); } // Special sections // This is where we nab our special sections, so we can enforce their order and treat them differently, if needed // upgrade_notice is not a section, but parse it like it is for now $final_sections = []; foreach ( array('description', 'installation', 'frequently_asked_questions', 'screenshots', 'changelog', 'change_log', 'upgrade_notice') as $special_section ) { if ( isset($sections[$special_section]) ) { $final_sections[$special_section] = $sections[$special_section]['content']; unset($sections[$special_section]); } } if ( isset($final_sections['change_log']) && empty($final_sections['changelog']) ) $final_sections['changelog'] = $final_sections['change_log']; $final_screenshots = []; if ( isset($final_sections['screenshots']) ) { preg_match_all('|
  • (.*?)
  • |s', $final_sections['screenshots'], $screenshots, PREG_SET_ORDER); if ( $screenshots ) { foreach ( (array) $screenshots as $ss ) $final_screenshots[] = $ss[1]; } } // Parse the upgrade_notice section specially: // 1.0 => blah, 1.1 => fnord $upgrade_notice = []; if ( isset($final_sections['upgrade_notice']) ) { $split = preg_split( '#

    (.*?)

    #', $final_sections['upgrade_notice'], -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY ); if ( count($split) >= 2 ) { for ( $i = 0; $i < count( $split ); $i += 2 ) { $upgrade_notice[$this->sanitize_text( $split[$i] )] = substr( $this->sanitize_text( $split[$i + 1] ), 0, 300 ); } } unset( $final_sections['upgrade_notice'] ); } // No description? // No problem... we'll just fall back to the old style of description // We'll even let you use markup this time! $excerpt = false; if ( !isset($final_sections['description']) ) { $final_sections = array_merge(array('description' => $this->filter_text( $_short_description[2], true )), $final_sections); $excerpt = true; } // dump the non-special sections into $remaining_content // their order will be determined by their original order in the readme.txt $remaining_content = ''; foreach ( $sections as $s_name => $s_data ) { $remaining_content .= "\n

    {$s_data['title']}

    \n{$s_data['content']}"; } $remaining_content = trim($remaining_content); // All done! // $r['tags'] and $r['contributors'] are simple arrays // $r['sections'] is an array with named elements $r = array( 'name' => $name, 'tags' => $tags, 'requires_at_least' => $requires_at_least, 'tested_up_to' => $tested_up_to, 'requires_php' => $requires_php, 'stable_tag' => $stable_tag, 'contributors' => $contributors, 'donate_link' => $donate_link, 'short_description' => $short_description, 'screenshots' => $final_screenshots, 'is_excerpt' => $excerpt, 'is_truncated' => $truncated, 'sections' => $final_sections, 'remaining_content' => $remaining_content, 'upgrade_notice' => $upgrade_notice ); return $r; } function chop_string( $string, $chop ) { // chop a "prefix" from a string: Agressive! uses strstr not 0 === strpos if ( $_string = strstr($string, $chop) ) { $_string = substr($_string, strlen($chop)); return trim($_string); } else { return trim($string); } } function user_sanitize( $text, $strict = false ) { // whitelisted chars if ( function_exists('user_sanitize') ) // bbPress native return user_sanitize( $text, $strict ); if ( $strict ) { $text = preg_replace('/[^a-z0-9-]/i', '', $text); $text = preg_replace('|-+|', '-', $text); } else { $text = preg_replace('/[^a-z0-9_-]/i', '', $text); } return $text; } function sanitize_text( $text ) { // not fancy $text = strip_tags($text); $text = esc_html($text); $text = trim($text); return $text; } function filter_text( $text, $markdown = false ) { // fancy, Markdown $text = trim($text); $text = call_user_func( array( __CLASS__, 'code_trick' ), $text, $markdown ); // A better parser than Markdown's for: backticks -> CODE if ( $markdown ) { // Parse markdown. if ( !class_exists('Parsedown', false) ) { /** @noinspection PhpIncludeInspection */ require_once(dirname(__FILE__) . '/Parsedown' . (version_compare(PHP_VERSION, '5.3.0', '>=') ? '' : 'Legacy') . '.php'); } $instance = Parsedown::instance(); $text = $instance->text($text); } $allowed = array( 'a' => array( 'href' => array(), 'title' => array(), 'rel' => array()), 'blockquote' => array('cite' => array()), 'br' => array(), 'p' => array(), 'code' => array(), 'pre' => array(), 'em' => array(), 'strong' => array(), 'ul' => array(), 'ol' => array(), 'li' => array(), 'h3' => array(), 'h4' => array() ); $text = balanceTags($text); $text = wp_kses( $text, $allowed ); $text = trim($text); return $text; } function code_trick( $text, $markdown ) { // Don't use bbPress native function - it's incompatible with Markdown // If doing markdown, first take any user formatted code blocks and turn them into backticks so that // markdown will preserve things like underscores in code blocks if ( $markdown ) $text = preg_replace_callback("!(
    |)(.*?)(
    |)!s", array( __CLASS__,'decodeit'), $text); $text = str_replace(array("\r\n", "\r"), "\n", $text); if ( !$markdown ) { // This gets the "inline" code blocks, but can't be used with Markdown. $text = preg_replace_callback("|(`)(.*?)`|", array( __CLASS__, 'encodeit'), $text); // This gets the "block level" code blocks and converts them to PRE CODE $text = preg_replace_callback("!(^|\n)`(.*?)`!s", array( __CLASS__, 'encodeit'), $text); } else { // Markdown can do inline code, we convert bbPress style block level code to Markdown style $text = preg_replace_callback("!(^|\n)([ \t]*?)`(.*?)`!s", array( __CLASS__, 'indent'), $text); } return $text; } function indent( $matches ) { $text = $matches[3]; $text = preg_replace('|^|m', $matches[2] . ' ', $text); return $matches[1] . $text; } function encodeit( $matches ) { if ( function_exists('encodeit') ) // bbPress native return encodeit( $matches ); $text = trim($matches[2]); $text = htmlspecialchars($text, ENT_QUOTES); $text = str_replace(array("\r\n", "\r"), "\n", $text); $text = preg_replace("|\n\n\n+|", "\n\n", $text); $text = str_replace('&lt;', '<', $text); $text = str_replace('&gt;', '>', $text); $text = "$text"; if ( "`" != $matches[1] ) $text = "
    $text
    "; return $text; } function decodeit( $matches ) { if ( function_exists('decodeit') ) // bbPress native return decodeit( $matches ); $text = $matches[2]; $trans_table = array_flip(get_html_translation_table(HTML_ENTITIES)); $text = strtr($text, $trans_table); $text = str_replace('
    ', '', $text); $text = str_replace('&', '&', $text); $text = str_replace(''', "'", $text); if ( '
    ' == $matches[1] )
    			$text = "\n$text\n";
    		return "`$text`";
    	}
    
    } // end class
    
    endif;