xref: /webtrees/app/Services/HousekeepingService.php (revision 3b49aa7b9a581e847403c99e295596cc59a5783c)
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