├── _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 | \2\s*> # 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 | \2> # 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 | \3> # 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 ( and <%)
345 |
346 | [ ]{0,'.$less_than_tab.'}
347 | (?s:
348 | <([?%]) # $2
349 | .*?
350 | \2>
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: 
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 = "
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 = "
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 . "$list_type>");
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$tag>";
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$tag>";
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] = ''.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 |
--------------------------------------------------------------------------------