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