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