├── _config.yml ├── log_analytics.php ├── README.rst ├── test-plugin.ini ├── index.php ├── test-plugin └── test-plugin.php └── markdown.php /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /log_analytics.php: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | This script is now DEPRECATED 2 | ============================= 3 | 4 | Auto Update script for WP Plugins not hosted on WordPress.org 5 | ============================================================= 6 | 7 | This script can be used for WordPress plugins that are not hosted on 8 | WordPress.org. It works transparently for users providing same experience as if 9 | the plugin was hosted on WordPress.org. 10 | 11 | Features 12 | -------- 13 | 14 | * Serve multiple plugins using same script 15 | * Serve different types of versions like stable, alpha, beta, lite, pro, test 16 | etc. 17 | * Easy to configure ini file 18 | 19 | How to use this script 20 | ---------------------- 21 | 22 | * Copy contents of this project (excluding ``test-plugin/``) to a location on 23 | your website 24 | * Rename ``test-plugin.ini`` to ``your-plugin-slug.ini`` 25 | * Modify ``your-plugin-slug.ini`` file according the help instructions in the 26 | file 27 | * Modify ``test-plugin/test-plugin.php`` file to reflect the location of 28 | ``index.php`` on your website 29 | * Copy contents of ``test-plugin/test-plugin.php`` file to your plugin's main 30 | file 31 | 32 | Credits 33 | ------- 34 | 35 | * `Kaspars `_, for original idea of this script 36 | * `michelf `_, for the markdown 37 | script 38 | -------------------------------------------------------------------------------- /test-plugin.ini: -------------------------------------------------------------------------------- 1 | ; Author of the plugin 2 | author = "Ronak Gandhi" 3 | 4 | ; Homepage of the plugin 5 | homepage = "https://github.com/ronakg/wp-plugin-auto-update" 6 | 7 | ; Plugin slug as it is registered with WordPress 8 | plugin_slug = "test-plugin" 9 | 10 | ; Plugin name as it appears to users 11 | plugin_name = "Test Plugin" 12 | 13 | ; Plugin description in markdown format - http://michelf.com/projects/php-markdown/ 14 | description = " 15 | _Test Plugin_ short description. 16 | 17 | ##Features: 18 | 19 | * Feature 1 20 | * Feature 2 21 | * Feature 3 22 | 23 | " 24 | 25 | installation = " 26 | * Step 1 27 | * Step 2 28 | " 29 | 30 | changelog = " 31 | #### 1.3 #### 32 | * Added new feature 33 | 34 | #### 1.2 #### 35 | * Bug fix 1 36 | * Bug fix 2 37 | " 38 | 39 | ;Packages 40 | ; 41 | ; [package_type] 42 | ; version = version number for this package like "1.1" or "1.3.4" or "5.0-beta" 43 | ; date = Release date of this version in "yyyy-mm-dd" format 44 | ; package = location of the zipped plugin package 45 | ; tested = Tested upto WordPress version like "3.3.1" 46 | ; upgrade_notice = A brief description about what this version brings 47 | 48 | [stable] 49 | version = "1.5" 50 | date = "2012-03-10" 51 | package = "https://github.com/downloads/ronakg/wp-plugin-auto-update/test-plugin-1.5.zip" 52 | tested = "3.3.1" 53 | upgrade_notice = "This notice appears in WordPress update notification area." 54 | 55 | [beta] 56 | version = "1.6-beta" 57 | date = "2012-03-15" 58 | package = "https://github.com/downloads/ronakg/wp-plugin-auto-update/test-plugin-1.5.zip" 59 | tested = "3.3.1" 60 | upgrade_notice = "This notice appears in WordPress update notification area." 61 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | version); 22 | } 23 | 24 | // Read packages file 25 | $config = parse_ini_file('./' . $args->slug . '.ini', 1); 26 | 27 | // Populate information from ini file 28 | $plugin_slug = $config['plugin_slug']; 29 | $author = $config['author']; 30 | $plugin_name = $config['plugin_name']; 31 | $homepage = $config['homepage']; 32 | 33 | $description = Markdown($config['description']); 34 | $installation = Markdown($config['installation']); 35 | $changelog = Markdown($config['changelog']); 36 | 37 | $packages[$plugin_slug] = array( 38 | 'config' => $config, 39 | 'info' => array( 40 | 'url' => $homepage, 41 | ), 42 | ); 43 | 44 | if (array_key_exists($args->package_type, $packages[$plugin_slug]['config'])) 45 | $package = $packages[$plugin_slug]['config'][$args->package_type]; 46 | else 47 | $package = $packages[$plugin_slug]['config']['stable']; 48 | 49 | $package['author'] = $author; 50 | $package['plugin_name'] = $plugin_name; 51 | $package['homepage'] = $homepage; 52 | 53 | // basic_check 54 | 55 | if ($action == 'basic_check') { 56 | if (version_compare($args->version, $package['version'], '<')) { 57 | $update_info = array_to_object($package); 58 | $update_info->slug = $plugin_slug; 59 | 60 | $update_info->new_version = $update_info->version; 61 | 62 | print serialize($update_info); 63 | } 64 | } 65 | 66 | // plugin_information 67 | 68 | if ($action == 'plugin_information') { 69 | $data = new stdClass; 70 | 71 | $data->name = $plugin_name; 72 | $data->slug = $plugin_slug; 73 | $data->version = $package['version']; 74 | $data->last_updated = $package['date']; 75 | $data->package = $package['package']; 76 | $data->tested = $package['tested']; 77 | $data->homepage = $homepage; 78 | $data->author = $author; 79 | $data->wp_info = unserialize(stripslashes($_POST['wp-info'])); 80 | $data->sections = array( 81 | 'description' => $description, 82 | 'installation' => $installation, 83 | 'changelog' => $changelog, 84 | ); 85 | 86 | print serialize($data); 87 | } 88 | 89 | function array_to_object($array = array()) { 90 | if (empty($array) || !is_array($array)) 91 | return false; 92 | 93 | $data = new stdClass; 94 | foreach ($array as $akey => $aval) 95 | $data->{$akey} = $aval; 96 | return $data; 97 | } 98 | ?> 99 | -------------------------------------------------------------------------------- /test-plugin/test-plugin.php: -------------------------------------------------------------------------------- 1 | api_url = $api_url; 41 | $this->package_type = $type; 42 | $this->plugin_slug = $slug; 43 | $this->plugin_file = $slug .'/'. $slug . '.php'; 44 | } 45 | 46 | public function print_api_result() { 47 | print_r($res); 48 | return $res; 49 | } 50 | 51 | public function check_for_plugin_update($checked_data) { 52 | if (empty($checked_data->checked)) 53 | return $checked_data; 54 | 55 | $request_args = array( 56 | 'slug' => $this->plugin_slug, 57 | 'version' => $checked_data->checked[$this->plugin_file], 58 | 'package_type' => $this->package_type, 59 | ); 60 | 61 | $request_string = $this->prepare_request('basic_check', $request_args); 62 | 63 | // Start checking for an update 64 | $raw_response = wp_remote_post($this->api_url, $request_string); 65 | 66 | if (!is_wp_error($raw_response) && ($raw_response['response']['code'] == 200)) { 67 | $response = unserialize($raw_response['body']); 68 | 69 | if (is_object($response) && !empty($response)) // Feed the update data into WP updater 70 | $checked_data->response[$this->plugin_file] = $response; 71 | } 72 | 73 | return $checked_data; 74 | } 75 | 76 | public function plugins_api_call($def, $action, $args) { 77 | if ($args->slug != $this->plugin_slug) 78 | return false; 79 | 80 | // Get the current version 81 | $plugin_info = get_site_transient('update_plugins'); 82 | $current_version = $plugin_info->checked[$this->plugin_file]; 83 | $args->version = $current_version; 84 | $args->package_type = $this->package_type; 85 | 86 | $request_string = $this->prepare_request($action, $args); 87 | 88 | $request = wp_remote_post($this->api_url, $request_string); 89 | 90 | if (is_wp_error($request)) { 91 | $res = new WP_Error('plugins_api_failed', __('An Unexpected HTTP Error occurred during the API request.

