| 1 | <?php |
|---|
| 2 | |
|---|
| 3 | /* |
|---|
| 4 | +-----------------------------------------------------------------------+ |
|---|
| 5 | | program/include/rcube_html_page.php | |
|---|
| 6 | | | |
|---|
| 7 | | This file is part of the Roundcube PHP suite | |
|---|
| 8 | | Copyright (C) 2005-2011 The Roundcube Dev Team | |
|---|
| 9 | | | |
|---|
| 10 | | Licensed under the GNU General Public License version 3 or | |
|---|
| 11 | | any later version with exceptions for skins & plugins. | |
|---|
| 12 | | See the README file for a full license statement. | |
|---|
| 13 | | | |
|---|
| 14 | | CONTENTS: | |
|---|
| 15 | | Class to build XHTML page output | |
|---|
| 16 | | | |
|---|
| 17 | +-----------------------------------------------------------------------+ |
|---|
| 18 | | Author: Thomas Bruederli <roundcube@gmail.com> | |
|---|
| 19 | +-----------------------------------------------------------------------+ |
|---|
| 20 | |
|---|
| 21 | $Id$ |
|---|
| 22 | |
|---|
| 23 | */ |
|---|
| 24 | |
|---|
| 25 | /** |
|---|
| 26 | * Class for HTML page creation |
|---|
| 27 | * |
|---|
| 28 | * @package HTML |
|---|
| 29 | */ |
|---|
| 30 | class rcube_html_page |
|---|
| 31 | { |
|---|
| 32 | protected $scripts_path = ''; |
|---|
| 33 | protected $script_files = array(); |
|---|
| 34 | protected $css_files = array(); |
|---|
| 35 | protected $scripts = array(); |
|---|
| 36 | protected $charset = RCMAIL_CHARSET; |
|---|
| 37 | protected $default_template = "<html>\n<head><title></title></head>\n<body></body>\n</html>"; |
|---|
| 38 | |
|---|
| 39 | protected $title = ''; |
|---|
| 40 | protected $header = ''; |
|---|
| 41 | protected $footer = ''; |
|---|
| 42 | protected $body = ''; |
|---|
| 43 | protected $base_path = ''; |
|---|
| 44 | |
|---|
| 45 | |
|---|
| 46 | /** Constructor */ |
|---|
| 47 | public function __construct() {} |
|---|
| 48 | |
|---|
| 49 | /** |
|---|
| 50 | * Link an external script file |
|---|
| 51 | * |
|---|
| 52 | * @param string File URL |
|---|
| 53 | * @param string Target position [head|foot] |
|---|
| 54 | */ |
|---|
| 55 | public function include_script($file, $position='head') |
|---|
| 56 | { |
|---|
| 57 | static $sa_files = array(); |
|---|
| 58 | |
|---|
| 59 | if (!preg_match('|^https?://|i', $file) && $file[0] != '/') { |
|---|
| 60 | $file = $this->scripts_path . $file; |
|---|
| 61 | if ($fs = @filemtime($file)) { |
|---|
| 62 | $file .= '?s=' . $fs; |
|---|
| 63 | } |
|---|
| 64 | } |
|---|
| 65 | |
|---|
| 66 | if (in_array($file, $sa_files)) { |
|---|
| 67 | return; |
|---|
| 68 | } |
|---|
| 69 | |
|---|
| 70 | $sa_files[] = $file; |
|---|
| 71 | |
|---|
| 72 | if (!is_array($this->script_files[$position])) { |
|---|
| 73 | $this->script_files[$position] = array(); |
|---|
| 74 | } |
|---|
| 75 | |
|---|
| 76 | $this->script_files[$position][] = $file; |
|---|
| 77 | } |
|---|
| 78 | |
|---|
| 79 | /** |
|---|
| 80 | * Add inline javascript code |
|---|
| 81 | * |
|---|
| 82 | * @param string JS code snippet |
|---|
| 83 | * @param string Target position [head|head_top|foot] |
|---|
| 84 | */ |
|---|
| 85 | public function add_script($script, $position='head') |
|---|
| 86 | { |
|---|
| 87 | if (!isset($this->scripts[$position])) { |
|---|
| 88 | $this->scripts[$position] = "\n" . rtrim($script); |
|---|
| 89 | } |
|---|
| 90 | else { |
|---|
| 91 | $this->scripts[$position] .= "\n" . rtrim($script); |
|---|
| 92 | } |
|---|
| 93 | } |
|---|
| 94 | |
|---|
| 95 | /** |
|---|
| 96 | * Link an external css file |
|---|
| 97 | * |
|---|
| 98 | * @param string File URL |
|---|
| 99 | */ |
|---|
| 100 | public function include_css($file) |
|---|
| 101 | { |
|---|
| 102 | $this->css_files[] = $file; |
|---|
| 103 | } |
|---|
| 104 | |
|---|
| 105 | /** |
|---|
| 106 | * Add HTML code to the page header |
|---|
| 107 | * |
|---|
| 108 | * @param string $str HTML code |
|---|
| 109 | */ |
|---|
| 110 | public function add_header($str) |
|---|
| 111 | { |
|---|
| 112 | $this->header .= "\n" . $str; |
|---|
| 113 | } |
|---|
| 114 | |
|---|
| 115 | /** |
|---|
| 116 | * Add HTML code to the page footer |
|---|
| 117 | * To be added right befor </body> |
|---|
| 118 | * |
|---|
| 119 | * @param string $str HTML code |
|---|
| 120 | */ |
|---|
| 121 | public function add_footer($str) |
|---|
| 122 | { |
|---|
| 123 | $this->footer .= "\n" . $str; |
|---|
| 124 | } |
|---|
| 125 | |
|---|
| 126 | /** |
|---|
| 127 | * Setter for page title |
|---|
| 128 | * |
|---|
| 129 | * @param string $t Page title |
|---|
| 130 | */ |
|---|
| 131 | public function set_title($t) |
|---|
| 132 | { |
|---|
| 133 | $this->title = $t; |
|---|
| 134 | } |
|---|
| 135 | |
|---|
| 136 | /** |
|---|
| 137 | * Setter for output charset. |
|---|
| 138 | * To be specified in a meta tag and sent as http-header |
|---|
| 139 | * |
|---|
| 140 | * @param string $charset Charset |
|---|
| 141 | */ |
|---|
| 142 | public function set_charset($charset) |
|---|
| 143 | { |
|---|
| 144 | $this->charset = $charset; |
|---|
| 145 | } |
|---|
| 146 | |
|---|
| 147 | /** |
|---|
| 148 | * Getter for output charset |
|---|
| 149 | * |
|---|
| 150 | * @return string Output charset |
|---|
| 151 | */ |
|---|
| 152 | public function get_charset() |
|---|
| 153 | { |
|---|
| 154 | return $this->charset; |
|---|
| 155 | } |
|---|
| 156 | |
|---|
| 157 | /** |
|---|
| 158 | * Reset all saved properties |
|---|
| 159 | */ |
|---|
| 160 | public function reset() |
|---|
| 161 | { |
|---|
| 162 | $this->script_files = array(); |
|---|
| 163 | $this->scripts = array(); |
|---|
| 164 | $this->title = ''; |
|---|
| 165 | $this->header = ''; |
|---|
| 166 | $this->footer = ''; |
|---|
| 167 | $this->body = ''; |
|---|
| 168 | } |
|---|
| 169 | |
|---|
| 170 | /** |
|---|
| 171 | * Process template and write to stdOut |
|---|
| 172 | * |
|---|
| 173 | * @param string HTML template |
|---|
| 174 | * @param string Base for absolute paths |
|---|
| 175 | */ |
|---|
| 176 | public function write($templ='', $base_path='') |
|---|
| 177 | { |
|---|
| 178 | $output = empty($templ) ? $this->default_template : trim($templ); |
|---|
| 179 | |
|---|
| 180 | // set default page title |
|---|
| 181 | if (empty($this->title)) { |
|---|
| 182 | $this->title = 'Roundcube Mail'; |
|---|
| 183 | } |
|---|
| 184 | |
|---|
| 185 | // replace specialchars in content |
|---|
| 186 | $page_title = Q($this->title, 'show', FALSE); |
|---|
| 187 | $page_header = ''; |
|---|
| 188 | $page_footer = ''; |
|---|
| 189 | |
|---|
| 190 | // include meta tag with charset |
|---|
| 191 | if (!empty($this->charset)) { |
|---|
| 192 | if (!headers_sent()) { |
|---|
| 193 | header('Content-Type: text/html; charset=' . $this->charset); |
|---|
| 194 | } |
|---|
| 195 | $page_header = '<meta http-equiv="content-type"'; |
|---|
| 196 | $page_header.= ' content="text/html; charset='; |
|---|
| 197 | $page_header.= $this->charset . '" />'."\n"; |
|---|
| 198 | } |
|---|
| 199 | |
|---|
| 200 | // definition of the code to be placed in the document header and footer |
|---|
| 201 | if (is_array($this->script_files['head'])) { |
|---|
| 202 | foreach ($this->script_files['head'] as $file) { |
|---|
| 203 | $page_header .= html::script($file); |
|---|
| 204 | } |
|---|
| 205 | } |
|---|
| 206 | |
|---|
| 207 | $head_script = $this->scripts['head_top'] . $this->scripts['head']; |
|---|
| 208 | if (!empty($head_script)) { |
|---|
| 209 | $page_header .= html::script(array(), $head_script); |
|---|
| 210 | } |
|---|
| 211 | |
|---|
| 212 | if (!empty($this->header)) { |
|---|
| 213 | $page_header .= $this->header; |
|---|
| 214 | } |
|---|
| 215 | |
|---|
| 216 | // put docready commands into page footer |
|---|
| 217 | if (!empty($this->scripts['docready'])) { |
|---|
| 218 | $this->add_script('$(document).ready(function(){ ' . $this->scripts['docready'] . "\n});", 'foot'); |
|---|
| 219 | } |
|---|
| 220 | |
|---|
| 221 | if (is_array($this->script_files['foot'])) { |
|---|
| 222 | foreach ($this->script_files['foot'] as $file) { |
|---|
| 223 | $page_footer .= html::script($file); |
|---|
| 224 | } |
|---|
| 225 | } |
|---|
| 226 | |
|---|
| 227 | if (!empty($this->footer)) { |
|---|
| 228 | $page_footer .= $this->footer . "\n"; |
|---|
| 229 | } |
|---|
| 230 | |
|---|
| 231 | if (!empty($this->scripts['foot'])) { |
|---|
| 232 | $page_footer .= html::script(array(), $this->scripts['foot']); |
|---|
| 233 | } |
|---|
| 234 | |
|---|
| 235 | // find page header |
|---|
| 236 | if ($hpos = stripos($output, '</head>')) { |
|---|
| 237 | $page_header .= "\n"; |
|---|
| 238 | } |
|---|
| 239 | else { |
|---|
| 240 | if (!is_numeric($hpos)) { |
|---|
| 241 | $hpos = stripos($output, '<body'); |
|---|
| 242 | } |
|---|
| 243 | if (!is_numeric($hpos) && ($hpos = stripos($output, '<html'))) { |
|---|
| 244 | while ($output[$hpos] != '>') { |
|---|
| 245 | $hpos++; |
|---|
| 246 | } |
|---|
| 247 | $hpos++; |
|---|
| 248 | } |
|---|
| 249 | $page_header = "<head>\n<title>$page_title</title>\n$page_header\n</head>\n"; |
|---|
| 250 | } |
|---|
| 251 | |
|---|
| 252 | // add page hader |
|---|
| 253 | if ($hpos) { |
|---|
| 254 | $output = substr_replace($output, $page_header, $hpos, 0); |
|---|
| 255 | } |
|---|
| 256 | else { |
|---|
| 257 | $output = $page_header . $output; |
|---|
| 258 | } |
|---|
| 259 | |
|---|
| 260 | // add page footer |
|---|
| 261 | if (($fpos = strripos($output, '</body>')) || ($fpos = strripos($output, '</html>'))) { |
|---|
| 262 | $output = substr_replace($output, $page_footer."\n", $fpos, 0); |
|---|
| 263 | } |
|---|
| 264 | else { |
|---|
| 265 | $output .= "\n".$page_footer; |
|---|
| 266 | } |
|---|
| 267 | |
|---|
| 268 | // add css files in head, before scripts, for speed up with parallel downloads |
|---|
| 269 | if (!empty($this->css_files) && |
|---|
| 270 | (($pos = stripos($output, '<script ')) || ($pos = stripos($output, '</head>'))) |
|---|
| 271 | ) { |
|---|
| 272 | $css = ''; |
|---|
| 273 | foreach ($this->css_files as $file) { |
|---|
| 274 | $css .= html::tag('link', array('rel' => 'stylesheet', |
|---|
| 275 | 'type' => 'text/css', 'href' => $file, 'nl' => true)); |
|---|
| 276 | } |
|---|
| 277 | $output = substr_replace($output, $css, $pos, 0); |
|---|
| 278 | } |
|---|
| 279 | |
|---|
| 280 | $this->base_path = $base_path; |
|---|
| 281 | |
|---|
| 282 | // correct absolute paths in images and other tags |
|---|
| 283 | // add timestamp to .js and .css filename |
|---|
| 284 | $output = preg_replace_callback( |
|---|
| 285 | '!(src|href|background)=(["\']?)([a-z0-9/_.-]+)(["\'\s>])!i', |
|---|
| 286 | array($this, 'file_callback'), $output); |
|---|
| 287 | $output = str_replace('$__skin_path', $base_path, $output); |
|---|
| 288 | |
|---|
| 289 | // trigger hook with final HTML content to be sent |
|---|
| 290 | $hook = rcmail::get_instance()->plugins->exec_hook("send_page", array('content' => $output)); |
|---|
| 291 | if (!$hook['abort']) { |
|---|
| 292 | if ($this->charset != RCMAIL_CHARSET) { |
|---|
| 293 | echo rcube_charset_convert($hook['content'], RCMAIL_CHARSET, $this->charset); |
|---|
| 294 | } |
|---|
| 295 | else { |
|---|
| 296 | echo $hook['content']; |
|---|
| 297 | } |
|---|
| 298 | } |
|---|
| 299 | } |
|---|
| 300 | |
|---|
| 301 | /** |
|---|
| 302 | * Callback function for preg_replace_callback in write() |
|---|
| 303 | * |
|---|
| 304 | * @return string Parsed string |
|---|
| 305 | */ |
|---|
| 306 | private function file_callback($matches) |
|---|
| 307 | { |
|---|
| 308 | $file = $matches[3]; |
|---|
| 309 | |
|---|
| 310 | // correct absolute paths |
|---|
| 311 | if ($file[0] == '/') { |
|---|
| 312 | $file = $this->base_path . $file; |
|---|
| 313 | } |
|---|
| 314 | |
|---|
| 315 | // add file modification timestamp |
|---|
| 316 | if (preg_match('/\.(js|css)$/', $file)) { |
|---|
| 317 | if ($fs = @filemtime($file)) { |
|---|
| 318 | $file .= '?s=' . $fs; |
|---|
| 319 | } |
|---|
| 320 | } |
|---|
| 321 | |
|---|
| 322 | return $matches[1] . '=' . $matches[2] . $file . $matches[4]; |
|---|
| 323 | } |
|---|
| 324 | } |
|---|