PageRenderTime 56ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/system/core/Output.php

https://github.com/freeacger/CodeIgniter
PHP | 936 lines | 457 code | 139 blank | 340 comment | 58 complexity | 430fa8abd38bcbf9d94638ea40fc9c97 MD5 | raw file
  1. <?php
  2. /**
  3. * CodeIgniter
  4. *
  5. * An open source application development framework for PHP 5.2.4 or newer
  6. *
  7. * NOTICE OF LICENSE
  8. *
  9. * Licensed under the Open Software License version 3.0
  10. *
  11. * This source file is subject to the Open Software License (OSL 3.0) that is
  12. * bundled with this package in the files license.txt / license.rst. It is
  13. * also available through the world wide web at this URL:
  14. * http://opensource.org/licenses/OSL-3.0
  15. * If you did not receive a copy of the license and are unable to obtain it
  16. * through the world wide web, please send an email to
  17. * licensing@ellislab.com so we can send you a copy immediately.
  18. *
  19. * @package CodeIgniter
  20. * @author EllisLab Dev Team
  21. * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/)
  22. * @license http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
  23. * @link http://codeigniter.com
  24. * @since Version 1.0
  25. * @filesource
  26. */
  27. defined('BASEPATH') OR exit('No direct script access allowed');
  28. /**
  29. * Output Class
  30. *
  31. * Responsible for sending final output to the browser.
  32. *
  33. * @package CodeIgniter
  34. * @subpackage Libraries
  35. * @category Output
  36. * @author EllisLab Dev Team
  37. * @link http://codeigniter.com/user_guide/libraries/output.html
  38. */
  39. class CI_Output {
  40. /**
  41. * Final output string
  42. *
  43. * @var string
  44. */
  45. public $final_output;
  46. /**
  47. * Cache expiration time
  48. *
  49. * @var int
  50. */
  51. public $cache_expiration = 0;
  52. /**
  53. * List of server headers
  54. *
  55. * @var array
  56. */
  57. public $headers = array();
  58. /**
  59. * List of mime types
  60. *
  61. * @var array
  62. */
  63. public $mimes = array();
  64. /**
  65. * Mime-type for the current page
  66. *
  67. * @var string
  68. */
  69. protected $mime_type = 'text/html';
  70. /**
  71. * Enable Profiler flag
  72. *
  73. * @var bool
  74. */
  75. public $enable_profiler = FALSE;
  76. /**
  77. * zLib output compression flag
  78. *
  79. * @var bool
  80. */
  81. protected $_zlib_oc = FALSE;
  82. /**
  83. * List of profiler sections
  84. *
  85. * @var array
  86. */
  87. protected $_profiler_sections = array();
  88. /**
  89. * Parse markers flag
  90. *
  91. * Whether or not to parse variables like {elapsed_time} and {memory_usage}.
  92. *
  93. * @var bool
  94. */
  95. public $parse_exec_vars = TRUE;
  96. /**
  97. * Class constructor
  98. *
  99. * Determines whether zLib output compression will be used.
  100. *
  101. * @return void
  102. */
  103. public function __construct()
  104. {
  105. $this->_zlib_oc = (bool) @ini_get('zlib.output_compression');
  106. // Get mime types for later
  107. $this->mimes =& get_mimes();
  108. log_message('debug', 'Output Class Initialized');
  109. }
  110. // --------------------------------------------------------------------
  111. /**
  112. * Get Output
  113. *
  114. * Returns the current output string.
  115. *
  116. * @return string
  117. */
  118. public function get_output()
  119. {
  120. return $this->final_output;
  121. }
  122. // --------------------------------------------------------------------
  123. /**
  124. * Set Output
  125. *
  126. * Sets the output string.
  127. *
  128. * @param string $output Output data
  129. * @return CI_Output
  130. */
  131. public function set_output($output)
  132. {
  133. $this->final_output = $output;
  134. return $this;
  135. }
  136. // --------------------------------------------------------------------
  137. /**
  138. * Append Output
  139. *
  140. * Appends data onto the output string.
  141. *
  142. * @param string $output Data to append
  143. * @return CI_Output
  144. */
  145. public function append_output($output)
  146. {
  147. if (empty($this->final_output))
  148. {
  149. $this->final_output = $output;
  150. }
  151. else
  152. {
  153. $this->final_output .= $output;
  154. }
  155. return $this;
  156. }
  157. // --------------------------------------------------------------------
  158. /**
  159. * Set Header
  160. *
  161. * Lets you set a server header which will be sent with the final output.
  162. *
  163. * Note: If a file is cached, headers will not be sent.
  164. * @todo We need to figure out how to permit headers to be cached.
  165. *
  166. * @param string $header Header
  167. * @param bool $replace Whether to replace the old header value, if already set
  168. * @return CI_Output
  169. */
  170. public function set_header($header, $replace = TRUE)
  171. {
  172. // If zlib.output_compression is enabled it will compress the output,
  173. // but it will not modify the content-length header to compensate for
  174. // the reduction, causing the browser to hang waiting for more data.
  175. // We'll just skip content-length in those cases.
  176. if ($this->_zlib_oc && strncasecmp($header, 'content-length', 14) === 0)
  177. {
  178. return $this;
  179. }
  180. $this->headers[] = array($header, $replace);
  181. return $this;
  182. }
  183. // --------------------------------------------------------------------
  184. /**
  185. * Set Content-Type Header
  186. *
  187. * @param string $mime_type Extension of the file we're outputting
  188. * @param string $charset Character set (default: NULL)
  189. * @return CI_Output
  190. */
  191. public function set_content_type($mime_type, $charset = NULL)
  192. {
  193. if (strpos($mime_type, '/') === FALSE)
  194. {
  195. $extension = ltrim($mime_type, '.');
  196. // Is this extension supported?
  197. if (isset($this->mimes[$extension]))
  198. {
  199. $mime_type =& $this->mimes[$extension];
  200. if (is_array($mime_type))
  201. {
  202. $mime_type = current($mime_type);
  203. }
  204. }
  205. }
  206. $this->mime_type = $mime_type;
  207. if (empty($charset))
  208. {
  209. $charset = config_item('charset');
  210. }
  211. $header = 'Content-Type: '.$mime_type
  212. .(empty($charset) ? NULL : '; charset='.$charset);
  213. $this->headers[] = array($header, TRUE);
  214. return $this;
  215. }
  216. // --------------------------------------------------------------------
  217. /**
  218. * Get Current Content-Type Header
  219. *
  220. * @return string 'text/html', if not already set
  221. */
  222. public function get_content_type()
  223. {
  224. for ($i = 0, $c = count($this->headers); $i < $c; $i++)
  225. {
  226. if (sscanf($this->headers[$i][0], 'Content-Type: %[^;]', $content_type) === 1)
  227. {
  228. return $content_type;
  229. }
  230. }
  231. return 'text/html';
  232. }
  233. // --------------------------------------------------------------------
  234. /**
  235. * Get Header
  236. *
  237. * @param string $header_name
  238. * @return string
  239. */
  240. public function get_header($header)
  241. {
  242. // Combine headers already sent with our batched headers
  243. $headers = array_merge(
  244. // We only need [x][0] from our multi-dimensional array
  245. array_map('array_shift', $this->headers),
  246. headers_list()
  247. );
  248. if (empty($headers) OR empty($header))
  249. {
  250. return NULL;
  251. }
  252. for ($i = 0, $c = count($headers); $i < $c; $i++)
  253. {
  254. if (strncasecmp($header, $headers[$i], $l = strlen($header)) === 0)
  255. {
  256. return trim(substr($headers[$i], $l+1));
  257. }
  258. }
  259. return NULL;
  260. }
  261. // --------------------------------------------------------------------
  262. /**
  263. * Set HTTP Status Header
  264. *
  265. * As of version 1.7.2, this is an alias for common function
  266. * set_status_header().
  267. *
  268. * @param int $code Status code (default: 200)
  269. * @param string $text Optional message
  270. * @return CI_Output
  271. */
  272. public function set_status_header($code = 200, $text = '')
  273. {
  274. set_status_header($code, $text);
  275. return $this;
  276. }
  277. // --------------------------------------------------------------------
  278. /**
  279. * Enable/disable Profiler
  280. *
  281. * @param bool $val TRUE to enable or FALSE to disable
  282. * @return CI_Output
  283. */
  284. public function enable_profiler($val = TRUE)
  285. {
  286. $this->enable_profiler = is_bool($val) ? $val : TRUE;
  287. return $this;
  288. }
  289. // --------------------------------------------------------------------
  290. /**
  291. * Set Profiler Sections
  292. *
  293. * Allows override of default/config settings for
  294. * Profiler section display.
  295. *
  296. * @param array $sections Profiler sections
  297. * @return CI_Output
  298. */
  299. public function set_profiler_sections($sections)
  300. {
  301. if (isset($sections['query_toggle_count']))
  302. {
  303. $this->_profiler_sections['query_toggle_count'] = (int) $sections['query_toggle_count'];
  304. unset($sections['query_toggle_count']);
  305. }
  306. foreach ($sections as $section => $enable)
  307. {
  308. $this->_profiler_sections[$section] = ($enable !== FALSE);
  309. }
  310. return $this;
  311. }
  312. // --------------------------------------------------------------------
  313. /**
  314. * Set Cache
  315. *
  316. * @param int $time Cache expiration time in seconds
  317. * @return CI_Output
  318. */
  319. public function cache($time)
  320. {
  321. $this->cache_expiration = is_numeric($time) ? $time : 0;
  322. return $this;
  323. }
  324. // --------------------------------------------------------------------
  325. /**
  326. * Display Output
  327. *
  328. * Processes sends the sends finalized output data to the browser along
  329. * with any server headers and profile data. It also stops benchmark
  330. * timers so the page rendering speed and memory usage can be shown.
  331. *
  332. * Note: All "view" data is automatically put into $this->final_output
  333. * by controller class.
  334. *
  335. * @uses CI_Output::$final_output
  336. * @param string $output Output data override
  337. * @return void
  338. */
  339. public function _display($output = '')
  340. {
  341. // Note: We use globals because we can't use $CI =& get_instance()
  342. // since this function is sometimes called by the caching mechanism,
  343. // which happens before the CI super object is available.
  344. global $BM, $CFG;
  345. // Grab the super object if we can.
  346. if (class_exists('CI_Controller'))
  347. {
  348. $CI =& get_instance();
  349. }
  350. // --------------------------------------------------------------------
  351. // Set the output data
  352. if ($output === '')
  353. {
  354. $output =& $this->final_output;
  355. }
  356. // --------------------------------------------------------------------
  357. // Is minify requested?
  358. if ($CFG->item('minify_output') === TRUE)
  359. {
  360. $output = $this->minify($output, $this->mime_type);
  361. }
  362. // --------------------------------------------------------------------
  363. // Do we need to write a cache file? Only if the controller does not have its
  364. // own _output() method and we are not dealing with a cache file, which we
  365. // can determine by the existence of the $CI object above
  366. if ($this->cache_expiration > 0 && isset($CI) && ! method_exists($CI, '_output'))
  367. {
  368. $this->_write_cache($output);
  369. }
  370. // --------------------------------------------------------------------
  371. // Parse out the elapsed time and memory usage,
  372. // then swap the pseudo-variables with the data
  373. $elapsed = $BM->elapsed_time('total_execution_time_start', 'total_execution_time_end');
  374. if ($this->parse_exec_vars === TRUE)
  375. {
  376. $memory = round(memory_get_usage() / 1024 / 1024, 2).'MB';
  377. $output = str_replace(array('{elapsed_time}', '{memory_usage}'), array($elapsed, $memory), $output);
  378. }
  379. // --------------------------------------------------------------------
  380. // Is compression requested?
  381. if ($CFG->item('compress_output') === TRUE && $this->_zlib_oc === FALSE
  382. && extension_loaded('zlib')
  383. && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE)
  384. {
  385. ob_start('ob_gzhandler');
  386. }
  387. // --------------------------------------------------------------------
  388. // Are there any server headers to send?
  389. if (count($this->headers) > 0)
  390. {
  391. foreach ($this->headers as $header)
  392. {
  393. @header($header[0], $header[1]);
  394. }
  395. }
  396. // --------------------------------------------------------------------
  397. // Does the $CI object exist?
  398. // If not we know we are dealing with a cache file so we'll
  399. // simply echo out the data and exit.
  400. if ( ! isset($CI))
  401. {
  402. echo $output;
  403. log_message('debug', 'Final output sent to browser');
  404. log_message('debug', 'Total execution time: '.$elapsed);
  405. return;
  406. }
  407. // --------------------------------------------------------------------
  408. // Do we need to generate profile data?
  409. // If so, load the Profile class and run it.
  410. if ($this->enable_profiler === TRUE)
  411. {
  412. $CI->load->library('profiler');
  413. if ( ! empty($this->_profiler_sections))
  414. {
  415. $CI->profiler->set_sections($this->_profiler_sections);
  416. }
  417. // If the output data contains closing </body> and </html> tags
  418. // we will remove them and add them back after we insert the profile data
  419. $output = preg_replace('|</body>.*?</html>|is', '', $output, -1, $count).$CI->profiler->run();
  420. if ($count > 0)
  421. {
  422. $output .= '</body></html>';
  423. }
  424. }
  425. // Does the controller contain a function named _output()?
  426. // If so send the output there. Otherwise, echo it.
  427. if (method_exists($CI, '_output'))
  428. {
  429. $CI->_output($output);
  430. }
  431. else
  432. {
  433. echo $output; // Send it to the browser!
  434. }
  435. log_message('debug', 'Final output sent to browser');
  436. log_message('debug', 'Total execution time: '.$elapsed);
  437. }
  438. // --------------------------------------------------------------------
  439. /**
  440. * Write Cache
  441. *
  442. * @param string $output Output data to cache
  443. * @return void
  444. */
  445. public function _write_cache($output)
  446. {
  447. $CI =& get_instance();
  448. $path = $CI->config->item('cache_path');
  449. $cache_path = ($path === '') ? APPPATH.'cache/' : $path;
  450. if ( ! is_dir($cache_path) OR ! is_really_writable($cache_path))
  451. {
  452. log_message('error', 'Unable to write cache file: '.$cache_path);
  453. return;
  454. }
  455. $uri = $CI->config->item('base_url').
  456. $CI->config->item('index_page').
  457. $CI->uri->uri_string();
  458. $cache_path .= md5($uri);
  459. if ( ! $fp = @fopen($cache_path, FOPEN_WRITE_CREATE_DESTRUCTIVE))
  460. {
  461. log_message('error', 'Unable to write cache file: '.$cache_path);
  462. return;
  463. }
  464. $expire = time() + ($this->cache_expiration * 60);
  465. // Put together our serialized info.
  466. $cache_info = serialize(array(
  467. 'expire' => $expire,
  468. 'headers' => $this->headers
  469. ));
  470. if (flock($fp, LOCK_EX))
  471. {
  472. fwrite($fp, $cache_info.'ENDCI--->'.$output);
  473. flock($fp, LOCK_UN);
  474. }
  475. else
  476. {
  477. log_message('error', 'Unable to secure a file lock for file at: '.$cache_path);
  478. return;
  479. }
  480. fclose($fp);
  481. @chmod($cache_path, FILE_WRITE_MODE);
  482. log_message('debug', 'Cache file written: '.$cache_path);
  483. // Send HTTP cache-control headers to browser to match file cache settings.
  484. $this->set_cache_header($_SERVER['REQUEST_TIME'], $expire);
  485. }
  486. // --------------------------------------------------------------------
  487. /**
  488. * Update/serve cached output
  489. *
  490. * @uses CI_Config
  491. * @uses CI_URI
  492. *
  493. * @param object &$CFG CI_Config class instance
  494. * @param object &$URI CI_URI class instance
  495. * @return bool TRUE on success or FALSE on failure
  496. */
  497. public function _display_cache(&$CFG, &$URI)
  498. {
  499. $cache_path = ($CFG->item('cache_path') === '') ? APPPATH.'cache/' : $CFG->item('cache_path');
  500. // Build the file path. The file name is an MD5 hash of the full URI
  501. $uri = $CFG->item('base_url').$CFG->item('index_page').$URI->uri_string;
  502. $filepath = $cache_path.md5($uri);
  503. if ( ! @file_exists($filepath) OR ! $fp = @fopen($filepath, FOPEN_READ))
  504. {
  505. return FALSE;
  506. }
  507. flock($fp, LOCK_SH);
  508. $cache = (filesize($filepath) > 0) ? fread($fp, filesize($filepath)) : '';
  509. flock($fp, LOCK_UN);
  510. fclose($fp);
  511. // Look for embedded serialized file info.
  512. if ( ! preg_match('/^(.*)ENDCI--->/', $cache, $match))
  513. {
  514. return FALSE;
  515. }
  516. $cache_info = unserialize($match[1]);
  517. $expire = $cache_info['expire'];
  518. $last_modified = filemtime($cache_path);
  519. // Has the file expired?
  520. if ($_SERVER['REQUEST_TIME'] >= $expire && is_really_writable($cache_path))
  521. {
  522. // If so we'll delete it.
  523. @unlink($filepath);
  524. log_message('debug', 'Cache file has expired. File deleted.');
  525. return FALSE;
  526. }
  527. else
  528. {
  529. // Or else send the HTTP cache control headers.
  530. $this->set_cache_header($last_modified, $expire);
  531. }
  532. // Add headers from cache file.
  533. foreach ($cache_info['headers'] as $header)
  534. {
  535. $this->set_header($header[0], $header[1]);
  536. }
  537. // Display the cache
  538. $this->_display(substr($cache, strlen($match[0])));
  539. log_message('debug', 'Cache file is current. Sending it to browser.');
  540. return TRUE;
  541. }
  542. // --------------------------------------------------------------------
  543. /**
  544. * Delete cache
  545. *
  546. * @param string $uri URI string
  547. * @return bool
  548. */
  549. public function delete_cache($uri = '')
  550. {
  551. $CI =& get_instance();
  552. $cache_path = $CI->config->item('cache_path');
  553. if ($cache_path === '')
  554. {
  555. $cache_path = APPPATH.'cache/';
  556. }
  557. if ( ! is_dir($cache_path))
  558. {
  559. log_message('error', 'Unable to find cache path: '.$cache_path);
  560. return FALSE;
  561. }
  562. if (empty($uri))
  563. {
  564. $uri = $CI->uri->uri_string();
  565. }
  566. $cache_path .= md5($CI->config->item('base_url').$CI->config->item('index_page').$uri);
  567. if ( ! @unlink($cache_path))
  568. {
  569. log_message('error', 'Unable to delete cache file for '.$uri);
  570. return FALSE;
  571. }
  572. return TRUE;
  573. }
  574. // --------------------------------------------------------------------
  575. /**
  576. * Set Cache Header
  577. *
  578. * Set the HTTP headers to match the server-side file cache settings
  579. * in order to reduce bandwidth.
  580. *
  581. * @param int $last_modified Timestamp of when the page was last modified
  582. * @param int $expiration Timestamp of when should the requested page expire from cache
  583. * @return void
  584. */
  585. public function set_cache_header($last_modified, $expiration)
  586. {
  587. $max_age = $expiration - $_SERVER['REQUEST_TIME'];
  588. if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $last_modified <= strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']))
  589. {
  590. $this->set_status_header(304);
  591. exit;
  592. }
  593. else
  594. {
  595. header('Pragma: public');
  596. header('Cache-Control: max-age=' . $max_age . ', public');
  597. header('Expires: '.gmdate('D, d M Y H:i:s', $expiration).' GMT');
  598. header('Last-modified: '.gmdate('D, d M Y H:i:s', $last_modified).' GMT');
  599. }
  600. }
  601. // --------------------------------------------------------------------
  602. /**
  603. * Minify
  604. *
  605. * Reduce excessive size of HTML/CSS/JavaScript content.
  606. *
  607. * @param string $output Output to minify
  608. * @param string $type Output content MIME type
  609. * @return string Minified output
  610. */
  611. public function minify($output, $type = 'text/html')
  612. {
  613. switch ($type)
  614. {
  615. case 'text/html':
  616. if (($size_before = strlen($output)) === 0)
  617. {
  618. return '';
  619. }
  620. // Find all the <pre>,<code>,<textarea>, and <javascript> tags
  621. // We'll want to return them to this unprocessed state later.
  622. preg_match_all('{<pre.+</pre>}msU', $output, $pres_clean);
  623. preg_match_all('{<code.+</code>}msU', $output, $codes_clean);
  624. preg_match_all('{<textarea.+</textarea>}msU', $output, $textareas_clean);
  625. preg_match_all('{<script.+</script>}msU', $output, $javascript_clean);
  626. // Minify the CSS in all the <style> tags.
  627. preg_match_all('{<style.+</style>}msU', $output, $style_clean);
  628. foreach ($style_clean[0] as $s)
  629. {
  630. $output = str_replace($s, $this->_minify_script_style($s, TRUE), $output);
  631. }
  632. // Minify the javascript in <script> tags.
  633. foreach ($javascript_clean[0] as $s)
  634. {
  635. $javascript_mini[] = $this->_minify_script_style($s, TRUE);
  636. }
  637. // Replace multiple spaces with a single space.
  638. $output = preg_replace('!\s{2,}!', ' ', $output);
  639. // Remove comments (non-MSIE conditionals)
  640. $output = preg_replace('{\s*<!--[^\[<>].*(?<!!)-->\s*}msU', '', $output);
  641. // Remove spaces around block-level elements.
  642. $output = preg_replace('/\s*(<\/?(html|head|title|meta|script|link|style|body|table|thead|tbody|tfoot|tr|th|td|h[1-6]|div|p|br)[^>]*>)\s*/is', '$1', $output);
  643. // Replace mangled <pre> etc. tags with unprocessed ones.
  644. if ( ! empty($pres_clean))
  645. {
  646. preg_match_all('{<pre.+</pre>}msU', $output, $pres_messed);
  647. $output = str_replace($pres_messed[0], $pres_clean[0], $output);
  648. }
  649. if ( ! empty($codes_clean))
  650. {
  651. preg_match_all('{<code.+</code>}msU', $output, $codes_messed);
  652. $output = str_replace($codes_messed[0], $codes_clean[0], $output);
  653. }
  654. if ( ! empty($textareas_clean))
  655. {
  656. preg_match_all('{<textarea.+</textarea>}msU', $output, $textareas_messed);
  657. $output = str_replace($textareas_messed[0], $textareas_clean[0], $output);
  658. }
  659. if (isset($javascript_mini))
  660. {
  661. preg_match_all('{<script.+</script>}msU', $output, $javascript_messed);
  662. $output = str_replace($javascript_messed[0], $javascript_mini, $output);
  663. }
  664. $size_removed = $size_before - strlen($output);
  665. $savings_percent = round(($size_removed / $size_before * 100));
  666. log_message('debug', 'Minifier shaved '.($size_removed / 1000).'KB ('.$savings_percent.'%) off final HTML output.');
  667. break;
  668. case 'text/css':
  669. case 'text/javascript':
  670. $output = $this->_minify_script_style($output);
  671. break;
  672. default: break;
  673. }
  674. return $output;
  675. }
  676. // --------------------------------------------------------------------
  677. /**
  678. * Minify Style and Script
  679. *
  680. * Reduce excessive size of CSS/JavaScript content. To remove spaces this
  681. * script walks the string as an array and determines if the pointer is inside
  682. * a string created by single quotes or double quotes. spaces inside those
  683. * strings are not stripped. Opening and closing tags are severed from
  684. * the string initially and saved without stripping whitespace to preserve
  685. * the tags and any associated properties if tags are present
  686. *
  687. * Minification logic/workflow is similar to methods used by Douglas Crockford
  688. * in JSMIN. http://www.crockford.com/javascript/jsmin.html
  689. *
  690. * KNOWN ISSUE: ending a line with a closing parenthesis ')' and no semicolon
  691. * where there should be one will break the Javascript. New lines after a
  692. * closing parenthesis are not recognized by the script. For best results
  693. * be sure to terminate lines with a semicolon when appropriate.
  694. *
  695. * @param string $output Output to minify
  696. * @param bool $has_tags Specify if the output has style or script tags
  697. * @return string Minified output
  698. */
  699. protected function _minify_script_style($output, $has_tags = FALSE)
  700. {
  701. // We only need this if there are tags in the file
  702. if ($has_tags === TRUE)
  703. {
  704. // Remove opening tag and save for later
  705. $pos = strpos($output, '>') + 1;
  706. $open_tag = substr($output, 0, $pos);
  707. $output = substr_replace($output, '', 0, $pos);
  708. // Remove closing tag and save it for later
  709. $end_pos = strlen($output);
  710. $pos = strpos($output, '</');
  711. $closing_tag = substr($output, $pos, $end_pos);
  712. $output = substr_replace($output, '', $pos);
  713. }
  714. // Remove CSS comments
  715. $output = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!i', '', $output);
  716. // Remove spaces around curly brackets, colons,
  717. // semi-colons, parenthesis, commas
  718. $output = preg_replace('!\s*(:|;|,|}|{|\(|\))\s*!i', '$1', $output);
  719. // Replace tabs with spaces
  720. // Replace carriage returns & multiple new lines with single new line
  721. // and trim any leading or trailing whitespace
  722. $output = trim(preg_replace(array('/\t+/', '/\r/', '/\n+/'), array(' ', "\n", "\n"), $output));
  723. // Remove spaces when safe to do so.
  724. $in_string = $in_dstring = $prev = FALSE;
  725. $array_output = str_split($output);
  726. foreach ($array_output as $key => $value)
  727. {
  728. if ($in_string === FALSE && $in_dstring === FALSE)
  729. {
  730. if ($value === ' ')
  731. {
  732. // Get the next element in the array for comparisons
  733. $next = $array_output[$key + 1];
  734. // Strip spaces preceded/followed by a non-ASCII character
  735. // or not preceded/followed by an alphanumeric
  736. // or not preceded/followed \ $ and _
  737. if ((preg_match('/^[\x20-\x7f]*$/D', $next) OR preg_match('/^[\x20-\x7f]*$/D', $prev))
  738. && ( ! ctype_alnum($next) OR ! ctype_alnum($prev))
  739. && ! in_array($next, array('\\', '_', '$'), TRUE)
  740. && ! in_array($prev, array('\\', '_', '$'), TRUE)
  741. )
  742. {
  743. unset($array_output[$key]);
  744. }
  745. }
  746. else
  747. {
  748. // Save this value as previous for the next iteration
  749. // if it is not a blank space
  750. $prev = $value;
  751. }
  752. }
  753. if ($value === "'")
  754. {
  755. $in_string = ! $in_string;
  756. }
  757. elseif ($value === '"')
  758. {
  759. $in_dstring = ! $in_dstring;
  760. }
  761. }
  762. // Put the string back together after spaces have been stripped
  763. $output = implode($array_output);
  764. // Remove new line characters unless previous or next character is
  765. // printable or Non-ASCII
  766. preg_match_all('/[\n]/', $output, $lf, PREG_OFFSET_CAPTURE);
  767. $removed_lf = 0;
  768. foreach ($lf as $feed_position)
  769. {
  770. foreach ($feed_position as $position)
  771. {
  772. $position = $position[1] - $removed_lf;
  773. $next = $output[$position + 1];
  774. $prev = $output[$position - 1];
  775. if ( ! ctype_print($next) && ! ctype_print($prev)
  776. && ! preg_match('/^[\x20-\x7f]*$/D', $next)
  777. && ! preg_match('/^[\x20-\x7f]*$/D', $prev)
  778. )
  779. {
  780. $output = substr_replace($output, '', $position, 1);
  781. $removed_lf++;
  782. }
  783. }
  784. }
  785. // Put the opening and closing tags back if applicable
  786. return isset($open_tag)
  787. ? $open_tag.$output.$closing_tag
  788. : $output;
  789. }
  790. }
  791. /* End of file Output.php */
  792. /* Location: ./system/core/Output.php */