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 OC_Util 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 OC_Util, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
7 | class OC_Util { |
||
8 | public static $scripts = array(); |
||
9 | public static $styles = array(); |
||
10 | public static $headers = array(); |
||
11 | private static $rootMounted = false; |
||
12 | private static $fsSetup = false; |
||
13 | |||
14 | private static function initLocalStorageRootFS() { |
||
24 | |||
25 | /** |
||
26 | * mounting an object storage as the root fs will in essence remove the |
||
27 | * necessity of a data folder being present. |
||
28 | * TODO make home storage aware of this and use the object storage instead of local disk access |
||
29 | * |
||
30 | * @param array $config containing 'class' and optional 'arguments' |
||
31 | */ |
||
32 | private static function initObjectStoreRootFS($config) { |
||
53 | |||
54 | /** |
||
55 | * Can be set up |
||
56 | * |
||
57 | * @param string $user |
||
58 | * @return boolean |
||
59 | * @description configure the initial filesystem based on the configuration |
||
60 | */ |
||
61 | public static function setupFS($user = '') { |
||
137 | |||
138 | /** |
||
139 | * check if a password is required for each public link |
||
140 | * |
||
141 | * @return boolean |
||
142 | */ |
||
143 | public static function isPublicLinkPasswordRequired() { |
||
148 | |||
149 | /** |
||
150 | * check if sharing is disabled for the current user |
||
151 | * |
||
152 | * @return boolean |
||
153 | */ |
||
154 | public static function isSharingDisabledForUser() { |
||
171 | |||
172 | /** |
||
173 | * check if share API enforces a default expire date |
||
174 | * |
||
175 | * @return boolean |
||
176 | */ |
||
177 | public static function isDefaultExpireDateEnforced() { |
||
187 | |||
188 | /** |
||
189 | * Get the quota of a user |
||
190 | * |
||
191 | * @param string $user |
||
192 | * @return int Quota bytes |
||
193 | */ |
||
194 | public static function getUserQuota($user) { |
||
206 | |||
207 | /** |
||
208 | * copies the skeleton to the users /files |
||
209 | * |
||
210 | * @param \OC\User\User $user |
||
211 | * @param \OCP\Files\Folder $userDirectory |
||
212 | */ |
||
213 | public static function copySkeleton(\OC\User\User $user, \OCP\Files\Folder $userDirectory) { |
||
228 | |||
229 | /** |
||
230 | * copies a directory recursively by using streams |
||
231 | * |
||
232 | * @param string $source |
||
233 | * @param \OCP\Files\Folder $target |
||
234 | * @return void |
||
235 | */ |
||
236 | public static function copyr($source, \OCP\Files\Folder $target) { |
||
251 | |||
252 | /** |
||
253 | * @return void |
||
254 | */ |
||
255 | public static function tearDownFS() { |
||
260 | |||
261 | /** |
||
262 | * get the current installed version of ownCloud |
||
263 | * |
||
264 | * @return array |
||
265 | */ |
||
266 | public static function getVersion() { |
||
270 | |||
271 | /** |
||
272 | * get the current installed version string of ownCloud |
||
273 | * |
||
274 | * @return string |
||
275 | */ |
||
276 | public static function getVersionString() { |
||
280 | |||
281 | /** |
||
282 | * @description get the current installed edition of ownCloud. There is the community |
||
283 | * edition that just returns an empty string and the enterprise edition |
||
284 | * that returns "Enterprise". |
||
285 | * @return string |
||
286 | */ |
||
287 | public static function getEditionString() { |
||
295 | |||
296 | /** |
||
297 | * @description get the update channel of the current installed of ownCloud. |
||
298 | * @return string |
||
299 | */ |
||
300 | public static function getChannel() { |
||
304 | |||
305 | /** |
||
306 | * @description get the build number of the current installed of ownCloud. |
||
307 | * @return string |
||
308 | */ |
||
309 | public static function getBuild() { |
||
313 | |||
314 | /** |
||
315 | * @description load the version.php into the session as cache |
||
316 | */ |
||
317 | private static function loadVersion() { |
||
334 | |||
335 | /** |
||
336 | * generates a path for JS/CSS files. If no application is provided it will create the path for core. |
||
337 | * |
||
338 | * @param string $application application to get the files from |
||
339 | * @param string $directory directory withing this application (css, js, vendor, etc) |
||
340 | * @param string $file the file inside of the above folder |
||
341 | * @return string the path |
||
342 | */ |
||
343 | private static function generatePath($application, $directory, $file) { |
||
354 | |||
355 | /** |
||
356 | * add a javascript file |
||
357 | * |
||
358 | * @param string $application application id |
||
359 | * @param string|null $file filename |
||
360 | * @return void |
||
361 | */ |
||
362 | public static function addScript($application, $file = null) { |
||
372 | |||
373 | /** |
||
374 | * add a javascript file from the vendor sub folder |
||
375 | * |
||
376 | * @param string $application application id |
||
377 | * @param string|null $file filename |
||
378 | * @return void |
||
379 | */ |
||
380 | public static function addVendorScript($application, $file = null) { |
||
386 | |||
387 | /** |
||
388 | * add a translation JS file |
||
389 | * |
||
390 | * @param string $application application id |
||
391 | * @param string $languageCode language code, defaults to the current language |
||
392 | */ |
||
393 | public static function addTranslations($application, $languageCode = null) { |
||
407 | |||
408 | /** |
||
409 | * add a css file |
||
410 | * |
||
411 | * @param string $application application id |
||
412 | * @param string|null $file filename |
||
413 | * @return void |
||
414 | */ |
||
415 | public static function addStyle($application, $file = null) { |
||
421 | |||
422 | /** |
||
423 | * add a css file from the vendor sub folder |
||
424 | * |
||
425 | * @param string $application application id |
||
426 | * @param string|null $file filename |
||
427 | * @return void |
||
428 | */ |
||
429 | public static function addVendorStyle($application, $file = null) { |
||
435 | |||
436 | /** |
||
437 | * Add a custom element to the header |
||
438 | * If $text is null then the element will be written as empty element. |
||
439 | * So use "" to get a closing tag. |
||
440 | * @param string $tag tag name of the element |
||
441 | * @param array $attributes array of attributes for the element |
||
442 | * @param string $text the text content for the element |
||
443 | */ |
||
444 | View Code Duplication | public static function addHeader($tag, $attributes, $text=null) { |
|
451 | |||
452 | /** |
||
453 | * formats a timestamp in the "right" way |
||
454 | * |
||
455 | * @param int $timestamp |
||
456 | * @param bool $dateOnly option to omit time from the result |
||
457 | * @param DateTimeZone|string $timeZone where the given timestamp shall be converted to |
||
458 | * @return string timestamp |
||
459 | * |
||
460 | * @deprecated Use \OC::$server->query('DateTimeFormatter') instead |
||
461 | */ |
||
462 | public static function formatDate($timestamp, $dateOnly = false, $timeZone = null) { |
||
474 | |||
475 | /** |
||
476 | * check if the current server configuration is suitable for ownCloud |
||
477 | * |
||
478 | * @param \OCP\IConfig $config |
||
479 | * @return array arrays with error messages and hints |
||
480 | */ |
||
481 | public static function checkServer(\OCP\IConfig $config) { |
||
652 | |||
653 | /** |
||
654 | * Check the database version |
||
655 | * |
||
656 | * @return array errors array |
||
657 | */ |
||
658 | public static function checkDatabaseVersion() { |
||
687 | |||
688 | |||
689 | /** |
||
690 | * check if there are still some encrypted files stored |
||
691 | * |
||
692 | * @return boolean |
||
693 | */ |
||
694 | View Code Duplication | public static function encryptedFiles() { |
|
710 | |||
711 | /** |
||
712 | * check if a backup from the encryption keys exists |
||
713 | * |
||
714 | * @return boolean |
||
715 | */ |
||
716 | View Code Duplication | public static function backupKeysExists() { |
|
732 | |||
733 | /** |
||
734 | * Check for correct file permissions of data directory |
||
735 | * |
||
736 | * @param string $dataDirectory |
||
737 | * @return array arrays with error messages and hints |
||
738 | */ |
||
739 | public static function checkDataDirectoryPermissions($dataDirectory) { |
||
762 | |||
763 | /** |
||
764 | * Check that the data directory exists and is valid by |
||
765 | * checking the existence of the ".ocdata" file. |
||
766 | * |
||
767 | * @param string $dataDirectory data directory path |
||
768 | * @return bool true if the data directory is valid, false otherwise |
||
769 | */ |
||
770 | public static function checkDataDirectoryValidity($dataDirectory) { |
||
782 | |||
783 | /** |
||
784 | * @param array $errors |
||
785 | * @param string[] $messages |
||
786 | */ |
||
787 | public static function displayLoginPage($errors = array(), $messages = []) { |
||
809 | |||
810 | |||
811 | /** |
||
812 | * Check if the app is enabled, redirects to home if not |
||
813 | * |
||
814 | * @param string $app |
||
815 | * @return void |
||
816 | */ |
||
817 | public static function checkAppEnabled($app) { |
||
823 | |||
824 | /** |
||
825 | * Check if the user is logged in, redirects to home if not. With |
||
826 | * redirect URL parameter to the request URI. |
||
827 | * |
||
828 | * @return void |
||
829 | */ |
||
830 | public static function checkLoggedIn() { |
||
839 | |||
840 | /** |
||
841 | * Check if the user is a admin, redirects to home if not |
||
842 | * |
||
843 | * @return void |
||
844 | */ |
||
845 | View Code Duplication | public static function checkAdminUser() { |
|
852 | |||
853 | /** |
||
854 | * Check if it is allowed to remember login. |
||
855 | * |
||
856 | * @note Every app can set 'rememberlogin' to 'false' to disable the remember login feature |
||
857 | * |
||
858 | * @return bool |
||
859 | */ |
||
860 | public static function rememberLoginAllowed() { |
||
873 | |||
874 | /** |
||
875 | * Check if the user is a subadmin, redirects to home if not |
||
876 | * |
||
877 | * @return null|boolean $groups where the current user is subadmin |
||
878 | */ |
||
879 | View Code Duplication | public static function checkSubAdminUser() { |
|
887 | |||
888 | /** |
||
889 | * Returns the URL of the default page |
||
890 | * based on the system configuration and |
||
891 | * the apps visible for the current user |
||
892 | * |
||
893 | * @return string URL |
||
894 | */ |
||
895 | public static function getDefaultPageUrl() { |
||
921 | |||
922 | /** |
||
923 | * Redirect to the user default page |
||
924 | * |
||
925 | * @return void |
||
926 | */ |
||
927 | public static function redirectToDefaultPage() { |
||
932 | |||
933 | /** |
||
934 | * get an id unique for this instance |
||
935 | * |
||
936 | * @return string |
||
937 | */ |
||
938 | public static function getInstanceId() { |
||
947 | |||
948 | /** |
||
949 | * Register an get/post call. Important to prevent CSRF attacks. |
||
950 | * |
||
951 | * @return string Generated token. |
||
952 | * @description |
||
953 | * Creates a 'request token' (random) and stores it inside the session. |
||
954 | * Ever subsequent (ajax) request must use such a valid token to succeed, |
||
955 | * otherwise the request will be denied as a protection against CSRF. |
||
956 | * @see OC_Util::isCallRegistered() |
||
957 | */ |
||
958 | public static function callRegister() { |
||
970 | |||
971 | /** |
||
972 | * Check an ajax get/post call if the request token is valid. |
||
973 | * |
||
974 | * @return boolean False if request token is not set or is invalid. |
||
975 | * @see OC_Util::callRegister() |
||
976 | */ |
||
977 | public static function isCallRegistered() { |
||
980 | |||
981 | /** |
||
982 | * Check an ajax get/post call if the request token is valid. Exit if not. |
||
983 | * |
||
984 | * @return void |
||
985 | */ |
||
986 | public static function callCheck() { |
||
991 | |||
992 | /** |
||
993 | * Public function to sanitize HTML |
||
994 | * |
||
995 | * This function is used to sanitize HTML and should be applied on any |
||
996 | * string or array of strings before displaying it on a web page. |
||
997 | * |
||
998 | * @param string|array &$value |
||
999 | * @return string|array an array of sanitized strings or a single sanitized string, depends on the input parameter. |
||
1000 | */ |
||
1001 | public static function sanitizeHTML(&$value) { |
||
1010 | |||
1011 | /** |
||
1012 | * Public function to encode url parameters |
||
1013 | * |
||
1014 | * This function is used to encode path to file before output. |
||
1015 | * Encoding is done according to RFC 3986 with one exception: |
||
1016 | * Character '/' is preserved as is. |
||
1017 | * |
||
1018 | * @param string $component part of URI to encode |
||
1019 | * @return string |
||
1020 | */ |
||
1021 | public static function encodePath($component) { |
||
1026 | |||
1027 | /** |
||
1028 | * Check if the .htaccess file is working |
||
1029 | * |
||
1030 | * @throws OC\HintException If the testfile can't get written. |
||
1031 | * @return bool |
||
1032 | * @description Check if the .htaccess file is working by creating a test |
||
1033 | * file in the data directory and trying to access via http |
||
1034 | */ |
||
1035 | public static function isHtaccessWorking() { |
||
1077 | |||
1078 | /** |
||
1079 | * Check if the setlocal call does not work. This can happen if the right |
||
1080 | * local packages are not available on the server. |
||
1081 | * |
||
1082 | * @return bool |
||
1083 | */ |
||
1084 | public static function isSetLocaleWorking() { |
||
1096 | |||
1097 | /** |
||
1098 | * Check if it's possible to get the inline annotations |
||
1099 | * |
||
1100 | * @return bool |
||
1101 | */ |
||
1102 | public static function isAnnotationsWorking() { |
||
1108 | |||
1109 | /** |
||
1110 | * Check if the PHP module fileinfo is loaded. |
||
1111 | * |
||
1112 | * @return bool |
||
1113 | */ |
||
1114 | public static function fileInfoLoaded() { |
||
1117 | |||
1118 | /** |
||
1119 | * Check if the ownCloud server can connect to the internet |
||
1120 | * |
||
1121 | * @return bool |
||
1122 | */ |
||
1123 | public static function isInternetConnectionWorking() { |
||
1150 | |||
1151 | /** |
||
1152 | * Check if the connection to the internet is disabled on purpose |
||
1153 | * |
||
1154 | * @return string |
||
1155 | */ |
||
1156 | public static function isInternetConnectionEnabled() { |
||
1159 | |||
1160 | /** |
||
1161 | * clear all levels of output buffering |
||
1162 | * |
||
1163 | * @return void |
||
1164 | */ |
||
1165 | public static function obEnd() { |
||
1170 | |||
1171 | |||
1172 | /** |
||
1173 | * Generates a cryptographic secure pseudo-random string |
||
1174 | * |
||
1175 | * @param int $length of the random string |
||
1176 | * @return string |
||
1177 | * @deprecated Use \OC::$server->getSecureRandom()->getMediumStrengthGenerator()->generate($length); instead |
||
1178 | */ |
||
1179 | public static function generateRandomBytes($length = 30) { |
||
1182 | |||
1183 | /** |
||
1184 | * Checks if a secure random number generator is available |
||
1185 | * |
||
1186 | * @return true |
||
1187 | * @deprecated Function will be removed in the future and does only return true. |
||
1188 | */ |
||
1189 | public static function secureRNGAvailable() { |
||
1192 | |||
1193 | /** |
||
1194 | * Get URL content |
||
1195 | * @param string $url Url to get content |
||
1196 | * @deprecated Use \OC::$server->getHTTPHelper()->getUrlContent($url); |
||
1197 | * @throws Exception If the URL does not start with http:// or https:// |
||
1198 | * @return string of the response or false on error |
||
1199 | * This function get the content of a page via curl, if curl is enabled. |
||
1200 | * If not, file_get_contents is used. |
||
1201 | */ |
||
1202 | public static function getUrlContent($url) { |
||
1209 | |||
1210 | /** |
||
1211 | * Checks whether the server is running on Windows |
||
1212 | * |
||
1213 | * @return bool true if running on Windows, false otherwise |
||
1214 | */ |
||
1215 | public static function runningOnWindows() { |
||
1218 | |||
1219 | /** |
||
1220 | * Checks whether the server is running on Mac OS X |
||
1221 | * |
||
1222 | * @return bool true if running on Mac OS X, false otherwise |
||
1223 | */ |
||
1224 | public static function runningOnMac() { |
||
1227 | |||
1228 | /** |
||
1229 | * Checks whether server is running on HHVM |
||
1230 | * |
||
1231 | * @return bool True if running on HHVM, false otherwise |
||
1232 | */ |
||
1233 | public static function runningOnHhvm() { |
||
1236 | |||
1237 | /** |
||
1238 | * Handles the case that there may not be a theme, then check if a "default" |
||
1239 | * theme exists and take that one |
||
1240 | * |
||
1241 | * @return string the theme |
||
1242 | */ |
||
1243 | public static function getTheme() { |
||
1254 | |||
1255 | /** |
||
1256 | * Clear the opcode cache if one exists |
||
1257 | * This is necessary for writing to the config file |
||
1258 | * in case the opcode cache does not re-validate files |
||
1259 | * |
||
1260 | * @return void |
||
1261 | */ |
||
1262 | public static function clearOpcodeCache() { |
||
1284 | |||
1285 | /** |
||
1286 | * Normalize a unicode string |
||
1287 | * |
||
1288 | * @param string $value a not normalized string |
||
1289 | * @return bool|string |
||
1290 | */ |
||
1291 | public static function normalizeUnicode($value) { |
||
1304 | |||
1305 | /** |
||
1306 | * @param boolean|string $file |
||
1307 | * @return string |
||
1308 | */ |
||
1309 | public static function basename($file) { |
||
1314 | |||
1315 | /** |
||
1316 | * A human readable string is generated based on version, channel and build number |
||
1317 | * |
||
1318 | * @return string |
||
1319 | */ |
||
1320 | public static function getHumanVersion() { |
||
1328 | |||
1329 | /** |
||
1330 | * Returns whether the given file name is valid |
||
1331 | * |
||
1332 | * @param string $file file name to check |
||
1333 | * @return bool true if the file name is valid, false otherwise |
||
1334 | */ |
||
1335 | public static function isValidFileName($file) { |
||
1350 | |||
1351 | /** |
||
1352 | * Check whether the instance needs to perform an upgrade, |
||
1353 | * either when the core version is higher or any app requires |
||
1354 | * an upgrade. |
||
1355 | * |
||
1356 | * @param \OCP\IConfig $config |
||
1357 | * @return bool whether the core or any app needs an upgrade |
||
1358 | */ |
||
1359 | public static function needUpgrade(\OCP\IConfig $config) { |
||
1381 | |||
1382 | /** |
||
1383 | * Check if PhpCharset config is UTF-8 |
||
1384 | * |
||
1385 | * @return string |
||
1386 | */ |
||
1387 | public static function isPhpCharSetUtf8() { |
||
1390 | |||
1391 | } |
||
1392 |
Let’s take a look at an example:
In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.
Available Fixes
Change the type-hint for the parameter:
Add an additional type-check:
Add the method to the interface: