├── CHANGELOG.textile ├── LICENSE ├── README.textile ├── SECURITY.md ├── composer.json └── src └── Netcarver └── Textile ├── DataBag.php ├── Parser.php └── Tag.php /CHANGELOG.textile: -------------------------------------------------------------------------------- 1 | h1. Changelog 2 | 3 | Here's a summary of changes in each release. The list doesn't include some small changes or updates to test cases. 4 | 5 | h2. Version 4.1.4 - upcoming 6 | 7 | h2. "Version 4.1.3 - 2025/01/07":https://github.com/textile/php-textile/releases/tag/v4.1.3 8 | 9 | * This is a security update, which fixes stored XSS vulnerability in image link handing. 10 | * Fixed: In restricted mode, restrict image link protocol. Previously and an image link's href allowed all protocols in restricted mode. Now it goes through the same validation as text links. 11 | 12 | h2. "Version 4.1.2 - 2024/08/29":https://github.com/textile/php-textile/releases/tag/v4.1.2 13 | 14 | * Fix PHP 8.4 compatibility issues (closes "#227":https://github.com/textile/php-textile/issues/227). 15 | 16 | h2. "Version 4.1.1 - 2024/06/07":https://github.com/textile/php-textile/releases/tag/v4.1.1 17 | 18 | * Links are now rendered when block tags are disabled (closes "#225":https://github.com/textile/php-textile/issues/225). 19 | 20 | h2. "Version 4.1.0 - 2024/01/02":https://github.com/textile/php-textile/releases/tag/v4.1.0 21 | 22 | * Support @:@ and @!@ characters in CSS class names (closes "#224":https://github.com/textile/php-textile/issues/224). 23 | * Support including textile escape sequences inside link title attribute (closes "#223":https://github.com/textile/php-textile/issues/223). 24 | 25 | h2. "Version 4.0.0 - 2022/12/03":https://github.com/textile/php-textile/releases/tag/v4.0.0 26 | 27 | * HTML void tags render a self-closing slash based on the given document type. If @Parser::setDocumentType()@ is given @Parser::DOCTYPE_XHTML@, self-closing tags are used, otherwise not. 28 | 29 | h2. "Version 3.8.0 - 2022/12/03":https://github.com/textile/php-textile/releases/tag/v3.8.0 30 | 31 | * Added @Parser::setAlignClasses()@ and @Parser::isAlignClassesEnabled()@. This can be used to enable img alignment classes in XHTML output document mode, instead of the default align attribute. 32 | * Added @Parser::DOCTYPE_HTML5@ and @Parser::DOCTYPE_XHTML@ constants. These can be used with @Parser::setDocumentType()@ to specify the output document type. 33 | 34 | h2. "Version 3.7.7 - 2022/05/01":https://github.com/textile/php-textile/releases/tag/v3.7.7 35 | 36 | * Fix deprecation errors that appear on PHP >= 8.1 about preg_split limit argument's NULL value. 37 | 38 | h2. "Version 3.7.6 - 2020/01/08":https://github.com/textile/php-textile/releases/tag/v3.7.6 39 | 40 | * Support consecutive links without whitespace between using bracket syntax (closes "#202":https://github.com/textile/php-textile/issues/202, "#205":https://github.com/textile/php-textile/pull/205 and "#206":https://github.com/textile/php-textile/pull/206). 41 | 42 | h2. "Version 3.7.5 - 2019/12/16":https://github.com/textile/php-textile/releases/tag/v3.7.5 43 | 44 | * Fix PHP 7.4 compatibility issues (closes "#199":https://github.com/textile/php-textile/issues/199). 45 | 46 | h2. "Version 3.7.4 - 2019/12/15":https://github.com/textile/php-textile/releases/tag/v3.7.4 47 | 48 | * Fix issue where an inline tag preceding the last character, that is a glyph, is not rendered if the block tags are disabled with @Parser::setBlockTags@ (closes "#198":https://github.com/textile/php-textile/issues/198). 49 | 50 | h2. "Version 3.7.3 - 2019/08/30":https://github.com/textile/php-textile/releases/tag/v3.7.3 51 | 52 | * Fix issues where divider tags placed on their own line within a paragraph, would disable Textile processing for that paragraph block (closes "#194":https://github.com/textile/php-textile/issues/194). 53 | 54 | h2. "Version 3.7.2 - 2019/06/08":https://github.com/textile/php-textile/releases/tag/v3.7.2 55 | 56 | * Fix quote and bracket processing around span and other inline tags (closes "#191":https://github.com/textile/php-textile/issues/191 and "#192":https://github.com/textile/php-textile/issues/192). 57 | 58 | h2. "Version 3.7.1 - 2019/01/26":https://github.com/textile/php-textile/releases/tag/v3.7.1 59 | 60 | * Fix and omit anchor links prefixing (closes "#190":https://github.com/textile/php-textile/issues/190). 61 | * Alignment attribute can be specified last within a block tag definition (closes "#189":https://github.com/textile/php-textile/issues/190) 62 | 63 | h2. "Version 3.7.0 - 2018/12/15":https://github.com/textile/php-textile/releases/tag/v3.7.0 64 | 65 | * Fix problems with list parsing; no longer matches inline-syntax, such as strongs, as list item markers (closes "#172":https://github.com/textile/php-textile/issues/172). 66 | * Add option to disable specific substitution symbols by setting them to FALSE (see "#158":https://github.com/textile/php-textile/issues/158). 67 | * Add option to apply classes, such as language-identifiers, to the code element within a @bc@ (closes "#96":https://github.com/textile/php-textile/issues/96). 68 | * Add @Parser::configure()@, the method can be extended to create pre-configured parser classes. 69 | * Automatic paragraph wrapping now checks the contents and does not wrap paragraphs already wrapped in non-phrasing HTML tags (closes "#22":https://github.com/textile/php-textile/issues/22 and "#63":https://github.com/textile/php-textile/issues/63). 70 | * Add option to disable Textile formatting for blocks wrapped in non-standard HTML-like tags. 71 | * Check for starting list depth (closes "#24":https://github.com/textile/php-textile/issues/24). 72 | * Add @Parser::setImagePrefix()@, @Parser::setLinkPrefix()@, @Parser::getImagePrefix()@ and @Parser::getLinkPrefix()@ (closes "#169":https://github.com/textile/php-textile/issues/169). 73 | * Add @Parser::setRawBlocks()@ and @Parser::isRawBlocksEnabled()@. 74 | * Deprecate @Parser::setRelativeImagePrefix()@ and @Parser::$relativeImagePrefix@ in favour of the new decoupled methods. 75 | 76 | h2. "Version 3.6.1 - 2018/10/21":https://github.com/textile/php-textile/releases/tag/v3.6.1 77 | 78 | * Fix problems with attribute parsing under PHP >= 7.1 (closes "#175":https://github.com/textile/php-textile/issues/175 and "#176":https://github.com/textile/php-textile/issues/176). 79 | * Fix test suite compatibility issues under PHP >= 7.2 (closes "#184":https://github.com/textile/php-textile/issues/184). 80 | * Fix missing deprecation notices. 81 | * Add test fixture for Unicode characters in image title attributes. 82 | * Tidy entity encoding process (closes "#182":https://github.com/textile/php-textile/issues/182). 83 | * Drop legacy PHP 5.5 and 5.4 unit test targets due to test suite's depedencies' requirements. 84 | 85 | h2. "Version 3.6.0 - 2016/11/17":https://github.com/textile/php-textile/releases/tag/v3.6.0 86 | 87 | * Fix empty-like link texts (closes "#141":https://github.com/textile/php-textile/issues/141). 88 | * Fix empty-like RedCloth definitions (closes "#142":https://github.com/textile/php-textile/issues/142). 89 | * Fix empty-like table summaries (closes "#143":https://github.com/textile/php-textile/issues/143). 90 | * Fix image dimension generation on Windows when doc_root can not be resolved (closes "#140":https://github.com/textile/php-textile/issues/140). 91 | * Fix HTTP protocol restrictions not affecting images (closes "#144":https://github.com/textile/php-textile/issues/144). 92 | * Add more versatile parsing method, @Parser::parse()@. 93 | * Add @Parser::setRestricted()@ and @Parser::isRestrictedModeEnabled()@. 94 | * Add @Parser::setLite()@ and @Parser::isLiteModeEnabled()@. 95 | * Add @Parser::setDocumentType()@ and @Parser::getDocumentType()@. 96 | * Add @Parser::setDocumentRootDirectory()@ and @Parser::getDocumentRootDirectory()@. 97 | * Add @Parser::setImages()@ and @Parser::isImageTagEnabled()@. 98 | * Add @Parser::setBlockTags()@ and @Parser::isBlockTagEnabled()@ (closes "#138":https://github.com/textile/php-textile/issues/138). 99 | * Add @Parser::setLinkRelationShip()@ and @Parser::getLinkRelationShip()@. 100 | * Add @Parser::setLineWrap()@ and @Parser::isLineWrapEnabled()@ (closes "#139":https://github.com/textile/php-textile/issues/139). 101 | * Deprecate @Parser::textileThis()@, @Parser::textileRestricted()@ and @Parser::textileCommon()@ in favour of the more versatile @Parser::parse()@. 102 | * Fix: @Parser::relURL()@ now supports unicode characters (closes "#146":https://github.com/textile/php-textile/issues/146). 103 | * Fix: Undefined variable warning. 104 | * Feature/Fix: Allow link text that contains newline characters (closes "#154":https://github.com/textile/php-textile/issues/154, "#155":https://github.com/textile/php-textile/issues/155 & "#167":https://github.com/textile/php-textile/issues/167). 105 | * Fix: Stop encoding '+' characters in tel: links (closes "#156":https://github.com/textile/php-textile/issues/156). 106 | * Make dimension glyph replacements a little stricter. 107 | * Fix: Prevent hyphenated class on td cells adding incorrect style (closes "#164":https://github.com/textile/php-textile/issues/164). 108 | * Jail read of image dimensions to images within the document root path (closes "#145":https://github.com/textile/php-textile/issues/145). 109 | * Various code cleanups, typo corrections and refactoring. 110 | * Documentation fixes and extensions. 111 | 112 | h2. "Version 3.5.5 - 2014/01/02":https://github.com/textile/php-textile/releases/tag/v3.5.5 113 | 114 | * Fix rendering of left and right image alignment in non-lite restricted mode (closes "#132":https://github.com/textile/php-textile/issues/132). 115 | * Fix wrong triggered error type when using the deprecated @$encode@ option of @Parser::textileThis()@. 116 | * Fix attribute regular expression to stop it matching multiple times (closes "#131":https://github.com/textile/php-textile/issues/131). 117 | * Fix rendering of lists in table cells with span attributes set (closes "#135":https://github.com/textile/php-textile/issues/135). 118 | * Throws an exception if @Parser::__construct()@ is given invalid document type, instead of eating it silently and returning document using the default content-type you weren't wishing for. Prevents issues where you want HTML5, but silently got XHTML due to typo or an issue in application design. 119 | * Clean user-supplied styles prior to sorting and re-formatting. 120 | * Remove dead code and duplicated procedures as outlined by code coverage reports. 121 | * Remove unused internal method @Parser::fSpecial()@. 122 | * Test code coverage, coding style and run unit tests against "HHVM":http://hhvm.com. 123 | * Footnote reference numbers support unicode characters. 124 | * Use named sub-patterns in regular expressions (closes "#121":https://github.com/textile/php-textile/issues/121). 125 | * Refactored link detection code. 126 | 127 | h2. "Version 3.5.4 - 2013/11/06":https://github.com/textile/php-textile/releases/tag/v3.5.4 128 | 129 | * Fix broken image alignment in HTML5 mode (closes "#123":https://github.com/textile/php-textile/issues/123). 130 | * Fix duplicate HTML IDs that occur when a footnote isn't referenced in the content (closes "#125":https://github.com/textile/php-textile/issues/125). 131 | * Don't include image alignment to the URL in restricted mode. 132 | * Detect and process quoted quote symbols. 133 | * New link parser (closes "#86":https://github.com/textile/php-textile/issues/86, "#87":https://github.com/textile/php-textile/issues/87 and "#128":https://github.com/textile/php-textile/issues/128). 134 | 135 | h2. "Version 3.5.3 - 2013/10/30":https://github.com/textile/php-textile/releases/tag/v3.5.3 136 | 137 | * Fix double image URL encoding (closes "#102":https://github.com/textile/php-textile/issues/102). 138 | * Fix URL reference token spoofing. 139 | * Fix broken parser output when $strict argument was set to TRUE (closes "#119":https://github.com/textile/php-textile/issues/119). 140 | * Fix memory leaking tag cache. Tag cache is never reset between @textileThis()@ and @textileRestricted()@ calls referencing the same instance. 141 | * Fix rare instances where a link displays a wrong URL mentioned elsewhere in the document. 142 | * Fix invalid markup generated when Redcloth-style definition list is used inside a table cell. 143 | * Link aliases follow same allowed URL schemes as normal links. 144 | * Update @hasRawText()@ and @fPBr()@ to detect a wider range of raw HTML and XHTML. 145 | * Unify attribute order with Redcloth. 146 | * Reduce list and blockquote indentation level to match paragraphs and other block tags. 147 | * Restrict how spans are parsed (closes "#106":https://github.com/textile/php-textile/issues/106). 148 | * Fix citations on spans (closes "#120":https://github.com/textile/php-textile/issues/120). 149 | * Refactor @parseAttribsToArray()@ slightly. 150 | 151 | h2. "Version 3.5.2 - 2013/10/25":https://github.com/textile/php-textile/releases/tag/v3.5.2 152 | 153 | * Improved support for Redcloth-style definition lists. 154 | *# Allow multiple terms 155 | *# Allow linebreaks in terms 156 | * Fix incorrectly rendered @rel@ attributes (closes "#103":https://github.com/textile/php-textile/issues/103). 157 | * Fix @getSymbol()@ so it actually returns the named symbol (closes "#104":https://github.com/textile/php-textile/issues/104). 158 | * Fix unicode link aliases that were broken on some PCRE_UTF8 supporting systems. 159 | * Fix collapsing whitespace and preserve newlines. Preserves whitespace inside long @bc@, @notextile@ and @pre@ blocks, rather than collapsing two or more empty lines down to one. Renders whitespace as it was defined, rather than using hard-coded single LF to separate lines (closes "#109":https://github.com/textile/php-textile/issues/109 and "#111":https://github.com/textile/php-textile/pull/111). 160 | * Fix the number of code tags rendered inside long code blocks (closes "#116":https://github.com/textile/php-textile/issues/116). 161 | * Fix token spoofing from the document body by randomizing token references (closes "#115":https://github.com/textile/php-textile/issues/115). 162 | * Add image dimensions to images even when Textile is run on command line. On CLI, images are looked from the current working directory. 163 | * Define internal class properties as protected rather than at all, causing them to be created as public. 164 | * Move internal property definitions from the constructor to class definition. 165 | * Added runnable PHPUnit tests, integration with "Travis CI":https://travis-ci.org/. 166 | * Removed error suppression, the code doesn't intentionally produce notices. 167 | * Add @br@ tags to headings instead of leaving linebreaks untouched. 168 | 169 | h2. "Version 3.5.1 - 2013/01/01":https://github.com/textile/php-textile/releases/tag/v3.5.1 170 | 171 | * Remove horizontal alignment from inline elements (closes "#66":https://github.com/textile/php-textile/issues/66). 172 | * Reinstate automatic generation of image width and height generation for relative images (closes "#101":https://github.com/textile/php-textile/issues/101). 173 | * Add @setDimensionlessImages()@ and @getDimensionlessImages()@ to suppress width and height generation for relative images and better support content for responsive layouts (closes "#100":https://github.com/textile/php-textile/issues/101). 174 | * Allow "." in class attributes (closes "#97":https://github.com/textile/php-textile/issues/97). 175 | 176 | h2. "Version 3.5.0 - 2012/12/12":https://github.com/textile/php-textile/releases/tag/v3.5.0 177 | 178 | * Add composer.json to allow installation via the "Composer PHP package manager":https://getcomposer.org/. 179 | * Regular expression improvements (issues "#78":https://github.com/textile/php-textile/issues/78, "#81":https://github.com/textile/php-textile/issues/81 and "#83":https://github.com/textile/php-textile/issues/83). 180 | * Allow pre-encoded @>@ and @<@ as alignments. 181 | * Self-referencing links can now be combined with link aliases. eg. @"$":alias1@ is now possible. 182 | * Fix memory leak and performance degradation when calling same Textile object multiple times. 183 | * Do not double-encode @+@ or @%@ in urls. 184 | * Remove legacy SVN lines and old Textpattern integration methods. 185 | * Refactored code, removing deprecated methods. 186 | * Fix undefined variable in Redcloth-style definition lists. 187 | * Improvements to image handling (closes "#69":https://github.com/textile/php-textile/issues/69). 188 | * Extend recognition of dimension sign to more complex cases. Eg... 189 | *# @-0.5 x -.1 x +100@ => %-0.5 × -.1 × +100% 190 | *# @10 x -€ 110,00@ => %10 × -€ 110,00% (this replacement is available only if unicode support is included in your PCRE implementation and any character that is a currency symbol should work) 191 | * Change parse tokens to further prevent glyphs from matching them internally. 192 | * Improve handling of textile within table cells without leading or trailing spaces (eg. @|"$":https://github.com"|_Here we are_|==code==|@ etc). 193 | * Improve handling of lists within table cells. (closes "#79":https://github.com/textile/php-textile/issues/79) 194 | * Allow mixed nested lists (Already supported in Redcloth). 195 | * Improve detection of open quotes in situations like @["(Berk). Hilton"]@ (where the open quote was previously was incorrectly detected encoded). 196 | * Fix a problem with links followed by ':', ';' or '?' like @Do you like "cheese":/cheese?@ where the '?' becomes part of the href rather than a '?' at the end of the sentence. This happens on platforms where PCRE has unicode support. 197 | * Removal of leading \t from generated paragraphs & better indentation of generated lists in the HTML (closes "#90":https://github.com/textile/php-textile/issues/90). 198 | * New method @textileEncode()@. This is preferred to calling @textileThis()@ with the $encode flag. 199 | * "PSR-0, PSR-1 & PSR-2":https://github.com/php-fig/fig-standards/tree/master/accepted conformity added. 200 | *# Split class Textile into classes @Parser@, @DataBag@ & @Tag@ and moved them into @src/textile/php-textile@ directory for PSR-0. 201 | *# Unified method names & coding styles according to PSR-1 & PSR-2. 202 | *# Switched from using defines to explicit set methods; @setSymbol()@ & @setRelativeImagePrefix()@. 203 | *# Added visibility controls to all properties and methods. 204 | * Dropped textile's PHP4 heritage and moved over to using a @__construct()@ method. 205 | 206 | h2. "Version 2.4.1 - 2012/08/23":https://github.com/textile/php-textile/releases/tag/v2.4.1 207 | 208 | * Add @sftp@, @callto@, @tel@ and @file@ schemes to the URI whitelist in unrestricted mode. 209 | * Support international format @tel@ URIs via linkrefs (link aliases). 210 | * Extend linkrefs to all available URI schemes in unrestricted mode. 211 | * Reverted looser matching of list-like structures as it introduced problems in block-level elements that 212 | can legitimately have literals or other non-lists in them that are similar to textile's lists. 213 | See issue "#65"::https://github.com/textile/php-textile/issues/65 for some examples. 214 | * Fixed "#61"::https://github.com/textile/php-textile/issues/61 which caused strong numeric strings at the start of a line to be turned into lists. 215 | * Reverted auto-breaking in table cells as it introduced "#71"::https://github.com/textile/php-textile/issues/71 (breaking lists within table cells). 216 | * Fixed "#67"::https://github.com/textile/php-textile/issues/67 which ran textile within table cells through the glyph routine twice. 217 | 218 | h2. "Version 2.4.0 - 2012/05/07":https://github.com/textile/php-textile/releases/tag/v2.4.0 219 | 220 | * Conditionally use utf8 in span regex pattern. Closes "#53"::https://github.com/textile/php-textile/issues/53. 221 | * Small code cleanups 222 | * Convert \n to @
@ inside table rows. 223 | * Added HTML comment block handling. 224 | * Added ability to control the start attribute and continuation of ordered lists. 225 | * Adds basic Redcloth-style definition list support. 226 | * Add ability to customise footnote refs and anchors. 227 | * Add https protocol to linkrefs. 228 | * Add unicode support for linkref urls. 229 | * Added redcloth style list continuation. 230 | * Loosen recognition of notedefs and notelists. 231 | * Better recognition of lists adjoined to previous text. (This was reverted in 2.4.1 as it caused issues inside block elements like bc. or bq). 232 | * Bugfix: allow apostrophe between ) and a word character. 233 | * Allow notelist refs to be customised. 234 | 235 | h2. "Version 2.3.2 - 2012/03/20":https://github.com/textile/php-textile/releases/tag/v2.3.2 236 | 237 | * Allow multiple classes in block attributes. 238 | * Improve handling of (classA classB#Bad id) blocks. 239 | * Add span $tail fix & support for trailing [ in spans. 240 | * Add doctype to textile constructor & use abbr instead of acronym for html5 doctypes. 241 | * HTML5 doctype extends class rather than use invalid align attribute. 242 | * Allow auto-assigned classes to be output in restricted mode. 243 | * Add support for encoding Unicode characters in links. 244 | * Notelists: fix order of @sup@ and @a@. Fixes issue "#20"::https://github.com/textile/php-textile/issues/20 245 | * Add simple set of symbols ¤§µ¶†‡•∗∴◊♠♣♥♦ to notelist regex. Requested in "#38"::https://github.com/textile/php-textile/issues/38 246 | * Remove attributes from embedded code tag output by bc. 247 | * Simplify en-dash glyph rule -- should fix "#50"::https://github.com/textile/php-textile/issues/50. 248 | * Simplify self-hyperlinked text, remove the scheme for http, https, ftp and mailto link text. 249 | 250 | h2. "Version 2.3.0 - 2012/01/10":https://github.com/textile/php-textile/releases/tag/v2.3 251 | 252 | * Fix potential DoS in @cleanba()@. 253 | * Fix the issue where class would eat the note label. 254 | * Sanitise image URLs. 255 | * Allow inline span tags to be applied within non-English quotation marks. 256 | * Allow non-English quotation marks inside inline span tags. 257 | * Allow pipe closure of captions. 258 | * Allow missing closing pipe in colgroups. 259 | * Note-style links can use index 0. 260 | * Encode quotes in restricted mode, rather than improperly leaving them as is. 261 | * Improve lang, style, id and class handling. 262 | * Add rel attributes to linked images. 263 | * Center aligned cells aren't treated as captions. 264 | * Removed use of deprecated @split()@ function. 265 | * Disallow unsafe block attributes in restricted mode. 266 | 267 | h2. "Version 2.2.0 - 2010/09/22":https://github.com/textile/php-textile/releases/tag/v2.2public 268 | 269 | This is our first release forked from "revision 3359":https://code.google.com/p/textpattern/source/detail?r=3359 of "Textpattern CMS":http://textpattern.com. Here are the changes since Textile v2.0.0: 270 | 271 | * Allow duplicate notelists with different backref characters. 272 | * Properly render empty table cells. 273 | * Add support for glyphs such as fractions, plus-minus and degrees. 274 | * Optimize glyph encoding setup. 275 | * Optimize style attribute processing. 276 | * Less restrictive paragraph breaking. 277 | * Output cleaner inline styles. 278 | * More restrictive apostrophe encoding and matching. 279 | * Remove horizontal and vertical alignment attributes from list elements. 280 | * Lists can use dot terminator. 281 | * Improved table generation: allow linebreaks in table cells, colgroups, thead, tbody and tfoot elements. 282 | * Add auto-numbered notelists. 283 | * Add Textile comment tag. Comments aren't displayed in the generated markup. 284 | * Add self-links where the URL can be used with the link text (@"$":http://example.com@). 285 | * Allow glyph parsing across tag boundaries. 286 | * Add definition lists. 287 | * Fix duplicate IDs in footnotes. 288 | * Caps span isn't added to acronyms. 289 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2003-2004, Dean Allen 4 | All rights reserved. 5 | 6 | Thanks to Carlo Zottmann for refactoring Textile's procedural 7 | code into a class framework. 8 | 9 | Additions and fixes Copyright (c) 2006 Alex Shiels https://twitter.com/tellyworth 10 | Additions and fixes Copyright (c) 2010 Stef Dawson https://stefdawson.com/ 11 | Additions and fixes Copyright (c) 2010-17 Netcarver https://github.com/netcarver 12 | Additions and fixes Copyright (c) 2011 Jeff Soo http://ipsedixit.net/ 13 | Additions and fixes Copyright (c) 2012 Robert Wetzlmayr https://wetzlmayr.com/ 14 | Additions and fixes Copyright (c) 2012-24 Jukka Svahn https://rahforum.biz/ 15 | 16 | Redistribution and use in source and binary forms, with or without 17 | modification, are permitted provided that the following conditions are met: 18 | 19 | 1. Redistributions of source code must retain the above copyright notice, this 20 | list of conditions and the following disclaimer. 21 | 22 | 2. Redistributions in binary form must reproduce the above copyright notice, 23 | this list of conditions and the following disclaimer in the documentation 24 | and/or other materials provided with the distribution. 25 | 26 | 3. Neither the name of the Textile nor the names of its 27 | contributors may be used to endorse or promote products derived from 28 | this software without specific prior written permission. 29 | 30 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 31 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 32 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 33 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 34 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 35 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 36 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 37 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 38 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 39 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 40 | -------------------------------------------------------------------------------- /README.textile: -------------------------------------------------------------------------------- 1 | h1. PHP-Textile 2 | 3 | "Textile reference":https://textile-lang.com/ | "Live editor":https://textile-lang.com/doc 4 | 5 | !https://sonarcloud.io/api/project_badges/measure?project=textile_php-textile&metric=coverage!:https://sonarcloud.io/summary/overall?id=textile_php-textile !https://img.shields.io/packagist/v/netcarver/textile.svg!:https://packagist.org/packages/netcarver/textile !https://img.shields.io/packagist/dt/netcarver/textile.svg!:https://packagist.org/packages/netcarver/textile 6 | 7 | PHP-Textile is a modern Textile markup language parser for PHP. Textile is a humane web text generator that takes lightweight, readable, plaintext-like markup language and converts it into well formed HTML. 8 | 9 | h2. Install 10 | 11 | Using "Composer":https://getcomposer.org/: 12 | 13 | bc. $ composer require netcarver/textile 14 | 15 | h2. Usage 16 | 17 | The Textile parser can be accessed through the @Netcarver\Textile\Parser@ class. The class is highly configurable, and actual parsing happens with the @parse@ method: 18 | 19 | bc. require './vendor/autoload.php'; 20 | $parser = new \Netcarver\Textile\Parser(); 21 | echo $parser->parse('h1. Hello World!'); 22 | 23 | h3. Parsing untrusted input 24 | 25 | If you are using PHP-Textile to format user-supplied input, blog comments for instance, remember to enable restricted parser mode: 26 | 27 | bc. $parser = new \Netcarver\Textile\Parser(); 28 | echo $parser 29 | ->setRestricted(true) 30 | ->parse('!bad/image/not/allowed.svg!'); 31 | 32 | In restricted mode PHP-Textile doesn't allow more powerful formatting options such as inline styles, and removes any raw HTML. 33 | 34 | h3. Parsing single-line fields 35 | 36 | If you are using PHP-Textile as a field-level formatter to parse just inline spans and glyphs, use the @setBlockTags@ method to disable block tags: 37 | 38 | bc. $parser = new \Netcarver\Textile\Parser(); 39 | echo $parser 40 | ->setBlockTags(false) 41 | ->parse('Hello *strong* world!'); 42 | 43 | The above outputs: 44 | 45 | bc. Hello strong world! 46 | 47 | h3. Doctypes 48 | 49 | Currently, PHP-Textile can target either XHTML or HTML5 output with XHTML being the default for backward compatibility. The targeted doctype can be changed via the @setDocumentType@ method: 50 | 51 | bc. $parser = new \Netcarver\Textile\Parser(); 52 | echo $parser 53 | ->setDocumentType('html5') 54 | ->parse('HTML(HyperText Markup Language)'); 55 | 56 | h3. Setting alternate glyphs 57 | 58 | Textile's typographic substitutions can be overridden with the @setSymbol@ method. If you need to setup Textile to do non-standard substitutions, call @setSymbol@ before you parse the input with @parse@. 59 | 60 | bc. $parser = new \Netcarver\Textile\Parser(); 61 | $parser 62 | ->setSymbol('half', '1⁄2') 63 | ->parse('Hello [1/2] World!'); 64 | 65 | The symbol names you can pass to @setSymbol@ can be found "here":https://github.com/textile/php-textile/blob/v3.6.1/src/Netcarver/Textile/Parser.php#L710. 66 | 67 | h3. Prefixing relative image and link paths 68 | 69 | Setting prefix might be useful if you want to point relative paths to certain consistent location: 70 | 71 | bc. $parser = new \Netcarver\Textile\Parser(); 72 | $parser 73 | ->setImagePrefix('/user/uploads') 74 | ->setLinkPrefix('/') 75 | ->parse('!image.jpg! "link":page'); 76 | 77 | h2. Getting in contact 78 | 79 | The PHP-Textile project welcomes constructive input and bug reports from users. Please "open an issue":https://github.com/textile/php-textile/issues on the repository for a comment, feature request or bug. 80 | 81 | h2. Development 82 | 83 | See "CONTRIBUTING.textile":https://github.com/textile/php-textile/blob/master/.github/CONTRIBUTING.textile. 84 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | You can privately report security vulnerabilities to PHP-Textile team by opening a 6 | [new draft security advisory](https://github.com/textile/php-textile/security/advisories/new) 7 | to us on GitHub. 8 | 9 | When opening a new advisory, take the following considerations into account: 10 | 11 | * Before opening a security advisory, please try to confirm that the security 12 | issue is caused by PHP-Textile, and not by third-party or configuration 13 | error. 14 | * Provide details as to the nature of the vulnerability, and examples of the steps to 15 | replicate it. 16 | * PHP-Textile is a free, open-source project run by volunteers, and we do not offer monetary 17 | rewards or provide bug bounties for discovering security issues. 18 | * Due to the volunteer-nature, our response times may not be immediate. We do kindly ask to allow 19 | us a reasonable amount of time to evaluate and correct the issue before making details public. 20 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "netcarver/textile", 3 | "description": "Textile markup language parser", 4 | "license": "BSD-3-Clause", 5 | "homepage": "https://github.com/textile/php-textile", 6 | "keywords": ["php-textile", "textile", "parser", "markup", "language", "html", "format", "plaintext", "document"], 7 | "support": { 8 | "wiki": "https://github.com/textile/php-textile/wiki", 9 | "issues": "https://github.com/textile/php-textile/issues", 10 | "source": "https://github.com/textile/php-textile" 11 | }, 12 | "autoload": { 13 | "psr-4": { 14 | "Netcarver\\Textile\\": "src/Netcarver/Textile/" 15 | } 16 | }, 17 | "autoload-dev": { 18 | "psr-4": { 19 | "Netcarver\\Textile\\Test\\": "test/Netcarver/Textile/Test/" 20 | } 21 | }, 22 | "require": { 23 | "php": ">=5.3.0" 24 | }, 25 | "require-dev": { 26 | "phpstan/phpstan": "1.12.11", 27 | "phpunit/phpunit": "^9.5.20", 28 | "squizlabs/php_codesniffer": "3.*", 29 | "symfony/yaml": "^5.4.40", 30 | "psy/psysh": "^0.12.4" 31 | }, 32 | "extra": { 33 | "branch-alias": { 34 | "dev-master": "4.1-dev" 35 | } 36 | }, 37 | "scripts": { 38 | "test": [ 39 | "@composer lint", 40 | "@composer test:static", 41 | "@composer test:unit" 42 | ], 43 | "project:bump": "@php ./scripts/release.php", 44 | "project:bump-dev": "@php ./scripts/release.php --dev", 45 | "lint": "phpcs", 46 | "lint-fix": "phpcbf", 47 | "repl": "psysh", 48 | "test:static": "phpstan analyse --level 8 src", 49 | "test:unit": "XDEBUG_MODE=coverage phpunit" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Netcarver/Textile/DataBag.php: -------------------------------------------------------------------------------- 1 | 'value')); 49 | * $plant->flower('rose')->color('red'); 50 | * 51 | * @internal 52 | */ 53 | class DataBag 54 | { 55 | /** 56 | * The data array stored in the bag. 57 | * 58 | * @var array 59 | */ 60 | protected $data; 61 | 62 | /** 63 | * Constructor. 64 | * 65 | * @param array|null $data The initial data array stored in the bag 66 | */ 67 | public function __construct($data = null) 68 | { 69 | $this->data = (array) $data; 70 | } 71 | 72 | /** 73 | * Adds a value to the bag. 74 | * 75 | * Empty values are rejected, unless the 76 | * second argument is set TRUE. 77 | * 78 | * bc. use Netcarver\Textile\DataBag; 79 | * $plant = new DataBag(array('key' => 'value')); 80 | * $plant->flower('rose')->color('red')->emptyValue(false, true); 81 | * 82 | * @param string $name The name 83 | * @param array $params Arguments 84 | * @return DataBag 85 | */ 86 | public function __call($name, array $params) 87 | { 88 | if (!empty($params[1]) || !empty($params[0])) { 89 | $this->data[$name] = $params[0]; 90 | } 91 | 92 | return $this; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Netcarver/Textile/Parser.php: -------------------------------------------------------------------------------- 1 | for refactoring 16 | * Textile's procedural code into a class framework 17 | * 18 | * Additions and fixes Copyright (c) 2006 Alex Shiels https://twitter.com/tellyworth 19 | * Additions and fixes Copyright (c) 2010 Stef Dawson http://stefdawson.com/ 20 | * Additions and fixes Copyright (c) 2010-17 Netcarver https://github.com/netcarver 21 | * Additions and fixes Copyright (c) 2011 Jeff Soo http://ipsedixit.net/ 22 | * Additions and fixes Copyright (c) 2012 Robert Wetzlmayr http://wetzlmayr.com/ 23 | * Additions and fixes Copyright (c) 2012-24 Jukka Svahn https://rahforum.biz/ 24 | * 25 | * Redistribution and use in source and binary forms, with or without 26 | * modification, are permitted provided that the following conditions are met: 27 | * 28 | * * Redistributions of source code must retain the above copyright notice, 29 | * this list of conditions and the following disclaimer. 30 | * 31 | * * Redistributions in binary form must reproduce the above copyright notice, 32 | * this list of conditions and the following disclaimer in the documentation 33 | * and/or other materials provided with the distribution. 34 | * 35 | * * Neither the name Textile nor the names of its contributors may be used to 36 | * endorse or promote products derived from this software without specific 37 | * prior written permission. 38 | * 39 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 40 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 41 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 42 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 43 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 44 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 45 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 46 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 47 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 48 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 49 | * POSSIBILITY OF SUCH DAMAGE. 50 | */ 51 | 52 | /* 53 | Textile usage examples. 54 | 55 | Block modifier syntax: 56 | 57 | Header: h(1-6). 58 | Paragraphs beginning with 'hn. ' (where n is 1-6) are wrapped in header tags. 59 | Example: h1. Header... ->

