blob: e8f0b1590c206d4cc1ed7e930be244ef2ee8b03e [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 *
Andrey Andreevbdb96ca2014-10-28 00:13:31 +02007 * This content is released under the MIT License (MIT)
Michael Dodge362b8002013-01-04 23:18:39 -07008 *
Andrey Andreevbdb96ca2014-10-28 00:13:31 +02009 * Copyright (c) 2014, British Columbia Institute of Technology
Michael Dodge362b8002013-01-04 23:18:39 -070010 *
Andrey Andreevbdb96ca2014-10-28 00:13:31 +020011 * Permission is hereby granted, free of charge, to any person obtaining a copy
12 * of this software and associated documentation files (the "Software"), to deal
13 * in the Software without restriction, including without limitation the rights
14 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 * copies of the Software, and to permit persons to whom the Software is
16 * furnished to do so, subject to the following conditions:
Michael Dodge362b8002013-01-04 23:18:39 -070017 *
Andrey Andreevbdb96ca2014-10-28 00:13:31 +020018 * The above copyright notice and this permission notice shall be included in
19 * all copies or substantial portions of the Software.
20 *
21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 * THE SOFTWARE.
28 *
29 * @package CodeIgniter
30 * @author EllisLab Dev Team
darwinel871754a2014-02-11 17:34:57 +010031 * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (http://ellislab.com/)
Andrey Andreevbdb96ca2014-10-28 00:13:31 +020032 * @copyright Copyright (c) 2014, British Columbia Institute of Technology (http://bcit.ca/)
33 * @license http://opensource.org/licenses/MIT MIT License
34 * @link http://codeigniter.com
35 * @since Version 1.0.0
Michael Dodge362b8002013-01-04 23:18:39 -070036 * @filesource
37 */
38defined('BASEPATH') OR exit('No direct script access allowed');
39
40/**
41 * Output Class
42 *
43 * Responsible for sending final output to the browser.
44 *
45 * @package CodeIgniter
46 * @subpackage Libraries
47 * @category Output
48 * @author EllisLab Dev Team
49 * @link http://codeigniter.com/user_guide/libraries/output.html
50 */
51class CI_Output {
52
53 /**
54 * Final output string
55 *
56 * @var string
57 */
58 public $final_output;
59
60 /**
61 * Cache expiration time
62 *
63 * @var int
64 */
65 public $cache_expiration = 0;
66
67 /**
68 * List of server headers
69 *
70 * @var array
71 */
Andrey Andreev155ee722014-01-10 15:50:54 +020072 public $headers = array();
Michael Dodge362b8002013-01-04 23:18:39 -070073
74 /**
75 * List of mime types
76 *
77 * @var array
78 */
Andrey Andreev155ee722014-01-10 15:50:54 +020079 public $mimes = array();
Michael Dodge362b8002013-01-04 23:18:39 -070080
81 /**
82 * Mime-type for the current page
83 *
84 * @var string
85 */
Andrey Andreev155ee722014-01-10 15:50:54 +020086 protected $mime_type = 'text/html';
Michael Dodge362b8002013-01-04 23:18:39 -070087
88 /**
89 * Enable Profiler flag
90 *
91 * @var bool
92 */
93 public $enable_profiler = FALSE;
94
95 /**
Andrey Andreev155ee722014-01-10 15:50:54 +020096 * php.ini zlib.output_compression flag
Michael Dodge362b8002013-01-04 23:18:39 -070097 *
98 * @var bool
99 */
Andrey Andreev155ee722014-01-10 15:50:54 +0200100 protected $_zlib_oc = FALSE;
101
102 /**
103 * CI output compression flag
104 *
105 * @var bool
106 */
107 protected $_compress_output = FALSE;
Michael Dodge362b8002013-01-04 23:18:39 -0700108
109 /**
110 * List of profiler sections
111 *
112 * @var array
113 */
Eric Roberts3e6b5822013-01-17 18:30:25 -0600114 protected $_profiler_sections = array();
Michael Dodge362b8002013-01-04 23:18:39 -0700115
116 /**
117 * Parse markers flag
118 *
119 * Whether or not to parse variables like {elapsed_time} and {memory_usage}.
120 *
121 * @var bool
122 */
Andrey Andreev155ee722014-01-10 15:50:54 +0200123 public $parse_exec_vars = TRUE;
Michael Dodge362b8002013-01-04 23:18:39 -0700124
125 /**
126 * Class constructor
127 *
128 * Determines whether zLib output compression will be used.
129 *
130 * @return void
131 */
132 public function __construct()
133 {
Andrey Andreevf6274742014-02-20 18:05:58 +0200134 $this->_zlib_oc = (bool) ini_get('zlib.output_compression');
Andrey Andreev155ee722014-01-10 15:50:54 +0200135 $this->_compress_output = (
136 $this->_zlib_oc === FALSE
Andrey Andreev9916bfc2014-01-10 16:21:07 +0200137 && config_item('compress_output') === TRUE
Andrey Andreev155ee722014-01-10 15:50:54 +0200138 && extension_loaded('zlib')
139 );
Michael Dodge362b8002013-01-04 23:18:39 -0700140
141 // Get mime types for later
142 $this->mimes =& get_mimes();
143
144 log_message('debug', 'Output Class Initialized');
145 }
146
147 // --------------------------------------------------------------------
148
149 /**
150 * Get Output
151 *
152 * Returns the current output string.
153 *
154 * @return string
155 */
156 public function get_output()
157 {
158 return $this->final_output;
159 }
160
161 // --------------------------------------------------------------------
162
163 /**
164 * Set Output
165 *
166 * Sets the output string.
167 *
168 * @param string $output Output data
169 * @return CI_Output
170 */
171 public function set_output($output)
172 {
173 $this->final_output = $output;
174 return $this;
175 }
176
177 // --------------------------------------------------------------------
178
179 /**
180 * Append Output
181 *
182 * Appends data onto the output string.
183 *
184 * @param string $output Data to append
185 * @return CI_Output
186 */
187 public function append_output($output)
188 {
Andrey Andreev2ab4ffb2014-02-24 16:15:09 +0200189 $this->final_output .= $output;
Michael Dodge362b8002013-01-04 23:18:39 -0700190 return $this;
191 }
192
193 // --------------------------------------------------------------------
194
195 /**
196 * Set Header
197 *
198 * Lets you set a server header which will be sent with the final output.
199 *
200 * Note: If a file is cached, headers will not be sent.
201 * @todo We need to figure out how to permit headers to be cached.
202 *
203 * @param string $header Header
204 * @param bool $replace Whether to replace the old header value, if already set
205 * @return CI_Output
206 */
207 public function set_header($header, $replace = TRUE)
208 {
209 // If zlib.output_compression is enabled it will compress the output,
210 // but it will not modify the content-length header to compensate for
211 // the reduction, causing the browser to hang waiting for more data.
212 // We'll just skip content-length in those cases.
213 if ($this->_zlib_oc && strncasecmp($header, 'content-length', 14) === 0)
214 {
215 return $this;
216 }
217
218 $this->headers[] = array($header, $replace);
219 return $this;
220 }
221
222 // --------------------------------------------------------------------
223
224 /**
225 * Set Content-Type Header
226 *
227 * @param string $mime_type Extension of the file we're outputting
228 * @param string $charset Character set (default: NULL)
229 * @return CI_Output
230 */
231 public function set_content_type($mime_type, $charset = NULL)
232 {
233 if (strpos($mime_type, '/') === FALSE)
234 {
235 $extension = ltrim($mime_type, '.');
236
237 // Is this extension supported?
238 if (isset($this->mimes[$extension]))
239 {
240 $mime_type =& $this->mimes[$extension];
241
242 if (is_array($mime_type))
243 {
244 $mime_type = current($mime_type);
245 }
246 }
247 }
248
249 $this->mime_type = $mime_type;
250
251 if (empty($charset))
252 {
253 $charset = config_item('charset');
254 }
255
256 $header = 'Content-Type: '.$mime_type
vlakoffd5ce5082014-04-25 10:13:04 +0200257 .(empty($charset) ? '' : '; charset='.$charset);
Michael Dodge362b8002013-01-04 23:18:39 -0700258
259 $this->headers[] = array($header, TRUE);
260 return $this;
261 }
262
263 // --------------------------------------------------------------------
264
265 /**
266 * Get Current Content-Type Header
267 *
268 * @return string 'text/html', if not already set
269 */
270 public function get_content_type()
271 {
272 for ($i = 0, $c = count($this->headers); $i < $c; $i++)
273 {
274 if (sscanf($this->headers[$i][0], 'Content-Type: %[^;]', $content_type) === 1)
275 {
276 return $content_type;
277 }
278 }
279
280 return 'text/html';
281 }
282
283 // --------------------------------------------------------------------
284
285 /**
286 * Get Header
287 *
288 * @param string $header_name
289 * @return string
290 */
291 public function get_header($header)
292 {
293 // Combine headers already sent with our batched headers
294 $headers = array_merge(
295 // We only need [x][0] from our multi-dimensional array
296 array_map('array_shift', $this->headers),
297 headers_list()
298 );
299
300 if (empty($headers) OR empty($header))
301 {
302 return NULL;
303 }
304
305 for ($i = 0, $c = count($headers); $i < $c; $i++)
306 {
307 if (strncasecmp($header, $headers[$i], $l = strlen($header)) === 0)
308 {
309 return trim(substr($headers[$i], $l+1));
310 }
311 }
312
313 return NULL;
314 }
315
316 // --------------------------------------------------------------------
317
318 /**
319 * Set HTTP Status Header
320 *
321 * As of version 1.7.2, this is an alias for common function
322 * set_status_header().
323 *
324 * @param int $code Status code (default: 200)
325 * @param string $text Optional message
326 * @return CI_Output
327 */
328 public function set_status_header($code = 200, $text = '')
329 {
330 set_status_header($code, $text);
331 return $this;
332 }
333
334 // --------------------------------------------------------------------
335
336 /**
337 * Enable/disable Profiler
338 *
339 * @param bool $val TRUE to enable or FALSE to disable
340 * @return CI_Output
341 */
342 public function enable_profiler($val = TRUE)
343 {
344 $this->enable_profiler = is_bool($val) ? $val : TRUE;
345 return $this;
346 }
347
348 // --------------------------------------------------------------------
349
350 /**
351 * Set Profiler Sections
352 *
353 * Allows override of default/config settings for
354 * Profiler section display.
355 *
356 * @param array $sections Profiler sections
357 * @return CI_Output
358 */
359 public function set_profiler_sections($sections)
360 {
361 if (isset($sections['query_toggle_count']))
362 {
363 $this->_profiler_sections['query_toggle_count'] = (int) $sections['query_toggle_count'];
364 unset($sections['query_toggle_count']);
365 }
366
367 foreach ($sections as $section => $enable)
368 {
369 $this->_profiler_sections[$section] = ($enable !== FALSE);
370 }
371
372 return $this;
373 }
374
375 // --------------------------------------------------------------------
376
377 /**
378 * Set Cache
379 *
380 * @param int $time Cache expiration time in seconds
381 * @return CI_Output
382 */
383 public function cache($time)
384 {
385 $this->cache_expiration = is_numeric($time) ? $time : 0;
386 return $this;
387 }
388
389 // --------------------------------------------------------------------
390
391 /**
392 * Display Output
393 *
394 * Processes sends the sends finalized output data to the browser along
395 * with any server headers and profile data. It also stops benchmark
396 * timers so the page rendering speed and memory usage can be shown.
397 *
398 * Note: All "view" data is automatically put into $this->final_output
399 * by controller class.
400 *
401 * @uses CI_Output::$final_output
402 * @param string $output Output data override
403 * @return void
404 */
405 public function _display($output = '')
406 {
Andrey Andreevc26b9eb2014-02-24 11:31:36 +0200407 // Note: We use load_class() because we can't use $CI =& get_instance()
Michael Dodge362b8002013-01-04 23:18:39 -0700408 // since this function is sometimes called by the caching mechanism,
409 // which happens before the CI super object is available.
Andrey Andreevc26b9eb2014-02-24 11:31:36 +0200410 $BM =& load_class('Benchmark', 'core');
411 $CFG =& load_class('Config', 'core');
Michael Dodge362b8002013-01-04 23:18:39 -0700412
413 // Grab the super object if we can.
Andrey Andreev49e68de2013-02-21 16:30:55 +0200414 if (class_exists('CI_Controller', FALSE))
Michael Dodge362b8002013-01-04 23:18:39 -0700415 {
416 $CI =& get_instance();
417 }
418
419 // --------------------------------------------------------------------
420
421 // Set the output data
422 if ($output === '')
423 {
424 $output =& $this->final_output;
425 }
426
427 // --------------------------------------------------------------------
428
429 // Is minify requested?
430 if ($CFG->item('minify_output') === TRUE)
431 {
432 $output = $this->minify($output, $this->mime_type);
433 }
434
435 // --------------------------------------------------------------------
436
437 // Do we need to write a cache file? Only if the controller does not have its
438 // own _output() method and we are not dealing with a cache file, which we
439 // can determine by the existence of the $CI object above
440 if ($this->cache_expiration > 0 && isset($CI) && ! method_exists($CI, '_output'))
441 {
442 $this->_write_cache($output);
443 }
444
445 // --------------------------------------------------------------------
446
447 // Parse out the elapsed time and memory usage,
448 // then swap the pseudo-variables with the data
449
450 $elapsed = $BM->elapsed_time('total_execution_time_start', 'total_execution_time_end');
451
452 if ($this->parse_exec_vars === TRUE)
453 {
454 $memory = round(memory_get_usage() / 1024 / 1024, 2).'MB';
Michael Dodge362b8002013-01-04 23:18:39 -0700455 $output = str_replace(array('{elapsed_time}', '{memory_usage}'), array($elapsed, $memory), $output);
456 }
457
458 // --------------------------------------------------------------------
459
460 // Is compression requested?
Andrey Andreev155ee722014-01-10 15:50:54 +0200461 if (isset($CI) // This means that we're not serving a cache file, if we were, it would already be compressed
462 && $this->_compress_output === TRUE
Michael Dodge362b8002013-01-04 23:18:39 -0700463 && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE)
464 {
465 ob_start('ob_gzhandler');
466 }
467
468 // --------------------------------------------------------------------
469
470 // Are there any server headers to send?
471 if (count($this->headers) > 0)
472 {
473 foreach ($this->headers as $header)
474 {
475 @header($header[0], $header[1]);
476 }
477 }
478
479 // --------------------------------------------------------------------
480
481 // Does the $CI object exist?
482 // If not we know we are dealing with a cache file so we'll
483 // simply echo out the data and exit.
484 if ( ! isset($CI))
485 {
Andrey Andreev155ee722014-01-10 15:50:54 +0200486 if ($this->_compress_output === TRUE)
487 {
488 if (isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE)
489 {
490 header('Content-Encoding: gzip');
491 header('Content-Length: '.strlen($output));
492 }
493 else
494 {
495 // User agent doesn't support gzip compression,
496 // so we'll have to decompress our cache
497 $output = gzinflate(substr($output, 10, -8));
498 }
499 }
500
Michael Dodge362b8002013-01-04 23:18:39 -0700501 echo $output;
502 log_message('debug', 'Final output sent to browser');
503 log_message('debug', 'Total execution time: '.$elapsed);
504 return;
505 }
506
507 // --------------------------------------------------------------------
508
509 // Do we need to generate profile data?
510 // If so, load the Profile class and run it.
511 if ($this->enable_profiler === TRUE)
512 {
513 $CI->load->library('profiler');
514 if ( ! empty($this->_profiler_sections))
515 {
516 $CI->profiler->set_sections($this->_profiler_sections);
517 }
518
519 // If the output data contains closing </body> and </html> tags
520 // we will remove them and add them back after we insert the profile data
521 $output = preg_replace('|</body>.*?</html>|is', '', $output, -1, $count).$CI->profiler->run();
522 if ($count > 0)
523 {
524 $output .= '</body></html>';
525 }
526 }
527
528 // Does the controller contain a function named _output()?
529 // If so send the output there. Otherwise, echo it.
530 if (method_exists($CI, '_output'))
531 {
532 $CI->_output($output);
533 }
534 else
535 {
536 echo $output; // Send it to the browser!
537 }
538
539 log_message('debug', 'Final output sent to browser');
540 log_message('debug', 'Total execution time: '.$elapsed);
541 }
542
543 // --------------------------------------------------------------------
544
545 /**
546 * Write Cache
547 *
548 * @param string $output Output data to cache
549 * @return void
550 */
551 public function _write_cache($output)
552 {
553 $CI =& get_instance();
554 $path = $CI->config->item('cache_path');
555 $cache_path = ($path === '') ? APPPATH.'cache/' : $path;
556
557 if ( ! is_dir($cache_path) OR ! is_really_writable($cache_path))
558 {
559 log_message('error', 'Unable to write cache file: '.$cache_path);
560 return;
561 }
562
Andrey Andreev155ee722014-01-10 15:50:54 +0200563 $uri = $CI->config->item('base_url')
564 .$CI->config->item('index_page')
565 .$CI->uri->uri_string();
Michael Dodge362b8002013-01-04 23:18:39 -0700566
Andrey Andreeva704aa72014-12-04 12:37:07 +0200567 if ($CI->config->item('cache_query_string') && ! empty($_SERVER['QUERY_STRING']))
568 {
569 $uri .= '?'.$_SERVER['QUERY_STRING'];
570 }
Stefano Mazzegaac41ca62014-12-03 11:55:47 +0100571
Michael Dodge362b8002013-01-04 23:18:39 -0700572 $cache_path .= md5($uri);
573
Andrey Andreev7cf682a2014-03-13 14:55:45 +0200574 if ( ! $fp = @fopen($cache_path, 'w+b'))
Michael Dodge362b8002013-01-04 23:18:39 -0700575 {
576 log_message('error', 'Unable to write cache file: '.$cache_path);
577 return;
578 }
579
Michael Dodge362b8002013-01-04 23:18:39 -0700580 if (flock($fp, LOCK_EX))
581 {
Andrey Andreev155ee722014-01-10 15:50:54 +0200582 // If output compression is enabled, compress the cache
583 // itself, so that we don't have to do that each time
584 // we're serving it
585 if ($this->_compress_output === TRUE)
586 {
587 $output = gzencode($output);
588
589 if ($this->get_header('content-type') === NULL)
590 {
591 $this->set_content_type($this->mime_type);
592 }
593 }
594
595 $expire = time() + ($this->cache_expiration * 60);
596
597 // Put together our serialized info.
598 $cache_info = serialize(array(
599 'expire' => $expire,
600 'headers' => $this->headers
601 ));
602
Andrey Andreevd8b1ad32014-01-15 17:42:52 +0200603 $output = $cache_info.'ENDCI--->'.$output;
604
605 for ($written = 0, $length = strlen($output); $written < $length; $written += $result)
606 {
607 if (($result = fwrite($fp, substr($output, $written))) === FALSE)
608 {
609 break;
610 }
611 }
612
Michael Dodge362b8002013-01-04 23:18:39 -0700613 flock($fp, LOCK_UN);
614 }
615 else
616 {
617 log_message('error', 'Unable to secure a file lock for file at: '.$cache_path);
618 return;
619 }
Andrey Andreev155ee722014-01-10 15:50:54 +0200620
Michael Dodge362b8002013-01-04 23:18:39 -0700621 fclose($fp);
Michael Dodge362b8002013-01-04 23:18:39 -0700622
Andrey Andreevd8b1ad32014-01-15 17:42:52 +0200623 if (is_int($result))
624 {
Andrey Andreev45965742014-08-27 20:40:11 +0300625 chmod($cache_path, 0640);
Andrey Andreevd8b1ad32014-01-15 17:42:52 +0200626 log_message('debug', 'Cache file written: '.$cache_path);
Michael Dodge362b8002013-01-04 23:18:39 -0700627
Andrey Andreevd8b1ad32014-01-15 17:42:52 +0200628 // Send HTTP cache-control headers to browser to match file cache settings.
629 $this->set_cache_header($_SERVER['REQUEST_TIME'], $expire);
630 }
631 else
632 {
633 @unlink($cache_path);
634 log_message('error', 'Unable to write the complete cache content at: '.$cache_path);
635 }
Michael Dodge362b8002013-01-04 23:18:39 -0700636 }
637
638 // --------------------------------------------------------------------
639
640 /**
641 * Update/serve cached output
642 *
643 * @uses CI_Config
644 * @uses CI_URI
645 *
646 * @param object &$CFG CI_Config class instance
647 * @param object &$URI CI_URI class instance
648 * @return bool TRUE on success or FALSE on failure
649 */
650 public function _display_cache(&$CFG, &$URI)
651 {
652 $cache_path = ($CFG->item('cache_path') === '') ? APPPATH.'cache/' : $CFG->item('cache_path');
653
654 // Build the file path. The file name is an MD5 hash of the full URI
Andrey Andreev33572252014-12-03 20:19:38 +0200655 $uri = $CFG->item('base_url').$CFG->item('index_page').$URI->uri_string;
Andrey Andreeva704aa72014-12-04 12:37:07 +0200656
657 if ($CFG->item('cache_query_string') && ! empty($_SERVER['QUERY_STRING']))
658 {
659 $uri .= '?'.$_SERVER['QUERY_STRING'];
660 }
Stefano Mazzegaac41ca62014-12-03 11:55:47 +0100661
Michael Dodge362b8002013-01-04 23:18:39 -0700662 $filepath = $cache_path.md5($uri);
663
Andrey Andreev7cf682a2014-03-13 14:55:45 +0200664 if ( ! file_exists($filepath) OR ! $fp = @fopen($filepath, 'rb'))
Michael Dodge362b8002013-01-04 23:18:39 -0700665 {
666 return FALSE;
667 }
668
669 flock($fp, LOCK_SH);
670
671 $cache = (filesize($filepath) > 0) ? fread($fp, filesize($filepath)) : '';
672
673 flock($fp, LOCK_UN);
674 fclose($fp);
675
Eric Robertsc90e67e2013-01-11 21:20:54 -0600676 // Look for embedded serialized file info.
677 if ( ! preg_match('/^(.*)ENDCI--->/', $cache, $match))
Michael Dodge362b8002013-01-04 23:18:39 -0700678 {
679 return FALSE;
680 }
Purwandi5dc6d512013-01-19 17:43:08 +0700681
Eric Robertsc90e67e2013-01-11 21:20:54 -0600682 $cache_info = unserialize($match[1]);
683 $expire = $cache_info['expire'];
Michael Dodge362b8002013-01-04 23:18:39 -0700684
685 $last_modified = filemtime($cache_path);
Michael Dodge362b8002013-01-04 23:18:39 -0700686
687 // Has the file expired?
688 if ($_SERVER['REQUEST_TIME'] >= $expire && is_really_writable($cache_path))
689 {
690 // If so we'll delete it.
691 @unlink($filepath);
692 log_message('debug', 'Cache file has expired. File deleted.');
693 return FALSE;
694 }
695 else
696 {
697 // Or else send the HTTP cache control headers.
698 $this->set_cache_header($last_modified, $expire);
699 }
Purwandi5dc6d512013-01-19 17:43:08 +0700700
Eric Robertsc90e67e2013-01-11 21:20:54 -0600701 // Add headers from cache file.
702 foreach ($cache_info['headers'] as $header)
703 {
704 $this->set_header($header[0], $header[1]);
705 }
Michael Dodge362b8002013-01-04 23:18:39 -0700706
707 // Display the cache
708 $this->_display(substr($cache, strlen($match[0])));
709 log_message('debug', 'Cache file is current. Sending it to browser.');
710 return TRUE;
711 }
712
713 // --------------------------------------------------------------------
714
715 /**
716 * Delete cache
717 *
718 * @param string $uri URI string
719 * @return bool
720 */
721 public function delete_cache($uri = '')
722 {
723 $CI =& get_instance();
724 $cache_path = $CI->config->item('cache_path');
725 if ($cache_path === '')
726 {
727 $cache_path = APPPATH.'cache/';
728 }
729
730 if ( ! is_dir($cache_path))
731 {
732 log_message('error', 'Unable to find cache path: '.$cache_path);
733 return FALSE;
734 }
735
736 if (empty($uri))
737 {
738 $uri = $CI->uri->uri_string();
Andrey Andreeva704aa72014-12-04 12:37:07 +0200739
740 if ($CI->config->item('cache_query_string') && ! empty($_SERVER['QUERY_STRING']))
741 {
742 $uri .= '?'.$_SERVER['QUERY_STRING'];
743 }
Michael Dodge362b8002013-01-04 23:18:39 -0700744 }
745
746 $cache_path .= md5($CI->config->item('base_url').$CI->config->item('index_page').$uri);
747
748 if ( ! @unlink($cache_path))
749 {
750 log_message('error', 'Unable to delete cache file for '.$uri);
751 return FALSE;
752 }
753
754 return TRUE;
755 }
756
757 // --------------------------------------------------------------------
758
759 /**
760 * Set Cache Header
761 *
762 * Set the HTTP headers to match the server-side file cache settings
763 * in order to reduce bandwidth.
764 *
765 * @param int $last_modified Timestamp of when the page was last modified
766 * @param int $expiration Timestamp of when should the requested page expire from cache
767 * @return void
768 */
769 public function set_cache_header($last_modified, $expiration)
770 {
771 $max_age = $expiration - $_SERVER['REQUEST_TIME'];
772
773 if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $last_modified <= strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']))
774 {
775 $this->set_status_header(304);
776 exit;
777 }
778 else
779 {
780 header('Pragma: public');
Andrey Andreev3ca060a2013-11-27 16:30:31 +0200781 header('Cache-Control: max-age='.$max_age.', public');
Michael Dodge362b8002013-01-04 23:18:39 -0700782 header('Expires: '.gmdate('D, d M Y H:i:s', $expiration).' GMT');
783 header('Last-modified: '.gmdate('D, d M Y H:i:s', $last_modified).' GMT');
784 }
785 }
786
787 // --------------------------------------------------------------------
788
789 /**
790 * Minify
791 *
792 * Reduce excessive size of HTML/CSS/JavaScript content.
793 *
794 * @param string $output Output to minify
795 * @param string $type Output content MIME type
796 * @return string Minified output
797 */
798 public function minify($output, $type = 'text/html')
799 {
800 switch ($type)
801 {
802 case 'text/html':
803
804 if (($size_before = strlen($output)) === 0)
805 {
806 return '';
807 }
808
809 // Find all the <pre>,<code>,<textarea>, and <javascript> tags
810 // We'll want to return them to this unprocessed state later.
811 preg_match_all('{<pre.+</pre>}msU', $output, $pres_clean);
812 preg_match_all('{<code.+</code>}msU', $output, $codes_clean);
813 preg_match_all('{<textarea.+</textarea>}msU', $output, $textareas_clean);
814 preg_match_all('{<script.+</script>}msU', $output, $javascript_clean);
815
816 // Minify the CSS in all the <style> tags.
817 preg_match_all('{<style.+</style>}msU', $output, $style_clean);
818 foreach ($style_clean[0] as $s)
819 {
Andrey Andreev6a424902013-10-28 14:16:18 +0200820 $output = str_replace($s, $this->_minify_js_css($s, 'css', TRUE), $output);
Michael Dodge362b8002013-01-04 23:18:39 -0700821 }
822
823 // Minify the javascript in <script> tags.
824 foreach ($javascript_clean[0] as $s)
825 {
Andrey Andreev6a424902013-10-28 14:16:18 +0200826 $javascript_mini[] = $this->_minify_js_css($s, 'js', TRUE);
Michael Dodge362b8002013-01-04 23:18:39 -0700827 }
828
829 // Replace multiple spaces with a single space.
830 $output = preg_replace('!\s{2,}!', ' ', $output);
831
832 // Remove comments (non-MSIE conditionals)
Michael Dodge4d02e352013-01-04 23:22:51 -0700833 $output = preg_replace('{\s*<!--[^\[<>].*(?<!!)-->\s*}msU', '', $output);
Michael Dodge362b8002013-01-04 23:18:39 -0700834
835 // Remove spaces around block-level elements.
Purwandi5dc6d512013-01-19 17:43:08 +0700836 $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 -0700837
838 // Replace mangled <pre> etc. tags with unprocessed ones.
839
840 if ( ! empty($pres_clean))
841 {
842 preg_match_all('{<pre.+</pre>}msU', $output, $pres_messed);
843 $output = str_replace($pres_messed[0], $pres_clean[0], $output);
844 }
845
846 if ( ! empty($codes_clean))
847 {
848 preg_match_all('{<code.+</code>}msU', $output, $codes_messed);
849 $output = str_replace($codes_messed[0], $codes_clean[0], $output);
850 }
851
Andrey Andreev3ffce982013-01-21 15:24:09 +0200852 if ( ! empty($textareas_clean))
Michael Dodge362b8002013-01-04 23:18:39 -0700853 {
854 preg_match_all('{<textarea.+</textarea>}msU', $output, $textareas_messed);
855 $output = str_replace($textareas_messed[0], $textareas_clean[0], $output);
856 }
857
858 if (isset($javascript_mini))
859 {
860 preg_match_all('{<script.+</script>}msU', $output, $javascript_messed);
861 $output = str_replace($javascript_messed[0], $javascript_mini, $output);
862 }
863
864 $size_removed = $size_before - strlen($output);
865 $savings_percent = round(($size_removed / $size_before * 100));
866
867 log_message('debug', 'Minifier shaved '.($size_removed / 1000).'KB ('.$savings_percent.'%) off final HTML output.');
868
869 break;
870
871 case 'text/css':
Andrey Andreev6a424902013-10-28 14:16:18 +0200872
873 return $this->_minify_js_css($output, 'css');
874
Michael Dodge362b8002013-01-04 23:18:39 -0700875 case 'text/javascript':
bayssmekanique7b903252013-03-12 13:25:24 -0700876 case 'application/javascript':
877 case 'application/x-javascript':
Michael Dodge362b8002013-01-04 23:18:39 -0700878
Andrey Andreev6a424902013-10-28 14:16:18 +0200879 return $this->_minify_js_css($output, 'js');
Michael Dodge362b8002013-01-04 23:18:39 -0700880
881 default: break;
882 }
883
884 return $output;
885 }
886
887 // --------------------------------------------------------------------
888
Andrey Andreev3c3bbac2013-10-31 15:10:49 +0200889 /**
890 * Minify JavaScript and CSS code
891 *
892 * Strips comments and excessive whitespace characters
893 *
894 * @param string $output
895 * @param string $type 'js' or 'css'
896 * @param bool $tags Whether $output contains the 'script' or 'style' tag
897 * @return string
898 */
Andrey Andreev6a424902013-10-28 14:16:18 +0200899 protected function _minify_js_css($output, $type, $tags = FALSE)
900 {
901 if ($tags === TRUE)
902 {
903 $tags = array('close' => strrchr($output, '<'));
904
905 $open_length = strpos($output, '>') + 1;
906 $tags['open'] = substr($output, 0, $open_length);
907
908 $output = substr($output, $open_length, -strlen($tags['close']));
909
910 // Strip spaces from the tags
911 $tags = preg_replace('#\s{2,}#', ' ', $tags);
912 }
913
914 $output = trim($output);
915
916 if ($type === 'js')
917 {
918 // Catch all string literals and comment blocks
Andrey Andreev0949b362013-10-31 16:07:40 +0200919 if (preg_match_all('#((?:((?<!\\\)\'|")|(/\*)|(//)).*(?(2)(?<!\\\)\2|(?(3)\*/|\n)))#msuUS', $output, $match, PREG_OFFSET_CAPTURE))
Andrey Andreev6a424902013-10-28 14:16:18 +0200920 {
921 $js_literals = $js_code = array();
922 for ($match = $match[0], $c = count($match), $i = $pos = $offset = 0; $i < $c; $i++)
923 {
Andrey Andreev3c3bbac2013-10-31 15:10:49 +0200924 $js_code[$pos++] = trim(substr($output, $offset, $match[$i][1] - $offset));
Andrey Andreev6a424902013-10-28 14:16:18 +0200925 $offset = $match[$i][1] + strlen($match[$i][0]);
926
927 // Save only if we haven't matched a comment block
928 if ($match[$i][0][0] !== '/')
929 {
930 $js_literals[$pos++] = array_shift($match[$i]);
931 }
932 }
933 $js_code[$pos] = substr($output, $offset);
934
935 // $match might be quite large, so free it up together with other vars that we no longer need
936 unset($match, $offset, $pos);
937 }
938 else
939 {
940 $js_code = array($output);
941 $js_literals = array();
942 }
943
944 $varname = 'js_code';
945 }
946 else
947 {
948 $varname = 'output';
949 }
950
951 // Standartize new lines
952 $$varname = str_replace(array("\r\n", "\r"), "\n", $$varname);
953
954 if ($type === 'js')
955 {
956 $patterns = array(
Andrey Andreev99f9b9a2013-10-30 23:07:23 +0200957 '#\s*([!\#%&()*+,\-./:;<=>?@\[\]^`{|}~])\s*#' => '$1', // Remove spaces following and preceeding JS-wise non-special & non-word characters
Andrey Andreev6a424902013-10-28 14:16:18 +0200958 '#\s{2,}#' => ' ' // Reduce the remaining multiple whitespace characters to a single space
959 );
960 }
961 else
962 {
963 $patterns = array(
964 '#/\*.*(?=\*/)\*/#s' => '', // Remove /* block comments */
965 '#\n?//[^\n]*#' => '', // Remove // line comments
Andrey Andreev99f9b9a2013-10-30 23:07:23 +0200966 '#\s*([^\w.\#%])\s*#U' => '$1', // Remove spaces following and preceeding non-word characters, excluding dots, hashes and the percent sign
Andrey Andreev6a424902013-10-28 14:16:18 +0200967 '#\s{2,}#' => ' ' // Reduce the remaining multiple space characters to a single space
968 );
969 }
970
971 $$varname = preg_replace(array_keys($patterns), array_values($patterns), $$varname);
972
973 // Glue back JS quoted strings
974 if ($type === 'js')
975 {
976 $js_code += $js_literals;
977 ksort($js_code);
978 $output = implode($js_code);
979 unset($js_code, $js_literals, $varname, $patterns);
980 }
981
982 return is_array($tags)
983 ? $tags['open'].$output.$tags['close']
984 : $output;
985 }
986
Michael Dodge362b8002013-01-04 23:18:39 -0700987}
988
989/* End of file Output.php */
Andrey Andreevd8dba5d2012-12-17 15:42:01 +0200990/* Location: ./system/core/Output.php */