@@ -286,15 +286,9 @@ public function clear(): self
286286 */
287287 public function cache ($ expires ): self
288288 {
289- if ($ expires === false ) {
289+ if ($ expires === false || $ expires === 0 ) {
290290 $ this ->headers ['Expires ' ] = 'Mon, 26 Jul 1997 05:00:00 GMT ' ;
291-
292- $ this ->headers ['Cache-Control ' ] = [
293- 'no-store, no-cache, must-revalidate ' ,
294- 'post-check=0, pre-check=0 ' ,
295- 'max-age=0 ' ,
296- ];
297-
291+ $ this ->headers ['Cache-Control ' ] = 'no-store, no-cache, must-revalidate, max-age=0 ' ;
298292 $ this ->headers ['Pragma ' ] = 'no-cache ' ;
299293 } else {
300294 $ expires = \is_int ($ expires ) ? $ expires : strtotime ($ expires );
@@ -437,15 +431,31 @@ public function send(): void
437431 $ this ->processResponseCallbacks ();
438432 }
439433
440- if (headers_sent () === false ) {
441- $ this ->sendHeaders (); // @codeCoverageIgnore
434+ if ($ this ->headersSent () === false ) {
435+ // If you haven't set a Cache-Control header, we'll assume you don't want caching
436+ if ($ this ->getHeader ('Cache-Control ' ) === null ) {
437+ $ this ->cache (false );
438+ }
439+
440+ $ this ->sendHeaders ();
442441 }
443442
444443 echo $ this ->body ;
445444
446445 $ this ->sent = true ;
447446 }
448447
448+ /**
449+ * Headers have been sent
450+ *
451+ * @return bool
452+ * @codeCoverageIgnore
453+ */
454+ public function headersSent (): bool
455+ {
456+ return headers_sent ();
457+ }
458+
449459 /**
450460 * Adds a callback to process the response body before it's sent. These are processed in the order
451461 * they are added
@@ -470,4 +480,42 @@ protected function processResponseCallbacks(): void
470480 $ this ->body = $ callback ($ this ->body );
471481 }
472482 }
483+
484+ /**
485+ * Downloads a file.
486+ *
487+ * @param string $filePath The path to the file to be downloaded.
488+ *
489+ * @return void
490+ */
491+ public function downloadFile (string $ filePath ): void
492+ {
493+ if (file_exists ($ filePath ) === false ) {
494+ throw new Exception ("$ filePath cannot be found. " );
495+ }
496+
497+ $ fileSize = filesize ($ filePath );
498+
499+ $ mimeType = mime_content_type ($ filePath );
500+ $ mimeType = $ mimeType !== false ? $ mimeType : 'application/octet-stream ' ;
501+
502+ $ this ->send ();
503+ $ this ->setRealHeader ('Content-Description: File Transfer ' );
504+ $ this ->setRealHeader ('Content-Type: ' . $ mimeType );
505+ $ this ->setRealHeader ('Content-Disposition: attachment; filename=" ' . basename ($ filePath ) . '" ' );
506+ $ this ->setRealHeader ('Expires: 0 ' );
507+ $ this ->setRealHeader ('Cache-Control: must-revalidate ' );
508+ $ this ->setRealHeader ('Pragma: public ' );
509+ $ this ->setRealHeader ('Content-Length: ' . $ fileSize );
510+
511+ // // Clear the output buffer
512+ ob_clean ();
513+ flush ();
514+
515+ // // Read the file and send it to the output buffer
516+ readfile ($ filePath );
517+ if (empty (getenv ('PHPUNIT_TEST ' ))) {
518+ exit ; // @codeCoverageIgnore
519+ }
520+ }
473521}
0 commit comments