Header...

60 | 61 | Paragraph: p. (also applied by default) 62 | Example: p. Text ->

Text

63 | 64 | Blockquote: bq. 65 | Example: bq. Block quotation... ->
Block quotation...
66 | 67 | Blockquote with citation: bq.:http://citation.url 68 | Example: bq.:http://example.com/ Text... 69 | ->
Text...
70 | 71 | Footnote: fn(1-100). 72 | Example: fn1. Footnote... ->

Footnote...

73 | 74 | Numeric list: #, ## 75 | Consecutive paragraphs beginning with # are wrapped in ordered list tags. 76 | Example:
  1. ordered list
77 | 78 | Bulleted list: *, ** 79 | Consecutive paragraphs beginning with * are wrapped in unordered list tags. 80 | Example:
  • unordered list
81 | 82 | Definition list: 83 | Terms ;, ;; 84 | Definitions :, :: 85 | Consecutive paragraphs beginning with ; or : are wrapped in definition list tags. 86 | Example:
term
definition
87 | 88 | Redcloth-style Definition list: 89 | - Term1 := Definition1 90 | - Term2 := Extended 91 | definition =: 92 | 93 | Phrase modifier syntax: 94 | 95 | _emphasis_ -> emphasis 96 | __italic__ -> italic 97 | *strong* -> strong 98 | **bold** -> bold 99 | ??citation?? -> citation 100 | -deleted text- -> deleted 101 | +inserted text+ -> inserted 102 | ^superscript^ -> superscript 103 | ~subscript~ -> subscript 104 | @code@ -> computer code 105 | %(bob)span% -> span 106 | 107 | ==notextile== -> leave text alone (do not format) 108 | 109 | "linktext":url -> linktext 110 | "linktext(title)":url -> linktext 111 | "$":url -> url 112 | "$(title)":url -> url 113 | 114 | !imageurl! -> 115 | !imageurl(alt text)! -> alt text 116 | !imageurl!:linkurl -> 117 | 118 | ABC(Always Be Closing) -> ABC 119 | 120 | Linked Notes: 121 | 122 | Allows the generation of an automated list of notes with links. 123 | 124 | Linked notes are composed of three parts, a set of named _definitions_, a set of 125 | _references_ to those definitions and one or more _placeholders_ indicating where 126 | the consolidated list of notes is to be placed in your document. 127 | 128 | Definitions: 129 | 130 | Each note definition must occur in its own paragraph and should look like this... 131 | 132 | note#mynotelabel. Your definition text here. 133 | 134 | You are free to use whatever label you wish after the # as long as it is made up 135 | of letters, numbers, colon(:) or dash(-). 136 | 137 | References: 138 | 139 | Each note reference is marked in your text like this[#mynotelabel] and 140 | it will be replaced with a superscript reference that links into the list of 141 | note definitions. 142 | 143 | List placeholder(s): 144 | 145 | The note list can go anywhere in your document. You have to indicate where 146 | like this: 147 | 148 | notelist. 149 | 150 | notelist can take attributes (class#id) like this: notelist(class#id). 151 | 152 | By default, the note list will show each definition in the order that they 153 | are referenced in the text by the _references_. It will show each definition with 154 | a full list of backlinks to each reference. If you do not want this, you can choose 155 | to override the backlinks like this... 156 | 157 | notelist(class#id)!. Produces a list with no backlinks. 158 | notelist(class#id)^. Produces a list with only the first backlink. 159 | 160 | Should you wish to have a specific definition display backlinks differently to this 161 | then you can override the backlink method by appending a link override to the 162 | _definition_ you wish to customise. 163 | 164 | note#label. Uses the citelist's setting for backlinks. 165 | note#label!. Causes that definition to have no backlinks. 166 | note#label^. Causes that definition to have one backlink (to the first ref.) 167 | note#label*. Causes that definition to have all backlinks. 168 | 169 | Any unreferenced notes will be left out of the list unless you explicitly state 170 | you want them by adding a '+'. Like this... 171 | 172 | notelist(class#id)!+. Giving a list of all notes without any backlinks. 173 | 174 | You can mix and match the list backlink control and unreferenced links controls 175 | but the backlink control (if any) must go first. Like so: notelist^+. , not 176 | like this: notelist+^. 177 | 178 | Example... 179 | Scientists say[#lavader] the moon is small. 180 | 181 | note#other. An unreferenced note. 182 | 183 | note#lavader(myliclass). "Proof":http://example.com of a small moon. 184 | 185 | notelist(myclass#myid)+. 186 | 187 | Would output (the actual IDs used would be randomised)... 188 | 189 |