Try again'), $request->get_error_message()); 92 | } else { 93 | $res = unserialize($request['body']); 94 | 95 | if ($res === false) 96 | $res = new WP_Error('plugins_api_failed', __('An unknown error occurred'), $request['body']); 97 | } 98 | 99 | return $res; 100 | } 101 | 102 | public function prepare_request($action, $args) { 103 | $site_url = site_url(); 104 | 105 | $wp_info = array( 106 | 'site-url' => $site_url, 107 | 'version' => $wp_version, 108 | ); 109 | 110 | return array( 111 | 'body' => array( 112 | 'action' => $action, 'request' => serialize($args), 113 | 'api-key' => md5($site_url), 114 | 'wp-info' => serialize($wp_info), 115 | ), 116 | 'user-agent' => 'WordPress/' . $wp_version . '; ' . get_bloginfo('url') 117 | ); 118 | } 119 | } 120 | 121 | $wp_plugin_auto_update = new WpPluginAutoUpdate('http://www.ronakg.com/wp_plugin_auto_update/', 'stable', basename(dirname(__FILE__))); 122 | 123 | if (DEBUG) { 124 | // Enable update check on every request. Normally you don't need 125 | // this! This is for testing only! 126 | set_site_transient('update_plugins', null); 127 | 128 | // Show which variables are being requested when query plugin API 129 | add_filter('plugins_api_result', array($wp_plugin_auto_update, 'print_api_result'), 10, 3); 130 | } 131 | 132 | // Take over the update check 133 | add_filter('pre_set_site_transient_update_plugins', array($wp_plugin_auto_update, 'check_for_plugin_update')); 134 | 135 | // Take over the Plugin info screen 136 | add_filter('plugins_api', array($wp_plugin_auto_update, 'plugins_api_call'), 10, 3); 137 | ?> 138 | -------------------------------------------------------------------------------- /markdown.php: -------------------------------------------------------------------------------- 1 | 8 | # 9 | # Original Markdown 10 | # Copyright (c) 2004-2006 John Gruber 11 | # 12 | # 13 | 14 | 15 | define( 'MARKDOWN_VERSION', "1.0.1o" ); # Sun 8 Jan 2012 16 | 17 | 18 | # 19 | # Global default settings: 20 | # 21 | 22 | # Change to ">" for HTML output 23 | @define( 'MARKDOWN_EMPTY_ELEMENT_SUFFIX', " />"); 24 | 25 | # Define the width of a tab for code blocks. 26 | @define( 'MARKDOWN_TAB_WIDTH', 4 ); 27 | 28 | ### Standard Function Interface ### 29 | 30 | @define( 'MARKDOWN_PARSER_CLASS', 'Markdown_Parser' ); 31 | 32 | function Markdown($text) { 33 | # 34 | # Initialize the parser and return the result of its transform method. 35 | # 36 | # Setup static parser variable. 37 | static $parser; 38 | if (!isset($parser)) { 39 | $parser_class = MARKDOWN_PARSER_CLASS; 40 | $parser = new $parser_class; 41 | } 42 | 43 | # Transform text using parser. 44 | return $parser->transform($text); 45 | } 46 | 47 | # 48 | # Markdown Parser Class 49 | # 50 | 51 | class Markdown_Parser { 52 | 53 | # Regex to match balanced [brackets]. 54 | # Needed to insert a maximum bracked depth while converting to PHP. 55 | var $nested_brackets_depth = 6; 56 | var $nested_brackets_re; 57 | 58 | var $nested_url_parenthesis_depth = 4; 59 | var $nested_url_parenthesis_re; 60 | 61 | # Table of hash values for escaped characters: 62 | var $escape_chars = '\`*_{}[]()>#+-.!'; 63 | var $escape_chars_re; 64 | 65 | # Change to ">" for HTML output. 66 | var $empty_element_suffix = MARKDOWN_EMPTY_ELEMENT_SUFFIX; 67 | var $tab_width = MARKDOWN_TAB_WIDTH; 68 | 69 | # Change to `true` to disallow markup or entities. 70 | var $no_markup = false; 71 | var $no_entities = false; 72 | 73 | # Predefined urls and titles for reference links and images. 74 | var $predef_urls = array(); 75 | var $predef_titles = array(); 76 | 77 | 78 | function Markdown_Parser() { 79 | # 80 | # Constructor function. Initialize appropriate member variables. 81 | # 82 | $this->_initDetab(); 83 | $this->prepareItalicsAndBold(); 84 | 85 | $this->nested_brackets_re = 86 | str_repeat('(?>[^\[\]]+|\[', $this->nested_brackets_depth). 87 | str_repeat('\])*', $this->nested_brackets_depth); 88 | 89 | $this->nested_url_parenthesis_re = 90 | str_repeat('(?>[^()\s]+|\(', $this->nested_url_parenthesis_depth). 91 | str_repeat('(?>\)))*', $this->nested_url_parenthesis_depth); 92 | 93 | $this->escape_chars_re = '['.preg_quote($this->escape_chars).']'; 94 | 95 | # Sort document, block, and span gamut in ascendent priority order. 96 | asort($this->document_gamut); 97 | asort($this->block_gamut); 98 | asort($this->span_gamut); 99 | } 100 | 101 | 102 | # Internal hashes used during transformation. 103 | var $urls = array(); 104 | var $titles = array(); 105 | var $html_hashes = array(); 106 | 107 | # Status flag to avoid invalid nesting. 108 | var $in_anchor = false; 109 | 110 | 111 | function setup() { 112 | # 113 | # Called before the transformation process starts to setup parser 114 | # states. 115 | # 116 | # Clear global hashes. 117 | $this->urls = $this->predef_urls; 118 | $this->titles = $this->predef_titles; 119 | $this->html_hashes = array(); 120 | 121 | $in_anchor = false; 122 | } 123 | 124 | function teardown() { 125 | # 126 | # Called after the transformation process to clear any variable 127 | # which may be taking up memory unnecessarly. 128 | # 129 | $this->urls = array(); 130 | $this->titles = array(); 131 | $this->html_hashes = array(); 132 | } 133 | 134 | 135 | function transform($text) { 136 | # 137 | # Main function. Performs some preprocessing on the input text 138 | # and pass it through the document gamut. 139 | # 140 | $this->setup(); 141 | 142 | # Remove UTF-8 BOM and marker character in input, if present. 143 | $text = preg_replace('{^\xEF\xBB\xBF|\x1A}', '', $text); 144 | 145 | # Standardize line endings: 146 | # DOS to Unix and Mac to Unix 147 | $text = preg_replace('{\r\n?}', "\n", $text); 148 | 149 | # Make sure $text ends with a couple of newlines: 150 | $text .= "\n\n"; 151 | 152 | # Convert all tabs to spaces. 153 | $text = $this->detab($text); 154 | 155 | # Turn block-level HTML blocks into hash entries 156 | $text = $this->hashHTMLBlocks($text); 157 | 158 | # Strip any lines consisting only of spaces and tabs. 159 | # This makes subsequent regexen easier to write, because we can 160 | # match consecutive blank lines with /\n+/ instead of something 161 | # contorted like /[ ]*\n+/ . 162 | $text = preg_replace('/^[ ]+$/m', '', $text); 163 | 164 | # Run document gamut methods. 165 | foreach ($this->document_gamut as $method => $priority) { 166 | $text = $this->$method($text); 167 | } 168 | 169 | $this->teardown(); 170 | 171 | return $text . "\n"; 172 | } 173 | 174 | var $document_gamut = array( 175 | # Strip link definitions, store in hashes. 176 | "stripLinkDefinitions" => 20, 177 | 178 | "runBasicBlockGamut" => 30, 179 | ); 180 | 181 | 182 | function stripLinkDefinitions($text) { 183 | # 184 | # Strips link definitions from text, stores the URLs and titles in 185 | # hash references. 186 | # 187 | $less_than_tab = $this->tab_width - 1; 188 | 189 | # Link defs are in the form: ^[id]: url "optional title" 190 | $text = preg_replace_callback('{ 191 | ^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?: # id = $1 192 | [ ]* 193 | \n? # maybe *one* newline 194 | [ ]* 195 | (?: 196 | <(.+?)> # url = $2 197 | | 198 | (\S+?) # url = $3 199 | ) 200 | [ ]* 201 | \n? # maybe one newline 202 | [ ]* 203 | (?: 204 | (?<=\s) # lookbehind for whitespace 205 | ["(] 206 | (.*?) # title = $4 207 | [")] 208 | [ ]* 209 | )? # title is optional 210 | (?:\n+|\Z) 211 | }xm', 212 | array(&$this, '_stripLinkDefinitions_callback'), 213 | $text); 214 | return $text; 215 | } 216 | function _stripLinkDefinitions_callback($matches) { 217 | $link_id = strtolower($matches[1]); 218 | $url = $matches[2] == '' ? $matches[3] : $matches[2]; 219 | $this->urls[$link_id] = $url; 220 | $this->titles[$link_id] =& $matches[4]; 221 | return ''; # String that will replace the block 222 | } 223 | 224 | 225 | function hashHTMLBlocks($text) { 226 | if ($this->no_markup) return $text; 227 | 228 | $less_than_tab = $this->tab_width - 1; 229 | 230 | # Hashify HTML blocks: 231 | # We only want to do this for block-level HTML tags, such as headers, 232 | # lists, and tables. That's because we still want to wrap

