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