Lines
80.00%
40 / 50
Methods
60.00%
3 / 5
Classes
0.00%
0 / 1
| Name | Lines | Methods | CRAP | ||||
|---|---|---|---|---|---|---|---|
| __construct | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| send | 60.00% | 3 / 5 | 0.00% | 0 / 1 | 3.58 | ||
| getResponse | 100.00% | 5 / 5 | 100.00% | 1 / 1 | 1 | ||
| setHttpHeaders | 78.94% | 30 / 38 | 0.00% | 0 / 1 | 5.23 | ||
| streamContent | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| 41 | class FileDownloader | |
| 42 | { | |
| 43 | /** HTTP Content Disposition. */ | |
| 44 | private string $disposition = HeaderUtils::DISPOSITION_INLINE; | |
| 45 | ||
| 46 | /** HTTP streaming data length. */ | |
| 47 | private readonly int $size; | |
| 48 | ||
| 49 | private Response $response; | |
| 50 | ||
| 51 | /** | |
| 52 | * Constructor. | |
| 53 | * | |
| 54 | * @param string $type Type | |
| 55 | * @param string $content Content | |
| 56 | */ | |
| 57 | public function __construct( | |
| 58 | private readonly string $type, | |
| 59 | private readonly string $content, | |
| 60 | ) { | |
| 61 | $this->size = strlen($this->content); | |
| 62 | } | |
| 63 | ||
| 64 | /** | |
| 65 | * Sends data. | |
| 66 | * | |
| 67 | * @param string $disposition Disposition | |
| 68 | * @throws Exception | |
| 69 | */ | |
| 70 | public function send(string $disposition): void | |
| 71 | { | |
| 72 | // Sanity checks | |
| 73 | if (headers_sent()) { | |
| 74 | throw new Exception(message: 'Error: unable to send my headers: someone already sent other headers!'); | |
| 75 | } | |
| 76 | ||
| 77 | if (ob_get_contents()) { | |
| 78 | throw new Exception(message: 'Error: unable to send my data: someone already sent other data!'); | |
| 79 | } | |
| 80 | ||
| 81 | $this->getResponse($disposition)->send(); | |
| 82 | } | |
| 83 | ||
| 84 | /** | |
| 85 | * Builds the streaming response so it can be returned by a controller and | |
| 86 | * sent by the HTTP kernel, instead of being flushed directly to the client. | |
| 87 | * | |
| 88 | * @param string $disposition Disposition | |
| 89 | */ | |
| 90 | public function getResponse(string $disposition): Response | |
| 91 | { | |
| 92 | $this->disposition = $disposition; | |
| 93 | ||
| 94 | $this->response = new Response(); | |
| 95 | $this->setHttpHeaders(); | |
| 96 | $this->response->setContent(content: $this->streamContent()); | |
| 97 | ||
| 98 | return $this->response; | |
| 99 | } | |
| 100 | ||
| 101 | /** | |
| 102 | * Sends HTTP Headers. | |
| 103 | */ | |
| 104 | private function setHttpHeaders(): void | |
| 105 | { | |
| 106 | // Evaluate data upon export type request | |
| 107 | switch ($this->type) { | |
| 108 | case 'pdf': | |
| 109 | $filename = 'phpmyfaq.pdf'; | |
| 110 | $description = 'phpMyFAQ PDF export file'; | |
| 111 | $mimeType = 'application/pdf'; | |
| 112 | break; | |
| 113 | case 'csv': | |
| 114 | $filename = 'phpmyfaq.csv'; | |
| 115 | $description = 'phpMyFAQ CSV export file'; | |
| 116 | $mimeType = 'text/csv'; | |
| 117 | break; | |
| 118 | case 'json': | |
| 119 | $filename = 'phpmyfaq.json'; | |
| 120 | $description = 'phpMyFAQ JSON export file'; | |
| 121 | $mimeType = 'application/json'; | |
| 122 | break; | |
| 123 | // In this case, no default statement is required: | |
| 124 | // the one above is just for clean coding style | |
| 125 | default: | |
| 126 | $filename = 'phpmyfaq.pmf'; | |
| 127 | $description = 'phpMyFAQ Generic export file'; | |
| 128 | $mimeType = 'application/octet-stream'; | |
| 129 | break; | |
| 130 | } | |
| 131 | ||
| 132 | $filename = Export::getExportTimestamp() . '_' . $filename; | |
| 133 | ||
| 134 | // Set the correct HTTP headers: | |
| 135 | // 1. Prevent proxies&browsers caching | |
| 136 | $this->response->setLastModified(date: new DateTime()); | |
| 137 | $this->response->setExpires(date: new DateTime()); | |
| 138 | $this->response->setCache(options: [ | |
| 139 | 'must_revalidate' => true, | |
| 140 | 'no_cache' => true, | |
| 141 | 'no_store' => true, | |
| 142 | 'no_transform' => false, | |
| 143 | 'public' => false, | |
| 144 | 'private' => true, | |
| 145 | ]); | |
| 146 | ||
| 147 | // 2. Set the correct values for file streaming | |
| 148 | $this->response->headers->set(key: 'Content-Type', values: $mimeType); | |
| 149 | ||
| 150 | // 3. RFC2616, ยง19.5.1: $filename must be a quoted-string | |
| 151 | $disposition = HeaderUtils::makeDisposition(disposition: $this->disposition, filename: $filename); | |
| 152 | $this->response->headers->set(key: 'Content-Disposition', values: $disposition); | |
| 153 | $this->response->headers->set(key: 'Content-Description', values: $description); | |
| 154 | $this->response->headers->set(key: 'Content-Transfer-Encoding', values: 'binary'); | |
| 155 | $this->response->headers->set(key: 'Accept-Ranges', values: 'none'); | |
| 156 | $this->response->headers->set(key: 'Content-Length', values: (string) $this->size); | |
| 157 | } | |
| 158 | ||
| 159 | /** | |
| 160 | * Streams the content. | |
| 161 | */ | |
| 162 | private function streamContent(): string | |
| 163 | { | |
| 164 | return $this->content; | |
| 165 | } | |
| 166 | } |