xref: /webtrees/app/Services/HousekeepingService.php (revision 12c79f746ea1903d66aea1fe0412eda6149a5532)
1<?php
2/**
3 * webtrees: online genealogy
4 * Copyright (C) 2018 webtrees development team
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16namespace Fisharebest\Webtrees\Services;
17
18use Exception;
19use Fisharebest\Webtrees\Database;
20use League\Flysystem\Filesystem;
21
22/**
23 * Clean up old data, files and folders.
24 */
25class HousekeepingService
26{
27    // This is a list of old files and directories, from earlier versions of webtrees.
28    // git diff 1.7.9..master --name-status | grep ^D
29    const OLD_PATHS = [
30        // Removed in 1.0.2
31        'language/en.mo',
32        // Removed in 1.0.3
33        'themechange.php',
34        // Removed in 1.1.0
35        'addremotelink.php',
36        'addsearchlink.php',
37        'client.php',
38        'dir_editor.php',
39        'editconfig_gedcom.php',
40        'editgedcoms.php',
41        'edit_merge.php',
42        'edit_news.php',
43        'genservice.php',
44        'logs.php',
45        'manageservers.php',
46        'media.php',
47        'module_admin.php',
48        //WT_ROOT.'modules', // Do not delete - users may have stored custom modules/data here
49        'opensearch.php',
50        'PEAR.php',
51        'pgv_to_wt.php',
52        'places',
53        //WT_ROOT.'robots.txt', // Do not delete this - it may contain user data
54        'serviceClientTest.php',
55        'siteconfig.php',
56        'SOAP',
57        'themes/clouds/mozilla.css',
58        'themes/clouds/netscape.css',
59        'themes/colors/mozilla.css',
60        'themes/colors/netscape.css',
61        'themes/fab/mozilla.css',
62        'themes/fab/netscape.css',
63        'themes/minimal/mozilla.css',
64        'themes/minimal/netscape.css',
65        'themes/webtrees/mozilla.css',
66        'themes/webtrees/netscape.css',
67        'themes/webtrees/style_rtl.css',
68        'themes/xenea/mozilla.css',
69        'themes/xenea/netscape.css',
70        'uploadmedia.php',
71        'useradmin.php',
72        'webservice',
73        'wtinfo.php',
74        // Removed in 1.1.2
75        'treenav.php',
76        // Removed in 1.2.0
77        'themes/clouds/jquery',
78        'themes/colors/jquery',
79        'themes/fab/jquery',
80        'themes/minimal/jquery',
81        'themes/webtrees/jquery',
82        'themes/xenea/jquery',
83        // Removed in 1.2.2
84        'themes/clouds/chrome.css',
85        'themes/clouds/opera.css',
86        'themes/clouds/print.css',
87        'themes/clouds/style_rtl.css',
88        'themes/colors/chrome.css',
89        'themes/colors/opera.css',
90        'themes/colors/print.css',
91        'themes/colors/style_rtl.css',
92        'themes/fab/chrome.css',
93        'themes/fab/opera.css',
94        'themes/minimal/chrome.css',
95        'themes/minimal/opera.css',
96        'themes/minimal/print.css',
97        'themes/minimal/style_rtl.css',
98        'themes/xenea/chrome.css',
99        'themes/xenea/opera.css',
100        'themes/xenea/print.css',
101        'themes/xenea/style_rtl.css',
102        // Removed in 1.2.3
103        //WT_ROOT.'modules_v2', // Do not delete - users may have stored custom modules/data here
104        // Removed in 1.2.4
105        'modules_v3/gedcom_favorites/help_text.php',
106        'modules_v3/GEDFact_assistant/_MEDIA/media_3_find.php',
107        'modules_v3/GEDFact_assistant/_MEDIA/media_3_search_add.php',
108        'modules_v3/GEDFact_assistant/_MEDIA/media_5_input.js',
109        'modules_v3/GEDFact_assistant/_MEDIA/media_5_input.php',
110        'modules_v3/GEDFact_assistant/_MEDIA/media_7_parse_addLinksTbl.php',
111        'modules_v3/GEDFact_assistant/_MEDIA/media_query_1a.php',
112        'modules_v3/GEDFact_assistant/_MEDIA/media_query_2a.php',
113        'modules_v3/GEDFact_assistant/_MEDIA/media_query_3a.php',
114        'modules_v3/lightbox/css/album_page_RTL2.css',
115        'modules_v3/lightbox/css/album_page_RTL.css',
116        'modules_v3/lightbox/css/album_page_RTL_ff.css',
117        'modules_v3/lightbox/css/clearbox_music.css',
118        'modules_v3/lightbox/css/clearbox_music_RTL.css',
119        'modules_v3/user_favorites/db_schema',
120        'modules_v3/user_favorites/help_text.php',
121        'search_engine.php',
122        'themes/clouds/modules.css',
123        'themes/colors/modules.css',
124        'themes/fab/modules.css',
125        'themes/minimal/modules.css',
126        'themes/webtrees/modules.css',
127        'themes/xenea/modules.css',
128        // Removed in 1.2.5
129        'modules_v3/clippings/index.php',
130        'modules_v3/googlemap/css/googlemap_style.css',
131        'modules_v3/googlemap/css/wt_v3_places_edit.css',
132        'modules_v3/googlemap/index.php',
133        'modules_v3/lightbox/index.php',
134        'modules_v3/recent_changes/help_text.php',
135        'modules_v3/todays_events/help_text.php',
136        'sidebar.php',
137        // Removed in 1.2.6
138        'modules_v3/sitemap/admin_index.php',
139        'modules_v3/sitemap/help_text.php',
140        'modules_v3/tree/css/styles',
141        'modules_v3/tree/css/treebottom.gif',
142        'modules_v3/tree/css/treebottomleft.gif',
143        'modules_v3/tree/css/treebottomright.gif',
144        'modules_v3/tree/css/tree.jpg',
145        'modules_v3/tree/css/treeleft.gif',
146        'modules_v3/tree/css/treeright.gif',
147        'modules_v3/tree/css/treetop.gif',
148        'modules_v3/tree/css/treetopleft.gif',
149        'modules_v3/tree/css/treetopright.gif',
150        'modules_v3/tree/css/treeview_print.css',
151        'modules_v3/tree/help_text.php',
152        'modules_v3/tree/images/print.png',
153        // Removed in 1.2.7
154        'login_register.php',
155        'modules_v3/top10_givnnames/help_text.php',
156        'modules_v3/top10_surnames/help_text.php',
157        // Removed in 1.3.0
158        'admin_site_ipaddress.php',
159        'downloadgedcom.php',
160        'export_gedcom.php',
161        'gedcheck.php',
162        'images',
163        'modules_v3/googlemap/admin_editconfig.php',
164        'modules_v3/googlemap/admin_placecheck.php',
165        'modules_v3/googlemap/flags.php',
166        'modules_v3/googlemap/images/pedigree_map.gif',
167        'modules_v3/googlemap/pedigree_map.php',
168        'modules_v3/lightbox/admin_config.php',
169        'modules_v3/lightbox/album.php',
170        'modules_v3/tree/css/vline.jpg',
171        // Removed in 1.3.1
172        'imageflush.php',
173        'modules_v3/googlemap/wt_v3_pedigree_map.js.php',
174        'modules_v3/lightbox/js/tip_balloon_RTL.js',
175        // Removed in 1.3.2
176        'modules_v3/address_report',
177        'modules_v3/lightbox/functions/lb_horiz_sort.php',
178        'modules_v3/random_media/help_text.php',
179        // Removed in 1.4.0
180        'imageview.php',
181        'media/MediaInfo.txt',
182        'media/thumbs/ThumbsInfo.txt',
183        'modules_v3/GEDFact_assistant/css/media_0_inverselink.css',
184        'modules_v3/lightbox/help_text.php',
185        'modules_v3/lightbox/images/blank.gif',
186        'modules_v3/lightbox/images/close_1.gif',
187        'modules_v3/lightbox/images/image_add.gif',
188        'modules_v3/lightbox/images/image_copy.gif',
189        'modules_v3/lightbox/images/image_delete.gif',
190        'modules_v3/lightbox/images/image_edit.gif',
191        'modules_v3/lightbox/images/image_link.gif',
192        'modules_v3/lightbox/images/images.gif',
193        'modules_v3/lightbox/images/image_view.gif',
194        'modules_v3/lightbox/images/loading.gif',
195        'modules_v3/lightbox/images/next.gif',
196        'modules_v3/lightbox/images/nextlabel.gif',
197        'modules_v3/lightbox/images/norm_2.gif',
198        'modules_v3/lightbox/images/overlay.png',
199        'modules_v3/lightbox/images/prev.gif',
200        'modules_v3/lightbox/images/prevlabel.gif',
201        'modules_v3/lightbox/images/private.gif',
202        'modules_v3/lightbox/images/slideshow.jpg',
203        'modules_v3/lightbox/images/transp80px.gif',
204        'modules_v3/lightbox/images/zoom_1.gif',
205        'modules_v3/lightbox/js',
206        'modules_v3/lightbox/music',
207        'modules_v3/lightbox/pic',
208        'themes/webtrees/chrome.css',
209        // Removed in 1.4.1
210        'modules_v3/lightbox/images/image_edit.png',
211        'modules_v3/lightbox/images/image_view.png',
212        // Removed in 1.4.2
213        'modules_v3/lightbox/images/image_view.png',
214        'modules_v3/top10_pageviews/help_text.php',
215        'themes/clouds/jquery-ui-1.10.0',
216        'themes/colors/jquery-ui-1.10.0',
217        'themes/fab/jquery-ui-1.10.0',
218        'themes/minimal/jquery-ui-1.10.0',
219        'themes/webtrees/jquery-ui-1.10.0',
220        'themes/xenea/jquery-ui-1.10.0',
221        // Removed in 1.5.0
222        'modules_v3/GEDFact_assistant/_CENS/census_note_decode.php',
223        'modules_v3/GEDFact_assistant/_CENS/census_asst_date.php',
224        'modules_v3/googlemap/wt_v3_googlemap.js.php',
225        'modules_v3/lightbox/functions/lightbox_print_media.php',
226        'modules_v3/upcoming_events/help_text.php',
227        'modules_v3/stories/help_text.php',
228        'modules_v3/user_messages/help_text.php',
229        'themes/clouds/favicon.png',
230        'themes/clouds/images',
231        'themes/clouds/msie.css',
232        'themes/clouds/style.css',
233        'themes/colors/css',
234        'themes/colors/favicon.png',
235        'themes/colors/images',
236        'themes/colors/ipad.css',
237        'themes/colors/msie.css',
238        'themes/fab/favicon.png',
239        'themes/fab/images',
240        'themes/fab/msie.css',
241        'themes/fab/style.css',
242        'themes/minimal/favicon.png',
243        'themes/minimal/images',
244        'themes/minimal/msie.css',
245        'themes/minimal/style.css',
246        'themes/webtrees/favicon.png',
247        'themes/webtrees/images',
248        'themes/webtrees/msie.css',
249        'themes/webtrees/style.css',
250        'themes/xenea/favicon.png',
251        'themes/xenea/images',
252        'themes/xenea/msie.css',
253        'themes/xenea/style.css',
254        // Removed in 1.5.1
255        'themes/clouds/css-1.5.0',
256        'themes/colors/css-1.5.0',
257        'themes/fab/css-1.5.0',
258        'themes/minimal/css-1.5.0',
259        'themes/webtrees/css-1.5.0',
260        'themes/xenea/css-1.5.0',
261        // Removed in 1.5.2
262        'themes/clouds/css-1.5.1',
263        'themes/colors/css-1.5.1',
264        'themes/fab/css-1.5.1',
265        'themes/minimal/css-1.5.1',
266        'themes/webtrees/css-1.5.1',
267        'themes/xenea/css-1.5.1',
268        // Removed in 1.5.3
269        'modules_v3/GEDFact_assistant/_CENS/census_asst_help.php',
270        'modules_v3/googlemap/admin_places.php',
271        'modules_v3/googlemap/defaultconfig.php',
272        'modules_v3/googlemap/googlemap.php',
273        'modules_v3/googlemap/placehierarchy.php',
274        'modules_v3/googlemap/places_edit.php',
275        'modules_v3/googlemap/util.js',
276        'modules_v3/googlemap/wt_v3_places_edit.js.php',
277        'modules_v3/googlemap/wt_v3_places_edit_overlays.js.php',
278        'modules_v3/googlemap/wt_v3_street_view.php',
279        'readme.html',
280        'themes/clouds/css-1.5.2',
281        'themes/colors/css-1.5.2',
282        'themes/fab/css-1.5.2',
283        'themes/minimal/css-1.5.2',
284        'themes/webtrees/css-1.5.2',
285        'themes/xenea/css-1.5.2',
286        // Removed in 1.6.0
287        'downloadbackup.php',
288        'modules_v3/ckeditor/ckeditor-4.3.2-custom',
289        'site-php-version.php',
290        'themes/clouds/css-1.5.3',
291        'themes/colors/css-1.5.3',
292        'themes/fab/css-1.5.3',
293        'themes/minimal/css-1.5.3',
294        'themes/webtrees/css-1.5.3',
295        'themes/xenea/css-1.5.3',
296        // Removed in 1.6.2
297        'themes/clouds/jquery-ui-1.10.3',
298        'themes/colors/css-1.6.0',
299        'themes/colors/jquery-ui-1.10.3',
300        'themes/fab/css-1.6.0',
301        'themes/fab/jquery-ui-1.10.3',
302        'themes/minimal/css-1.6.0',
303        'themes/minimal/jquery-ui-1.10.3',
304        'themes/webtrees/css-1.6.0',
305        'themes/webtrees/jquery-ui-1.10.3',
306        'themes/xenea/css-1.6.0',
307        'themes/xenea/jquery-ui-1.10.3',
308        // Removed in 1.7.0
309        'admin_site_other.php',
310        'js',
311        'language/en_GB.mo',
312        // Replaced with en-GB.mo
313        'language/en_US.mo',
314        // Replaced with en-US.mo
315        'language/pt_BR.mo',
316        // Replaced with pt-BR.mo
317        'language/zh_CN.mo',
318        // Replaced with zh-Hans.mo
319        'language/extra',
320        'library',
321        'modules_v3/batch_update/admin_batch_update.php',
322        'modules_v3/batch_update/plugins',
323        'modules_v3/charts/help_text.php',
324        'modules_v3/ckeditor/ckeditor-4.4.1-custom',
325        'modules_v3/clippings/clippings_ctrl.php',
326        'modules_v3/clippings/help_text.php',
327        'modules_v3/faq/help_text.php',
328        'modules_v3/gedcom_favorites/db_schema',
329        'modules_v3/gedcom_news/db_schema',
330        'modules_v3/googlemap/db_schema',
331        'modules_v3/googlemap/help_text.php',
332        'modules_v3/html/help_text.php',
333        'modules_v3/logged_in/help_text.php',
334        'modules_v3/review_changes/help_text.php',
335        'modules_v3/todo/help_text.php',
336        'modules_v3/tree/class_treeview.php',
337        'modules_v3/user_blog/db_schema',
338        'modules_v3/yahrzeit/help_text.php',
339        'save.php',
340        'themes/clouds/css-1.6.2',
341        'themes/clouds/templates',
342        'themes/clouds/header.php',
343        'themes/clouds/footer.php',
344        'themes/colors/css-1.6.2',
345        'themes/colors/templates',
346        'themes/colors/header.php',
347        'themes/colors/footer.php',
348        'themes/fab/css-1.6.2',
349        'themes/fab/templates',
350        'themes/fab/header.php',
351        'themes/fab/footer.php',
352        'themes/minimal/css-1.6.2',
353        'themes/minimal/templates',
354        'themes/minimal/header.php',
355        'themes/minimal/footer.php',
356        'themes/webtrees/css-1.6.2',
357        'themes/webtrees/templates',
358        'themes/webtrees/header.php',
359        'themes/webtrees/footer.php',
360        'themes/xenea/css-1.6.2',
361        'themes/xenea/templates',
362        'themes/xenea/header.php',
363        'themes/xenea/footer.php',
364        // Removed in 1.7.2
365        'assets/js-1.7.0',
366        // Removed in 1.7.3
367        'modules_v3/GEDFact_assistant/census/date.js',
368        'modules_v3/GEDFact_assistant/census/dynamicoptionlist.js',
369        // Removed in 1.7.4
370        'assets/js-1.7.2',
371        'themes/clouds/css-1.7.0',
372        'themes/colors/css-1.7.0',
373        'themes/fab/css-1.7.0',
374        'themes/minimal/css-1.7.0',
375        'themes/webtrees/css-1.7.0',
376        'themes/xenea/css-1.7.0',
377        // Removed in 1.7.5
378        'themes/clouds/css-1.7.4',
379        'themes/colors/css-1.7.4',
380        'themes/fab/css-1.7.4',
381        'themes/minimal/css-1.7.4',
382        'themes/webtrees/css-1.7.4',
383        'themes/xenea/css-1.7.4',
384        // Removed in 1.7.7
385        'assets/js-1.7.4',
386        'modules_v3/googlemap/images/css_sprite_facts.png',
387        'modules_v3/googlemap/images/flag_shadow.png',
388        'modules_v3/googlemap/images/shadow-left-large.png',
389        'modules_v3/googlemap/images/shadow-left-small.png',
390        'modules_v3/googlemap/images/shadow-right-large.png',
391        'modules_v3/googlemap/images/shadow-right-small.png',
392        'modules_v3/googlemap/images/shadow50.png',
393        'modules_v3/googlemap/images/transparent-left-large.png',
394        'modules_v3/googlemap/images/transparent-left-small.png',
395        'modules_v3/googlemap/images/transparent-right-large.png',
396        'modules_v3/googlemap/images/transparent-right-small.png',
397        // Removed in 1.7.8
398        'themes/clouds/css-1.7.5',
399        'themes/colors/css-1.7.5',
400        'themes/fab/css-1.7.5',
401        'themes/minimal/css-1.7.5',
402        'themes/webtrees/css-1.7.5',
403        'themes/xenea/css-1.7.5',
404        // Removed in 2.0.0
405        'action.php',
406        'addmedia.php',
407        'addmin.php',
408        'admin_media.php',
409        'admin_media_upload.php',
410        'admin_module_blocks.php',
411        'admin_module_charts.php',
412        'admin_module_menus.php',
413        'admin_module_reports.php',
414        'admin_module_sidebar.php',
415        'admin_module_tabs.php',
416        'admin_modules.php',
417        'admin_pgv_to_wt.php',
418        'admin_site_access.php',
419        'admin_site_change.php',
420        'admin_site_clean.php',
421        'admin_site_config.php',
422        'admin_site_info.php',
423        'admin_site_logs.php',
424        'admin_site_merge.php',
425        'admin_site_readme.php',
426        'admin_site_upgrade.php',
427        'admin_trees_check.php',
428        'admin_trees_config.php',
429        'admin_trees_download.php',
430        'admin_trees_duplicates.php',
431        'admin_trees_export.php',
432        'admin_trees_manage.php',
433        'admin_trees_merge.php',
434        'admin_trees_places.php',
435        'admin_trees_renumber.php',
436        'admin_trees_unconnected.php',
437        'admin_users.php',
438        'admin_users_bulk.php',
439        'ancestry.php',
440        'app/Controller',
441        'app/HitCounter.php',
442        'app/Module/ClippingsCart/ClippingsCartController.php',
443        'app/Module/FamiliesSidebarModule.php',
444        'app/Module/FamilyTreeFavorites',
445        'app/Module/GoogleMaps',
446        'app/Module/IndividualSidebarModule.php',
447        'app/Module/PageMenuModule.php',
448        'app/Query',
449        'app/SpecialChars',
450        'assets/js-1.7.7',
451        'assets/js-1.7.9',
452        'autocomplete.php',
453        'block_edit.php',
454        'branches.php',
455        'calendar.php',
456        'compact.php',
457        'data/html_purifier_cache',
458        'descendancy.php',
459        'editnews.php',
460        'edituser.php',
461        'edit_changes.php',
462        'edit_interface.php',
463        'expand_view.php',
464        'familybook.php',
465        'famlist.php',
466        'fanchart.php',
467        'find.php',
468        'help_text.php',
469        'hourglass.php',
470        'hourglass_ajax.php',
471        'import.php',
472        'includes',
473        'index_edit.php',
474        'indilist.php',
475        'inverselink.php',
476        'lifespan.php',
477        'login.php',
478        'logout.php',
479        'mediafirewall.php',
480        'medialist.php',
481        'message.php',
482        'module.php',
483        'notelist.php',
484        'packages',
485        'pedigree.php',
486        'relationship.php',
487        'repolist.php',
488        'reportengine.php',
489        'search.php',
490        'search_advanced.php',
491        'site-offline.php',
492        'site-unavailable.php',
493        'sourcelist.php',
494        'statistics.php',
495        'statisticsplot.php',
496        'themes/_administration',
497        'themes/_custom',
498        'themes/clouds/css-1.7.8',
499        'themes/clouds/jquery-ui-1.11.2',
500        'themes/colors/css-1.7.8',
501        'themes/colors/jquery-ui-1.11.2',
502        'themes/fab/css-1.7.8',
503        'themes/fab/jquery-ui-1.11.2',
504        'themes/minimal/css-1.7.8',
505        'themes/minimal/jquery-ui-1.11.2',
506        'themes/webtrees/css-1.7.8',
507        'themes/webtrees/jquery-ui-1.11.2',
508        'themes/xenea/css-1.7.8',
509        'themes/xenea/jquery-ui-1.11.2',
510        'timeline.php',
511    ];
512
513    /**
514     * Delete files and folders that belonged to an earlier version of webtrees.
515     * Return a list of those that we could not delete.
516     *
517     * @param Filesystem $filesystem
518     *
519     * @return array
520     */
521    public function deleteOldWebtreesFiles(Filesystem $filesystem): array
522    {
523        $paths_to_delete = [];
524
525        foreach (self::OLD_PATHS as $path) {
526            if (!$this->deleteFileOrFolder($filesystem, $path)) {
527                $paths_to_delete[] = $path;
528            }
529        }
530
531        return $paths_to_delete;
532    }
533
534    /**
535     * Delete old cache files.
536     *
537     * @param Filesystem $filesystem
538     * @param string     $path_to_cache
539     * @param int        $max_age_in_seconds
540     */
541    public function deleteOldCacheFiles(Filesystem $filesystem, string $path_to_cache, int $max_age_in_seconds)
542    {
543        $list = $filesystem->listContents($path_to_cache, true);
544
545        foreach ($list as $metadata) {
546            if ($metadata['timestamp'] ?? WT_TIMESTAMP < WT_TIMESTAMP - $max_age_in_seconds) {
547                $this->deleteFileOrFolder($filesystem, $metadata['path']);
548            }
549        }
550    }
551
552    /**
553     * @param int $max_age_in_seconds
554     */
555    public function deleteOldLogs(int $max_age_in_seconds)
556    {
557        if (Database::isConnected()) {
558            Database::prepare(
559                "DELETE FROM `##log` WHERE log_type IN ('error', 'media') AND log_time < FROM_UNIXTIME(:timestamp)"
560            )->execute([
561                'timestamp' => WT_TIMESTAMP - $max_age_in_seconds
562            ]);
563        }
564    }
565
566    /**
567     * @param int $max_age_in_seconds
568     */
569    public function deleteOldSessions(int $max_age_in_seconds)
570    {
571        if (Database::isConnected()) {
572            Database::prepare(
573                "DELETE FROM `##session` WHERE session_time < FROM_UNIXTIME(:timestamp)"
574            )->execute([
575                'timestamp' => WT_TIMESTAMP - $max_age_in_seconds
576            ]);
577        }
578    }
579
580    /**
581     * Delete a file or folder, if we can.
582     *
583     * @param Filesystem $filesystem
584     * @param string     $path
585     *
586     * @return bool
587     */
588    private function deleteFileOrFolder(Filesystem $filesystem, string $path): bool
589    {
590        if ($filesystem->has($path)) {
591            try {
592                $metadata = $filesystem->getMetadata($path);
593
594                if ($metadata['type'] === 'dir') {
595                    $filesystem->deleteDir($path);
596                }
597
598                if ($metadata['type'] === 'file') {
599                    $filesystem->delete($path);
600                }
601            } catch (Exception $ex) {
602                return false;
603            }
604        }
605
606        return true;
607    }
608}
609