]> BookStack Code Mirror - bookstack/blob - app/Services/FileService.php
a04d840018a92ce1bcf4ce6416ec9ff4083ac530
[bookstack] / app / Services / FileService.php
1 <?php namespace BookStack\Services;
2
3
4 use BookStack\Exceptions\FileUploadException;
5 use BookStack\File;
6 use Exception;
7 use Illuminate\Contracts\Filesystem\FileNotFoundException;
8 use Illuminate\Support\Collection;
9 use Symfony\Component\HttpFoundation\File\UploadedFile;
10
11 class FileService extends UploadService
12 {
13
14     /**
15      * Get a file from storage.
16      * @param File $file
17      * @return string
18      */
19     public function getFile(File $file)
20     {
21         $filePath = $this->getStorageBasePath() . $file->path;
22         return $this->getStorage()->get($filePath);
23     }
24
25     /**
26      * Store a new file upon user upload.
27      * @param UploadedFile $uploadedFile
28      * @param int $page_id
29      * @return File
30      * @throws FileUploadException
31      */
32     public function saveNewUpload(UploadedFile $uploadedFile, $page_id)
33     {
34         $fileName = $uploadedFile->getClientOriginalName();
35         $filePath = $this->putFileInStorage($fileName, $uploadedFile);
36         $largestExistingOrder = File::where('uploaded_to', '=', $page_id)->max('order');
37
38         $file = File::forceCreate([
39             'name' => $fileName,
40             'path' => $filePath,
41             'uploaded_to' => $page_id,
42             'created_by' => user()->id,
43             'updated_by' => user()->id,
44             'order' => $largestExistingOrder + 1
45         ]);
46
47         return $file;
48     }
49
50     /**
51      * Store a upload, saving to a file and deleting any existing uploads
52      * attached to that file.
53      * @param UploadedFile $uploadedFile
54      * @param File $file
55      * @return File
56      * @throws FileUploadException
57      */
58     public function saveUpdatedUpload(UploadedFile $uploadedFile, File $file)
59     {
60         if (!$file->external) {
61             $this->deleteFileInStorage($file);
62         }
63
64         $fileName = $uploadedFile->getClientOriginalName();
65         $filePath = $this->putFileInStorage($fileName, $uploadedFile);
66
67         $file->name = $fileName;
68         $file->path = $filePath;
69         $file->external = false;
70         $file->save();
71         return $file;
72     }
73
74     /**
75      * Save a new File attachment from a given link and name.
76      * @param string $name
77      * @param string $link
78      * @param int $page_id
79      * @return File
80      */
81     public function saveNewFromLink($name, $link, $page_id)
82     {
83         $largestExistingOrder = File::where('uploaded_to', '=', $page_id)->max('order');
84         return File::forceCreate([
85             'name' => $name,
86             'path' => $link,
87             'external' => true,
88             'uploaded_to' => $page_id,
89             'created_by' => user()->id,
90             'updated_by' => user()->id,
91             'order' => $largestExistingOrder + 1
92         ]);
93     }
94
95     /**
96      * Get the file storage base path, amended for storage type.
97      * This allows us to keep a generic path in the database.
98      * @return string
99      */
100     private function getStorageBasePath()
101     {
102         return $this->isLocal() ? 'storage/' : '';
103     }
104
105     /**
106      * Updates the file ordering for a listing of attached files.
107      * @param array $fileList
108      * @param $pageId
109      */
110     public function updateFileOrderWithinPage($fileList, $pageId)
111     {
112         foreach ($fileList as $index => $file) {
113             File::where('uploaded_to', '=', $pageId)->where('id', '=', $file['id'])->update(['order' => $index]);
114         }
115     }
116
117
118     /**
119      * Update the details of a file.
120      * @param File $file
121      * @param $requestData
122      * @return File
123      */
124     public function updateFile(File $file, $requestData)
125     {
126         $file->name = $requestData['name'];
127         if (isset($requestData['link']) && trim($requestData['link']) !== '') {
128             $file->path = $requestData['link'];
129             if (!$file->external) {
130                 $this->deleteFileInStorage($file);
131                 $file->external = true;
132             }
133         }
134         $file->save();
135         return $file;
136     }
137
138     /**
139      * Delete a File from the database and storage.
140      * @param File $file
141      */
142     public function deleteFile(File $file)
143     {
144         if ($file->external) {
145             $file->delete();
146             return;
147         }
148         
149         $this->deleteFileInStorage($file);
150         $file->delete();
151     }
152
153     /**
154      * Delete a file from the filesystem it sits on.
155      * Cleans any empty leftover folders.
156      * @param File $file
157      */
158     protected function deleteFileInStorage(File $file)
159     {
160         $storedFilePath = $this->getStorageBasePath() . $file->path;
161         $storage = $this->getStorage();
162         $dirPath = dirname($storedFilePath);
163
164         $storage->delete($storedFilePath);
165         if (count($storage->allFiles($dirPath)) === 0) {
166             $storage->deleteDirectory($dirPath);
167         }
168     }
169
170     /**
171      * Store a file in storage with the given filename
172      * @param $fileName
173      * @param UploadedFile $uploadedFile
174      * @return string
175      * @throws FileUploadException
176      */
177     protected function putFileInStorage($fileName, UploadedFile $uploadedFile)
178     {
179         $fileData = file_get_contents($uploadedFile->getRealPath());
180
181         $storage = $this->getStorage();
182         $fileBasePath = 'uploads/files/' . Date('Y-m-M') . '/';
183         $storageBasePath = $this->getStorageBasePath() . $fileBasePath;
184
185         $uploadFileName = $fileName;
186         while ($storage->exists($storageBasePath . $uploadFileName)) {
187             $uploadFileName = str_random(3) . $uploadFileName;
188         }
189
190         $filePath = $fileBasePath . $uploadFileName;
191         $fileStoragePath = $this->getStorageBasePath() . $filePath;
192
193         try {
194             $storage->put($fileStoragePath, $fileData);
195         } catch (Exception $e) {
196             throw new FileUploadException('File path ' . $fileStoragePath . ' could not be uploaded to. Ensure it is writable to the server.');
197         }
198         return $filePath;
199     }
200
201 }