Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like MediaModule often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use MediaModule, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
26 | class MediaModule implements SingletonInterface |
||
27 | { |
||
28 | |||
29 | /** |
||
30 | * @var string |
||
31 | */ |
||
32 | const SIGNATURE = 'user_MediaM1'; |
||
33 | |||
34 | /** |
||
35 | * @var string |
||
36 | */ |
||
37 | const PARAMETER_PREFIX = 'tx_media_user_mediam1'; |
||
38 | |||
39 | /** |
||
40 | * @var ResourceStorage |
||
41 | */ |
||
42 | protected $currentStorage; |
||
43 | |||
44 | /** |
||
45 | * @return string |
||
46 | */ |
||
47 | static public function getSignature() |
||
48 | { |
||
49 | return self::SIGNATURE; |
||
50 | } |
||
51 | |||
52 | /** |
||
53 | * @return string |
||
54 | */ |
||
55 | static public function getParameterPrefix() |
||
56 | { |
||
57 | return self::PARAMETER_PREFIX; |
||
58 | } |
||
59 | |||
60 | /** |
||
61 | * Return all storage allowed for the Backend User. |
||
62 | * |
||
63 | * @throws \RuntimeException |
||
64 | * @return ResourceStorage[] |
||
65 | */ |
||
66 | public function getAllowedStorages() |
||
67 | { |
||
68 | |||
69 | $storages = $this->getBackendUser()->getFileStorages(); |
||
70 | if (empty($storages)) { |
||
71 | throw new \RuntimeException('No storage is accessible for the current BE User. Forgotten to define a mount point for this BE User?', 1380801970); |
||
72 | } |
||
73 | return $storages; |
||
74 | } |
||
75 | |||
76 | /** |
||
77 | * Returns the current file storage in use. |
||
78 | * |
||
79 | * @return ResourceStorage |
||
80 | */ |
||
81 | public function getCurrentStorage() |
||
82 | { |
||
83 | if (is_null($this->currentStorage)) { |
||
84 | |||
85 | $storageIdentifier = $this->getStorageIdentifierFromSessionOrArguments(); |
||
86 | |||
87 | if ($storageIdentifier > 0) { |
||
88 | $currentStorage = ResourceFactory::getInstance()->getStorageObject($storageIdentifier); |
||
89 | } else { |
||
90 | |||
91 | // We differentiate the cases whether the User is admin or not. |
||
92 | if ($this->getBackendUser()->isAdmin()) { |
||
93 | |||
94 | $currentStorage = ResourceFactory::getInstance()->getDefaultStorage(); |
||
95 | |||
96 | // Not default storage has been flagged in "sys_file_storage". |
||
97 | // Fallback approach: take the first storage as the current. |
||
98 | if (!$currentStorage) { |
||
99 | /** @var $storageRepository StorageRepository */ |
||
100 | $storageRepository = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\StorageRepository::class); |
||
101 | |||
102 | $storages = $storageRepository->findAll(); |
||
103 | $currentStorage = current($storages); |
||
104 | } |
||
105 | } else { |
||
106 | $fileMounts = $this->getBackendUser()->getFileMountRecords(); |
||
107 | $firstFileMount = current($fileMounts); |
||
108 | $currentStorage = ResourceFactory::getInstance()->getStorageObject($firstFileMount['base']); |
||
109 | } |
||
110 | } |
||
111 | |||
112 | $this->currentStorage = $currentStorage; |
||
113 | } |
||
114 | return $this->currentStorage; |
||
115 | } |
||
116 | |||
117 | /** |
||
118 | * Retrieve a possible storage identifier from the session or from the arguments. |
||
119 | * |
||
120 | * @return int |
||
121 | */ |
||
122 | protected function getStorageIdentifierFromSessionOrArguments() |
||
123 | { |
||
124 | |||
125 | // Default value |
||
126 | $storageIdentifier = 0; |
||
127 | |||
128 | // Get last selected storage from User settings |
||
129 | if (SessionUtility::getInstance()->get('lastSelectedStorage') > 0) { |
||
130 | $storageIdentifier = SessionUtility::getInstance()->get('lastSelectedStorage'); |
||
131 | } |
||
132 | |||
133 | $argumentPrefix = $this->getModuleLoader()->getParameterPrefix(); |
||
134 | $arguments = GeneralUtility::_GET($argumentPrefix); |
||
135 | |||
136 | // Override selected storage from the session if GET argument "storage" is detected. |
||
137 | if (!empty($arguments['storage']) && (int)$arguments['storage'] > 0) { |
||
138 | $storageIdentifier = (int)$arguments['storage']; |
||
139 | |||
140 | // Save state |
||
141 | SessionUtility::getInstance()->set('lastSelectedStorage', $storageIdentifier); |
||
142 | } |
||
143 | |||
144 | return (int)$storageIdentifier; |
||
145 | } |
||
146 | |||
147 | /** |
||
148 | * Return the combined parameter from the URL. |
||
149 | * |
||
150 | * @return string |
||
151 | */ |
||
152 | public function getCombinedIdentifier() |
||
153 | { |
||
154 | |||
155 | // Fetch possible combined identifier. |
||
156 | $combinedIdentifier = GeneralUtility::_GET('id'); |
||
157 | |||
158 | if ($combinedIdentifier) { |
||
159 | |||
160 | // Fix a bug at the Core level: the "id" parameter is encoded again when translating file. |
||
161 | // Add a loop to decode maximum 999 time! |
||
162 | $semaphore = 0; |
||
163 | $semaphoreLimit = 999; |
||
164 | while (!$this->isWellDecoded($combinedIdentifier) && $semaphore < $semaphoreLimit) { |
||
165 | $combinedIdentifier = urldecode($combinedIdentifier); |
||
166 | $semaphore++; |
||
167 | } |
||
168 | } |
||
169 | |||
170 | return $combinedIdentifier; |
||
171 | } |
||
172 | |||
173 | /** |
||
174 | * @param $combinedIdentifier |
||
175 | * @return bool |
||
176 | */ |
||
177 | protected function isWellDecoded($combinedIdentifier) |
||
178 | { |
||
179 | return preg_match('/.*:.*/', $combinedIdentifier); |
||
180 | } |
||
181 | |||
182 | /** |
||
183 | * @return Folder |
||
184 | */ |
||
185 | public function getFirstAvailableFolder() |
||
186 | { |
||
187 | |||
188 | // Take the first object of the first storage. |
||
189 | $storages = $this->getBackendUser()->getFileStorages(); |
||
190 | $storage = reset($storages); |
||
191 | if ($storage) { |
||
192 | $folder = $storage->getRootLevelFolder(); |
||
193 | } else { |
||
194 | throw new \RuntimeException('Could not find any folder to be displayed.', 1444665954); |
||
195 | } |
||
196 | return $folder; |
||
197 | } |
||
198 | |||
199 | /** |
||
200 | * @return Folder |
||
201 | */ |
||
202 | public function getCurrentFolder() |
||
203 | { |
||
204 | |||
205 | $combinedIdentifier = $this->getCombinedIdentifier(); |
||
206 | |||
207 | if ($combinedIdentifier) { |
||
208 | $folder = $this->getFolderForCombinedIdentifier($combinedIdentifier); |
||
209 | } else { |
||
210 | $folder = $this->getFirstAvailableFolder(); |
||
211 | } |
||
212 | |||
213 | return $folder; |
||
214 | } |
||
215 | |||
216 | /** |
||
217 | * @param string $combinedIdentifier |
||
218 | * @return Folder |
||
219 | */ |
||
220 | public function getFolderForCombinedIdentifier($combinedIdentifier) |
||
221 | { |
||
222 | |||
223 | // Code taken from FileListController.php |
||
224 | $storage = ResourceFactory::getInstance()->getStorageObjectFromCombinedIdentifier($combinedIdentifier); |
||
225 | $identifier = substr($combinedIdentifier, strpos($combinedIdentifier, ':') + 1); |
||
226 | if (!$storage->hasFolder($identifier)) { |
||
227 | $identifier = $storage->getFolderIdentifierFromFileIdentifier($identifier); |
||
228 | } |
||
229 | |||
230 | // Retrieve the folder object. |
||
231 | $folder = ResourceFactory::getInstance()->getFolderObjectFromCombinedIdentifier($storage->getUid() . ':' . $identifier); |
||
232 | |||
233 | // Disallow the rendering of the processing folder (e.g. could be called manually) |
||
234 | // and all folders without any defined storage |
||
235 | if ($folder && ($folder->getStorage()->getUid() == 0 || trim($folder->getStorage()->getProcessingFolder()->getIdentifier(), '/') === trim($folder->getIdentifier(), '/'))) { |
||
236 | $storage = ResourceFactory::getInstance()->getStorageObjectFromCombinedIdentifier($combinedIdentifier); |
||
237 | $folder = $storage->getRootLevelFolder(); |
||
238 | } |
||
239 | |||
240 | return $folder; |
||
241 | } |
||
242 | |||
243 | /** |
||
244 | * Tell whether the Folder Tree is display or not. |
||
245 | * |
||
246 | * @return bool |
||
247 | */ |
||
248 | public function hasFolderTree() |
||
249 | { |
||
250 | $configuration = $this->getModuleConfiguration(); |
||
251 | return (bool)$configuration['has_folder_tree']; |
||
252 | } |
||
253 | |||
254 | /** |
||
255 | * Tell whether the sub-folders must be included when browsing. |
||
256 | * |
||
257 | * @return bool |
||
258 | */ |
||
259 | public function hasRecursiveSelection() |
||
260 | { |
||
261 | |||
262 | $parameterPrefix = $this->getModuleLoader()->getParameterPrefix(); |
||
263 | $parameters = GeneralUtility::_GET($parameterPrefix); |
||
264 | |||
265 | $hasRecursiveSelection = true; |
||
266 | if (isset($parameters['hasRecursiveSelection'])) { |
||
267 | $hasRecursiveSelection = (bool)$parameters['hasRecursiveSelection']; |
||
268 | } |
||
269 | |||
270 | return $hasRecursiveSelection; |
||
271 | } |
||
272 | |||
273 | /** |
||
274 | * Return the target folder for the uploaded file. |
||
275 | * |
||
276 | * @param UploadedFileInterface $uploadedFile |
||
277 | * @param ResourceStorage $storage |
||
278 | * @return \TYPO3\CMS\Core\Resource\Folder |
||
279 | */ |
||
280 | View Code Duplication | public function getTargetFolderForUploadedFile(UploadedFileInterface $uploadedFile, ResourceStorage $storage) |
|
|
|||
281 | { |
||
282 | |||
283 | // default is the root level |
||
284 | $folder = $storage->getRootLevelFolder(); // get the root folder by default |
||
285 | |||
286 | // Get a possible mount point coming from the storage record. |
||
287 | $storageRecord = $storage->getStorageRecord(); |
||
288 | $mountPointIdentifier = $storageRecord['mount_point_file_type_' . $uploadedFile->getType()]; |
||
289 | if ($mountPointIdentifier > 0) { |
||
290 | |||
291 | // We don't have a Mount Point repository in FAL, so query the database directly. |
||
292 | $record = $this->getDataService()->getRecord('sys_filemounts', ['uid' => $mountPointIdentifier]); |
||
293 | |||
294 | if (!empty($record['path'])) { |
||
295 | $folder = $storage->getFolder($record['path']); |
||
296 | } |
||
297 | } |
||
298 | return $folder; |
||
299 | } |
||
300 | |||
301 | /** |
||
302 | * Return a new target folder when moving file from one storage to another. |
||
303 | * |
||
304 | * @param ResourceStorage $storage |
||
305 | * @param File $file |
||
306 | * @return \TYPO3\CMS\Core\Resource\Folder |
||
307 | */ |
||
308 | View Code Duplication | public function getDefaultFolderInStorage(ResourceStorage $storage, File $file) |
|
309 | { |
||
310 | |||
311 | // default is the root level |
||
312 | $folder = $storage->getRootLevelFolder(); |
||
313 | |||
314 | // Retrieve storage record and a possible configured mount point. |
||
315 | $storageRecord = $storage->getStorageRecord(); |
||
316 | $mountPointIdentifier = $storageRecord['mount_point_file_type_' . $file->getType()]; |
||
317 | |||
318 | if ($mountPointIdentifier > 0) { |
||
319 | |||
320 | // We don't have a Mount Point repository in FAL, so query the database directly. |
||
321 | $record = $this->getDataService()->getRecord('sys_filemounts', ['uid' => $mountPointIdentifier]); |
||
322 | if (!empty($record['path'])) { |
||
323 | $folder = $storage->getFolder($record['path']); |
||
324 | } |
||
325 | } |
||
326 | return $folder; |
||
327 | } |
||
328 | |||
329 | /** |
||
330 | * @return array |
||
331 | */ |
||
332 | protected function getModuleConfiguration() |
||
333 | { |
||
334 | return GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('media'); |
||
335 | } |
||
336 | |||
337 | /** |
||
338 | * @return object|DataService |
||
339 | */ |
||
340 | protected function getDataService(): DataService |
||
341 | { |
||
342 | return GeneralUtility::makeInstance(DataService::class); |
||
343 | } |
||
344 | |||
345 | /** |
||
346 | * Returns an instance of the current Backend User. |
||
347 | * |
||
348 | * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication |
||
349 | */ |
||
350 | protected function getBackendUser() |
||
354 | |||
355 | /** |
||
356 | * Return the module loader. |
||
357 | * |
||
358 | * @return \Fab\Vidi\Module\ModuleLoader|object |
||
359 | */ |
||
360 | protected function getModuleLoader() |
||
364 | |||
365 | } |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.