1<?php 2 3/** 4 * webtrees: online genealogy 5 * Copyright (C) 2021 webtrees development team 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <https://www.gnu.org/licenses/>. 16 */ 17 18declare(strict_types=1); 19 20namespace Fisharebest\Webtrees\Services; 21 22use Fisharebest\Webtrees\Carbon; 23use Illuminate\Database\Capsule\Manager as DB; 24use League\Flysystem\Filesystem; 25use League\Flysystem\FilesystemException; 26use League\Flysystem\FilesystemOperator; 27use League\Flysystem\FilesystemReader; 28use League\Flysystem\UnableToDeleteDirectory; 29use League\Flysystem\UnableToDeleteFile; 30 31/** 32 * Clean up old data, files and folders. 33 */ 34class HousekeepingService 35{ 36 // This is a list of old files and directories, from earlier versions of webtrees. 37 // git diff 1.7.9..master --name-status | grep ^D 38 private const OLD_PATHS = [ 39 // Removed in 1.0.3 40 'themechange.php', 41 // Removed in 1.1.0 42 'addremotelink.php', 43 'addsearchlink.php', 44 'client.php', 45 'dir_editor.php', 46 'editconfig_gedcom.php', 47 'editgedcoms.php', 48 'edit_merge.php', 49 'edit_news.php', 50 'genservice.php', 51 'logs.php', 52 'manageservers.php', 53 'media.php', 54 'module_admin.php', 55 //'modules', // Do not delete - users may have stored custom modules/data here 56 'opensearch.php', 57 'PEAR.php', 58 'pgv_to_wt.php', 59 'places', 60 //'robots.txt', // Do not delete this - it may contain user data 61 'serviceClientTest.php', 62 'siteconfig.php', 63 'SOAP', 64 'uploadmedia.php', 65 'useradmin.php', 66 'webservice', 67 'wtinfo.php', 68 // Removed in 1.1.2 69 'treenav.php', 70 // Removed in 1.2.3 71 'modules_v2', 72 // Removed in 1.2.4 73 'search_engine.php', 74 // Removed in 1.2.5 75 'sidebar.php', 76 // Removed in 1.2.6 77 // Removed in 1.2.7 78 'login_register.php', 79 // Removed in 1.3.0 80 'admin_site_ipaddress.php', 81 'downloadgedcom.php', 82 'export_gedcom.php', 83 'gedcheck.php', 84 'images', 85 // Removed in 1.3.1 86 'imageflush.php', 87 '/lightbox/js/tip_balloon_RTL.js', 88 // Removed in 1.4.0 89 'imageview.php', 90 'media/MediaInfo.txt', 91 'media/thumbs/ThumbsInfo.txt', 92 // Removed in 1.5.3 93 'readme.html', 94 // Removed in 1.6.0 95 'downloadbackup.php', 96 'site-php-version.php', 97 // Removed in 1.7.0 98 'admin_site_other.php', 99 'js', 100 'library', 101 'save.php', 102 // Removed in 1.7.2 103 'assets/js-1.7.0', 104 // Removed in 1.7.4 105 'assets/js-1.7.2', 106 // Removed in 1.7.7 107 'assets/js-1.7.4', 108 // Removed in 2.0.0 109 'action.php', 110 'addmedia.php', 111 'addmin.php', 112 'admin_media.php', 113 'admin_media_upload.php', 114 'admin_module_blocks.php', 115 'admin_module_charts.php', 116 'admin_module_menus.php', 117 'admin_module_reports.php', 118 'admin_module_sidebar.php', 119 'admin_module_tabs.php', 120 'admin_modules.php', 121 'admin_pgv_to_wt.php', 122 'admin_site_access.php', 123 'admin_site_change.php', 124 'admin_site_clean.php', 125 'admin_site_config.php', 126 'admin_site_info.php', 127 'admin_site_logs.php', 128 'admin_site_merge.php', 129 'admin_site_readme.php', 130 'admin_site_upgrade.php', 131 'admin_trees_check.php', 132 'admin_trees_config.php', 133 'admin_trees_download.php', 134 'admin_trees_duplicates.php', 135 'admin_trees_export.php', 136 'admin_trees_manage.php', 137 'admin_trees_merge.php', 138 'admin_trees_places.php', 139 'admin_trees_renumber.php', 140 'admin_trees_unconnected.php', 141 'admin_users.php', 142 'admin_users_bulk.php', 143 'ancestry.php', 144 'app/Controller', 145 'app/HitCounter.php', 146 'app/Module/ClippingsCart/ClippingsCartController.php', 147 'app/Module/FamiliesSidebarModule.php', 148 'app/Module/FamilyTreeFavorites', 149 'app/Module/GoogleMaps', 150 'app/Module/IndividualSidebarModule.php', 151 'app/Module/PageMenuModule.php', 152 'app/Query', 153 'app/SpecialChars', 154 'assets/js-1.7.7', 155 'assets/js-1.7.9', 156 'autocomplete.php', 157 'block_edit.php', 158 'branches.php', 159 'calendar.php', 160 'compact.php', 161 'data/html_purifier_cache', 162 'descendancy.php', 163 'editnews.php', 164 'edituser.php', 165 'edit_changes.php', 166 'edit_interface.php', 167 'expand_view.php', 168 'familybook.php', 169 'famlist.php', 170 'fanchart.php', 171 'find.php', 172 'help_text.php', 173 'hourglass.php', 174 'hourglass_ajax.php', 175 'import.php', 176 'includes', 177 'index_edit.php', 178 'indilist.php', 179 'inverselink.php', 180 'language', 181 'lifespan.php', 182 'login.php', 183 'logout.php', 184 'mediafirewall.php', 185 'medialist.php', 186 'message.php', 187 'module.php', 188 'modules_v3', 189 'notelist.php', 190 'packages', 191 'pedigree.php', 192 'relationship.php', 193 'repolist.php', 194 'reportengine.php', 195 'search.php', 196 'search_advanced.php', 197 'site-offline.php', 198 'site-unavailable.php', 199 'sourcelist.php', 200 'statistics.php', 201 'statisticsplot.php', 202 'themes', 203 'timeline.php', 204 // Removed in 2.0.3 205 'public/css/vendor.css', 206 // Removed in 2.0.4 207 'public/favicon-120.png', 208 'public/favicon-144.png', 209 'public/favicon-57.png', 210 'public/favicon-76.png', 211 'public/favicon-96.png', 212 // Removed in 2.0.7 213 'public/ckeditor-4.11.2-custom', 214 // Removed in 2.0.12 215 'public/ckeditor-4.14.1-custom', 216 // Removed in 2.1.0 217 'modules_v4/example-footer.disable', 218 'modules_v4/example-middleware.disable', 219 'modules_v4/example-report.disable', 220 'modules_v4/example-report.disable', 221 'modules_v4/example-server-configuration.disable', 222 'modules_v4/example-theme.disable', 223 'modules_v4/example.disable', 224 'public/favicon-196.png', 225 'public/site.webmanifest', 226 ]; 227 228 /** 229 * Delete files and folders that belonged to an earlier version of webtrees. 230 * Return a list of those that we could not delete. 231 * 232 * @param FilesystemOperator $filesystem 233 * 234 * @return array<string> 235 */ 236 public function deleteOldWebtreesFiles(FilesystemOperator $filesystem): array 237 { 238 $paths_to_delete = []; 239 240 foreach (self::OLD_PATHS as $path) { 241 if (!$this->deleteFileOrFolder($filesystem, $path)) { 242 $paths_to_delete[] = $path; 243 } 244 } 245 246 return $paths_to_delete; 247 } 248 249 /** 250 * Delete old cache files. 251 * 252 * @param FilesystemOperator $filesystem 253 * @param string $path 254 * @param int $max_age Seconds 255 * 256 * @return void 257 */ 258 public function deleteOldFiles(FilesystemOperator $filesystem, string $path, int $max_age): void 259 { 260 $threshold = Carbon::now()->unix() - $max_age; 261 262 $list = $filesystem->listContents($path, FilesystemReader::LIST_DEEP); 263 264 foreach ($list as $metadata) { 265 // The timestamp can be absent or false. 266 $timestamp = $metadata['timestamp'] ?? false; 267 268 if ($timestamp !== false && $timestamp < $threshold) { 269 $this->deleteFileOrFolder($filesystem, $metadata['path']); 270 } 271 } 272 } 273 274 /** 275 * @param int $max_age_in_seconds 276 * 277 * @return void 278 */ 279 public function deleteOldLogs(int $max_age_in_seconds): void 280 { 281 $timestamp = Carbon::now()->subSeconds($max_age_in_seconds); 282 283 DB::table('log') 284 ->whereIn('log_type', ['error', 'media']) 285 ->where('log_time', '<', $timestamp) 286 ->delete(); 287 } 288 289 /** 290 * @param int $max_age_in_seconds 291 * 292 * @return void 293 */ 294 public function deleteOldSessions(int $max_age_in_seconds): void 295 { 296 $timestamp = Carbon::now()->subSeconds($max_age_in_seconds); 297 298 DB::table('session') 299 ->where('session_time', '<', $timestamp) 300 ->delete(); 301 } 302 303 /** 304 * Delete a file or folder, if we can. 305 * 306 * @param FilesystemOperator $filesystem 307 * @param string $path 308 * 309 * @return bool 310 */ 311 private function deleteFileOrFolder(FilesystemOperator $filesystem, string $path): bool 312 { 313 try { 314 $filesystem->delete($path); 315 } catch (FilesystemException | UnableToDeleteFile $ex) { 316 try { 317 $filesystem->deleteDirectory($path); 318 } catch (FilesystemException | UnableToDeleteDirectory $ex) { 319 return false; 320 } 321 } 322 323 return true; 324 } 325} 326