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