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