Scientists say1 the moon is small.

190 | 191 |
    192 |
  1. a 193 | Proof of a small moon.
  2. 194 |
  3. An unreferenced note.
  4. 195 |
196 | 197 | The 'a b c' backlink characters can be altered too. 198 | For example if you wanted the notes to have numeric backlinks starting from 1: 199 | 200 | notelist:1. 201 | 202 | Table syntax: 203 | 204 | Simple tables: 205 | 206 | |a|simple|table|row| 207 | |And|Another|table|row| 208 | |With an||empty|cell| 209 | 210 | |=. My table caption goes here 211 | |_. A|_. table|_. header|_.row| 212 | |A|simple|table|row| 213 | 214 | Note: Table captions *must* be the first line of the table else treated as a center-aligned cell. 215 | 216 | Tables with attributes: 217 | 218 | table{border:1px solid black}. My table summary here 219 | {background:#ddd;color:red}. |{}| | | | 220 | 221 | To specify thead / tfoot / tbody groups, add one of these on its own line 222 | above the row(s) you wish to wrap (you may specify attributes before the dot): 223 | 224 | |^. # thead 225 | |-. # tbody 226 | |~. # tfoot 227 | 228 | Column groups: 229 | 230 | |:\3. 100| 231 | 232 | Becomes: 233 | 234 | 235 | You can omit either or both of the \N or width values. You may also 236 | add cells after the colgroup definition to specify col elements with 237 | span, width, or standard Textile attributes: 238 | 239 | |:. 50|(firstcol). |\2. 250||300| 240 | 241 | Becomes: 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | (Note that, per the HTML specification, you should not add span 250 | to the colgroup if specifying col elements.) 251 | 252 | Applying Attributes: 253 | 254 | Most anywhere Textile code is used, attributes such as arbitrary css style, 255 | css classes, and ids can be applied. The syntax is fairly consistent. 256 | 257 | The following characters quickly alter the alignment of block elements: 258 | 259 | < -> left align ex. p<. left-aligned para 260 | > -> right align h3>. right-aligned header 3 261 | = -> centred h4=. centred header 4 262 | <> -> justified p<>. justified paragraph 263 | 264 | These will change vertical alignment in table cells: 265 | 266 | ^ -> top ex. |^. top-aligned table cell| 267 | - -> middle |-. middle aligned| 268 | ~ -> bottom |~. bottom aligned cell| 269 | 270 | Plain (parentheses) inserted between block syntax and the closing dot-space 271 | indicate classes and ids: 272 | 273 | p(hector). paragraph ->

paragraph

274 | 275 | p(#fluid). paragraph ->

paragraph

276 | 277 | (classes and ids can be combined) 278 | p(hector#fluid). paragraph ->

paragraph

279 | 280 | Curly {brackets} insert arbitrary css style 281 | 282 | p{line-height:18px}. paragraph ->

paragraph

283 | 284 | h3{color:red}. header 3 ->

header 3

285 | 286 | Square [brackets] insert language attributes 287 | 288 | p[no]. paragraph ->

paragraph

289 | 290 | %[fr]phrase% -> phrase 291 | 292 | Usually Textile block element syntax requires a dot and space before the block 293 | begins, but since lists don't, they can be styled just using braces 294 | 295 | #{color:blue} one ->
    296 | # big
  1. one
  2. 297 | # list
  3. big
  4. 298 |
  5. list
  6. 299 |
300 | 301 | Using the span tag to style a phrase 302 | 303 | It goes like this, %{color:red}the fourth the fifth% 304 | -> It goes like this, the fourth the fifth 305 | 306 | Ordered list start and continuation: 307 | 308 | You can control the start attribute of an ordered list like so; 309 | 310 | #5 Item 5 311 | # Item 6 312 | 313 | You can resume numbering list items after some intervening anonymous block like so... 314 | 315 | #_ Item 7 316 | # Item 8 317 | */ 318 | 319 | namespace Netcarver\Textile; 320 | 321 | /** 322 | * Textile parser. 323 | * 324 | * The Parser class takes Textile input and converts it to well formatted HTML. 325 | * This is the library's main class, hosting the parsing functionality and 326 | * exposing a simple public interface for you to use. 327 | * 328 | * The most basic use case would involve initialising a instance of the class 329 | * and calling the Parser::parse() method: 330 | * 331 | * bc. $parser = new \Netcarver\Textile\Parser(); 332 | * echo $parser->parse('h1. Hello World!'); 333 | * 334 | * The above generates: 335 | * 336 | * bc.

Hello World!

337 | * 338 | * The functionality of the parser can be customized with the setters: 339 | * 340 | * bc. $parser = new \Netcarver\Textile\Parser(); 341 | * $parser->setImages(false)->parse('!no-image.jpg!'); 342 | * 343 | * The Parser class can also be extended to create pre-configured classes: 344 | * 345 | * bc.. namespace MyApp; 346 | * 347 | * use \Netcarver\Textile\Parser; 348 | * 349 | * class CommentParser extends Parser 350 | * { 351 | * protected function configure() 352 | * { 353 | * $this->setImages(false)->setRestricted(true)->setLite(true); 354 | * } 355 | * } 356 | * 357 | * p. Keep in mind that the classes' protected methods and properties should be 358 | * considered part of the private API and depending on them should be avoided. 359 | * Instead try to only use the public methods marked as being part of the 360 | * public API. 361 | * 362 | * @see Parser::__construct() 363 | * @see Parser::parse() 364 | */ 365 | 366 | class Parser 367 | { 368 | /** 369 | * HTML5 document type. 370 | * 371 | * @since 3.8.0 372 | */ 373 | const DOCTYPE_HTML5 = 'html5'; 374 | 375 | /** 376 | * XHTML document type. 377 | * 378 | * @since 3.8.0 379 | */ 380 | const DOCTYPE_XHTML = 'xhtml'; 381 | 382 | /** 383 | * Version number. 384 | * 385 | * @var string 386 | */ 387 | protected $ver = '4.1.4-dev'; 388 | 389 | /** 390 | * Regular expression snippets. 391 | * 392 | * @var string[] 393 | */ 394 | protected $regex_snippets = array(); 395 | 396 | /** 397 | * Pattern for horizontal align. 398 | * 399 | * @var string 400 | */ 401 | protected $hlgn = "(?:\<(?!>)|<>|>|<|(?|\<\>|\=|[()]+(?! ))"; 402 | 403 | /** 404 | * Pattern for vertical align. 405 | * 406 | * @var string 407 | */ 408 | protected $vlgn = "[\-^~]"; 409 | 410 | /** 411 | * Pattern for HTML classes and IDs. 412 | * 413 | * Does not allow classes/ids/languages/styles to span across 414 | * newlines if used in a dotall regular expression. 415 | * 416 | * @var string 417 | */ 418 | protected $clas = "(?:\([^)\n]+\))"; 419 | 420 | /** 421 | * Pattern for language attribute. 422 | * 423 | * @var string 424 | */ 425 | protected $lnge = "(?:\[[^]\n]+\])"; 426 | 427 | /** 428 | * Pattern for style attribute. 429 | * 430 | * @var string 431 | */ 432 | protected $styl = "(?:\{[^}\n]+\})"; 433 | 434 | /** 435 | * Regular expression pattern for column spans in tables. 436 | * 437 | * @var string 438 | */ 439 | protected $cspn = "(?:\\\\[0-9]+)"; 440 | 441 | /** 442 | * Regular expression for row spans in tables. 443 | * 444 | * @var string 445 | */ 446 | protected $rspn = "(?:\/[0-9]+)"; 447 | 448 | /** 449 | * Regular expression for horizontal or vertical alignment. 450 | * 451 | * @var string 452 | */ 453 | protected $a; 454 | 455 | /** 456 | * Regular expression for column or row spans in tables. 457 | * 458 | * @var string 459 | */ 460 | protected $s; 461 | 462 | /** 463 | * Pattern that matches a class, style, language and horizontal alignment attributes. 464 | * 465 | * @var string 466 | */ 467 | protected $c; 468 | 469 | /** 470 | * Pattern that matches class, style and language attributes. 471 | * 472 | * Allows all 16 possible permutations of class, style and language attributes. 473 | * No attribute, c, cl, cs, cls, csl, l, lc, ls, lcs, lsc, s, sc, sl, scl or slc. 474 | * 475 | * @var string 476 | */ 477 | protected $cls; 478 | 479 | /** 480 | * Whitelisted block tags. 481 | * 482 | * @var string[] 483 | */ 484 | protected $blocktag_whitelist = array(); 485 | 486 | /** 487 | * Whether raw blocks are enabled. 488 | * 489 | * @var bool 490 | * @since 3.7.0 491 | */ 492 | protected $rawBlocksEnabled = false; 493 | 494 | /** 495 | * An array of patterns used for matching phrasing tags. 496 | * 497 | * Phrasing tags, unline others, are wrapped in a paragraph even if they 498 | * already wrap the block. 499 | * 500 | * @var string[] 501 | * @since 3.7.0 502 | */ 503 | protected $phrasingContent = array( 504 | 'a', 505 | 'abbr', 506 | 'acronym', 507 | 'area', 508 | 'audio', 509 | 'b', 510 | 'bdo', 511 | 'br', 512 | 'button', 513 | 'canvas', 514 | 'cite', 515 | 'code', 516 | 'command', 517 | 'data', 518 | 'datalist', 519 | 'del', 520 | 'dfn', 521 | 'em', 522 | 'embed', 523 | 'i', 524 | 'iframe', 525 | 'img', 526 | 'input', 527 | 'ins', 528 | 'kbd', 529 | 'keygen', 530 | 'label', 531 | 'link', 532 | 'map', 533 | 'mark', 534 | 'math', 535 | 'meta', 536 | 'meter', 537 | 'noscript', 538 | 'object', 539 | 'output', 540 | 'progress', 541 | 'q', 542 | 'ruby', 543 | 'samp', 544 | 'script', 545 | 'select', 546 | 'small', 547 | 'span', 548 | 'strong', 549 | 'sub', 550 | 'sup', 551 | 'svg', 552 | 'textarea', 553 | 'time', 554 | 'var', 555 | 'video', 556 | 'wbr', 557 | ); 558 | 559 | /** 560 | * An array of patterns used to match divider tags. 561 | * 562 | * Blocks containing only self-closing divider tags are not wrapped in 563 | * paragraph tags. 564 | * 565 | * @var string[] 566 | * @since 3.7.0 567 | */ 568 | protected $dividerContent = array( 569 | 'br', 570 | 'hr', 571 | 'img', 572 | ); 573 | 574 | /** 575 | * An array of patterns used to match unwrappable block tags. 576 | * 577 | * Blocks containing any of these unwrappable tags will not be wrapped in 578 | * paragraphs. 579 | * 580 | * @var string[] 581 | * @since 3.7.0 582 | */ 583 | protected $blockContent = array( 584 | 'address', 585 | 'article', 586 | 'aside', 587 | 'blockquote', 588 | 'details', 589 | 'div', 590 | 'dl', 591 | 'fieldset', 592 | 'figure', 593 | 'footer', 594 | 'form', 595 | 'h1', 596 | 'h2', 597 | 'h3', 598 | 'h4', 599 | 'h5', 600 | 'h6', 601 | 'header', 602 | 'hgroup', 603 | 'main', 604 | 'menu', 605 | 'nav', 606 | 'ol', 607 | 'p', 608 | 'pre', 609 | 's', 610 | 'section', 611 | 'table', 612 | 'template', 613 | 'ul', 614 | ); 615 | 616 | /** 617 | * An array of built patterns. 618 | * 619 | * @var array|null 620 | * @since 3.7.0 621 | */ 622 | protected $patterns = null; 623 | 624 | /** 625 | * Whether block tags are enabled. 626 | * 627 | * @var bool 628 | * @since 3.6.0 629 | */ 630 | protected $blockTagsEnabled = true; 631 | 632 | /** 633 | * Whether lines are wrapped. 634 | * 635 | * @var bool 636 | * @since 3.6.0 637 | */ 638 | protected $lineWrapEnabled = true; 639 | 640 | /** 641 | * Whether aligning with class selectors is enabled. 642 | * 643 | * @var bool|null 644 | */ 645 | protected $isAlignClassesEnabled = null; 646 | 647 | /** 648 | * Pattern for punctation. 649 | * 650 | * @var string 651 | */ 652 | protected $pnct = '[\!"#\$%&\'()\*\+,\-\./:;<=>\?@\[\\\]\^_`{\|}\~]'; 653 | 654 | /** 655 | * Pattern for URL. 656 | * 657 | * @var string 658 | */ 659 | protected $urlch; 660 | 661 | /** 662 | * Matched marker symbols. 663 | * 664 | * @var string 665 | */ 666 | protected $syms = '¤§µ¶†‡•∗∴◊♠♣♥♦'; 667 | 668 | /** 669 | * HTML rel attribute used for links. 670 | * 671 | * @var string 672 | */ 673 | protected $rel = ''; 674 | 675 | /** 676 | * Array of footnotes. 677 | * 678 | * @var array 679 | */ 680 | protected $fn = array(); 681 | 682 | /** 683 | * Shelved content. 684 | * 685 | * Stores fragments of the source text that have been parsed 686 | * and require no more processing. 687 | * 688 | * @var array 689 | */ 690 | protected $shelf = array(); 691 | 692 | /** 693 | * Restricted mode. 694 | * 695 | * @var bool 696 | */ 697 | protected $restricted = false; 698 | 699 | /** 700 | * Disallow images. 701 | * 702 | * @var bool 703 | */ 704 | protected $noimage = false; 705 | 706 | /** 707 | * Lite mode. 708 | * 709 | * @var bool 710 | */ 711 | protected $lite = false; 712 | 713 | /** 714 | * Accepted link protocols. 715 | * 716 | * @var string[] 717 | */ 718 | protected $url_schemes = array(); 719 | 720 | /** 721 | * Restricted link protocols. 722 | * 723 | * @var string[] 724 | */ 725 | protected $restricted_url_schemes = array( 726 | 'http', 727 | 'https', 728 | 'ftp', 729 | 'mailto', 730 | ); 731 | 732 | /** 733 | * Unrestricted link protocols. 734 | * 735 | * @var string[] 736 | */ 737 | protected $unrestricted_url_schemes = array( 738 | 'http', 739 | 'https', 740 | 'ftp', 741 | 'mailto', 742 | 'file', 743 | 'tel', 744 | 'callto', 745 | 'sftp', 746 | ); 747 | 748 | /** 749 | * Span tags. 750 | * 751 | * @var array 752 | */ 753 | protected $span_tags = array( 754 | '*' => 'strong', 755 | '**' => 'b', 756 | '??' => 'cite', 757 | '_' => 'em', 758 | '__' => 'i', 759 | '-' => 'del', 760 | '%' => 'span', 761 | '+' => 'ins', 762 | '~' => 'sub', 763 | '^' => 'sup', 764 | ); 765 | 766 | /** 767 | * Span wrappers. 768 | * 769 | * @var array 770 | * @since 3.7.2 771 | */ 772 | protected $spanWrappers = array( 773 | '[' => ']', 774 | ); 775 | 776 | /** 777 | * Patterns for finding glyphs. 778 | * 779 | * An array of regex patterns used to find text features 780 | * such as apostrophes, fractions and em-dashes. Each 781 | * entry in this array must have a corresponding entry in 782 | * the $glyph_replace array. 783 | * 784 | * @var string[] 785 | * @see Parser::$glyph_replace 786 | */ 787 | protected $glyph_search = array(); 788 | 789 | /** 790 | * Glyph replacements. 791 | * 792 | * An array of replacements used to insert typographic glyphs 793 | * into the text. Each entry must have a corresponding entry in 794 | * the $glyph_search array and may refer to values captured in 795 | * the corresponding search regex. 796 | * 797 | * @var array 798 | * @see Parser::$glyph_search 799 | */ 800 | protected $glyph_replace = array(); 801 | 802 | /** 803 | * Indicates whether glyph substitution is required. 804 | * 805 | * Dirty flag, set by Parser::setSymbol(), indicating the parser needs to 806 | * rebuild the glyph substitutions before the next parse. 807 | * 808 | * @var bool 809 | * @see Parser::setSymbol() 810 | */ 811 | protected $rebuild_glyphs = true; 812 | 813 | /** 814 | * Relative image path. 815 | * 816 | * @var string 817 | * @deprecated in 3.7.0 818 | * @see Parser::$relImagePrefix 819 | * @see Parser::$relLinkPrefix 820 | */ 821 | protected $relativeImagePrefix; 822 | 823 | /** 824 | * Relative link prefix. 825 | * 826 | * @var string 827 | * @since 3.7.0 828 | */ 829 | protected $relLinkPrefix = ''; 830 | 831 | /** 832 | * Prefix applied to relative images. 833 | * 834 | * @var string 835 | * @since 3.7.0 836 | */ 837 | protected $relImagePrefix = ''; 838 | 839 | /** 840 | * Maximum nesting level for inline elements. 841 | * 842 | * @var int 843 | */ 844 | protected $max_span_depth = 5; 845 | 846 | /** 847 | * Server document root. 848 | * 849 | * @var string 850 | */ 851 | protected $doc_root; 852 | 853 | /** 854 | * Target document type. 855 | * 856 | * @var string 857 | */ 858 | protected $doctype; 859 | 860 | /** 861 | * An array of supported doctypes. 862 | * 863 | * @var string[] 864 | * @since 3.6.0 865 | */ 866 | protected $doctypes = array( 867 | self::DOCTYPE_XHTML, 868 | self::DOCTYPE_HTML5, 869 | ); 870 | 871 | /** 872 | * Substitution symbols. 873 | * 874 | * Basic symbols used in textile glyph replacements. To override these, call 875 | * setSymbol method before calling Parser::parse(). 876 | * 877 | * @var array 878 | * @see Parser::setSymbol() 879 | * @see Parser::parse() 880 | */ 881 | protected $symbols = array( 882 | 'quote_single_open' => '‘', 883 | 'quote_single_close' => '’', 884 | 'quote_double_open' => '“', 885 | 'quote_double_close' => '”', 886 | 'apostrophe' => '’', 887 | 'prime' => '′', 888 | 'prime_double' => '″', 889 | 'ellipsis' => '…', 890 | 'emdash' => '—', 891 | 'endash' => '–', 892 | 'dimension' => '×', 893 | 'trademark' => '™', 894 | 'registered' => '®', 895 | 'copyright' => '©', 896 | 'half' => '½', 897 | 'quarter' => '¼', 898 | 'threequarters' => '¾', 899 | 'degrees' => '°', 900 | 'plusminus' => '±', 901 | 'fn_ref_pattern' => '{marker}', 902 | 'fn_foot_pattern' => '{marker}', 903 | 'nl_ref_pattern' => '{marker}', 904 | 'caps' => '{content}', 905 | 'acronym' => null, 906 | ); 907 | 908 | /** 909 | * Dimensionless images flag. 910 | * 911 | * @var bool 912 | */ 913 | protected $dimensionless_images = false; 914 | 915 | /** 916 | * Directory separator. 917 | * 918 | * @var string 919 | */ 920 | protected $ds = '/'; 921 | 922 | /** 923 | * Whether mbstring extension is installed. 924 | * 925 | * @var bool 926 | */ 927 | protected $mb; 928 | 929 | /** 930 | * Multi-byte conversion map. 931 | * 932 | * @var int[] 933 | */ 934 | protected $cmap = array(0x0080, 0xffff, 0, 0xffff); 935 | 936 | /** 937 | * Stores note index. 938 | * 939 | * @var int 940 | */ 941 | protected $note_index = 1; 942 | 943 | /** 944 | * Stores unreferenced notes. 945 | * 946 | * @var array|int|string>> 947 | */ 948 | protected $unreferencedNotes = array(); 949 | 950 | /** 951 | * Stores note lists. 952 | * 953 | * @var array 954 | */ 955 | protected $notelist_cache = array(); 956 | 957 | /** 958 | * Stores notes. 959 | * 960 | * @var array|int|string>> 961 | */ 962 | protected $notes = array(); 963 | 964 | /** 965 | * Stores URL references. 966 | * 967 | * @var array 968 | */ 969 | protected $urlrefs = array(); 970 | 971 | /** 972 | * Stores span depth. 973 | * 974 | * @var int 975 | */ 976 | protected $span_depth = 0; 977 | 978 | /** 979 | * Unique ID used for reference tokens. 980 | * 981 | * @var string 982 | */ 983 | protected $uid; 984 | 985 | /** 986 | * Token reference index. 987 | * 988 | * @var int 989 | */ 990 | protected $refIndex = 1; 991 | 992 | /** 993 | * Stores references values. 994 | * 995 | * @var array 996 | */ 997 | protected $refCache = array(); 998 | 999 | /** 1000 | * Matched open and closed quotes. 1001 | * 1002 | * @var array 1003 | */ 1004 | protected $quotes = array( 1005 | '"' => '"', 1006 | "'" => "'", 1007 | '(' => ')', 1008 | '{' => '}', 1009 | '[' => ']', 1010 | '«' => '»', 1011 | '»' => '«', 1012 | '‹' => '›', 1013 | '›' => '‹', 1014 | '„' => '“', 1015 | '‚' => '‘', 1016 | '‘' => '’', 1017 | '”' => '“', 1018 | ); 1019 | 1020 | /** 1021 | * Regular expression that matches starting quotes. 1022 | * 1023 | * @var string 1024 | */ 1025 | protected $quote_starts; 1026 | 1027 | /** 1028 | * Ordered list starts. 1029 | * 1030 | * @var array 1031 | */ 1032 | protected $olstarts = array(); 1033 | 1034 | /** 1035 | * Link prefix. 1036 | * 1037 | * @var string 1038 | */ 1039 | protected $linkPrefix; 1040 | 1041 | /** 1042 | * Link index. 1043 | * 1044 | * @var int 1045 | */ 1046 | protected $linkIndex = 1; 1047 | 1048 | /** 1049 | * Constructor. 1050 | * 1051 | * The constructor allows setting options that affect the class instance as 1052 | * a whole, such as the output doctype. To instruct the parser to return 1053 | * HTML5 markup instead of XHTML, set $doctype argument to 'html5'. 1054 | * 1055 | * bc. use Netcarver\Textile\Parser; 1056 | * $parser = new Parser(Parser::DOCTYPE_HTML5); 1057 | * echo $parser->parse('HTML(HyperText Markup Language)"); 1058 | * 1059 | * @param string $doctype The output document type, either 'xhtml' or 'html5' 1060 | * @throws \InvalidArgumentException 1061 | * @see Parser::configure() 1062 | * @see Parser::parse() 1063 | * @see Parser::setDocumentType() 1064 | * @see Parser::DOCTYPE_HTML5 1065 | * @see Parser::DOCTYPE_XHTML 1066 | * @api 1067 | */ 1068 | public function __construct($doctype = 'xhtml') 1069 | { 1070 | $this->setDocumentType($doctype)->setRestricted(false); 1071 | $uid = uniqid((string) rand()); 1072 | $this->uid = 'textileRef:'.$uid.':'; 1073 | $this->linkPrefix = $uid.'-'; 1074 | $this->a = "(?:$this->hlgn|$this->vlgn)*"; 1075 | $this->s = "(?:$this->cspn|$this->rspn)*"; 1076 | $this->c = "(?:$this->clas|$this->styl|$this->lnge|$this->hlgn)*"; 1077 | 1078 | $this->cls = '(?:'. 1079 | "$this->clas(?:". 1080 | "$this->lnge(?:$this->styl)?|$this->styl(?:$this->lnge)?". 1081 | ')?|'. 1082 | "$this->lnge(?:". 1083 | "$this->clas(?:$this->styl)?|$this->styl(?:$this->clas)?". 1084 | ')?|'. 1085 | "$this->styl(?:". 1086 | "$this->clas(?:$this->lnge)?|$this->lnge(?:$this->clas)?". 1087 | ')?'. 1088 | ')?'; 1089 | 1090 | if ($this->isUnicodePcreSupported()) { 1091 | $this->regex_snippets = array( 1092 | 'acr' => '\p{Lu}\p{Nd}', 1093 | 'abr' => '\p{Lu}', 1094 | 'nab' => '\p{Ll}', 1095 | 'wrd' => '(?:\p{L}|\p{M}|\p{N}|\p{Pc})', 1096 | 'mod' => 'u', // Make sure to mark the unicode patterns as such, Some servers seem to need this. 1097 | 'cur' => '\p{Sc}', 1098 | 'digit' => '\p{N}', 1099 | 'space' => '(?:\p{Zs}|\h|\v)', 1100 | 'char' => '(?:[^\p{Zs}\h\v])', 1101 | ); 1102 | } else { 1103 | $this->regex_snippets = array( 1104 | 'acr' => 'A-Z0-9', 1105 | 'abr' => 'A-Z', 1106 | 'nab' => 'a-z', 1107 | 'wrd' => '\w', 1108 | 'mod' => '', 1109 | 'cur' => '', 1110 | 'digit' => '\d', 1111 | 'space' => '(?:\s|\h|\v)', 1112 | 'char' => '\S', 1113 | ); 1114 | } 1115 | 1116 | $this->urlch = '['.$this->regex_snippets['wrd'].'"$\-_.+!*\'(),";\/?:@=&%#{}|\\^~\[\]`]'; 1117 | $this->quote_starts = implode('|', array_map('preg_quote', array_keys($this->quotes))); 1118 | 1119 | if (defined('DIRECTORY_SEPARATOR')) { 1120 | $this->ds = DIRECTORY_SEPARATOR; 1121 | } 1122 | 1123 | if (php_sapi_name() === 'cli') { 1124 | if (($cwd = getcwd()) !== false) { 1125 | $this->setDocumentRootDirectory($cwd); 1126 | } 1127 | } elseif (!empty($_SERVER['DOCUMENT_ROOT'])) { 1128 | $this->setDocumentRootDirectory($_SERVER['DOCUMENT_ROOT']); 1129 | } elseif (!empty($_SERVER['PATH_TRANSLATED'])) { 1130 | $this->setDocumentRootDirectory($_SERVER['PATH_TRANSLATED']); 1131 | } 1132 | 1133 | $this->configure(); 1134 | } 1135 | 1136 | /** 1137 | * Configure the current parser. 1138 | * 1139 | * This method can be extended to create a pre-configured parser class. 1140 | * 1141 | * bc.. namespace MyApp; 1142 | * 1143 | * use Netcarver\Textile\Parser; 1144 | * 1145 | * class CommentParser extends Parser 1146 | * { 1147 | * protected function configure() 1148 | * { 1149 | * $this->setImages(false)->setRestricted(true)->setLite(true); 1150 | * } 1151 | * } 1152 | * 1153 | * @since 3.7.0 1154 | * @return void Return value is ignored 1155 | * @api 1156 | */ 1157 | protected function configure() 1158 | { 1159 | } 1160 | 1161 | /** 1162 | * Sets the output document type. 1163 | * 1164 | * bc. use Netcarver\Textile\Parser; 1165 | * $parser = new Parser(); 1166 | * echo $parser 1167 | * ->setDocumentType(Parser::DOCTYPE_HTML5) 1168 | * ->parse('HTML(HyperText Markup Language)"); 1169 | * 1170 | * @param string $doctype Either 'xhtml' or 'html5' 1171 | * @return Parser This instance 1172 | * @since 3.6.0 1173 | * @see Parser::getDocumentType() 1174 | * @see Parser::DOCTYPE_HTML5 1175 | * @see Parser::DOCTYPE_XHTML 1176 | * @api 1177 | */ 1178 | public function setDocumentType($doctype) 1179 | { 1180 | if (in_array($doctype, $this->doctypes, true)) { 1181 | if ($this->getDocumentType() !== $doctype) { 1182 | $this->doctype = $doctype; 1183 | $this->rebuild_glyphs = true; 1184 | } 1185 | 1186 | return $this; 1187 | } 1188 | 1189 | throw new \InvalidArgumentException('Invalid doctype given.'); 1190 | } 1191 | 1192 | /** 1193 | * Gets the current output document type. 1194 | * 1195 | * bc. $parser = new \Netcarver\Textile\Parser(); 1196 | * echo $parser->getDocumentType(); 1197 | * 1198 | * @return string The document type 1199 | * @since 3.6.0 1200 | * @see Parser::setDocumentType() 1201 | * @api 1202 | */ 1203 | public function getDocumentType() 1204 | { 1205 | return $this->doctype; 1206 | } 1207 | 1208 | /** 1209 | * Sets the document root directory path. 1210 | * 1211 | * This method sets the path that is used to resolve relative file paths 1212 | * within local filesystem. This is used to fetch image dimensions, for 1213 | * instance. 1214 | * 1215 | * bc. $parser = new \Netcarver\Textile\Parser(); 1216 | * $parser->setDocumentRootDirectory('/path/to/document/root/dir'); 1217 | * 1218 | * If not set, document root defaults to the current working directory if 1219 | * PHP-Textile is used via CLI. On server environment, DOCUMENT_ROOT or 1220 | * PATH_TRANSLATED server variable is used based on which ever is available. 1221 | * 1222 | * @param string $path The root path 1223 | * @return Parser This instance 1224 | * @since 3.6.0 1225 | * @see Parser::getDocumentRootDirectory() 1226 | * @api 1227 | */ 1228 | public function setDocumentRootDirectory($path) 1229 | { 1230 | $this->doc_root = rtrim($path, '\\/').$this->ds; 1231 | return $this; 1232 | } 1233 | 1234 | /** 1235 | * Gets the current document root directory path. 1236 | * 1237 | * bc. $parser = new \Netcarver\Textile\Parser(); 1238 | * echo $parser->getDocumentRootDirectory(); 1239 | * 1240 | * @return string Path to the document root directory 1241 | * @since 3.6.0 1242 | * @see Parser::setDocumentRootDirectory() 1243 | * @api 1244 | */ 1245 | public function getDocumentRootDirectory() 1246 | { 1247 | return $this->doc_root; 1248 | } 1249 | 1250 | /** 1251 | * Enables lite mode. 1252 | * 1253 | * If enabled, allowed tags are limited. Parser will prevent the use extra 1254 | * Textile formatting, accepting only paragraphs and blockquotes as valid 1255 | * block tags. 1256 | * 1257 | * bc. $parser = new \Netcarver\Textile\Parser(); 1258 | * $parser 1259 | * ->setLite(true) 1260 | * ->parse('h1. Headings are disabled too'); 1261 | * 1262 | * Generates: 1263 | * 1264 | * bc.

h1. Headings are disabled too

1265 | * 1266 | * This doesn't prevent unsafe input values. If you wish to parse untrusted 1267 | * user-given Textile input, also enable the restricted parser mode with 1268 | * Parser::setRestricted(). 1269 | * 1270 | * bc. $parser = new \Netcarver\Textile\Parser(); 1271 | * echo $parser 1272 | * ->setRestricted(true) 1273 | * ->setLite(true) 1274 | * ->parse('h1. Hello World!'); 1275 | * 1276 | * @param bool $lite TRUE to enable 1277 | * @return Parser This instance 1278 | * @since 3.6.0 1279 | * @see Parser::isLiteModeEnabled() 1280 | * @see Parser::setRestricted() 1281 | * @api 1282 | */ 1283 | public function setLite($lite) 1284 | { 1285 | $this->lite = (bool) $lite; 1286 | return $this; 1287 | } 1288 | 1289 | /** 1290 | * Gets the lite mode status. 1291 | * 1292 | * bc. $parser = new \Netcarver\Textile\Parser(); 1293 | * if ($parser->isLiteModeEnabled() === true) { 1294 | * echo 'Lite mode is enabled.'; 1295 | * } 1296 | * 1297 | * @return bool TRUE if enabled, FALSE otherwise 1298 | * @since 3.6.0 1299 | * @see Parser::setLite() 1300 | * @api 1301 | */ 1302 | public function isLiteModeEnabled() 1303 | { 1304 | return (bool) $this->lite; 1305 | } 1306 | 1307 | /** 1308 | * Disables and enables images. 1309 | * 1310 | * If disabled, image tags are not generated. This option is ideal for 1311 | * minimalist output such as text-only comments. 1312 | * 1313 | * bc. $parser = new \Netcarver\Textile\Parser(); 1314 | * echo $parser 1315 | * ->setImages(true) 1316 | * ->parse('!image.png!'); 1317 | * 1318 | * Generates: 1319 | * 1320 | * bc.

!image.png!

1321 | * 1322 | * @param bool $enabled TRUE to enable, FALSE to disable 1323 | * @return Parser This instance 1324 | * @since 3.6.0 1325 | * @see Parser::isImageTagEnabled() 1326 | * @api 1327 | */ 1328 | public function setImages($enabled) 1329 | { 1330 | $this->noimage = !$enabled; 1331 | return $this; 1332 | } 1333 | 1334 | /** 1335 | * Whether images are enabled. 1336 | * 1337 | * bc. $parser = new \Netcarver\Textile\Parser(); 1338 | * if ($parser->isImageTagEnabled() === true) { 1339 | * echo 'Images are enabled.'; 1340 | * } 1341 | * 1342 | * @return bool TRUE if enabled, FALSE otherwise 1343 | * @since 3.6.0 1344 | * @see Parser::setImages() 1345 | * @api 1346 | */ 1347 | public function isImageTagEnabled() 1348 | { 1349 | return !$this->noimage; 1350 | } 1351 | 1352 | /** 1353 | * Sets link relationship status value. 1354 | * 1355 | * This method sets the HTML relationship tokens that are applied to links 1356 | * generated by PHP-Textile. 1357 | * 1358 | * bc. $parser = new \Netcarver\Textile\Parser(); 1359 | * echo $parser 1360 | * ->setLinkRelationShip('nofollow') 1361 | * ->parse('"Link":http://example.com/'); 1362 | * 1363 | * Generates: 1364 | * 1365 | * bc.

Link

1366 | * 1367 | * @param string|array $relationship The HTML rel attribute value 1368 | * @return Parser This instance 1369 | * @since 3.6.0 1370 | * @see Parser::getLinkRelationShip() 1371 | * @api 1372 | */ 1373 | public function setLinkRelationShip($relationship) 1374 | { 1375 | $this->rel = (string) implode(' ', (array) $relationship); 1376 | return $this; 1377 | } 1378 | 1379 | /** 1380 | * Gets the link relationship status value. 1381 | * 1382 | * bc. $parser = new \Netcarver\Textile\Parser(); 1383 | * echo $parse 1384 | * ->setLinkRelationShip('nofollow') 1385 | * ->getLinkRelationShip(); 1386 | * 1387 | * The above outputs "nofollow". 1388 | * 1389 | * @return string The value 1390 | * @since 3.6.0 1391 | * @see Parser::setLinkRelationShip() 1392 | * @api 1393 | */ 1394 | public function getLinkRelationShip() 1395 | { 1396 | return $this->rel; 1397 | } 1398 | 1399 | /** 1400 | * Enables restricted parser mode. 1401 | * 1402 | * This option should be enabled when parsing untrusted user input, 1403 | * including comments or forum posts. When enabled, the parser escapes any 1404 | * raw HTML input, ignores unsafe attributes and links only whitelisted URL 1405 | * schemes. 1406 | * 1407 | * For instance the following malicious input: 1408 | * 1409 | * bc. $parser = new \Netcarver\Textile\Parser(); 1410 | * echo $parser 1411 | * ->setRestricted(true) 1412 | * ->parse('Innocent _looking_ "link":javacript:window.alert().'); 1413 | * 1414 | * Returns safe, sanitized HTML with valid Textile input still parsed: 1415 | * 1416 | * bc.

Innocent looking “link”:javacript:window.alert().

1417 | * 1418 | * If left disabled, the parser allows users to mix raw HTML and Textile. 1419 | * Using the parser in non-restricted on untrusted input, like comments 1420 | * and forum posts, will lead to XSS issues, as users will be able to use 1421 | * any HTML code, JavaScript links and Textile attributes in their input. 1422 | * 1423 | * @param bool $enabled TRUE to enable, FALSE to disable 1424 | * @return Parser This instance 1425 | * @since 3.6.0 1426 | * @see Parser::isRestrictedModeEnabled() 1427 | * @api 1428 | */ 1429 | public function setRestricted($enabled) 1430 | { 1431 | if ($enabled) { 1432 | $this->url_schemes = $this->restricted_url_schemes; 1433 | $this->restricted = true; 1434 | } else { 1435 | $this->url_schemes = $this->unrestricted_url_schemes; 1436 | $this->restricted = false; 1437 | } 1438 | 1439 | return $this; 1440 | } 1441 | 1442 | /** 1443 | * Whether restricted parser mode is enabled. 1444 | * 1445 | * bc. $parser = new \Netcarver\Textile\Parser(); 1446 | * if ($parser->isRestrictedModeEnabled() === true) { 1447 | * echo 'PHP-Textile is in restricted mode.'; 1448 | * } 1449 | * 1450 | * @return bool TRUE if enabled, FALSE otherwise 1451 | * @since 3.6.0 1452 | * @see Parser::setRestricted() 1453 | * @api 1454 | */ 1455 | public function isRestrictedModeEnabled() 1456 | { 1457 | return (bool) $this->restricted; 1458 | } 1459 | 1460 | /** 1461 | * Enables and disables raw blocks. 1462 | * 1463 | * When raw blocks are enabled, any paragraph blocks wrapped in a tag 1464 | * not matching Parser::$blockContent or Parser::$phrasingContent will not 1465 | * be parsed, and instead is left as is. 1466 | * 1467 | * bc. $parser = new \Netcarver\Textile\Parser(); 1468 | * echo $parser 1469 | * ->setRawBlocks(true) 1470 | * ->parse('
A *raw* block.
'); 1471 | * 1472 | * The above generates: 1473 | * 1474 | * bc.
A *raw* block.
1475 | * 1476 | * @param bool $enabled TRUE to enable, FALSE to disable 1477 | * @return Parser This instance 1478 | * @since 3.7.0 1479 | * @see Parser::isRawBlocksEnabled() 1480 | * @see Parser::isRawBlock() 1481 | * @api 1482 | */ 1483 | public function setRawBlocks($enabled) 1484 | { 1485 | $this->rawBlocksEnabled = (bool) $enabled; 1486 | return $this; 1487 | } 1488 | 1489 | /** 1490 | * Whether raw blocks are enabled. 1491 | * 1492 | * bc. $parser = new \Netcarver\Textile\Parser(); 1493 | * if ($parser->isRawBlocksEnabled() === true) { 1494 | * echo 'Raw blocks are enabled'; 1495 | * } 1496 | * 1497 | * @return bool TRUE if enabled, FALSE otherwise 1498 | * @since 3.7.0 1499 | * @see Parser::setRawBlocks() 1500 | * @api 1501 | */ 1502 | public function isRawBlocksEnabled() 1503 | { 1504 | return (bool) $this->rawBlocksEnabled; 1505 | } 1506 | 1507 | /** 1508 | * Sets class alignment mode independent of the document type. 1509 | * 1510 | * In HTML5 document type, img elements are generated with align-left, 1511 | * align-center and align-right class selectors rather than align 1512 | * attribute being added to the image. 1513 | * 1514 | * With this option you can enable that functionality in XHTML document type mode too. 1515 | * 1516 | * bc. $parser = new \Netcarver\Textile\Parser(); 1517 | * $parser 1518 | * ->setAlignClasses(true) 1519 | * ->parse(!

1524 | * 1525 | * @param bool $enabled TRUE to enable, FALSE to disable 1526 | * @return Parser This instance 1527 | * @since 3.8.0 1528 | * @api 1529 | */ 1530 | public function setAlignClasses($enabled) 1531 | { 1532 | $this->isAlignClassesEnabled = (bool) $enabled; 1533 | return $this; 1534 | } 1535 | 1536 | /** 1537 | * Whether class alignment mode is enabled. 1538 | * 1539 | * bc. $parser = new \Netcarver\Textile\Parser(); 1540 | * if ($parser->isAlignClassesEnabled() === true) { 1541 | * echo 'Images are aligned with class instead of align attribute'; 1542 | * } 1543 | * 1544 | * @return bool TRUE if enabled, FALSE otherwise 1545 | * @since 3.8.0 1546 | * @see Parser::setAlignClasses() 1547 | * @api 1548 | */ 1549 | public function isAlignClassesEnabled() 1550 | { 1551 | if ($this->isAlignClassesEnabled === null 1552 | && $this->getDocumentType() === self::DOCTYPE_HTML5 1553 | ) { 1554 | return true; 1555 | } 1556 | 1557 | return (bool) $this->isAlignClassesEnabled; 1558 | } 1559 | 1560 | /** 1561 | * Enables and disables block-level tags and formatting features. 1562 | * 1563 | * When disabled, block-level tags aren't rendered. This allows PHP-Textile 1564 | * to operate on a single line of text, rather than blocks of text and does 1565 | * not wrap the output in paragraph tags. 1566 | * 1567 | * bc. $parser = new \Netcarving\Textile\Parser(); 1568 | * echo $parser 1569 | * ->setBlockTags(false) 1570 | * ->parse('h1. Hello *strong* world!'); 1571 | * 1572 | * The above generates: 1573 | * 1574 | * bc. h1. Hello strong world! 1575 | * 1576 | * @param bool $enabled TRUE to enable, FALSE to disable 1577 | * @return Parser This instance 1578 | * @since 3.6.0 1579 | * @see Parser::isBlockTagEnabled() 1580 | * @api 1581 | */ 1582 | public function setBlockTags($enabled) 1583 | { 1584 | $this->blockTagsEnabled = (bool) $enabled; 1585 | return $this; 1586 | } 1587 | 1588 | /** 1589 | * Whether block-level tags are enabled and parsed. 1590 | * 1591 | * bc. $parser = new \Netcarving\Textile\Parser(); 1592 | * if ($parser->isBlockTagAllowed() === true) { 1593 | * echo 'Block tags are enabled.'; 1594 | * } 1595 | * 1596 | * @return bool TRUE if enabled, FALSE otherwise 1597 | * @since 3.6.0 1598 | * @see Parser::setBlockTags() 1599 | * @api 1600 | */ 1601 | public function isBlockTagEnabled() 1602 | { 1603 | return (bool) $this->blockTagsEnabled; 1604 | } 1605 | 1606 | /** 1607 | * Enables and disables line-wrapping. 1608 | * 1609 | * If enabled, line-breaks are replaced by target document's break tag. If 1610 | * disabled, input document's line-breaks are ignored. This setting can be 1611 | * used if the the input document's lines are pre-wrapped. For instance, 1612 | * in case the input is from CLI content, or source code documentation. 1613 | * 1614 | * bc. $parser = new \Netcarving\Textile\Parser(); 1615 | * echo $parser 1616 | * ->setLineWrap(false) 1617 | * ->parse("Hello\nworld!"); 1618 | * 1619 | * The above generates: 1620 | * 1621 | * bc.

Hello world!

1622 | * 1623 | * @param bool $enabled TRUE to enable, FALSE to disable 1624 | * @return Parser This instance 1625 | * @since 3.6.0 1626 | * @see Parser::isLineWrapEnabled() 1627 | * @api 1628 | */ 1629 | public function setLineWrap($enabled) 1630 | { 1631 | $this->lineWrapEnabled = (bool) $enabled; 1632 | return $this; 1633 | } 1634 | 1635 | /** 1636 | * Whether line-wrapping is enabled. 1637 | * 1638 | * bc. $parser = new \Netcarving\Textile\Parser(); 1639 | * if ($parser->isLineWrapEnabled() === true) { 1640 | * echo 'Line-wrapping is enabled.'; 1641 | * } 1642 | * 1643 | * @return bool TRUE if enabled, FALSE otherwise 1644 | * @see Parser::setLineWrap() 1645 | * @since 3.6.0 1646 | * @api 1647 | */ 1648 | public function isLineWrapEnabled() 1649 | { 1650 | return (bool) $this->lineWrapEnabled; 1651 | } 1652 | 1653 | /** 1654 | * Sets a substitution symbol. 1655 | * 1656 | * This method lets you to redefine a substitution symbol. The following 1657 | * sets the 'half' glyph: 1658 | * 1659 | * bc. $parser = new \Netcarver\Textile\Parser(); 1660 | * echo $parser 1661 | * ->setSymbol('half', '1⁄2') 1662 | * ->parse('Hello [1/2] World!'); 1663 | * 1664 | * Generates: 1665 | * 1666 | * bc.

Hello 1&#⁄2 World!

1667 | * 1668 | * Symbol can be set to FALSE to disable it: 1669 | * 1670 | * bc. $parser = new \Netcarver\Textile\Parser(); 1671 | * $parser->setSymbol('dimension', false); 1672 | * 1673 | * See Parser::getSymbol() to find out all available symbols. 1674 | * 1675 | * @param string $name Name of the symbol to assign a new value to 1676 | * @param string|bool $value New value for the symbol, or FALSE to disable 1677 | * @return Parser This instance 1678 | * @see Parser::getSymbol() 1679 | * @api 1680 | */ 1681 | public function setSymbol($name, $value) 1682 | { 1683 | if ($value !== false) { 1684 | $value = (string) $value; 1685 | } 1686 | 1687 | $this->symbols[(string) $name] = $value; 1688 | $this->rebuild_glyphs = true; 1689 | return $this; 1690 | } 1691 | 1692 | /** 1693 | * Gets a symbol definitions. 1694 | * 1695 | * This method gets a symbol definition by name, or the full symbol table 1696 | * as an array. 1697 | * 1698 | * bc. $parser = new \Netcarver\Textile\Parser(); 1699 | * echo $parser->getSymbol('dimension'); 1700 | * 1701 | * To get all available symbol definitions: 1702 | * 1703 | * bc. $parser = new \Netcarver\Textile\Parser(); 1704 | * print_r($parser->getSymbol()); 1705 | * 1706 | * @param string|null $name The name of the symbol, or NULL if requesting the symbol table 1707 | * @return array|string|bool The symbol table or the requested symbol 1708 | * @throws \InvalidArgumentException 1709 | * @see Parser::setSymbol() 1710 | * @api 1711 | */ 1712 | public function getSymbol($name = null) 1713 | { 1714 | if ($name !== null) { 1715 | if (isset($this->symbols[$name])) { 1716 | return $this->symbols[$name]; 1717 | } 1718 | 1719 | throw new \InvalidArgumentException('The specified name does not match any symbols.'); 1720 | } 1721 | 1722 | return $this->symbols; 1723 | } 1724 | 1725 | /** 1726 | * Sets base relative image prefix. 1727 | * 1728 | * The given string is used to prefix relative image paths, usually an 1729 | * absolute HTTP address pointing a the site's image, or upload, directory. 1730 | * PHP-Textile to convert relative paths to absolute, or prefixed paths. 1731 | * 1732 | * bc. $parser = new \Netcarver\Textile\Parser(); 1733 | * $parser->setImagePrefix('https://static.example.com/images/'); 1734 | * 1735 | * @param string $prefix The prefix 1736 | * @return Parser This instance 1737 | * @since 3.7.0 1738 | * @see Parser::getImagePrefix() 1739 | * @api 1740 | */ 1741 | public function setImagePrefix($prefix) 1742 | { 1743 | $this->relImagePrefix = (string) $prefix; 1744 | return $this; 1745 | } 1746 | 1747 | /** 1748 | * Gets base relative image prefix. 1749 | * 1750 | * bc. $parser = new \Netcarver\Textile\Parser(); 1751 | * echo $parser->getImagePrefix(); 1752 | * 1753 | * @return string The prefix 1754 | * @since 3.7.0 1755 | * @see Parser::setImagePrefix() 1756 | * @api 1757 | */ 1758 | public function getImagePrefix() 1759 | { 1760 | return (string) $this->relImagePrefix; 1761 | } 1762 | 1763 | /** 1764 | * Sets base relative link prefix. 1765 | * 1766 | * The given string is used to prefix relative link paths. This allows 1767 | * PHP-Textile convert relative paths to absolute, or prefixed, links. 1768 | * 1769 | * bc. $parser = new \Netcarver\Textile\Parser(); 1770 | * $parser->setLinkPrefix('https://example.com/'); 1771 | * 1772 | * @param string $prefix The prefix 1773 | * @return Parser This instance 1774 | * @since 3.7.0 1775 | * @see Parser::getLinkPrefix() 1776 | * @api 1777 | */ 1778 | public function setLinkPrefix($prefix) 1779 | { 1780 | $this->relLinkPrefix = (string) $prefix; 1781 | return $this; 1782 | } 1783 | 1784 | /** 1785 | * Gets base relative link prefix. 1786 | * 1787 | * bc. $parser = new \Netcarver\Textile\Parser(); 1788 | * echo $parser->getLinkPrefix(); 1789 | * 1790 | * @return string The prefix 1791 | * @since 3.7.0 1792 | * @see Parser::setLinkPrefix() 1793 | * @api 1794 | */ 1795 | public function getLinkPrefix() 1796 | { 1797 | return (string) $this->relLinkPrefix; 1798 | } 1799 | 1800 | /** 1801 | * Sets base relative image and link directory path. 1802 | * 1803 | * This is used when Textile is supplied with a relative image or link path. 1804 | * Allows client systems to have PHP-Textile convert relative paths to 1805 | * absolute or prefixed paths. This method is used to set that base path, 1806 | * usually an absolute HTTP address pointing to a directory. Note that 1807 | * despite its name it applies to both links and images. 1808 | * 1809 | * bc. $parser = new \Netcarver\Textile\Parser(); 1810 | * $parser->setRelativeImagePrefix('https://example.com/'); 1811 | * 1812 | * @param string $prefix The string to prefix all relative image paths with 1813 | * @return Parser This instance 1814 | * @deprecated in 3.7.0 1815 | * @see Parser::setImagePrefix 1816 | * @see Parser::setLinkPrefix 1817 | * @api 1818 | */ 1819 | public function setRelativeImagePrefix($prefix = '') 1820 | { 1821 | trigger_error( 1822 | 'Parser::setRelativeImagePrefix() is deprecated.'. 1823 | 'Use Parser::setImagePrefix() and Parser::setLinkPrefix() instead.', 1824 | E_USER_DEPRECATED 1825 | ); 1826 | 1827 | $this->relativeImagePrefix = $prefix; 1828 | return $this; 1829 | } 1830 | 1831 | /** 1832 | * Enables dimensionless images. 1833 | * 1834 | * If enabled, image width and height attributes will not be included in 1835 | * rendered image tags. Normally, PHP-Textile will add width and height 1836 | * to images linked with a local relative path, as long as the image file 1837 | * can be accessed. 1838 | * 1839 | * bc. $parser = new \Netcarver\Textile\Parser(); 1840 | * echo $parser 1841 | * ->setDimensionlessImages(true) 1842 | * ->parse('!image.jpg!'); 1843 | * 1844 | * @param bool $dimensionless TRUE to disable image dimensions, FALSE to enable 1845 | * @return Parser This instance 1846 | * @see Parser::getDimensionlessImages() 1847 | * @api 1848 | */ 1849 | public function setDimensionlessImages($dimensionless = true) 1850 | { 1851 | $this->dimensionless_images = (bool) $dimensionless; 1852 | return $this; 1853 | } 1854 | 1855 | /** 1856 | * Whether dimensionless images are enabled. 1857 | * 1858 | * bc. $parser = new \Netcarver\Textile\Parser(); 1859 | * if ($parser->getDimensionlessImages() === true) { 1860 | * echo 'Images do not get dimensions.'; 1861 | * } 1862 | * 1863 | * @return bool TRUE if images will not get dimensions, FALSE otherwise 1864 | * @see Parser::setDimensionlessImages() 1865 | * @api 1866 | */ 1867 | public function getDimensionlessImages() 1868 | { 1869 | return (bool) $this->dimensionless_images; 1870 | } 1871 | 1872 | /** 1873 | * Gets PHP-Textile version number. 1874 | * 1875 | * bc. $parser = new \Netcarver\Textile\Parser(); 1876 | * echo $parser->getVersion(); 1877 | * 1878 | * @return string Version number 1879 | * @api 1880 | */ 1881 | public function getVersion() 1882 | { 1883 | return $this->ver; 1884 | } 1885 | 1886 | /** 1887 | * Encodes the given text. 1888 | * 1889 | * bc. $parser = new \Netcarver\Textile\Parser(); 1890 | * $parser->textileEncode('Some content to encode.'); 1891 | * 1892 | * @param string $text The text to be encoded 1893 | * @return string The encoded text 1894 | * @api 1895 | */ 1896 | public function textileEncode($text) 1897 | { 1898 | return (string)preg_replace('/&(?!(?:[a-z][a-z\d]*|#(?:\d+|x[a-f\d]+));)/i', '&', $text); 1899 | } 1900 | 1901 | /** 1902 | * Parses the given Textile input according to the previously set options. 1903 | * 1904 | * The parser's features can be changed by using the various public setter 1905 | * methods this class has. The most basic use case is: 1906 | * 1907 | * bc. $parser = new \Netcarver\Textile\Parser(); 1908 | * echo $parser->parse('h1. Hello World!'); 1909 | * 1910 | * The above parses trusted input in full-feature mode, generating: 1911 | * 1912 | * bc.

Hello World!

1913 | * 1914 | * Additionally the parser can be run in safe, restricted mode using the 1915 | * Parser::setRestricted() method. 1916 | * 1917 | * bc. $parser = new \Netcarver\Textile\Parser(); 1918 | * echo $parser 1919 | * ->setRestricted(true) 1920 | * ->parse('h1. Hello World!'); 1921 | * 1922 | * This enables restricted mode and allows safe parsing of untrusted input. 1923 | * PHP-Textile will disable unsafe attributes, links and escapes any raw 1924 | * HTML input. This option should be enabled when parsing untrusted user 1925 | * input. 1926 | * 1927 | * If restricted mode is disabled, the parser allows users to mix raw HTML 1928 | * and Textile. 1929 | * 1930 | * @param string $text The Textile input to parse 1931 | * @return string Parsed Textile input 1932 | * @since 3.6.0 1933 | * @api 1934 | */ 1935 | public function parse($text) 1936 | { 1937 | $this->prepare(); 1938 | $text = (string) $text; 1939 | 1940 | if ($this->isRestrictedModeEnabled()) { 1941 | // Escape any raw HTML. 1942 | $text = $this->encodeHTML($text, false); 1943 | } 1944 | 1945 | $text = $this->cleanWhiteSpace($text); 1946 | $text = $this->cleanUniqueTokens($text); 1947 | 1948 | if ($this->isBlockTagEnabled()) { 1949 | if ($this->isLiteModeEnabled()) { 1950 | $this->blocktag_whitelist = array('bq', 'p'); 1951 | $text = $this->blocks($text."\n\n"); 1952 | } else { 1953 | $this->blocktag_whitelist = array( 1954 | 'bq', 1955 | 'p', 1956 | 'bc', 1957 | 'notextile', 1958 | 'pre', 1959 | 'h[1-6]', 1960 | 'fn'.$this->regex_snippets['digit'].'+', 1961 | '###', 1962 | ); 1963 | $text = $this->blocks($text); 1964 | $text = $this->placeNoteLists($text); 1965 | } 1966 | } else { 1967 | $text .= "\n\n"; 1968 | 1969 | // Treat quoted quote as a special glyph. 1970 | $text = $this->glyphQuotedQuote($text); 1971 | 1972 | // Inline markup (em, strong, sup, sub, del etc). 1973 | $text = $this->spans($text); 1974 | 1975 | // Generate links. 1976 | $text = $this->links($text); 1977 | 1978 | // Glyph level substitutions (mainly typographic -- " & ' => curly quotes, -- => em-dash etc. 1979 | $text = $this->glyphs($text); 1980 | } 1981 | 1982 | $text = $this->retrieve($text); 1983 | $text = $this->replaceGlyphs($text); 1984 | $text = $this->retrieveTags($text); 1985 | $text = $this->retrieveURLs($text); 1986 | 1987 | // Replace shelved instances that were inside tag and link attributes. 1988 | $text = $this->retrieve($text); 1989 | 1990 | $text = str_replace($this->getLineBreak(), $this->getLineBreak()."\n", $text); 1991 | 1992 | return $text; 1993 | } 1994 | 1995 | /** 1996 | * Parses the given Textile input in un-restricted mode. 1997 | * 1998 | * This method is deprecated, use Parser::parse() method instead. 1999 | * This method is equivalent of: 2000 | * 2001 | * bc. $parser = new \Netcarver\Textile\Parser(); 2002 | * echo $parser->parse('h1. Hello World!'); 2003 | * 2004 | * Additional arguments can be passed with setter methods: 2005 | * 2006 | * bc. $parser = new \Netcarver\Textile\Parser(); 2007 | * echo $parser 2008 | * ->setLite(true) 2009 | * ->setImages(true) 2010 | * ->setLinkRelationShip('nofollow') 2011 | * ->parse('h1. Hello World!'); 2012 | * 2013 | * @param string $text The Textile input to parse 2014 | * @param bool $lite Switch to lite mode 2015 | * @param bool $encode Encode input and return 2016 | * @param bool $noimage Disables images 2017 | * @param bool $strict This argument is ignored 2018 | * @param string $rel Relationship attribute applied to generated links 2019 | * @return string Parsed $text 2020 | * @see Parser::parse() 2021 | * @deprecated in 3.6.0 2022 | * @api 2023 | */ 2024 | public function textileThis($text, $lite = false, $encode = false, $noimage = false, $strict = false, $rel = '') 2025 | { 2026 | if ($encode) { 2027 | trigger_error( 2028 | '$encode argument is deprecated. Use Parser::textileEncode() instead.', 2029 | \E_USER_DEPRECATED 2030 | ); 2031 | 2032 | return $this->textileEncode($text); 2033 | } 2034 | 2035 | trigger_error( 2036 | 'Parser::textileThis() is deprecated. Use Parser::parse() instead.', 2037 | \E_USER_DEPRECATED 2038 | ); 2039 | 2040 | return $this 2041 | ->setRestricted(false) 2042 | ->setLite($lite) 2043 | ->setBlockTags(true) 2044 | ->setImages(!$noimage) 2045 | ->setLinkRelationShip($rel) 2046 | ->parse($text); 2047 | } 2048 | 2049 | /** 2050 | * Parses the given Textile input in restricted mode. 2051 | * 2052 | * This method is deprecated, use Parser::parse() method with 2053 | * Parser::setRestricted() and Parser::setLite() enabled, and 2054 | * Parser::setImages() disabled. 2055 | * 2056 | * This method's defaults are identical to: 2057 | * 2058 | * bc. $parser = new \Netcarver\Textile\Parser(); 2059 | * echo $parser 2060 | * ->setRestricted(true) 2061 | * ->setLite(true) 2062 | * ->setImages(false) 2063 | * ->setLinkRelationShip('nofollow') 2064 | * ->parse('h1. Hello World!'); 2065 | * 2066 | * As in the above, restricted mode should be used when parsing any 2067 | * untrusted user input, including comments or forum posts. 2068 | * 2069 | * @param string $text The Textile input to parse 2070 | * @param bool $lite Controls lite mode, allowing extra formatting 2071 | * @param bool $noimage Allow images 2072 | * @param string $rel Relationship attribute applied to generated links 2073 | * @return string Parsed input 2074 | * @see Parser::setRestricted() 2075 | * @see Parser::setLite() 2076 | * @see Parser::setImages() 2077 | * @see Parser::setLinkRelationShip() 2078 | * @see Parser::parse() 2079 | * @deprecated in 3.6.0 2080 | * @api 2081 | */ 2082 | public function textileRestricted($text, $lite = true, $noimage = true, $rel = 'nofollow') 2083 | { 2084 | trigger_error( 2085 | 'Parser::textileRestricted() is deprecated. Use Parser::parse() with Parser::setRestricted() instead.', 2086 | E_USER_DEPRECATED 2087 | ); 2088 | 2089 | return $this 2090 | ->setRestricted(true) 2091 | ->setLite($lite) 2092 | ->setBlockTags(true) 2093 | ->setImages(!$noimage) 2094 | ->setLinkRelationShip($rel) 2095 | ->parse($text); 2096 | } 2097 | 2098 | /** 2099 | * Parses Textile syntax. 2100 | * 2101 | * This method performs common parse actions. 2102 | * 2103 | * @param string $text The input to parse 2104 | * @param bool $lite Enables lite mode 2105 | * @return string Parsed input 2106 | * @deprecated in 3.6.0 2107 | */ 2108 | protected function textileCommon($text, $lite) 2109 | { 2110 | trigger_error('Parser::textileCommon() is deprecated.', E_USER_DEPRECATED); 2111 | return $this->setLite($lite)->parse($text); 2112 | } 2113 | 2114 | /** 2115 | * Output line break according to document type. 2116 | * 2117 | * @return string The break tag 2118 | * @since 4.0.0 2119 | * @see Parser::getDocumentType() 2120 | */ 2121 | protected function getLineBreak() 2122 | { 2123 | return ($this->getDocumentType() === self::DOCTYPE_HTML5) ? '
' : '
'; 2124 | } 2125 | 2126 | /** 2127 | * Prepares the glyph patterns from the symbol table. 2128 | * 2129 | * @return void 2130 | * @see Parser::setSymbol() 2131 | * @see Parser::getSymbol() 2132 | */ 2133 | protected function prepGlyphs() 2134 | { 2135 | if ($this->rebuild_glyphs === false) { 2136 | return; 2137 | } 2138 | 2139 | $pnc = '[[:punct:]]'; 2140 | $cur = ''; 2141 | 2142 | if ($this->regex_snippets['cur']) { 2143 | $cur = '(?:['.$this->regex_snippets['cur'].']'.$this->regex_snippets['space'].'*)?'; 2144 | } 2145 | 2146 | $this->glyph_search = array(); 2147 | $this->glyph_replace = array(); 2148 | 2149 | // Dimension sign 2150 | if ($this->symbols['dimension'] !== false) { 2151 | $this->glyph_search[] = '/(?<=\b|x)([0-9]++[\])]?[\'"]? ?)[x]( ?[\[(]?)(?=[+-]?'.$cur.'[0-9]*\.?[0-9]++)/i'. 2152 | $this->regex_snippets['mod']; 2153 | $this->glyph_replace[] = '$1'.$this->symbols['dimension'].'$2'; 2154 | } 2155 | 2156 | // Apostrophe 2157 | if ($this->symbols['apostrophe'] !== false) { 2158 | $this->glyph_search[] = '/('.$this->regex_snippets['wrd'].'|\))\''. 2159 | '('.$this->regex_snippets['wrd'].')/'.$this->regex_snippets['mod']; 2160 | $this->glyph_replace[] = '$1'.$this->symbols['apostrophe'].'$2'; 2161 | 2162 | // Back in '88/the '90s but not in his '90s', '1', '1.' '10m' or '5.png' 2163 | $this->glyph_search[] = '/('.$this->regex_snippets['space'].')\''. 2164 | '(\d+'.$this->regex_snippets['wrd'].'?)\b(?![.]?['.$this->regex_snippets['wrd'].']*?\')/'. 2165 | $this->regex_snippets['mod']; 2166 | $this->glyph_replace[] = '$1'.$this->symbols['apostrophe'].'$2'; 2167 | } 2168 | 2169 | // Single open following open bracket 2170 | if ($this->symbols['quote_single_open'] !== false) { 2171 | $this->glyph_search[] = "/([([{])'(?=\S)/".$this->regex_snippets['mod']; 2172 | $this->glyph_replace[] = '$1'.$this->symbols['quote_single_open']; 2173 | } 2174 | 2175 | // Single closing 2176 | if ($this->symbols['quote_single_close'] !== false) { 2177 | $this->glyph_search[] = '/(\S)\'(?='.$this->regex_snippets['space'].'|'.$pnc.'|<|$)/'. 2178 | $this->regex_snippets['mod']; 2179 | $this->glyph_replace[] = '$1'.$this->symbols['quote_single_close']; 2180 | } 2181 | 2182 | // Default single opening 2183 | if ($this->symbols['quote_single_open'] !== false) { 2184 | $this->glyph_search[] = "/'/"; 2185 | $this->glyph_replace[] = $this->symbols['quote_single_open']; 2186 | } 2187 | 2188 | // Double open following an open bracket. Allows things like Hello ["(Mum) & dad"] 2189 | if ($this->symbols['quote_double_open'] !== false) { 2190 | $this->glyph_search[] = '/([([{])"(?=\S)/'.$this->regex_snippets['mod']; 2191 | $this->glyph_replace[] = '$1'.$this->symbols['quote_double_open']; 2192 | } 2193 | 2194 | // Double closing 2195 | if ($this->symbols['quote_double_close'] !== false) { 2196 | $this->glyph_search[] = '/(\S)"(?='.$this->regex_snippets['space'].'|'.$pnc.'|<|$)/'. 2197 | $this->regex_snippets['mod']; 2198 | $this->glyph_replace[] = '$1'.$this->symbols['quote_double_close']; 2199 | } 2200 | 2201 | // Default double opening 2202 | if ($this->symbols['quote_double_open'] !== false) { 2203 | $this->glyph_search[] = '/"/'; 2204 | $this->glyph_replace[] = $this->symbols['quote_double_open']; 2205 | } 2206 | 2207 | if ($this->symbols['acronym'] === null) { 2208 | if ($this->getDocumentType() === 'html5') { 2209 | $acronym = '{content}'; 2210 | } else { 2211 | $acronym = '{content}'; 2212 | } 2213 | } else { 2214 | $acronym = $this->symbols['acronym']; 2215 | } 2216 | 2217 | // 3+ uppercase acronym 2218 | if ($acronym !== false) { 2219 | $this->glyph_search[] = '/\b(['.$this->regex_snippets['abr'].']['. 2220 | $this->regex_snippets['acr'].']{2,})\b(?:[(]([^)]*)[)])/'.$this->regex_snippets['mod']; 2221 | $this->glyph_replace[] = $this->replaceMarkers((string) $acronym, array( 2222 | 'title' => '$2', 2223 | 'content' => '$1', 2224 | )); 2225 | } 2226 | 2227 | // 3+ uppercase 2228 | if ($this->symbols['caps'] !== false) { 2229 | $this->glyph_search[] = '/('.$this->regex_snippets['space'].'|^|[>(;-])'. 2230 | '(['.$this->regex_snippets['abr'].']{3,})'. 2231 | '(['.$this->regex_snippets['nab'].']*)(?='. 2232 | $this->regex_snippets['space'].'|'.$pnc.'|<|$)'. 2233 | '(?=[^">]*?(<|$))/'.$this->regex_snippets['mod']; 2234 | $this->glyph_replace[] = $this->replaceMarkers('$1'.$this->symbols['caps'].'$3', array( 2235 | 'content' => $this->uid.':glyph:$2', 2236 | )); 2237 | } 2238 | 2239 | // Ellipsis 2240 | if ($this->symbols['ellipsis'] !== false) { 2241 | $this->glyph_search[] = '/([^.]?)\.{3}/'; 2242 | $this->glyph_replace[] = '$1'.$this->symbols['ellipsis']; 2243 | } 2244 | 2245 | // em dash 2246 | if ($this->symbols['emdash'] !== false) { 2247 | $this->glyph_search[] = '/--/'; 2248 | $this->glyph_replace[] = $this->symbols['emdash']; 2249 | } 2250 | 2251 | // en dash 2252 | if ($this->symbols['endash'] !== false) { 2253 | $this->glyph_search[] = '/ - /'; 2254 | $this->glyph_replace[] = ' '.$this->symbols['endash'].' '; 2255 | } 2256 | 2257 | // Trademark 2258 | if ($this->symbols['trademark'] !== false) { 2259 | $this->glyph_search[] = '/(\b ?|'.$this->regex_snippets['space'].'|^)[([]TM[])]/i'. 2260 | $this->regex_snippets['mod']; 2261 | $this->glyph_replace[] = '$1'.$this->symbols['trademark']; 2262 | } 2263 | 2264 | // Registered 2265 | if ($this->symbols['registered'] !== false) { 2266 | $this->glyph_search[] = '/(\b ?|'.$this->regex_snippets['space'].'|^)[([]R[])]/i'. 2267 | $this->regex_snippets['mod']; 2268 | $this->glyph_replace[] = '$1'.$this->symbols['registered']; 2269 | } 2270 | 2271 | // Copyright 2272 | if ($this->symbols['copyright'] !== false) { 2273 | $this->glyph_search[] = '/(\b ?|'.$this->regex_snippets['space'].'|^)[([]C[])]/i'. 2274 | $this->regex_snippets['mod']; 2275 | $this->glyph_replace[] = '$1'.$this->symbols['copyright']; 2276 | } 2277 | 2278 | // 1/4 2279 | if ($this->symbols['quarter'] !== false) { 2280 | $this->glyph_search[] = '/[([]1\/4[])]/'; 2281 | $this->glyph_replace[] = $this->symbols['quarter']; 2282 | } 2283 | 2284 | // 1/2 2285 | if ($this->symbols['half'] !== false) { 2286 | $this->glyph_search[] = '/[([]1\/2[])]/'; 2287 | $this->glyph_replace[] = $this->symbols['half']; 2288 | } 2289 | 2290 | // 3/4 2291 | if ($this->symbols['threequarters'] !== false) { 2292 | $this->glyph_search[] = '/[([]3\/4[])]/'; 2293 | $this->glyph_replace[] = $this->symbols['threequarters']; 2294 | } 2295 | 2296 | // Degrees -- that's a small 'oh' 2297 | if ($this->symbols['degrees'] !== false) { 2298 | $this->glyph_search[] = '/[([]o[])]/'; 2299 | $this->glyph_replace[] = $this->symbols['degrees']; 2300 | } 2301 | 2302 | // Plus minus 2303 | if ($this->symbols['plusminus'] !== false) { 2304 | $this->glyph_search[] = '/[([]\+\/-[])]/'; 2305 | $this->glyph_replace[] = $this->symbols['plusminus']; 2306 | } 2307 | 2308 | // No need to rebuild next run unless a symbol is redefined 2309 | $this->rebuild_glyphs = false; 2310 | } 2311 | 2312 | /** 2313 | * Gets the maximum allowed link index. 2314 | * 2315 | * @return int Maximum link index 2316 | * @since 3.5.5 2317 | */ 2318 | protected function getMaxLinkIndex() 2319 | { 2320 | return 1000000; 2321 | } 2322 | 2323 | /** 2324 | * Prepares the parser for parsing. 2325 | * 2326 | * This method prepares the transient internal state of 2327 | * Textile parser in preparation for parsing a new document. 2328 | * 2329 | * @param bool|null $lite Controls lite mode 2330 | * @param bool|null $noimage Disallow images 2331 | * @param string|null $rel A relationship attribute applied to links 2332 | * @return void 2333 | */ 2334 | protected function prepare($lite = null, $noimage = null, $rel = null) 2335 | { 2336 | if ($this->linkIndex >= $this->getMaxLinkIndex()) { 2337 | $this->linkPrefix .= '-'; 2338 | $this->linkIndex = 1; 2339 | } 2340 | 2341 | $this->unreferencedNotes = array(); 2342 | $this->notelist_cache = array(); 2343 | $this->notes = array(); 2344 | $this->urlrefs = array(); 2345 | $this->shelf = array(); 2346 | $this->fn = array(); 2347 | $this->span_depth = 0; 2348 | $this->refIndex = 1; 2349 | $this->refCache = array(); 2350 | $this->note_index = 1; 2351 | 2352 | if ($lite !== null) { 2353 | trigger_error( 2354 | '$lite argument is deprecated. Use Parser::setLite() instead.', 2355 | E_USER_DEPRECATED 2356 | ); 2357 | 2358 | $this->setLite($lite); 2359 | } 2360 | 2361 | if ($noimage !== null) { 2362 | trigger_error( 2363 | '$noimage argument is deprecated. Use Parser::setImages() instead.', 2364 | E_USER_DEPRECATED 2365 | ); 2366 | 2367 | $this->setImages(!$noimage); 2368 | } 2369 | 2370 | if ($rel !== null) { 2371 | trigger_error( 2372 | '$rel argument is deprecated. Use Parser::setRelative() instead.', 2373 | E_USER_DEPRECATED 2374 | ); 2375 | 2376 | $this->setLinkRelationShip($rel); 2377 | } 2378 | 2379 | if ($this->patterns === null) { 2380 | $block = implode('|', $this->blockContent); 2381 | $divider = implode('|', $this->dividerContent); 2382 | $phrasing = implode('|', $this->phrasingContent); 2383 | 2384 | $this->patterns = array( 2385 | 'block' => '/^(?:'.$block.')$/i', 2386 | 'contained' => '/^<\/?(?P[^\s<>\/]+)(?:\s.*|\/?>.*|)>$/si', 2387 | 'divider' => '/^(?:<\/?('.$divider.')(?:\s[^<>]*?|\/?)>(?:<\/\1\s*?>)?)+$/si', 2388 | 'phrasing' => '/^(?:'.$phrasing.')$/i', 2389 | 'wrapped' => '/^<\/?(?P[^\s<>\/]+)[^<>]*?>(?:.*<\/\1\s*?>)?$/si', 2390 | 'unwrappable' => '/<\/?(?:'.$block.')(?:\s[^<>]*?|\/?)>/si', 2391 | ); 2392 | } 2393 | 2394 | $this->prepGlyphs(); 2395 | } 2396 | 2397 | /** 2398 | * Cleans a HTML attribute value. 2399 | * 2400 | * This method checks for presence of URL encoding in the value. 2401 | * If the number encoded characters exceeds the threshold, 2402 | * the input is discarded. Otherwise the encoded 2403 | * instances are decoded. 2404 | * 2405 | * This method also strips any ", ' and = characters 2406 | * from the given value. This method does not guarantee 2407 | * valid HTML or full sanitization. 2408 | * 2409 | * @param string $in The input string 2410 | * @return string Cleaned string 2411 | */ 2412 | protected function cleanAttribs($in) 2413 | { 2414 | $tmp = $in; 2415 | $before = -1; 2416 | $after = 0; 2417 | $max = 3; 2418 | $i = 0; 2419 | 2420 | while (($after != $before) && ($i < $max)) { 2421 | $before = strlen($tmp); 2422 | $tmp = rawurldecode($tmp); 2423 | $after = strlen($tmp); 2424 | $i++; 2425 | } 2426 | 2427 | if ($i === $max) { 2428 | // If we hit the max allowed decodes, assume the input is tainted and consume it. 2429 | $out = ''; 2430 | } else { 2431 | $out = str_replace(array('"', "'", '='), '', $tmp); 2432 | } 2433 | 2434 | return $out; 2435 | } 2436 | 2437 | /** 2438 | * Constructs a HTML tag from an object. 2439 | * 2440 | * This is a helper method that creates a new 2441 | * instance of \Netcarver\Textile\Tag. 2442 | * 2443 | * @param string $name The HTML element name 2444 | * @param array $atts HTML attributes applied to the tag 2445 | * @param bool $selfclosing Determines if the tag should be self-closing 2446 | * @return Tag 2447 | */ 2448 | protected function newTag($name, $atts, $selfclosing = true) 2449 | { 2450 | return new Tag($name, $atts, $selfclosing && $this->getDocumentType() !== self::DOCTYPE_HTML5); 2451 | } 2452 | 2453 | /** 2454 | * Parses Textile attributes. 2455 | * 2456 | * @param string $in The Textile attribute string to be parsed 2457 | * @param string $element Focus the routine to interpret the attributes as applying to a specific HTML tag 2458 | * @param bool $include_id If FALSE, IDs are not included in the attribute list 2459 | * @param string $autoclass An additional classes applied to the output 2460 | * @return string HTML attribute list 2461 | * @see Parser::parseAttribsToArray() 2462 | */ 2463 | protected function parseAttribs($in, $element = '', $include_id = true, $autoclass = '') 2464 | { 2465 | $o = $this->parseAttribsToArray($in, $element, $include_id, $autoclass); 2466 | 2467 | return $this->formatAttributeString($o); 2468 | } 2469 | 2470 | /** 2471 | * Converts an array of named attribute => value mappings to a string. 2472 | * 2473 | * @param array $attribute_array 2474 | * @return string 2475 | */ 2476 | protected function formatAttributeString(array $attribute_array) 2477 | { 2478 | $out = ''; 2479 | 2480 | if (count($attribute_array)) { 2481 | foreach ($attribute_array as $k => $v) { 2482 | $out .= " $k=\"$v\""; 2483 | } 2484 | } 2485 | 2486 | return $out; 2487 | } 2488 | 2489 | /** 2490 | * Parses Textile attributes into an array. 2491 | * 2492 | * @param string $in The Textile attribute string to be parsed 2493 | * @param string $element Focus the routine to interpret the attributes as applying to a specific HTML tag 2494 | * @param bool $include_id If FALSE, IDs are not included in the attribute list 2495 | * @param string $autoclass An additional classes applied to the output 2496 | * @return array HTML attributes as key => value mappings 2497 | * @see Parser::parseAttribs() 2498 | */ 2499 | protected function parseAttribsToArray($in, $element = '', $include_id = true, $autoclass = '') 2500 | { 2501 | $style = array(); 2502 | $class = ''; 2503 | $lang = ''; 2504 | $colspan = ''; 2505 | $rowspan = ''; 2506 | $span = ''; 2507 | $width = ''; 2508 | $id = ''; 2509 | $matched = $in; 2510 | 2511 | if ($element == 'td') { 2512 | if (preg_match("/\\\\([0-9]+)/", $matched, $csp)) { 2513 | $colspan = $csp[1]; 2514 | } 2515 | 2516 | if (preg_match("/\/([0-9]+)/", $matched, $rsp)) { 2517 | $rowspan = $rsp[1]; 2518 | } 2519 | } 2520 | 2521 | if (($element === 'td' || $element === 'tr') 2522 | && preg_match("/^($this->vlgn)/", $matched, $vert) 2523 | ) { 2524 | $style[] = "vertical-align:" . $this->vAlign($vert[1]); 2525 | } 2526 | 2527 | if (preg_match("/\{([^}]*)\}/", $matched, $sty)) { 2528 | if ($sty[1] = $this->cleanAttribs($sty[1])) { 2529 | $style[] = rtrim($sty[1], ';'); 2530 | } 2531 | 2532 | $matched = str_replace($sty[0], '', $matched); 2533 | } 2534 | 2535 | if (preg_match("/\(([^()]+)\)/U", $matched, $cls)) { 2536 | $class_regex = "/^([-a-zA-Z 0-9_\.\/\[\]:!]*)$/"; 2537 | 2538 | // Consume entire class block -- valid or invalid. 2539 | $matched = str_replace($cls[0], '', $matched); 2540 | 2541 | // Only allow a restricted subset of the CSS standard characters for classes/ids. 2542 | // No encoding markers allowed. 2543 | if (preg_match("/\(([-a-zA-Z 0-9_\/\[\].:!#]+)\)/U", $cls[0], $cls)) { 2544 | $hashpos = strpos($cls[1], '#'); 2545 | // If a textile class block attribute was found with a '#' in it 2546 | // split it into the css class and css id... 2547 | if (false !== $hashpos) { 2548 | if (preg_match("/#([-a-zA-Z0-9_\.\:]*)$/", substr($cls[1], $hashpos), $ids)) { 2549 | $id = $ids[1]; 2550 | } 2551 | 2552 | if (preg_match($class_regex, substr($cls[1], 0, $hashpos), $ids)) { 2553 | $class = $ids[1]; 2554 | } 2555 | } else { 2556 | if (preg_match($class_regex, $cls[1], $ids)) { 2557 | $class = $ids[1]; 2558 | } 2559 | } 2560 | } 2561 | } 2562 | 2563 | if (preg_match("/\[([^]]+)\]/U", $matched, $lng)) { 2564 | // Consume entire lang block -- valid or invalid. 2565 | $matched = str_replace($lng[0], '', $matched); 2566 | if ($element === 'code' && preg_match("/\[([a-zA-Z0-9_-]+)\]/U", $lng[0], $lng1)) { 2567 | $lang = $lng1[1]; 2568 | } elseif (preg_match("/\[([a-zA-Z]{2}(?:[\-\_][a-zA-Z]{2})?)\]/U", $lng[0], $lng2)) { 2569 | $lang = $lng2[1]; 2570 | } 2571 | } 2572 | 2573 | if (preg_match("/(\(+)/", $matched, $pl)) { 2574 | $style[] = "padding-left:" . strlen($pl[1]) . "em"; 2575 | $matched = str_replace($pl[0], '', $matched); 2576 | } 2577 | 2578 | if (preg_match("/(\)+)/", $matched, $pr)) { 2579 | $style[] = "padding-right:" . strlen($pr[1]) . "em"; 2580 | $matched = str_replace($pr[0], '', $matched); 2581 | } 2582 | 2583 | if (preg_match("/($this->hlgn)/", $matched, $horiz)) { 2584 | $style[] = "text-align:" . $this->hAlign($horiz[1]); 2585 | } 2586 | 2587 | if ($element == 'col' 2588 | && preg_match("/(?:\\\\([0-9]+))?{$this->regex_snippets['space']}*([0-9]+)?/", $matched, $csp) 2589 | ) { 2590 | $span = isset($csp[1]) ? $csp[1] : ''; 2591 | $width = isset($csp[2]) ? $csp[2] : ''; 2592 | } 2593 | 2594 | if ($this->isRestrictedModeEnabled()) { 2595 | $o = array(); 2596 | $class = trim($autoclass); 2597 | 2598 | if ($class) { 2599 | $o['class'] = $this->cleanAttribs($class); 2600 | } 2601 | 2602 | if ($lang) { 2603 | $o['lang'] = $this->cleanAttribs($lang); 2604 | } 2605 | 2606 | ksort($o); 2607 | return $o; 2608 | } else { 2609 | $class = trim($class . ' ' . $autoclass); 2610 | } 2611 | 2612 | $o = array(); 2613 | 2614 | if ($class) { 2615 | $o['class'] = $this->cleanAttribs($class); 2616 | } 2617 | 2618 | if ($colspan) { 2619 | $o['colspan'] = $this->cleanAttribs($colspan); 2620 | } 2621 | 2622 | if ($id && $include_id) { 2623 | $o['id'] = $this->cleanAttribs($id); 2624 | } 2625 | 2626 | if ($lang) { 2627 | $o['lang'] = $this->cleanAttribs($lang); 2628 | } 2629 | 2630 | if ($rowspan) { 2631 | $o['rowspan'] = $this->cleanAttribs($rowspan); 2632 | } 2633 | 2634 | if ($span) { 2635 | $o['span'] = $this->cleanAttribs($span); 2636 | } 2637 | 2638 | if (!empty($style)) { 2639 | $so = ''; 2640 | $tmps = array(); 2641 | 2642 | foreach ($style as $s) { 2643 | $parts = explode(';', $s); 2644 | 2645 | foreach ($parts as $p) { 2646 | if ($p = trim(trim($p), ":")) { 2647 | $tmps[] = $p; 2648 | } 2649 | } 2650 | } 2651 | 2652 | sort($tmps); 2653 | 2654 | foreach ($tmps as $p) { 2655 | // @phpstan-ignore-next-line 2656 | if ($p) { 2657 | $so .= $p.';'; 2658 | } 2659 | } 2660 | 2661 | $o['style'] = trim(str_replace(array("\n", ';;'), array('', ';'), $so)); 2662 | } 2663 | 2664 | if ($width) { 2665 | $o['width'] = $this->cleanAttribs($width); 2666 | } 2667 | 2668 | ksort($o); 2669 | return $o; 2670 | } 2671 | 2672 | /** 2673 | * Checks whether the text block should be wrapped in a paragraph. 2674 | * 2675 | * @param string $text The input string 2676 | * @return bool TRUE if the text can be wrapped, FALSE otherwise 2677 | */ 2678 | protected function hasRawText($text) 2679 | { 2680 | if (preg_match($this->patterns['unwrappable'], $text)) { 2681 | return false; 2682 | } 2683 | 2684 | if (preg_match($this->patterns['divider'], $text)) { 2685 | return false; 2686 | } 2687 | 2688 | if (preg_match($this->patterns['wrapped'], $text, $m)) { 2689 | if (preg_match($this->patterns['phrasing'], $m['open'])) { 2690 | return true; 2691 | } 2692 | 2693 | return false; 2694 | } 2695 | 2696 | return true; 2697 | } 2698 | 2699 | /** 2700 | * Parses textile table structures into HTML. 2701 | * 2702 | * @param string $text The textile input 2703 | * @return string The parsed text 2704 | */ 2705 | protected function tables($text) 2706 | { 2707 | $text = $text . "\n\n"; 2708 | return (string)preg_replace_callback( 2709 | "/^(?:table(?P_?{$this->s}{$this->a}{$this->cls})\.". 2710 | "(?P.*)?\n)?^(?P{$this->a}{$this->cls}\.? ?\|.*\|){$this->regex_snippets['space']}*\n\n/smU", 2711 | array($this, "fTable"), 2712 | $text 2713 | ); 2714 | } 2715 | 2716 | /** 2717 | * Constructs an HTML table from a textile table structure. 2718 | * 2719 | * This method is used by Parser::tables() to process 2720 | * found table structures. 2721 | * 2722 | * @param array $matches 2723 | * @return string HTML table 2724 | * @see Parser::tables() 2725 | */ 2726 | protected function fTable($matches) 2727 | { 2728 | $tatts = $this->parseAttribs($matches['tatts'], 'table'); 2729 | $space = $this->regex_snippets['space']; 2730 | 2731 | $cap = ''; 2732 | $colgrp = ''; 2733 | $last_rgrp = ''; 2734 | $c_row = 1; 2735 | $sum = ''; 2736 | $rows = array(); 2737 | 2738 | $summary = trim($matches['summary']); 2739 | 2740 | if ($summary !== '') { 2741 | $sum = ' summary="'.htmlspecialchars($summary, ENT_QUOTES, 'UTF-8').'"'; 2742 | } 2743 | 2744 | /** @var array $components */ 2745 | $components = preg_split("/\|{$space}*?$/m", $matches['rows'], -1, PREG_SPLIT_NO_EMPTY); 2746 | 2747 | foreach ($components as $row) { 2748 | $row = ltrim($row); 2749 | 2750 | // Caption -- can only occur on row 1, otherwise treat '|=. foo |...' 2751 | // as a normal center-aligned cell. 2752 | if (($c_row <= 1) && preg_match( 2753 | "/^\|\=(?P$this->s$this->a$this->cls)\. (?P[^\n]*)(?P.*)/s", 2754 | ltrim($row), 2755 | $cmtch 2756 | )) { 2757 | $capts = $this->parseAttribs($cmtch['capts']); 2758 | $cap = "\t".trim($cmtch['cap'])."\n"; 2759 | $row = ltrim($cmtch['row']); 2760 | if (!$row) { 2761 | continue; 2762 | } 2763 | } 2764 | 2765 | $c_row += 1; 2766 | 2767 | // Colgroup 2768 | if (preg_match("/^\|:(?P$this->s$this->a$this->cls\. .*)/m", ltrim($row), $gmtch)) { 2769 | // Is this colgroup def missing a closing pipe? If so, there 2770 | // will be a newline in the middle of $row somewhere. 2771 | $nl = strpos($row, "\n"); 2772 | $idx = 0; 2773 | 2774 | foreach (explode('|', str_replace('.', '', $gmtch['cols'])) as $col) { 2775 | $gatts = $this->parseAttribs(trim($col), 'col'); 2776 | $colgrp .= "\t" : $gatts." />")."\n"; 2777 | $idx++; 2778 | } 2779 | 2780 | $colgrp .= "\t\n"; 2781 | 2782 | if ($nl === false) { 2783 | continue; 2784 | } else { 2785 | // Recover from our missing pipe and process the rest of the line. 2786 | $row = ltrim(substr($row, $nl)); 2787 | } 2788 | } 2789 | 2790 | // Row group 2791 | $rgrpatts = $rgrp = ''; 2792 | 2793 | if (preg_match( 2794 | "/(:?^\|(?P$this->vlgn)(?P$this->s$this->a$this->cls)\.{$space}*$\n)?^(?P.*)/sm", 2795 | ltrim($row), 2796 | $grpmatch 2797 | )) { 2798 | if (isset($grpmatch['part'])) { 2799 | if ($grpmatch['part'] === '^') { 2800 | $rgrp = 'head'; 2801 | } elseif ($grpmatch['part'] === '~') { 2802 | $rgrp = 'foot'; 2803 | } elseif ($grpmatch['part'] === '-') { 2804 | $rgrp = 'body'; 2805 | } 2806 | } 2807 | 2808 | if (isset($grpmatch['part'])) { 2809 | $rgrpatts = $this->parseAttribs($grpmatch['rgrpatts']); 2810 | } 2811 | 2812 | if (isset($grpmatch['row'])) { 2813 | $row = $grpmatch['row']; 2814 | } 2815 | } 2816 | 2817 | if (preg_match("/^(?P$this->a$this->cls\. )(?P.*)/m", ltrim($row), $rmtch)) { 2818 | $ratts = $this->parseAttribs($rmtch['ratts'], 'tr'); 2819 | $row = $rmtch['row']; 2820 | } else { 2821 | $ratts = ''; 2822 | } 2823 | 2824 | $cells = array(); 2825 | $cellctr = 0; 2826 | 2827 | foreach (explode("|", $row) as $cell) { 2828 | $ctyp = "d"; 2829 | 2830 | if (preg_match("/^_(?=[{$this->regex_snippets['space']}[:punct:]])/", $cell)) { 2831 | $ctyp = "h"; 2832 | } 2833 | 2834 | if (preg_match("/^(?P_?$this->s$this->a$this->cls\. )(?P.*)/s", $cell, $cmtch)) { 2835 | $catts = $this->parseAttribs($cmtch['catts'], 'td'); 2836 | $cell = $cmtch['cell']; 2837 | } else { 2838 | $catts = ''; 2839 | } 2840 | 2841 | if (!$this->isLiteModeEnabled()) { 2842 | $a = array(); 2843 | 2844 | if (preg_match('/(?'.$this->regex_snippets['space'].'*)(?P.*)/s', $cell, $a)) { 2845 | $cell = $this->redclothLists($a['cell']); 2846 | $cell = $this->textileLists($cell); 2847 | $cell = $a['space'] . $cell; 2848 | } 2849 | } 2850 | 2851 | if ($cellctr > 0) { 2852 | // Ignore first 'cell': it precedes the opening pipe 2853 | $cells[] = $this->doTagBr("t$ctyp", "\t\t\t$cell"); 2854 | } 2855 | 2856 | $cellctr++; 2857 | } 2858 | 2859 | $grp = ''; 2860 | 2861 | if ($rgrp && $last_rgrp) { 2862 | $grp .= "\t\n"; 2863 | } 2864 | 2865 | if ($rgrp) { 2866 | $grp .= "\t\n"; 2867 | } 2868 | 2869 | $last_rgrp = ($rgrp) ? $rgrp : $last_rgrp; 2870 | $rows[] = $grp."\t\t\n" . join("\n", $cells) . ($cells ? "\n" : "") . "\t\t"; 2871 | unset($cells, $catts); 2872 | } 2873 | 2874 | $rows = join("\n", $rows) . "\n"; 2875 | $close = ''; 2876 | 2877 | if ($last_rgrp) { 2878 | $close = "\t\n"; 2879 | } 2880 | 2881 | return "\n".$cap.$colgrp.$rows.$close."\n\n"; 2882 | } 2883 | 2884 | /** 2885 | * Parses RedCloth-style definition lists into HTML. 2886 | * 2887 | * @param string $text The textile input 2888 | * @return string The parsed text 2889 | */ 2890 | protected function redclothLists($text) 2891 | { 2892 | return (string)preg_replace_callback( 2893 | "/^([-]+$this->cls[ .].*:=.*)$(?![^-])/smU", 2894 | array($this, "fRedclothList"), 2895 | $text 2896 | ); 2897 | } 2898 | 2899 | /** 2900 | * Constructs a HTML definition list from a RedCloth-style definition structure. 2901 | * 2902 | * This method is used by Parser::redclothLists() to process 2903 | * found definition list structures. 2904 | * 2905 | * @param array $m 2906 | * @return string HTML definition list 2907 | * @see Parser::redclothLists() 2908 | */ 2909 | protected function fRedclothList($m) 2910 | { 2911 | $in = $m[0]; 2912 | $out = array(); 2913 | 2914 | /** @var array $text */ 2915 | $text = preg_split('/\n(?=-)/m', $in); 2916 | 2917 | foreach ($text as $line) { 2918 | $m = array(); 2919 | 2920 | if (preg_match("/^[-]+(?P$this->cls)\.? (?P.*)$/s", $line, $m)) { 2921 | $content = trim($m['content']); 2922 | $atts = $this->parseAttribs($m['atts']); 2923 | 2924 | if (!preg_match( 2925 | "/^(.*?){$this->regex_snippets['space']}*:=(.*?)". 2926 | "{$this->regex_snippets['space']}*(=:|:=)?". 2927 | "{$this->regex_snippets['space']}*$/s", 2928 | $content, 2929 | $xm 2930 | )) { 2931 | $xm = array( $content, $content, '' ); 2932 | } 2933 | 2934 | list(, $term, $def,) = $xm; 2935 | $term = trim($term); 2936 | $def = trim($def, ' '); 2937 | 2938 | if (!$out) { 2939 | if ($def === '') { 2940 | $out[] = ""; 2941 | } else { 2942 | $out[] = '
'; 2943 | } 2944 | } 2945 | 2946 | if ($term !== '') { 2947 | $pos = strpos($def, "\n"); 2948 | $def = trim($def); 2949 | 2950 | if ($this->isLineWrapEnabled()) { 2951 | $def = str_replace("\n", $this->getLineBreak(), $def); 2952 | } 2953 | 2954 | if ($pos === 0) { 2955 | $def = '

' . $def . '

'; 2956 | } 2957 | 2958 | if ($this->isLineWrapEnabled()) { 2959 | $term = str_replace("\n", $this->getLineBreak(), $term); 2960 | } 2961 | 2962 | $term = $this->graf($term); 2963 | $def = $this->graf($def); 2964 | 2965 | $out[] = "\t$term"; 2966 | 2967 | if ($def !== '') { 2968 | $out[] = "\t
$def
"; 2969 | } 2970 | } 2971 | } 2972 | } 2973 | 2974 | $out[] = '
'; 2975 | return implode("\n", $out); 2976 | } 2977 | 2978 | /** 2979 | * Parses Textile list structures into HTML. 2980 | * 2981 | * Searches for ordered, un-ordered and definition lists in the 2982 | * textile input and generates HTML lists for them. 2983 | * 2984 | * @param string $text The input 2985 | * @return string The parsed text 2986 | */ 2987 | protected function textileLists($text) 2988 | { 2989 | return (string)preg_replace_callback( 2990 | "/^((?:[*;:]+|[*;:#]*#(?:_|\d+)?)$this->cls[ .].*)$(?![^#*;:])/smU", 2991 | array($this, "fTextileList"), 2992 | $text 2993 | ); 2994 | } 2995 | 2996 | /** 2997 | * Constructs a HTML list from a Textile list structure. 2998 | * 2999 | * This method is used by Parser::textileLists() to process 3000 | * found list structures. 3001 | * 3002 | * @param array $m 3003 | * @return string HTML list 3004 | * @see Parser::textileLists() 3005 | */ 3006 | protected function fTextileList($m) 3007 | { 3008 | $text = $m[0]; 3009 | $lines = preg_split('/\n(?=[*#;:])/m', $m[0]); 3010 | $list = array(); 3011 | $prev = false; 3012 | $out = array(); 3013 | $lists = array(); 3014 | $litem = ''; 3015 | 3016 | if ($lines === false) { 3017 | return ''; 3018 | } 3019 | 3020 | foreach ($lines as $line) { 3021 | $match = preg_match( 3022 | "/^(?P[#*;:]+)(?P_|\d+)?(?P$this->cls)[ .](?P.*)$/s", 3023 | $line, 3024 | $m 3025 | ); 3026 | 3027 | if ($match) { 3028 | $list[] = array_merge($m, array( 3029 | 'level' => strlen($m['tl']), 3030 | )); 3031 | } else { 3032 | $list[count($list) - 1]['content'] .= "\n" . $line; 3033 | } 3034 | } 3035 | 3036 | if (!$list || $list[0]['level'] > 1) { 3037 | return $text; 3038 | } 3039 | 3040 | foreach ($list as $index => $m) { 3041 | $start = ''; 3042 | $content = trim((string) $m['content']); 3043 | $ltype = $this->liType((string) $m['tl']); 3044 | 3045 | if (isset($list[$index + 1])) { 3046 | $next = $list[$index + 1]; 3047 | } else { 3048 | $next = false; 3049 | } 3050 | 3051 | if (strpos((string) $m['tl'], ';') !== false) { 3052 | $litem = 'dt'; 3053 | } elseif (strpos((string) $m['tl'], ':') !== false) { 3054 | $litem = 'dd'; 3055 | } else { 3056 | $litem = 'li'; 3057 | } 3058 | 3059 | $showitem = ($content !== ''); 3060 | 3061 | if ('o' === $ltype) { 3062 | if (!isset($this->olstarts[$m['tl']])) { 3063 | $this->olstarts[$m['tl']] = 1; 3064 | } 3065 | 3066 | if (!$prev || $m['level'] > $prev['level']) { 3067 | if ($m['st'] === '') { 3068 | $this->olstarts[$m['tl']] = 1; 3069 | } elseif ($m['st'] !== '_') { 3070 | $this->olstarts[$m['tl']] = (int) $m['st']; 3071 | } 3072 | } 3073 | 3074 | if ((!$prev || $m['level'] > $prev['level']) && $m['st'] !== '') { 3075 | $start = ' start="' . $this->olstarts[$m['tl']] . '"'; 3076 | } 3077 | 3078 | if ($showitem) { 3079 | $this->olstarts[$m['tl']] += 1; 3080 | } 3081 | } 3082 | 3083 | if ($prev 3084 | && $prev['tl'] 3085 | && strpos((string) $prev['tl'], ';') !== false 3086 | && strpos((string) $m['tl'], ':') !== false 3087 | ) { 3088 | $lists[$m['tl']] = 2; 3089 | } 3090 | 3091 | $tabs = str_repeat("\t", ((int) $m['level']) - 1); 3092 | $atts = $this->parseAttribs((string) $m['atts']); 3093 | 3094 | if (!isset($lists[$m['tl']])) { 3095 | $lists[$m['tl']] = 1; 3096 | $line = $tabs.'<'.$ltype.'l'.$atts.$start.'>'; 3097 | 3098 | if ($showitem) { 3099 | $line .= "\n$tabs\t<$litem>$content"; 3100 | } 3101 | } elseif ($showitem) { 3102 | $line = "$tabs\t<$litem$atts>$content"; 3103 | } else { 3104 | $line = ''; 3105 | } 3106 | 3107 | if ((!$next || $next['level'] <= $m['level']) && $showitem) { 3108 | $line .= ""; 3109 | } 3110 | 3111 | foreach (array_reverse($lists) as $k => $v) { 3112 | $indent = strlen((string) $k); 3113 | 3114 | if (!$next || $indent > $next['level']) { 3115 | if ($v !== 2) { 3116 | $line .= "\n$tabsliType((string) $k) . "l>"; 3117 | } 3118 | 3119 | if ($v !== 2 && $indent > 1) { 3120 | $line .= ""; 3121 | } 3122 | 3123 | unset($lists[$k]); 3124 | } 3125 | } 3126 | 3127 | $prev = $m; 3128 | $out[] = $line; 3129 | } 3130 | 3131 | $out = implode("\n", $out); 3132 | return $this->doTagBr($litem, $out); 3133 | } 3134 | 3135 | /** 3136 | * Determines the list type from the Textile input symbol. 3137 | * 3138 | * @param string $in Textile input containing the possible list marker 3139 | * @return string Either 'd', 'o', 'u' 3140 | */ 3141 | protected function liType($in) 3142 | { 3143 | $m = array(); 3144 | $type = 'd'; 3145 | if (preg_match('/^(?P[#*]+)/', $in, $m)) { 3146 | $type = ('#' === substr($m['type'], -1)) ? 'o' : 'u'; 3147 | } 3148 | return $type; 3149 | } 3150 | 3151 | /** 3152 | * Adds br tags within the specified container tag. 3153 | * 3154 | * @param string $tag The tag 3155 | * @param string $in The input 3156 | * @return string 3157 | */ 3158 | protected function doTagBr($tag, $in) 3159 | { 3160 | return (string)preg_replace_callback( 3161 | '@<(?P'.preg_quote($tag).')(?P[^>]*?)>(?P.*)(?P)@s', 3162 | array($this, 'fBr'), 3163 | $in 3164 | ); 3165 | } 3166 | 3167 | /** 3168 | * Adds br tags to paragraphs and headings. 3169 | * 3170 | * @param string $in The input 3171 | * @return string 3172 | */ 3173 | protected function doPBr($in) 3174 | { 3175 | return (string)preg_replace_callback( 3176 | '@<(?Pp|h[1-6])(?P[^>]*?)>(?P.*)(?P)@s', 3177 | array($this, 'fPBr'), 3178 | $in 3179 | ); 3180 | } 3181 | 3182 | /** 3183 | * Less restrictive version of fBr method. 3184 | * 3185 | * Used only in paragraphs and headings where the next row may 3186 | * start with a smiley or perhaps something like '#8 bolt...' 3187 | * or '*** stars...'. 3188 | * 3189 | * @param array $m The input 3190 | * @return string 3191 | */ 3192 | protected function fPBr($m) 3193 | { 3194 | if ($this->isLineWrapEnabled()) { 3195 | // Replaces
\n instances that are not followed by white-space, 3196 | // or at end, with single LF. 3197 | $m['content'] = preg_replace( 3198 | "~{$this->regex_snippets['space']}*\n(?![{$this->regex_snippets['space']}|])~i", 3199 | "\n", 3200 | (string) $m['content'] 3201 | ); 3202 | } 3203 | 3204 | // Replaces those LFs that aren't followed by white-space, or at end, with
or a space. 3205 | $m['content'] = preg_replace( 3206 | "/\n(?![\s|])/", 3207 | $this->isLineWrapEnabled() ? $this->getLineBreak() : ' ', 3208 | (string) $m['content'] 3209 | ); 3210 | 3211 | return '<'.$m['tag'].$m['atts'].'>'.$m['content'].$m['closetag']; 3212 | } 3213 | 3214 | /** 3215 | * Formats line breaks. 3216 | * 3217 | * @param array $m The input 3218 | * @return string 3219 | */ 3220 | protected function fBr($m) 3221 | { 3222 | $content = preg_replace( 3223 | "@(.+)(?|
|||)\n(?![\s|])@", 3224 | $this->isLineWrapEnabled() ? '$1'.$this->getLineBreak() : '$1 ', 3225 | $m['content'] 3226 | ); 3227 | 3228 | return '<'.$m['tag'].$m['atts'].'>'.$content.$m['closetag']; 3229 | } 3230 | 3231 | /** 3232 | * Splits the given input into blocks. 3233 | * 3234 | * Blocks are separated by double line-break boundaries, and processed 3235 | * the blocks one by one. 3236 | * 3237 | * @param string $text Textile source text 3238 | * @return string Input text with blocks processed 3239 | */ 3240 | protected function blocks($text) 3241 | { 3242 | $regex = '/^(?P'.join('|', $this->blocktag_whitelist).')'. 3243 | '(?P'.$this->a.$this->cls.$this->a.')\.(?P\.?)(?::(?P\S+))? (?P.*)$/Ss'. 3244 | $this->regex_snippets['mod']; 3245 | 3246 | $textblocks = preg_split('/(\n{2,})/', $text, -1, PREG_SPLIT_DELIM_CAPTURE); 3247 | 3248 | if ($textblocks === false) { 3249 | return ''; 3250 | } 3251 | 3252 | $eatWhitespace = false; 3253 | $whitespace = ''; 3254 | $ext = ''; 3255 | $out = array(); 3256 | $c1 = ''; 3257 | 3258 | foreach ($textblocks as $block) { 3259 | // Line is just whitespace, keep it for the next block. 3260 | if (trim($block) === '') { 3261 | if ($eatWhitespace === false) { 3262 | $whitespace .= $block; 3263 | } 3264 | continue; 3265 | } 3266 | 3267 | // @phpstan-ignore-next-line 3268 | if (!$ext) { 3269 | $tag = 'p'; 3270 | $atts = ''; 3271 | $cite = ''; 3272 | $eat = false; 3273 | } 3274 | 3275 | $eatWhitespace = false; 3276 | $anonymous_block = !preg_match($regex, $block, $m); 3277 | 3278 | if (!$anonymous_block) { 3279 | // Last block was extended, so close it 3280 | // @phpstan-ignore-next-line 3281 | if ($ext) { 3282 | $out[count($out)-1] .= $c1; 3283 | } 3284 | 3285 | // Extract the new block's parts 3286 | extract($m); 3287 | list($o1, $o2, $content, $c2, $c1, $eat) = $this->fBlock($m); 3288 | 3289 | // Leave off c1 if this block is extended, we'll close it at the start of the next block 3290 | $block = $o1.$o2.$content.$c2; 3291 | 3292 | // @phpstan-ignore-next-line 3293 | if (!$ext) { 3294 | $block .= $c1; 3295 | } 3296 | } else { 3297 | $rawBlock = preg_match($this->patterns['divider'], $block) || 3298 | ($this->isRawBlocksEnabled() && $this->isRawBlock($block)); 3299 | 3300 | // @phpstan-ignore-next-line 3301 | if ($ext || (strpos($block, ' ') !== 0 && !$rawBlock)) { 3302 | list($o1, $o2, $content, $c2, $c1, $eat) = $this->fBlock(array( 3303 | '', 3304 | $tag, 3305 | $atts, 3306 | $ext, 3307 | $cite, 3308 | $block, 3309 | )); 3310 | 3311 | // Skip $o1/$c1 because this is part of a continuing extended block 3312 | // @phpstan-ignore-next-line 3313 | if ($tag == 'p' && !$this->hasRawText($content)) { 3314 | $block = $content; 3315 | } else { 3316 | $block = $o2.$content.$c2; 3317 | } 3318 | } elseif ($rawBlock && $this->isRestrictedModeEnabled()) { 3319 | $block = $this->shelve($this->rEncodeHTML($block)); 3320 | } elseif ($rawBlock) { 3321 | $block = $this->shelve($block); 3322 | } else { 3323 | $block = $this->graf($block); 3324 | } 3325 | } 3326 | 3327 | $block = $whitespace . $this->doPBr((string) $block); 3328 | if ($this->getDocumentType() === self::DOCTYPE_XHTML) { 3329 | $block = str_replace('
', '
', $block); 3330 | } 3331 | 3332 | // @phpstan-ignore-next-line 3333 | if ($ext && $anonymous_block) { 3334 | $out[count($out)-1] .= $block; 3335 | } elseif (!$eat) { 3336 | $out[] = $block; 3337 | } 3338 | 3339 | if ($eat) { 3340 | $eatWhitespace = true; 3341 | } else { 3342 | $whitespace = ''; 3343 | } 3344 | } 3345 | 3346 | // @phpstan-ignore-next-line 3347 | if ($ext) { 3348 | $out[count($out)-1] .= $c1; 3349 | } 3350 | 3351 | return join('', $out); 3352 | } 3353 | 3354 | /** 3355 | * Formats the given block. 3356 | * 3357 | * Adds block tags and formats the text content inside 3358 | * the block. 3359 | * 3360 | * @param array $m The block content to format 3361 | * @return array 3362 | */ 3363 | protected function fBlock($m) 3364 | { 3365 | list(, $tag, $att, $ext, $cite, $content) = $m; 3366 | $atts = $this->parseAttribs($att); 3367 | $space = $this->regex_snippets['space']; 3368 | 3369 | $o1 = ''; 3370 | $o2 = ''; 3371 | $c2 = ''; 3372 | $c1 = ''; 3373 | $eat = false; 3374 | 3375 | if ($tag === 'p') { 3376 | // Is this an anonymous block with a note definition? 3377 | $notedef = preg_replace_callback( 3378 | "/ 3379 | ^note\# # start of note def marker 3380 | (?P