blob: cf5178a0ccb991819b65b65407c47294b5247619 [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 */
Eric Roberts3e6b5822013-01-17 18:30:25 -060061 public $headers = array();
Michael Dodge362b8002013-01-04 23:18:39 -070062
63 /**
64 * List of mime types
65 *
66 * @var array
67 */
Eric Roberts3e6b5822013-01-17 18:30:25 -060068 public $mimes = array();
Michael Dodge362b8002013-01-04 23:18:39 -070069
70 /**
71 * Mime-type for the current page
72 *
73 * @var string
74 */
Eric Roberts3e6b5822013-01-17 18:30:25 -060075 protected $mime_type = 'text/html';
Michael Dodge362b8002013-01-04 23:18:39 -070076
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 */
Eric Roberts3e6b5822013-01-17 18:30:25 -060089 protected $_zlib_oc = FALSE;
Michael Dodge362b8002013-01-04 23:18:39 -070090
91 /**
92 * List of profiler sections
93 *
94 * @var array
95 */
Eric Roberts3e6b5822013-01-17 18:30:25 -060096 protected $_profiler_sections = array();
Michael Dodge362b8002013-01-04 23:18:39 -070097
98 /**
99 * Parse markers flag
100 *
101 * Whether or not to parse variables like {elapsed_time} and {memory_usage}.
102 *
103 * @var bool
104 */
Eric Roberts3e6b5822013-01-17 18:30:25 -0600105 public $parse_exec_vars = TRUE;
Michael Dodge362b8002013-01-04 23:18:39 -0700106
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);
Purwandi5dc6d512013-01-19 17:43:08 +0700546
Eric Robertsc90e67e2013-01-11 21:20:54 -0600547 // Put together our serialized info.
548 $cache_info = serialize(array(
549 'expire' => $expire,
550 'headers' => $this->headers
551 ));
Michael Dodge362b8002013-01-04 23:18:39 -0700552
553 if (flock($fp, LOCK_EX))
554 {
Eric Robertsc90e67e2013-01-11 21:20:54 -0600555 fwrite($fp, $cache_info.'ENDCI--->'.$output);
Michael Dodge362b8002013-01-04 23:18:39 -0700556 flock($fp, LOCK_UN);
557 }
558 else
559 {
560 log_message('error', 'Unable to secure a file lock for file at: '.$cache_path);
561 return;
562 }
563 fclose($fp);
564 @chmod($cache_path, FILE_WRITE_MODE);
565
566 log_message('debug', 'Cache file written: '.$cache_path);
567
568 // Send HTTP cache-control headers to browser to match file cache settings.
569 $this->set_cache_header($_SERVER['REQUEST_TIME'], $expire);
570 }
571
572 // --------------------------------------------------------------------
573
574 /**
575 * Update/serve cached output
576 *
577 * @uses CI_Config
578 * @uses CI_URI
579 *
580 * @param object &$CFG CI_Config class instance
581 * @param object &$URI CI_URI class instance
582 * @return bool TRUE on success or FALSE on failure
583 */
584 public function _display_cache(&$CFG, &$URI)
585 {
586 $cache_path = ($CFG->item('cache_path') === '') ? APPPATH.'cache/' : $CFG->item('cache_path');
587
588 // Build the file path. The file name is an MD5 hash of the full URI
589 $uri = $CFG->item('base_url').$CFG->item('index_page').$URI->uri_string;
590 $filepath = $cache_path.md5($uri);
591
592 if ( ! @file_exists($filepath) OR ! $fp = @fopen($filepath, FOPEN_READ))
593 {
594 return FALSE;
595 }
596
597 flock($fp, LOCK_SH);
598
599 $cache = (filesize($filepath) > 0) ? fread($fp, filesize($filepath)) : '';
600
601 flock($fp, LOCK_UN);
602 fclose($fp);
603
Eric Robertsc90e67e2013-01-11 21:20:54 -0600604 // Look for embedded serialized file info.
605 if ( ! preg_match('/^(.*)ENDCI--->/', $cache, $match))
Michael Dodge362b8002013-01-04 23:18:39 -0700606 {
607 return FALSE;
608 }
Purwandi5dc6d512013-01-19 17:43:08 +0700609
Eric Robertsc90e67e2013-01-11 21:20:54 -0600610 $cache_info = unserialize($match[1]);
611 $expire = $cache_info['expire'];
Michael Dodge362b8002013-01-04 23:18:39 -0700612
613 $last_modified = filemtime($cache_path);
Michael Dodge362b8002013-01-04 23:18:39 -0700614
615 // Has the file expired?
616 if ($_SERVER['REQUEST_TIME'] >= $expire && is_really_writable($cache_path))
617 {
618 // If so we'll delete it.
619 @unlink($filepath);
620 log_message('debug', 'Cache file has expired. File deleted.');
621 return FALSE;
622 }
623 else
624 {
625 // Or else send the HTTP cache control headers.
626 $this->set_cache_header($last_modified, $expire);
627 }
Purwandi5dc6d512013-01-19 17:43:08 +0700628
Eric Robertsc90e67e2013-01-11 21:20:54 -0600629 // Add headers from cache file.
630 foreach ($cache_info['headers'] as $header)
631 {
632 $this->set_header($header[0], $header[1]);
633 }
Michael Dodge362b8002013-01-04 23:18:39 -0700634
635 // Display the cache
636 $this->_display(substr($cache, strlen($match[0])));
637 log_message('debug', 'Cache file is current. Sending it to browser.');
638 return TRUE;
639 }
640
641 // --------------------------------------------------------------------
642
643 /**
644 * Delete cache
645 *
646 * @param string $uri URI string
647 * @return bool
648 */
649 public function delete_cache($uri = '')
650 {
651 $CI =& get_instance();
652 $cache_path = $CI->config->item('cache_path');
653 if ($cache_path === '')
654 {
655 $cache_path = APPPATH.'cache/';
656 }
657
658 if ( ! is_dir($cache_path))
659 {
660 log_message('error', 'Unable to find cache path: '.$cache_path);
661 return FALSE;
662 }
663
664 if (empty($uri))
665 {
666 $uri = $CI->uri->uri_string();
667 }
668
669 $cache_path .= md5($CI->config->item('base_url').$CI->config->item('index_page').$uri);
670
671 if ( ! @unlink($cache_path))
672 {
673 log_message('error', 'Unable to delete cache file for '.$uri);
674 return FALSE;
675 }
676
677 return TRUE;
678 }
679
680 // --------------------------------------------------------------------
681
682 /**
683 * Set Cache Header
684 *
685 * Set the HTTP headers to match the server-side file cache settings
686 * in order to reduce bandwidth.
687 *
688 * @param int $last_modified Timestamp of when the page was last modified
689 * @param int $expiration Timestamp of when should the requested page expire from cache
690 * @return void
691 */
692 public function set_cache_header($last_modified, $expiration)
693 {
694 $max_age = $expiration - $_SERVER['REQUEST_TIME'];
695
696 if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $last_modified <= strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']))
697 {
698 $this->set_status_header(304);
699 exit;
700 }
701 else
702 {
703 header('Pragma: public');
704 header('Cache-Control: max-age=' . $max_age . ', public');
705 header('Expires: '.gmdate('D, d M Y H:i:s', $expiration).' GMT');
706 header('Last-modified: '.gmdate('D, d M Y H:i:s', $last_modified).' GMT');
707 }
708 }
709
710 // --------------------------------------------------------------------
711
712 /**
713 * Minify
714 *
715 * Reduce excessive size of HTML/CSS/JavaScript content.
716 *
717 * @param string $output Output to minify
718 * @param string $type Output content MIME type
719 * @return string Minified output
720 */
721 public function minify($output, $type = 'text/html')
722 {
723 switch ($type)
724 {
725 case 'text/html':
726
727 if (($size_before = strlen($output)) === 0)
728 {
729 return '';
730 }
731
732 // Find all the <pre>,<code>,<textarea>, and <javascript> tags
733 // We'll want to return them to this unprocessed state later.
734 preg_match_all('{<pre.+</pre>}msU', $output, $pres_clean);
735 preg_match_all('{<code.+</code>}msU', $output, $codes_clean);
736 preg_match_all('{<textarea.+</textarea>}msU', $output, $textareas_clean);
737 preg_match_all('{<script.+</script>}msU', $output, $javascript_clean);
738
739 // Minify the CSS in all the <style> tags.
740 preg_match_all('{<style.+</style>}msU', $output, $style_clean);
741 foreach ($style_clean[0] as $s)
742 {
743 $output = str_replace($s, $this->_minify_script_style($s, TRUE), $output);
744 }
745
746 // Minify the javascript in <script> tags.
747 foreach ($javascript_clean[0] as $s)
748 {
749 $javascript_mini[] = $this->_minify_script_style($s, TRUE);
750 }
751
752 // Replace multiple spaces with a single space.
753 $output = preg_replace('!\s{2,}!', ' ', $output);
754
755 // Remove comments (non-MSIE conditionals)
Michael Dodge4d02e352013-01-04 23:22:51 -0700756 $output = preg_replace('{\s*<!--[^\[<>].*(?<!!)-->\s*}msU', '', $output);
Michael Dodge362b8002013-01-04 23:18:39 -0700757
758 // Remove spaces around block-level elements.
Purwandi5dc6d512013-01-19 17:43:08 +0700759 $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);
Michael Dodge362b8002013-01-04 23:18:39 -0700760
761 // Replace mangled <pre> etc. tags with unprocessed ones.
762
763 if ( ! empty($pres_clean))
764 {
765 preg_match_all('{<pre.+</pre>}msU', $output, $pres_messed);
766 $output = str_replace($pres_messed[0], $pres_clean[0], $output);
767 }
768
769 if ( ! empty($codes_clean))
770 {
771 preg_match_all('{<code.+</code>}msU', $output, $codes_messed);
772 $output = str_replace($codes_messed[0], $codes_clean[0], $output);
773 }
774
775 if ( ! empty($codes_clean))
776 {
777 preg_match_all('{<textarea.+</textarea>}msU', $output, $textareas_messed);
778 $output = str_replace($textareas_messed[0], $textareas_clean[0], $output);
779 }
780
781 if (isset($javascript_mini))
782 {
783 preg_match_all('{<script.+</script>}msU', $output, $javascript_messed);
784 $output = str_replace($javascript_messed[0], $javascript_mini, $output);
785 }
786
787 $size_removed = $size_before - strlen($output);
788 $savings_percent = round(($size_removed / $size_before * 100));
789
790 log_message('debug', 'Minifier shaved '.($size_removed / 1000).'KB ('.$savings_percent.'%) off final HTML output.');
791
792 break;
793
794 case 'text/css':
795 case 'text/javascript':
796
797 $output = $this->_minify_script_style($output);
798
799 break;
800
801 default: break;
802 }
803
804 return $output;
805 }
806
807 // --------------------------------------------------------------------
808
809 /**
810 * Minify Style and Script
811 *
812 * Reduce excessive size of CSS/JavaScript content. To remove spaces this
813 * script walks the string as an array and determines if the pointer is inside
814 * a string created by single quotes or double quotes. spaces inside those
815 * strings are not stripped. Opening and closing tags are severed from
816 * the string initially and saved without stripping whitespace to preserve
817 * the tags and any associated properties if tags are present
818 *
819 * Minification logic/workflow is similar to methods used by Douglas Crockford
820 * in JSMIN. http://www.crockford.com/javascript/jsmin.html
821 *
822 * KNOWN ISSUE: ending a line with a closing parenthesis ')' and no semicolon
823 * where there should be one will break the Javascript. New lines after a
824 * closing parenthesis are not recognized by the script. For best results
825 * be sure to terminate lines with a semicolon when appropriate.
826 *
827 * @param string $output Output to minify
828 * @param bool $has_tags Specify if the output has style or script tags
829 * @return string Minified output
830 */
831 protected function _minify_script_style($output, $has_tags = FALSE)
832 {
833 // We only need this if there are tags in the file
834 if ($has_tags === TRUE)
835 {
836 // Remove opening tag and save for later
837 $pos = strpos($output, '>') + 1;
838 $open_tag = substr($output, 0, $pos);
839 $output = substr_replace($output, '', 0, $pos);
840
841 // Remove closing tag and save it for later
842 $end_pos = strlen($output);
843 $pos = strpos($output, '</');
844 $closing_tag = substr($output, $pos, $end_pos);
845 $output = substr_replace($output, '', $pos);
846 }
847
848 // Remove CSS comments
849 $output = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!i', '', $output);
850
851 // Remove spaces around curly brackets, colons,
852 // semi-colons, parenthesis, commas
853 $output = preg_replace('!\s*(:|;|,|}|{|\(|\))\s*!i', '$1', $output);
854
855 // Replace tabs with spaces
856 // Replace carriage returns & multiple new lines with single new line
857 // and trim any leading or trailing whitespace
858 $output = trim(preg_replace(array('/\t+/', '/\r/', '/\n+/'), array(' ', "\n", "\n"), $output));
859
860 // Remove spaces when safe to do so.
861 $in_string = $in_dstring = $prev = FALSE;
862 $array_output = str_split($output);
863 foreach ($array_output as $key => $value)
864 {
865 if ($in_string === FALSE && $in_dstring === FALSE)
866 {
867 if ($value === ' ')
868 {
869 // Get the next element in the array for comparisons
870 $next = $array_output[$key + 1];
871
872 // Strip spaces preceded/followed by a non-ASCII character
873 // or not preceded/followed by an alphanumeric
874 // or not preceded/followed \ $ and _
875 if ((preg_match('/^[\x20-\x7f]*$/D', $next) OR preg_match('/^[\x20-\x7f]*$/D', $prev))
876 && ( ! ctype_alnum($next) OR ! ctype_alnum($prev))
877 && ! in_array($next, array('\\', '_', '$'), TRUE)
878 && ! in_array($prev, array('\\', '_', '$'), TRUE)
879 )
880 {
881 unset($array_output[$key]);
882 }
883 }
884 else
885 {
886 // Save this value as previous for the next iteration
887 // if it is not a blank space
888 $prev = $value;
889 }
890 }
891
892 if ($value === "'")
893 {
894 $in_string = ! $in_string;
895 }
896 elseif ($value === '"')
897 {
898 $in_dstring = ! $in_dstring;
899 }
900 }
901
902 // Put the string back together after spaces have been stripped
903 $output = implode($array_output);
904
905 // Remove new line characters unless previous or next character is
906 // printable or Non-ASCII
907 preg_match_all('/[\n]/', $output, $lf, PREG_OFFSET_CAPTURE);
908 $removed_lf = 0;
909 foreach ($lf as $feed_position)
910 {
911 foreach ($feed_position as $position)
912 {
913 $position = $position[1] - $removed_lf;
914 $next = $output[$position + 1];
915 $prev = $output[$position - 1];
916 if ( ! ctype_print($next) && ! ctype_print($prev)
917 && ! preg_match('/^[\x20-\x7f]*$/D', $next)
918 && ! preg_match('/^[\x20-\x7f]*$/D', $prev)
919 )
920 {
921 $output = substr_replace($output, '', $position, 1);
922 $removed_lf++;
923 }
924 }
925 }
926
927 // Put the opening and closing tags back if applicable
928 return isset($open_tag)
929 ? $open_tag.$output.$closing_tag
930 : $output;
931 }
932
933}
934
935/* End of file Output.php */
Andrey Andreevd8dba5d2012-12-17 15:42:01 +0200936/* Location: ./system/core/Output.php */