comparison program/lib/Roundcube/rcube_text2html.php @ 0:4681f974d28b

vanilla 1.3.3 distro, I hope
author Charlie Root
date Thu, 04 Jan 2018 15:52:31 -0500
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4681f974d28b
1 <?php
2
3 /**
4 +-----------------------------------------------------------------------+
5 | This file is part of the Roundcube Webmail client |
6 | Copyright (C) 2008-2014, The Roundcube Dev Team |
7 | |
8 | Licensed under the GNU General Public License version 3 or |
9 | any later version with exceptions for skins & plugins. |
10 | See the README file for a full license statement. |
11 | |
12 | PURPOSE: |
13 | Converts plain text to HTML |
14 +-----------------------------------------------------------------------+
15 | Author: Aleksander Machniak <alec@alec.pl> |
16 +-----------------------------------------------------------------------+
17 */
18
19 /**
20 * Converts plain text to HTML
21 *
22 * @package Framework
23 * @subpackage Utils
24 */
25 class rcube_text2html
26 {
27 /**
28 * Contains the HTML content after conversion.
29 *
30 * @var string $html
31 */
32 protected $html;
33
34 /**
35 * Contains the plain text.
36 *
37 * @var string $text
38 */
39 protected $text;
40
41 /**
42 * Configuration
43 *
44 * @var array $config
45 */
46 protected $config = array(
47 // non-breaking space
48 'space' => "\xC2\xA0",
49 // enables format=flowed parser
50 'flowed' => false,
51 // enables delsp=yes parser
52 'delsp' => false,
53 // enables wrapping for non-flowed text
54 'wrap' => true,
55 // line-break tag
56 'break' => "<br>\n",
57 // prefix and suffix (wrapper element)
58 'begin' => '<div class="pre">',
59 'end' => '</div>',
60 // enables links replacement
61 'links' => true,
62 // string replacer class
63 'replacer' => 'rcube_string_replacer',
64 // prefix and suffix of unwrappable line
65 'nobr_start' => '<span style="white-space:nowrap">',
66 'nobr_end' => '</span>',
67 );
68
69
70 /**
71 * Constructor.
72 *
73 * If the plain text source string (or file) is supplied, the class
74 * will instantiate with that source propagated, all that has
75 * to be done it to call get_html().
76 *
77 * @param string $source Plain text
78 * @param boolean $from_file Indicates $source is a file to pull content from
79 * @param array $config Class configuration
80 */
81 function __construct($source = '', $from_file = false, $config = array())
82 {
83 if (!empty($source)) {
84 $this->set_text($source, $from_file);
85 }
86
87 if (!empty($config) && is_array($config)) {
88 $this->config = array_merge($this->config, $config);
89 }
90 }
91
92 /**
93 * Loads source text into memory, either from $source string or a file.
94 *
95 * @param string $source Plain text
96 * @param boolean $from_file Indicates $source is a file to pull content from
97 */
98 function set_text($source, $from_file = false)
99 {
100 if ($from_file && file_exists($source)) {
101 $this->text = file_get_contents($source);
102 }
103 else {
104 $this->text = $source;
105 }
106
107 $this->_converted = false;
108 }
109
110 /**
111 * Returns the HTML content.
112 *
113 * @return string HTML content
114 */
115 function get_html()
116 {
117 if (!$this->_converted) {
118 $this->_convert();
119 }
120
121 return $this->html;
122 }
123
124 /**
125 * Prints the HTML.
126 */
127 function print_html()
128 {
129 print $this->get_html();
130 }
131
132 /**
133 * Workhorse function that does actual conversion (calls _converter() method).
134 */
135 protected function _convert()
136 {
137 // Convert TXT to HTML
138 $this->html = $this->_converter($this->text);
139 $this->_converted = true;
140 }
141
142 /**
143 * Workhorse function that does actual conversion.
144 *
145 * @param string Plain text
146 */
147 protected function _converter($text)
148 {
149 // make links and email-addresses clickable
150 $attribs = array('link_attribs' => array('rel' => 'noreferrer', 'target' => '_blank'));
151 $replacer = new $this->config['replacer']($attribs);
152
153 if ($this->config['flowed']) {
154 $flowed_char = 0x01;
155 $delsp = $this->config['delsp'];
156 $text = rcube_mime::unfold_flowed($text, chr($flowed_char), $delsp);
157 }
158
159 // search for patterns like links and e-mail addresses and replace with tokens
160 if ($this->config['links']) {
161 $text = $replacer->replace($text);
162 }
163
164 // split body into single lines
165 $text = preg_split('/\r?\n/', $text);
166 $quote_level = 0;
167 $last = null;
168
169 // wrap quoted lines with <blockquote>
170 for ($n = 0, $cnt = count($text); $n < $cnt; $n++) {
171 $flowed = false;
172 if ($this->config['flowed'] && ord($text[$n][0]) == $flowed_char) {
173 $flowed = true;
174 $text[$n] = substr($text[$n], 1);
175 }
176
177 if ($text[$n][0] == '>' && preg_match('/^(>+ {0,1})+/', $text[$n], $regs)) {
178 $q = substr_count($regs[0], '>');
179 $text[$n] = substr($text[$n], strlen($regs[0]));
180 $text[$n] = $this->_convert_line($text[$n], $flowed || $this->config['wrap']);
181 $_length = strlen(str_replace(' ', '', $text[$n]));
182
183 if ($q > $quote_level) {
184 if ($last !== null) {
185 $text[$last] .= (!$length ? "\n" : '')
186 . $replacer->get_replacement($replacer->add(
187 str_repeat('<blockquote>', $q - $quote_level)))
188 . $text[$n];
189
190 unset($text[$n]);
191 }
192 else {
193 $text[$n] = $replacer->get_replacement($replacer->add(
194 str_repeat('<blockquote>', $q - $quote_level))) . $text[$n];
195
196 $last = $n;
197 }
198 }
199 else if ($q < $quote_level) {
200 $text[$last] .= (!$length ? "\n" : '')
201 . $replacer->get_replacement($replacer->add(
202 str_repeat('</blockquote>', $quote_level - $q)))
203 . $text[$n];
204
205 unset($text[$n]);
206 }
207 else {
208 $last = $n;
209 }
210 }
211 else {
212 $text[$n] = $this->_convert_line($text[$n], $flowed || $this->config['wrap']);
213 $q = 0;
214 $_length = strlen(str_replace(' ', '', $text[$n]));
215
216 if ($quote_level > 0) {
217 $text[$last] .= (!$length ? "\n" : '')
218 . $replacer->get_replacement($replacer->add(
219 str_repeat('</blockquote>', $quote_level)))
220 . $text[$n];
221
222 unset($text[$n]);
223 }
224 else {
225 $last = $n;
226 }
227 }
228
229 $quote_level = $q;
230 $length = $_length;
231 }
232
233 if ($quote_level > 0) {
234 $text[$last] .= $replacer->get_replacement($replacer->add(
235 str_repeat('</blockquote>', $quote_level)));
236 }
237
238 $text = join("\n", $text);
239
240 // colorize signature (up to <sig_max_lines> lines)
241 $len = strlen($text);
242 $sig_sep = "--" . $this->config['space'] . "\n";
243 $sig_max_lines = rcube::get_instance()->config->get('sig_max_lines', 15);
244
245 while (($sp = strrpos($text, $sig_sep, $sp ? -$len+$sp-1 : 0)) !== false) {
246 if ($sp == 0 || $text[$sp-1] == "\n") {
247 // do not touch blocks with more that X lines
248 if (substr_count($text, "\n", $sp) < $sig_max_lines) {
249 $text = substr($text, 0, max(0, $sp))
250 .'<span class="sig">'.substr($text, $sp).'</span>';
251 }
252
253 break;
254 }
255 }
256
257 // insert url/mailto links and citation tags
258 $text = $replacer->resolve($text);
259
260 // replace line breaks
261 $text = str_replace("\n", $this->config['break'], $text);
262
263 return $this->config['begin'] . $text . $this->config['end'];
264 }
265
266 /**
267 * Converts spaces in line of text
268 */
269 protected function _convert_line($text, $is_flowed)
270 {
271 static $table;
272
273 if (empty($table)) {
274 $table = get_html_translation_table(HTML_SPECIALCHARS);
275 unset($table['?']);
276
277 // replace some whitespace characters
278 $table["\r"] = '';
279 $table["\t"] = ' ';
280 }
281
282 // skip signature separator
283 if ($text == '-- ') {
284 return '--' . $this->config['space'];
285 }
286
287 // replace HTML special and whitespace characters
288 $text = strtr($text, $table);
289
290 $nbsp = $this->config['space'];
291
292 // replace spaces with non-breaking spaces
293 if ($is_flowed) {
294 $pos = 0;
295 $diff = 0;
296 $len = strlen($nbsp);
297 $copy = $text;
298
299 while (($pos = strpos($text, ' ', $pos)) !== false) {
300 if ($pos == 0 || $text[$pos-1] == ' ') {
301 $copy = substr_replace($copy, $nbsp, $pos + $diff, 1);
302 $diff += $len - 1;
303 }
304 $pos++;
305 }
306
307 $text = $copy;
308 }
309 // make the whole line non-breakable if needed
310 else if ($text !== '' && preg_match('/[^a-zA-Z0-9_]/', $text)) {
311 // use non-breakable spaces to correctly display
312 // trailing/leading spaces and multi-space inside
313 $text = str_replace(' ', $nbsp, $text);
314 // wrap in nobr element, so it's not wrapped on e.g. - or /
315 $text = $this->config['nobr_start'] . $text . $this->config['nobr_end'];
316 }
317
318 return $text;
319 }
320 }