s around 233 | # "paragraphs" that are wrapped in non-block-level tags, such as anchors, 234 | # phrase emphasis, and spans. The list of tags we're looking for is 235 | # hard-coded: 236 | # 237 | # * List "a" is made of tags which can be both inline or block-level. 238 | # These will be treated block-level when the start tag is alone on 239 | # its line, otherwise they're not matched here and will be taken as 240 | # inline later. 241 | # * List "b" is made of tags which are always block-level; 242 | # 243 | $block_tags_a_re = 'ins|del'; 244 | $block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|'. 245 | 'script|noscript|form|fieldset|iframe|math'; 246 | 247 | # Regular expression for the content of a block tag. 248 | $nested_tags_level = 4; 249 | $attr = ' 250 | (?> # optional tag attributes 251 | \s # starts with whitespace 252 | (?> 253 | [^>"/]+ # text outside quotes 254 | | 255 | /+(?!>) # slash not followed by ">" 256 | | 257 | "[^"]*" # text inside double quotes (tolerate ">") 258 | | 259 | \'[^\']*\' # text inside single quotes (tolerate ">") 260 | )* 261 | )? 262 | '; 263 | $content = 264 | str_repeat(' 265 | (?> 266 | [^<]+ # content without tag 267 | | 268 | <\2 # nested opening tag 269 | '.$attr.' # attributes 270 | (?> 271 | /> 272 | | 273 | >', $nested_tags_level). # end of opening tag 274 | '.*?'. # last level nested tag content 275 | str_repeat(' 276 | # closing nested tag 277 | ) 278 | | 279 | <(?!/\2\s*> # other tags with a different name 280 | ) 281 | )*', 282 | $nested_tags_level); 283 | $content2 = str_replace('\2', '\3', $content); 284 | 285 | # First, look for nested blocks, e.g.: 286 | #

287 | #
288 | # tags for inner block must be indented. 289 | #
290 | #
291 | # 292 | # The outermost tags must start at the left margin for this to match, and 293 | # the inner nested divs must be indented. 294 | # We need to do this before the next, more liberal match, because the next 295 | # match will start at the first `
` and stop at the first `
`. 296 | $text = preg_replace_callback('{(?> 297 | (?> 298 | (?<=\n\n) # Starting after a blank line 299 | | # or 300 | \A\n? # the beginning of the doc 301 | ) 302 | ( # save in $1 303 | 304 | # Match from `\n` to `\n`, handling nested tags 305 | # in between. 306 | 307 | [ ]{0,'.$less_than_tab.'} 308 | <('.$block_tags_b_re.')# start tag = $2 309 | '.$attr.'> # attributes followed by > and \n 310 | '.$content.' # content, support nesting 311 | # the matching end tag 312 | [ ]* # trailing spaces/tabs 313 | (?=\n+|\Z) # followed by a newline or end of document 314 | 315 | | # Special version for tags of group a. 316 | 317 | [ ]{0,'.$less_than_tab.'} 318 | <('.$block_tags_a_re.')# start tag = $3 319 | '.$attr.'>[ ]*\n # attributes followed by > 320 | '.$content2.' # content, support nesting 321 | # the matching end tag 322 | [ ]* # trailing spaces/tabs 323 | (?=\n+|\Z) # followed by a newline or end of document 324 | 325 | | # Special case just for
. It was easier to make a special 326 | # case than to make the other regex more complicated. 327 | 328 | [ ]{0,'.$less_than_tab.'} 329 | <(hr) # start tag = $2 330 | '.$attr.' # attributes 331 | /?> # the matching end tag 332 | [ ]* 333 | (?=\n{2,}|\Z) # followed by a blank line or end of document 334 | 335 | | # Special case for standalone HTML comments: 336 | 337 | [ ]{0,'.$less_than_tab.'} 338 | (?s: 339 | 340 | ) 341 | [ ]* 342 | (?=\n{2,}|\Z) # followed by a blank line or end of document 343 | 344 | | # PHP and ASP-style processor instructions ( 351 | ) 352 | [ ]* 353 | (?=\n{2,}|\Z) # followed by a blank line or end of document 354 | 355 | ) 356 | )}Sxmi', 357 | array(&$this, '_hashHTMLBlocks_callback'), 358 | $text); 359 | 360 | return $text; 361 | } 362 | function _hashHTMLBlocks_callback($matches) { 363 | $text = $matches[1]; 364 | $key = $this->hashBlock($text); 365 | return "\n\n$key\n\n"; 366 | } 367 | 368 | 369 | function hashPart($text, $boundary = 'X') { 370 | # 371 | # Called whenever a tag must be hashed when a function insert an atomic 372 | # element in the text stream. Passing $text to through this function gives 373 | # a unique text-token which will be reverted back when calling unhash. 374 | # 375 | # The $boundary argument specify what character should be used to surround 376 | # the token. By convension, "B" is used for block elements that needs not 377 | # to be wrapped into paragraph tags at the end, ":" is used for elements 378 | # that are word separators and "X" is used in the general case. 379 | # 380 | # Swap back any tag hash found in $text so we do not have to `unhash` 381 | # multiple times at the end. 382 | $text = $this->unhash($text); 383 | 384 | # Then hash the block. 385 | static $i = 0; 386 | $key = "$boundary\x1A" . ++$i . $boundary; 387 | $this->html_hashes[$key] = $text; 388 | return $key; # String that will replace the tag. 389 | } 390 | 391 | 392 | function hashBlock($text) { 393 | # 394 | # Shortcut function for hashPart with block-level boundaries. 395 | # 396 | return $this->hashPart($text, 'B'); 397 | } 398 | 399 | 400 | var $block_gamut = array( 401 | # 402 | # These are all the transformations that form block-level 403 | # tags like paragraphs, headers, and list items. 404 | # 405 | "doHeaders" => 10, 406 | "doHorizontalRules" => 20, 407 | 408 | "doLists" => 40, 409 | "doCodeBlocks" => 50, 410 | "doBlockQuotes" => 60, 411 | ); 412 | 413 | function runBlockGamut($text) { 414 | # 415 | # Run block gamut tranformations. 416 | # 417 | # We need to escape raw HTML in Markdown source before doing anything 418 | # else. This need to be done for each block, and not only at the 419 | # begining in the Markdown function since hashed blocks can be part of 420 | # list items and could have been indented. Indented blocks would have 421 | # been seen as a code block in a previous pass of hashHTMLBlocks. 422 | $text = $this->hashHTMLBlocks($text); 423 | 424 | return $this->runBasicBlockGamut($text); 425 | } 426 | 427 | function runBasicBlockGamut($text) { 428 | # 429 | # Run block gamut tranformations, without hashing HTML blocks. This is 430 | # useful when HTML blocks are known to be already hashed, like in the first 431 | # whole-document pass. 432 | # 433 | foreach ($this->block_gamut as $method => $priority) { 434 | $text = $this->$method($text); 435 | } 436 | 437 | # Finally form paragraph and restore hashed blocks. 438 | $text = $this->formParagraphs($text); 439 | 440 | return $text; 441 | } 442 | 443 | 444 | function doHorizontalRules($text) { 445 | # Do Horizontal Rules: 446 | return preg_replace( 447 | '{ 448 | ^[ ]{0,3} # Leading space 449 | ([-*_]) # $1: First marker 450 | (?> # Repeated marker group 451 | [ ]{0,2} # Zero, one, or two spaces. 452 | \1 # Marker character 453 | ){2,} # Group repeated at least twice 454 | [ ]* # Tailing spaces 455 | $ # End of line. 456 | }mx', 457 | "\n".$this->hashBlock("empty_element_suffix")."\n", 458 | $text); 459 | } 460 | 461 | 462 | var $span_gamut = array( 463 | # 464 | # These are all the transformations that occur *within* block-level 465 | # tags like paragraphs, headers, and list items. 466 | # 467 | # Process character escapes, code spans, and inline HTML 468 | # in one shot. 469 | "parseSpan" => -30, 470 | 471 | # Process anchor and image tags. Images must come first, 472 | # because ![foo][f] looks like an anchor. 473 | "doImages" => 10, 474 | "doAnchors" => 20, 475 | 476 | # Make links out of things like `` 477 | # Must come after doAnchors, because you can use < and > 478 | # delimiters in inline links like [this](). 479 | "doAutoLinks" => 30, 480 | "encodeAmpsAndAngles" => 40, 481 | 482 | "doItalicsAndBold" => 50, 483 | "doHardBreaks" => 60, 484 | ); 485 | 486 | function runSpanGamut($text) { 487 | # 488 | # Run span gamut tranformations. 489 | # 490 | foreach ($this->span_gamut as $method => $priority) { 491 | $text = $this->$method($text); 492 | } 493 | 494 | return $text; 495 | } 496 | 497 | 498 | function doHardBreaks($text) { 499 | # Do hard breaks: 500 | return preg_replace_callback('/ {2,}\n/', 501 | array(&$this, '_doHardBreaks_callback'), $text); 502 | } 503 | function _doHardBreaks_callback($matches) { 504 | return $this->hashPart("empty_element_suffix\n"); 505 | } 506 | 507 | 508 | function doAnchors($text) { 509 | # 510 | # Turn Markdown link shortcuts into XHTML tags. 511 | # 512 | if ($this->in_anchor) return $text; 513 | $this->in_anchor = true; 514 | 515 | # 516 | # First, handle reference-style links: [link text] [id] 517 | # 518 | $text = preg_replace_callback('{ 519 | ( # wrap whole match in $1 520 | \[ 521 | ('.$this->nested_brackets_re.') # link text = $2 522 | \] 523 | 524 | [ ]? # one optional space 525 | (?:\n[ ]*)? # one optional newline followed by spaces 526 | 527 | \[ 528 | (.*?) # id = $3 529 | \] 530 | ) 531 | }xs', 532 | array(&$this, '_doAnchors_reference_callback'), $text); 533 | 534 | # 535 | # Next, inline-style links: [link text](url "optional title") 536 | # 537 | $text = preg_replace_callback('{ 538 | ( # wrap whole match in $1 539 | \[ 540 | ('.$this->nested_brackets_re.') # link text = $2 541 | \] 542 | \( # literal paren 543 | [ \n]* 544 | (?: 545 | <(.+?)> # href = $3 546 | | 547 | ('.$this->nested_url_parenthesis_re.') # href = $4 548 | ) 549 | [ \n]* 550 | ( # $5 551 | ([\'"]) # quote char = $6 552 | (.*?) # Title = $7 553 | \6 # matching quote 554 | [ \n]* # ignore any spaces/tabs between closing quote and ) 555 | )? # title is optional 556 | \) 557 | ) 558 | }xs', 559 | array(&$this, '_doAnchors_inline_callback'), $text); 560 | 561 | # 562 | # Last, handle reference-style shortcuts: [link text] 563 | # These must come last in case you've also got [link text][1] 564 | # or [link text](/foo) 565 | # 566 | $text = preg_replace_callback('{ 567 | ( # wrap whole match in $1 568 | \[ 569 | ([^\[\]]+) # link text = $2; can\'t contain [ or ] 570 | \] 571 | ) 572 | }xs', 573 | array(&$this, '_doAnchors_reference_callback'), $text); 574 | 575 | $this->in_anchor = false; 576 | return $text; 577 | } 578 | function _doAnchors_reference_callback($matches) { 579 | $whole_match = $matches[1]; 580 | $link_text = $matches[2]; 581 | $link_id =& $matches[3]; 582 | 583 | if ($link_id == "") { 584 | # for shortcut links like [this][] or [this]. 585 | $link_id = $link_text; 586 | } 587 | 588 | # lower-case and turn embedded newlines into spaces 589 | $link_id = strtolower($link_id); 590 | $link_id = preg_replace('{[ ]?\n}', ' ', $link_id); 591 | 592 | if (isset($this->urls[$link_id])) { 593 | $url = $this->urls[$link_id]; 594 | $url = $this->encodeAttribute($url); 595 | 596 | $result = "titles[$link_id] ) ) { 598 | $title = $this->titles[$link_id]; 599 | $title = $this->encodeAttribute($title); 600 | $result .= " title=\"$title\""; 601 | } 602 | 603 | $link_text = $this->runSpanGamut($link_text); 604 | $result .= ">$link_text"; 605 | $result = $this->hashPart($result); 606 | } 607 | else { 608 | $result = $whole_match; 609 | } 610 | return $result; 611 | } 612 | function _doAnchors_inline_callback($matches) { 613 | $whole_match = $matches[1]; 614 | $link_text = $this->runSpanGamut($matches[2]); 615 | $url = $matches[3] == '' ? $matches[4] : $matches[3]; 616 | $title =& $matches[7]; 617 | 618 | $url = $this->encodeAttribute($url); 619 | 620 | $result = "encodeAttribute($title); 623 | $result .= " title=\"$title\""; 624 | } 625 | 626 | $link_text = $this->runSpanGamut($link_text); 627 | $result .= ">$link_text"; 628 | 629 | return $this->hashPart($result); 630 | } 631 | 632 | 633 | function doImages($text) { 634 | # 635 | # Turn Markdown image shortcuts into tags. 636 | # 637 | # 638 | # First, handle reference-style labeled images: ![alt text][id] 639 | # 640 | $text = preg_replace_callback('{ 641 | ( # wrap whole match in $1 642 | !\[ 643 | ('.$this->nested_brackets_re.') # alt text = $2 644 | \] 645 | 646 | [ ]? # one optional space 647 | (?:\n[ ]*)? # one optional newline followed by spaces 648 | 649 | \[ 650 | (.*?) # id = $3 651 | \] 652 | 653 | ) 654 | }xs', 655 | array(&$this, '_doImages_reference_callback'), $text); 656 | 657 | # 658 | # Next, handle inline images: ![alt text](url "optional title") 659 | # Don't forget: encode * and _ 660 | # 661 | $text = preg_replace_callback('{ 662 | ( # wrap whole match in $1 663 | !\[ 664 | ('.$this->nested_brackets_re.') # alt text = $2 665 | \] 666 | \s? # One optional whitespace character 667 | \( # literal paren 668 | [ \n]* 669 | (?: 670 | <(\S*)> # src url = $3 671 | | 672 | ('.$this->nested_url_parenthesis_re.') # src url = $4 673 | ) 674 | [ \n]* 675 | ( # $5 676 | ([\'"]) # quote char = $6 677 | (.*?) # title = $7 678 | \6 # matching quote 679 | [ \n]* 680 | )? # title is optional 681 | \) 682 | ) 683 | }xs', 684 | array(&$this, '_doImages_inline_callback'), $text); 685 | 686 | return $text; 687 | } 688 | function _doImages_reference_callback($matches) { 689 | $whole_match = $matches[1]; 690 | $alt_text = $matches[2]; 691 | $link_id = strtolower($matches[3]); 692 | 693 | if ($link_id == "") { 694 | $link_id = strtolower($alt_text); # for shortcut links like ![this][]. 695 | } 696 | 697 | $alt_text = $this->encodeAttribute($alt_text); 698 | if (isset($this->urls[$link_id])) { 699 | $url = $this->encodeAttribute($this->urls[$link_id]); 700 | $result = "\"$alt_text\"";titles[$link_id])) { 702 | $title = $this->titles[$link_id]; 703 | $title = $this->encodeAttribute($title); 704 | $result .= " title=\"$title\""; 705 | } 706 | $result .= $this->empty_element_suffix; 707 | $result = $this->hashPart($result); 708 | } 709 | else { 710 | # If there's no such link ID, leave intact: 711 | $result = $whole_match; 712 | } 713 | 714 | return $result; 715 | } 716 | function _doImages_inline_callback($matches) { 717 | $whole_match = $matches[1]; 718 | $alt_text = $matches[2]; 719 | $url = $matches[3] == '' ? $matches[4] : $matches[3]; 720 | $title =& $matches[7]; 721 | 722 | $alt_text = $this->encodeAttribute($alt_text); 723 | $url = $this->encodeAttribute($url); 724 | $result = "\"$alt_text\"";encodeAttribute($title); 727 | $result .= " title=\"$title\""; # $title already quoted 728 | } 729 | $result .= $this->empty_element_suffix; 730 | 731 | return $this->hashPart($result); 732 | } 733 | 734 | 735 | function doHeaders($text) { 736 | # Setext-style headers: 737 | # Header 1 738 | # ======== 739 | # 740 | # Header 2 741 | # -------- 742 | # 743 | $text = preg_replace_callback('{ ^(.+?)[ ]*\n(=+|-+)[ ]*\n+ }mx', 744 | array(&$this, '_doHeaders_callback_setext'), $text); 745 | 746 | # atx-style headers: 747 | # # Header 1 748 | # ## Header 2 749 | # ## Header 2 with closing hashes ## 750 | # ... 751 | # ###### Header 6 752 | # 753 | $text = preg_replace_callback('{ 754 | ^(\#{1,6}) # $1 = string of #\'s 755 | [ ]* 756 | (.+?) # $2 = Header text 757 | [ ]* 758 | \#* # optional closing #\'s (not counted) 759 | \n+ 760 | }xm', 761 | array(&$this, '_doHeaders_callback_atx'), $text); 762 | 763 | return $text; 764 | } 765 | function _doHeaders_callback_setext($matches) { 766 | # Terrible hack to check we haven't found an empty list item. 767 | if ($matches[2] == '-' && preg_match('{^-(?: |$)}', $matches[1])) 768 | return $matches[0]; 769 | 770 | $level = $matches[2]{0} == '=' ? 1 : 2; 771 | $block = "".$this->runSpanGamut($matches[1]).""; 772 | return "\n" . $this->hashBlock($block) . "\n\n"; 773 | } 774 | function _doHeaders_callback_atx($matches) { 775 | $level = strlen($matches[1]); 776 | $block = "".$this->runSpanGamut($matches[2]).""; 777 | return "\n" . $this->hashBlock($block) . "\n\n"; 778 | } 779 | 780 | 781 | function doLists($text) { 782 | # 783 | # Form HTML ordered (numbered) and unordered (bulleted) lists. 784 | # 785 | $less_than_tab = $this->tab_width - 1; 786 | 787 | # Re-usable patterns to match list item bullets and number markers: 788 | $marker_ul_re = '[*+-]'; 789 | $marker_ol_re = '\d+[\.]'; 790 | $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)"; 791 | 792 | $markers_relist = array( 793 | $marker_ul_re => $marker_ol_re, 794 | $marker_ol_re => $marker_ul_re, 795 | ); 796 | 797 | foreach ($markers_relist as $marker_re => $other_marker_re) { 798 | # Re-usable pattern to match any entirel ul or ol list: 799 | $whole_list_re = ' 800 | ( # $1 = whole list 801 | ( # $2 802 | ([ ]{0,'.$less_than_tab.'}) # $3 = number of spaces 803 | ('.$marker_re.') # $4 = first list item marker 804 | [ ]+ 805 | ) 806 | (?s:.+?) 807 | ( # $5 808 | \z 809 | | 810 | \n{2,} 811 | (?=\S) 812 | (?! # Negative lookahead for another list item marker 813 | [ ]* 814 | '.$marker_re.'[ ]+ 815 | ) 816 | | 817 | (?= # Lookahead for another kind of list 818 | \n 819 | \3 # Must have the same indentation 820 | '.$other_marker_re.'[ ]+ 821 | ) 822 | ) 823 | ) 824 | '; // mx 825 | 826 | # We use a different prefix before nested lists than top-level lists. 827 | # See extended comment in _ProcessListItems(). 828 | 829 | if ($this->list_level) { 830 | $text = preg_replace_callback('{ 831 | ^ 832 | '.$whole_list_re.' 833 | }mx', 834 | array(&$this, '_doLists_callback'), $text); 835 | } 836 | else { 837 | $text = preg_replace_callback('{ 838 | (?:(?<=\n)\n|\A\n?) # Must eat the newline 839 | '.$whole_list_re.' 840 | }mx', 841 | array(&$this, '_doLists_callback'), $text); 842 | } 843 | } 844 | 845 | return $text; 846 | } 847 | function _doLists_callback($matches) { 848 | # Re-usable patterns to match list item bullets and number markers: 849 | $marker_ul_re = '[*+-]'; 850 | $marker_ol_re = '\d+[\.]'; 851 | $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)"; 852 | 853 | $list = $matches[1]; 854 | $list_type = preg_match("/$marker_ul_re/", $matches[4]) ? "ul" : "ol"; 855 | 856 | $marker_any_re = ( $list_type == "ul" ? $marker_ul_re : $marker_ol_re ); 857 | 858 | $list .= "\n"; 859 | $result = $this->processListItems($list, $marker_any_re); 860 | 861 | $result = $this->hashBlock("<$list_type>\n" . $result . ""); 862 | return "\n". $result ."\n\n"; 863 | } 864 | 865 | var $list_level = 0; 866 | 867 | function processListItems($list_str, $marker_any_re) { 868 | # 869 | # Process the contents of a single ordered or unordered list, splitting it 870 | # into individual list items. 871 | # 872 | # The $this->list_level global keeps track of when we're inside a list. 873 | # Each time we enter a list, we increment it; when we leave a list, 874 | # we decrement. If it's zero, we're not in a list anymore. 875 | # 876 | # We do this because when we're not inside a list, we want to treat 877 | # something like this: 878 | # 879 | # I recommend upgrading to version 880 | # 8. Oops, now this line is treated 881 | # as a sub-list. 882 | # 883 | # As a single paragraph, despite the fact that the second line starts 884 | # with a digit-period-space sequence. 885 | # 886 | # Whereas when we're inside a list (or sub-list), that line will be 887 | # treated as the start of a sub-list. What a kludge, huh? This is 888 | # an aspect of Markdown's syntax that's hard to parse perfectly 889 | # without resorting to mind-reading. Perhaps the solution is to 890 | # change the syntax rules such that sub-lists must start with a 891 | # starting cardinal number; e.g. "1." or "a.". 892 | 893 | $this->list_level++; 894 | 895 | # trim trailing blank lines: 896 | $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str); 897 | 898 | $list_str = preg_replace_callback('{ 899 | (\n)? # leading line = $1 900 | (^[ ]*) # leading whitespace = $2 901 | ('.$marker_any_re.' # list marker and space = $3 902 | (?:[ ]+|(?=\n)) # space only required if item is not empty 903 | ) 904 | ((?s:.*?)) # list item text = $4 905 | (?:(\n+(?=\n))|\n) # tailing blank line = $5 906 | (?= \n* (\z | \2 ('.$marker_any_re.') (?:[ ]+|(?=\n)))) 907 | }xm', 908 | array(&$this, '_processListItems_callback'), $list_str); 909 | 910 | $this->list_level--; 911 | return $list_str; 912 | } 913 | function _processListItems_callback($matches) { 914 | $item = $matches[4]; 915 | $leading_line =& $matches[1]; 916 | $leading_space =& $matches[2]; 917 | $marker_space = $matches[3]; 918 | $tailing_blank_line =& $matches[5]; 919 | 920 | if ($leading_line || $tailing_blank_line || 921 | preg_match('/\n{2,}/', $item)) 922 | { 923 | # Replace marker with the appropriate whitespace indentation 924 | $item = $leading_space . str_repeat(' ', strlen($marker_space)) . $item; 925 | $item = $this->runBlockGamut($this->outdent($item)."\n"); 926 | } 927 | else { 928 | # Recursion for sub-lists: 929 | $item = $this->doLists($this->outdent($item)); 930 | $item = preg_replace('/\n+$/', '', $item); 931 | $item = $this->runSpanGamut($item); 932 | } 933 | 934 | return "
  • " . $item . "
  • \n"; 935 | } 936 | 937 | 938 | function doCodeBlocks($text) { 939 | # 940 | # Process Markdown `
    ` blocks.
     941 | 	#
     942 | 		$text = preg_replace_callback('{
     943 | 				(?:\n\n|\A\n?)
     944 | 				(	            # $1 = the code block -- one or more lines, starting with a space/tab
     945 | 				  (?>
     946 | 					[ ]{'.$this->tab_width.'}  # Lines must start with a tab or a tab-width of spaces
     947 | 					.*\n+
     948 | 				  )+
     949 | 				)
     950 | 				((?=^[ ]{0,'.$this->tab_width.'}\S)|\Z)	# Lookahead for non-space at line-start, or end of doc
     951 | 			}xm',
     952 | 			array(&$this, '_doCodeBlocks_callback'), $text);
     953 | 
     954 | 		return $text;
     955 | 	}
     956 | 	function _doCodeBlocks_callback($matches) {
     957 | 		$codeblock = $matches[1];
     958 | 
     959 | 		$codeblock = $this->outdent($codeblock);
     960 | 		$codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
     961 | 
     962 | 		# trim leading newlines and trailing newlines
     963 | 		$codeblock = preg_replace('/\A\n+|\n+\z/', '', $codeblock);
     964 | 
     965 | 		$codeblock = "
    $codeblock\n
    "; 966 | return "\n\n".$this->hashBlock($codeblock)."\n\n"; 967 | } 968 | 969 | 970 | function makeCodeSpan($code) { 971 | # 972 | # Create a code span markup for $code. Called from handleSpanToken. 973 | # 974 | $code = htmlspecialchars(trim($code), ENT_NOQUOTES); 975 | return $this->hashPart("$code"); 976 | } 977 | 978 | 979 | var $em_relist = array( 980 | '' => '(?:(? '(?<=\S|^)(? '(?<=\S|^)(? '(?:(? '(?<=\S|^)(? '(?<=\S|^)(? '(?:(? '(?<=\S|^)(? '(?<=\S|^)(?em_relist as $em => $em_re) { 1002 | foreach ($this->strong_relist as $strong => $strong_re) { 1003 | # Construct list of allowed token expressions. 1004 | $token_relist = array(); 1005 | if (isset($this->em_strong_relist["$em$strong"])) { 1006 | $token_relist[] = $this->em_strong_relist["$em$strong"]; 1007 | } 1008 | $token_relist[] = $em_re; 1009 | $token_relist[] = $strong_re; 1010 | 1011 | # Construct master expression from list. 1012 | $token_re = '{('. implode('|', $token_relist) .')}'; 1013 | $this->em_strong_prepared_relist["$em$strong"] = $token_re; 1014 | } 1015 | } 1016 | } 1017 | 1018 | function doItalicsAndBold($text) { 1019 | $token_stack = array(''); 1020 | $text_stack = array(''); 1021 | $em = ''; 1022 | $strong = ''; 1023 | $tree_char_em = false; 1024 | 1025 | while (1) { 1026 | # 1027 | # Get prepared regular expression for seraching emphasis tokens 1028 | # in current context. 1029 | # 1030 | $token_re = $this->em_strong_prepared_relist["$em$strong"]; 1031 | 1032 | # 1033 | # Each loop iteration search for the next emphasis token. 1034 | # Each token is then passed to handleSpanToken. 1035 | # 1036 | $parts = preg_split($token_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE); 1037 | $text_stack[0] .= $parts[0]; 1038 | $token =& $parts[1]; 1039 | $text =& $parts[2]; 1040 | 1041 | if (empty($token)) { 1042 | # Reached end of text span: empty stack without emitting. 1043 | # any more emphasis. 1044 | while ($token_stack[0]) { 1045 | $text_stack[1] .= array_shift($token_stack); 1046 | $text_stack[0] .= array_shift($text_stack); 1047 | } 1048 | break; 1049 | } 1050 | 1051 | $token_len = strlen($token); 1052 | if ($tree_char_em) { 1053 | # Reached closing marker while inside a three-char emphasis. 1054 | if ($token_len == 3) { 1055 | # Three-char closing marker, close em and strong. 1056 | array_shift($token_stack); 1057 | $span = array_shift($text_stack); 1058 | $span = $this->runSpanGamut($span); 1059 | $span = "$span"; 1060 | $text_stack[0] .= $this->hashPart($span); 1061 | $em = ''; 1062 | $strong = ''; 1063 | } else { 1064 | # Other closing marker: close one em or strong and 1065 | # change current token state to match the other 1066 | $token_stack[0] = str_repeat($token{0}, 3-$token_len); 1067 | $tag = $token_len == 2 ? "strong" : "em"; 1068 | $span = $text_stack[0]; 1069 | $span = $this->runSpanGamut($span); 1070 | $span = "<$tag>$span"; 1071 | $text_stack[0] = $this->hashPart($span); 1072 | $$tag = ''; # $$tag stands for $em or $strong 1073 | } 1074 | $tree_char_em = false; 1075 | } else if ($token_len == 3) { 1076 | if ($em) { 1077 | # Reached closing marker for both em and strong. 1078 | # Closing strong marker: 1079 | for ($i = 0; $i < 2; ++$i) { 1080 | $shifted_token = array_shift($token_stack); 1081 | $tag = strlen($shifted_token) == 2 ? "strong" : "em"; 1082 | $span = array_shift($text_stack); 1083 | $span = $this->runSpanGamut($span); 1084 | $span = "<$tag>$span"; 1085 | $text_stack[0] .= $this->hashPart($span); 1086 | $$tag = ''; # $$tag stands for $em or $strong 1087 | } 1088 | } else { 1089 | # Reached opening three-char emphasis marker. Push on token 1090 | # stack; will be handled by the special condition above. 1091 | $em = $token{0}; 1092 | $strong = "$em$em"; 1093 | array_unshift($token_stack, $token); 1094 | array_unshift($text_stack, ''); 1095 | $tree_char_em = true; 1096 | } 1097 | } else if ($token_len == 2) { 1098 | if ($strong) { 1099 | # Unwind any dangling emphasis marker: 1100 | if (strlen($token_stack[0]) == 1) { 1101 | $text_stack[1] .= array_shift($token_stack); 1102 | $text_stack[0] .= array_shift($text_stack); 1103 | } 1104 | # Closing strong marker: 1105 | array_shift($token_stack); 1106 | $span = array_shift($text_stack); 1107 | $span = $this->runSpanGamut($span); 1108 | $span = "$span"; 1109 | $text_stack[0] .= $this->hashPart($span); 1110 | $strong = ''; 1111 | } else { 1112 | array_unshift($token_stack, $token); 1113 | array_unshift($text_stack, ''); 1114 | $strong = $token; 1115 | } 1116 | } else { 1117 | # Here $token_len == 1 1118 | if ($em) { 1119 | if (strlen($token_stack[0]) == 1) { 1120 | # Closing emphasis marker: 1121 | array_shift($token_stack); 1122 | $span = array_shift($text_stack); 1123 | $span = $this->runSpanGamut($span); 1124 | $span = "$span"; 1125 | $text_stack[0] .= $this->hashPart($span); 1126 | $em = ''; 1127 | } else { 1128 | $text_stack[0] .= $token; 1129 | } 1130 | } else { 1131 | array_unshift($token_stack, $token); 1132 | array_unshift($text_stack, ''); 1133 | $em = $token; 1134 | } 1135 | } 1136 | } 1137 | return $text_stack[0]; 1138 | } 1139 | 1140 | 1141 | function doBlockQuotes($text) { 1142 | $text = preg_replace_callback('/ 1143 | ( # Wrap whole match in $1 1144 | (?> 1145 | ^[ ]*>[ ]? # ">" at the start of a line 1146 | .+\n # rest of the first line 1147 | (.+\n)* # subsequent consecutive lines 1148 | \n* # blanks 1149 | )+ 1150 | ) 1151 | /xm', 1152 | array(&$this, '_doBlockQuotes_callback'), $text); 1153 | 1154 | return $text; 1155 | } 1156 | function _doBlockQuotes_callback($matches) { 1157 | $bq = $matches[1]; 1158 | # trim one level of quoting - trim whitespace-only lines 1159 | $bq = preg_replace('/^[ ]*>[ ]?|^[ ]+$/m', '', $bq); 1160 | $bq = $this->runBlockGamut($bq); # recurse 1161 | 1162 | $bq = preg_replace('/^/m', " ", $bq); 1163 | # These leading spaces cause problem with
     content, 
    1164 | 		# so we need to fix that:
    1165 | 		$bq = preg_replace_callback('{(\s*
    .+?
    )}sx', 1166 | array(&$this, '_doBlockQuotes_callback2'), $bq); 1167 | 1168 | return "\n". $this->hashBlock("
    \n$bq\n
    ")."\n\n"; 1169 | } 1170 | function _doBlockQuotes_callback2($matches) { 1171 | $pre = $matches[1]; 1172 | $pre = preg_replace('/^ /m', '', $pre); 1173 | return $pre; 1174 | } 1175 | 1176 | 1177 | function formParagraphs($text) { 1178 | # 1179 | # Params: 1180 | # $text - string to process with html

    tags 1181 | # 1182 | # Strip leading and trailing lines: 1183 | $text = preg_replace('/\A\n+|\n+\z/', '', $text); 1184 | 1185 | $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY); 1186 | 1187 | # 1188 | # Wrap

    tags and unhashify HTML blocks 1189 | # 1190 | foreach ($grafs as $key => $value) { 1191 | if (!preg_match('/^B\x1A[0-9]+B$/', $value)) { 1192 | # Is a paragraph. 1193 | $value = $this->runSpanGamut($value); 1194 | $value = preg_replace('/^([ ]*)/', "

    ", $value); 1195 | $value .= "

    "; 1196 | $grafs[$key] = $this->unhash($value); 1197 | } 1198 | else { 1199 | # Is a block. 1200 | # Modify elements of @grafs in-place... 1201 | $graf = $value; 1202 | $block = $this->html_hashes[$graf]; 1203 | $graf = $block; 1204 | // if (preg_match('{ 1205 | // \A 1206 | // ( # $1 =
    tag 1207 | //
    ]* 1209 | // \b 1210 | // markdown\s*=\s* ([\'"]) # $2 = attr quote char 1211 | // 1 1212 | // \2 1213 | // [^>]* 1214 | // > 1215 | // ) 1216 | // ( # $3 = contents 1217 | // .* 1218 | // ) 1219 | // (
    ) # $4 = closing tag 1220 | // \z 1221 | // }xs', $block, $matches)) 1222 | // { 1223 | // list(, $div_open, , $div_content, $div_close) = $matches; 1224 | // 1225 | // # We can't call Markdown(), because that resets the hash; 1226 | // # that initialization code should be pulled into its own sub, though. 1227 | // $div_content = $this->hashHTMLBlocks($div_content); 1228 | // 1229 | // # Run document gamut methods on the content. 1230 | // foreach ($this->document_gamut as $method => $priority) { 1231 | // $div_content = $this->$method($div_content); 1232 | // } 1233 | // 1234 | // $div_open = preg_replace( 1235 | // '{\smarkdown\s*=\s*([\'"]).+?\1}', '', $div_open); 1236 | // 1237 | // $graf = $div_open . "\n" . $div_content . "\n" . $div_close; 1238 | // } 1239 | $grafs[$key] = $graf; 1240 | } 1241 | } 1242 | 1243 | return implode("\n\n", $grafs); 1244 | } 1245 | 1246 | 1247 | function encodeAttribute($text) { 1248 | # 1249 | # Encode text for a double-quoted HTML attribute. This function 1250 | # is *not* suitable for attributes enclosed in single quotes. 1251 | # 1252 | $text = $this->encodeAmpsAndAngles($text); 1253 | $text = str_replace('"', '"', $text); 1254 | return $text; 1255 | } 1256 | 1257 | 1258 | function encodeAmpsAndAngles($text) { 1259 | # 1260 | # Smart processing for ampersands and angle brackets that need to 1261 | # be encoded. Valid character entities are left alone unless the 1262 | # no-entities mode is set. 1263 | # 1264 | if ($this->no_entities) { 1265 | $text = str_replace('&', '&', $text); 1266 | } else { 1267 | # Ampersand-encoding based entirely on Nat Irons's Amputator 1268 | # MT plugin: 1269 | $text = preg_replace('/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/', 1270 | '&', $text);; 1271 | } 1272 | # Encode remaining <'s 1273 | $text = str_replace('<', '<', $text); 1274 | 1275 | return $text; 1276 | } 1277 | 1278 | 1279 | function doAutoLinks($text) { 1280 | $text = preg_replace_callback('{<((https?|ftp|dict):[^\'">\s]+)>}i', 1281 | array(&$this, '_doAutoLinks_url_callback'), $text); 1282 | 1283 | # Email addresses: 1284 | $text = preg_replace_callback('{ 1285 | < 1286 | (?:mailto:)? 1287 | ( 1288 | (?: 1289 | [-!#$%&\'*+/=?^_`.{|}~\w\x80-\xFF]+ 1290 | | 1291 | ".*?" 1292 | ) 1293 | \@ 1294 | (?: 1295 | [-a-z0-9\x80-\xFF]+(\.[-a-z0-9\x80-\xFF]+)*\.[a-z]+ 1296 | | 1297 | \[[\d.a-fA-F:]+\] # IPv4 & IPv6 1298 | ) 1299 | ) 1300 | > 1301 | }xi', 1302 | array(&$this, '_doAutoLinks_email_callback'), $text); 1303 | 1304 | return $text; 1305 | } 1306 | function _doAutoLinks_url_callback($matches) { 1307 | $url = $this->encodeAttribute($matches[1]); 1308 | $link = "$url"; 1309 | return $this->hashPart($link); 1310 | } 1311 | function _doAutoLinks_email_callback($matches) { 1312 | $address = $matches[1]; 1313 | $link = $this->encodeEmailAddress($address); 1314 | return $this->hashPart($link); 1315 | } 1316 | 1317 | 1318 | function encodeEmailAddress($addr) { 1319 | # 1320 | # Input: an email address, e.g. "foo@example.com" 1321 | # 1322 | # Output: the email address as a mailto link, with each character 1323 | # of the address encoded as either a decimal or hex entity, in 1324 | # the hopes of foiling most address harvesting spam bots. E.g.: 1325 | # 1326 | #

    foo@exampl 1329 | # e.com

    1330 | # 1331 | # Based by a filter by Matthew Wickline, posted to BBEdit-Talk. 1332 | # With some optimizations by Milian Wolff. 1333 | # 1334 | $addr = "mailto:" . $addr; 1335 | $chars = preg_split('/(? $char) { 1339 | $ord = ord($char); 1340 | # Ignore non-ascii chars. 1341 | if ($ord < 128) { 1342 | $r = ($seed * (1 + $key)) % 100; # Pseudo-random function. 1343 | # roughly 10% raw, 45% hex, 45% dec 1344 | # '@' *must* be encoded. I insist. 1345 | if ($r > 90 && $char != '@') /* do nothing */; 1346 | else if ($r < 45) $chars[$key] = '&#x'.dechex($ord).';'; 1347 | else $chars[$key] = '&#'.$ord.';'; 1348 | } 1349 | } 1350 | 1351 | $addr = implode('', $chars); 1352 | $text = implode('', array_slice($chars, 7)); # text without `mailto:` 1353 | $addr = "$text"; 1354 | 1355 | return $addr; 1356 | } 1357 | 1358 | 1359 | function parseSpan($str) { 1360 | # 1361 | # Take the string $str and parse it into tokens, hashing embeded HTML, 1362 | # escaped characters and handling code spans. 1363 | # 1364 | $output = ''; 1365 | 1366 | $span_re = '{ 1367 | ( 1368 | \\\\'.$this->escape_chars_re.' 1369 | | 1370 | (?no_markup ? '' : ' 1373 | | 1374 | # comment 1375 | | 1376 | <\?.*?\?> | <%.*?%> # processing instruction 1377 | | 1378 | <[/!$]?[-a-zA-Z0-9:_]+ # regular tags 1379 | (?> 1380 | \s 1381 | (?>[^"\'>]+|"[^"]*"|\'[^\']*\')* 1382 | )? 1383 | > 1384 | ').' 1385 | ) 1386 | }xs'; 1387 | 1388 | while (1) { 1389 | # 1390 | # Each loop iteration seach for either the next tag, the next 1391 | # openning code span marker, or the next escaped character. 1392 | # Each token is then passed to handleSpanToken. 1393 | # 1394 | $parts = preg_split($span_re, $str, 2, PREG_SPLIT_DELIM_CAPTURE); 1395 | 1396 | # Create token from text preceding tag. 1397 | if ($parts[0] != "") { 1398 | $output .= $parts[0]; 1399 | } 1400 | 1401 | # Check if we reach the end. 1402 | if (isset($parts[1])) { 1403 | $output .= $this->handleSpanToken($parts[1], $parts[2]); 1404 | $str = $parts[2]; 1405 | } 1406 | else { 1407 | break; 1408 | } 1409 | } 1410 | 1411 | return $output; 1412 | } 1413 | 1414 | 1415 | function handleSpanToken($token, &$str) { 1416 | # 1417 | # Handle $token provided by parseSpan by determining its nature and 1418 | # returning the corresponding value that should replace it. 1419 | # 1420 | switch ($token{0}) { 1421 | case "\\": 1422 | return $this->hashPart("&#". ord($token{1}). ";"); 1423 | case "`": 1424 | # Search for end marker in remaining text. 1425 | if (preg_match('/^(.*?[^`])'.preg_quote($token).'(?!`)(.*)$/sm', 1426 | $str, $matches)) 1427 | { 1428 | $str = $matches[2]; 1429 | $codespan = $this->makeCodeSpan($matches[1]); 1430 | return $this->hashPart($codespan); 1431 | } 1432 | return $token; // return as text since no ending marker found. 1433 | default: 1434 | return $this->hashPart($token); 1435 | } 1436 | } 1437 | 1438 | 1439 | function outdent($text) { 1440 | # 1441 | # Remove one level of line-leading tabs or spaces 1442 | # 1443 | return preg_replace('/^(\t|[ ]{1,'.$this->tab_width.'})/m', '', $text); 1444 | } 1445 | 1446 | 1447 | # String length function for detab. `_initDetab` will create a function to 1448 | # hanlde UTF-8 if the default function does not exist. 1449 | var $utf8_strlen = 'mb_strlen'; 1450 | 1451 | function detab($text) { 1452 | # 1453 | # Replace tabs with the appropriate amount of space. 1454 | # 1455 | # For each line we separate the line in blocks delemited by 1456 | # tab characters. Then we reconstruct every line by adding the 1457 | # appropriate number of space between each blocks. 1458 | 1459 | $text = preg_replace_callback('/^.*\t.*$/m', 1460 | array(&$this, '_detab_callback'), $text); 1461 | 1462 | return $text; 1463 | } 1464 | function _detab_callback($matches) { 1465 | $line = $matches[0]; 1466 | $strlen = $this->utf8_strlen; # strlen function for UTF-8. 1467 | 1468 | # Split in blocks. 1469 | $blocks = explode("\t", $line); 1470 | # Add each blocks to the line. 1471 | $line = $blocks[0]; 1472 | unset($blocks[0]); # Do not add first block twice. 1473 | foreach ($blocks as $block) { 1474 | # Calculate amount of space, insert spaces, insert block. 1475 | $amount = $this->tab_width - 1476 | $strlen($line, 'UTF-8') % $this->tab_width; 1477 | $line .= str_repeat(" ", $amount) . $block; 1478 | } 1479 | return $line; 1480 | } 1481 | function _initDetab() { 1482 | # 1483 | # Check for the availability of the function in the `utf8_strlen` property 1484 | # (initially `mb_strlen`). If the function is not available, create a 1485 | # function that will loosely count the number of UTF-8 characters with a 1486 | # regular expression. 1487 | # 1488 | if (function_exists($this->utf8_strlen)) return; 1489 | $this->utf8_strlen = create_function('$text', 'return preg_match_all( 1490 | "/[\\\\x00-\\\\xBF]|[\\\\xC0-\\\\xFF][\\\\x80-\\\\xBF]*/", 1491 | $text, $m);'); 1492 | } 1493 | 1494 | 1495 | function unhash($text) { 1496 | # 1497 | # Swap back in all the tags hashed by _HashHTMLBlocks. 1498 | # 1499 | return preg_replace_callback('/(.)\x1A[0-9]+\1/', 1500 | array(&$this, '_unhash_callback'), $text); 1501 | } 1502 | function _unhash_callback($matches) { 1503 | return $this->html_hashes[$matches[0]]; 1504 | } 1505 | 1506 | } 1507 | 1508 | /* 1509 | 1510 | PHP Markdown 1511 | ============ 1512 | 1513 | Description 1514 | ----------- 1515 | 1516 | This is a PHP translation of the original Markdown formatter written in 1517 | Perl by John Gruber. 1518 | 1519 | Markdown is a text-to-HTML filter; it translates an easy-to-read / 1520 | easy-to-write structured text format into HTML. Markdown's text format 1521 | is most similar to that of plain text email, and supports features such 1522 | as headers, *emphasis*, code blocks, blockquotes, and links. 1523 | 1524 | Markdown's syntax is designed not as a generic markup language, but 1525 | specifically to serve as a front-end to (X)HTML. You can use span-level 1526 | HTML tags anywhere in a Markdown document, and you can use block level 1527 | HTML tags (like
    and as well). 1528 | 1529 | For more information about Markdown's syntax, see: 1530 | 1531 | 1532 | 1533 | 1534 | Bugs 1535 | ---- 1536 | 1537 | To file bug reports please send email to: 1538 | 1539 | 1540 | 1541 | Please include with your report: (1) the example input; (2) the output you 1542 | expected; (3) the output Markdown actually produced. 1543 | 1544 | 1545 | Version History 1546 | --------------- 1547 | 1548 | See the readme file for detailed release notes for this version. 1549 | 1550 | 1551 | Copyright and License 1552 | --------------------- 1553 | 1554 | PHP Markdown 1555 | Copyright (c) 2004-2009 Michel Fortin 1556 | 1557 | All rights reserved. 1558 | 1559 | Based on Markdown 1560 | Copyright (c) 2003-2006 John Gruber 1561 | 1562 | All rights reserved. 1563 | 1564 | Redistribution and use in source and binary forms, with or without 1565 | modification, are permitted provided that the following conditions are 1566 | met: 1567 | 1568 | * Redistributions of source code must retain the above copyright notice, 1569 | this list of conditions and the following disclaimer. 1570 | 1571 | * Redistributions in binary form must reproduce the above copyright 1572 | notice, this list of conditions and the following disclaimer in the 1573 | documentation and/or other materials provided with the distribution. 1574 | 1575 | * Neither the name "Markdown" nor the names of its contributors may 1576 | be used to endorse or promote products derived from this software 1577 | without specific prior written permission. 1578 | 1579 | This software is provided by the copyright holders and contributors "as 1580 | is" and any express or implied warranties, including, but not limited 1581 | to, the implied warranties of merchantability and fitness for a 1582 | particular purpose are disclaimed. In no event shall the copyright owner 1583 | or contributors be liable for any direct, indirect, incidental, special, 1584 | exemplary, or consequential damages (including, but not limited to, 1585 | procurement of substitute goods or services; loss of use, data, or 1586 | profits; or business interruption) however caused and on any theory of 1587 | liability, whether in contract, strict liability, or tort (including 1588 | negligence or otherwise) arising in any way out of the use of this 1589 | software, even if advised of the possibility of such damage. 1590 | 1591 | */ 1592 | ?> 1593 | --------------------------------------------------------------------------------