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\Module; 21 22use Fisharebest\Webtrees\Family; 23use Fisharebest\Webtrees\GedcomRecord; 24use Fisharebest\Webtrees\Individual; 25use Fisharebest\Webtrees\Location; 26use Fisharebest\Webtrees\Media; 27use Fisharebest\Webtrees\Note; 28use Fisharebest\Webtrees\Repository; 29use Fisharebest\Webtrees\Source; 30use Fisharebest\Webtrees\Submitter; 31use Fisharebest\Webtrees\Tree; 32use Illuminate\Database\Capsule\Manager as DB; 33use Illuminate\Database\Query\Builder; 34use Illuminate\Support\Collection; 35 36/** 37 * Trait ModuleDataFixTrait - default implementation of ModuleDataFixTrait 38 */ 39trait ModuleDataFixTrait 40{ 41 /** 42 * Options form. 43 * 44 * @param Tree $tree 45 * 46 * @return string 47 */ 48 public function fixOptions(/** @scrutinizer ignore-unused */ Tree $tree): string 49 { 50 return ''; 51 } 52 53 /** 54 * A list of all records that need examining. This may include records 55 * that do not need updating, if we can't detect this quickly using SQL. 56 * 57 * @param Tree $tree 58 * @param array<string,string> $params 59 * 60 * @return Collection<int,object> 61 */ 62 public function recordsToFix(Tree $tree, array $params): Collection 63 { 64 $families = $this->familiesToFix($tree, $params); 65 $individuals = $this->individualsToFix($tree, $params); 66 $locations = $this->locationsToFix($tree, $params); 67 $media = $this->mediaToFix($tree, $params); 68 $notes = $this->notesToFix($tree, $params); 69 $repositories = $this->repositoriesToFix($tree, $params); 70 $sources = $this->sourcesToFix($tree, $params); 71 $submitters = $this->submittersToFix($tree, $params); 72 73 $records = new Collection(); 74 75 if ($families !== null) { 76 $records = $records->concat($this->mergePendingRecords($families, $tree, Family::RECORD_TYPE)); 77 } 78 79 if ($individuals !== null) { 80 $records = $records->concat($this->mergePendingRecords($individuals, $tree, Individual::RECORD_TYPE)); 81 } 82 83 if ($locations !== null) { 84 $records = $records->concat($this->mergePendingRecords($locations, $tree, Location::RECORD_TYPE)); 85 } 86 87 if ($media !== null) { 88 $records = $records->concat($this->mergePendingRecords($media, $tree, Media::RECORD_TYPE)); 89 } 90 91 if ($notes !== null) { 92 $records = $records->concat($this->mergePendingRecords($notes, $tree, Note::RECORD_TYPE)); 93 } 94 95 if ($repositories !== null) { 96 $records = $records->concat($this->mergePendingRecords($repositories, $tree, Repository::RECORD_TYPE)); 97 } 98 99 if ($sources !== null) { 100 $records = $records->concat($this->mergePendingRecords($sources, $tree, Source::RECORD_TYPE)); 101 } 102 103 if ($submitters !== null) { 104 $records = $records->concat($this->mergePendingRecords($submitters, $tree, Submitter::RECORD_TYPE)); 105 } 106 107 return $records 108 ->unique() 109 ->sort(static function (object $x, object $y) { 110 return $x->xref <=> $y->xref; 111 }); 112 } 113 114 /** 115 * Does a record need updating? 116 * 117 * @param GedcomRecord $record 118 * @param array<string,string> $params 119 * 120 * @return bool 121 */ 122 public function doesRecordNeedUpdate(/** @scrutinizer ignore-unused */ GedcomRecord $record, /** @scrutinizer ignore-unused */ array $params): bool 123 { 124 return false; 125 } 126 127 /** 128 * Show the changes we would make 129 * 130 * @param GedcomRecord $record 131 * @param array<string,string> $params 132 * 133 * @return string 134 */ 135 public function previewUpdate(GedcomRecord $record, /** @scrutinizer ignore-unused */ array $params): string 136 { 137 return $record->fullName(); 138 } 139 140 /** 141 * Fix a record 142 * 143 * @param GedcomRecord $record 144 * @param array<string,string> $params 145 * 146 * @return void 147 */ 148 public function updateRecord(/** @scrutinizer ignore-unused */ GedcomRecord $record, /** @scrutinizer ignore-unused */ array $params): void 149 { 150 } 151 152 /** 153 * XREFs of family records that might need fixing. 154 * 155 * @param Tree $tree 156 * @param array<string,string> $params 157 * 158 * @return Collection<int,string>|null 159 */ 160 protected function familiesToFix(/** @scrutinizer ignore-unused */ Tree $tree, /** @scrutinizer ignore-unused */ array $params): ?Collection 161 { 162 return null; 163 } 164 165 /** 166 * @param Tree $tree 167 * @param array<string,string> $params 168 * 169 * @return Builder 170 */ 171 protected function familiesToFixQuery(Tree $tree, array $params): Builder 172 { 173 $query = DB::table('families') 174 ->where('f_file', '=', $tree->id()); 175 176 if (isset($params['start'], $params['end'])) { 177 $query->whereBetween('f_id', [$params['start'], $params['end']]); 178 } 179 180 return $query; 181 } 182 183 /** 184 * XREFs of individual records that might need fixing. 185 * 186 * @param Tree $tree 187 * @param array<string,string> $params 188 * 189 * @return Collection<int,string>|null 190 */ 191 protected function individualsToFix(/** @scrutinizer ignore-unused */ Tree $tree, /** @scrutinizer ignore-unused */ array $params): ?Collection 192 { 193 return null; 194 } 195 196 /** 197 * @param Tree $tree 198 * @param array<string,string> $params 199 * 200 * @return Builder 201 */ 202 protected function individualsToFixQuery(Tree $tree, array $params): Builder 203 { 204 $query = DB::table('individuals') 205 ->where('i_file', '=', $tree->id()); 206 207 if (isset($params['start'], $params['end'])) { 208 $query->whereBetween('i_id', [$params['start'], $params['end']]); 209 } 210 211 return $query; 212 } 213 214 /** 215 * XREFs of location records that might need fixing. 216 * 217 * @param Tree $tree 218 * @param array<string,string> $params 219 * 220 * @return Collection<int,string>|null 221 */ 222 protected function locationsToFix(/** @scrutinizer ignore-unused */ Tree $tree, /** @scrutinizer ignore-unused */ array $params): ?Collection 223 { 224 return null; 225 } 226 227 /** 228 * @param Tree $tree 229 * @param array<string,string> $params 230 * 231 * @return Builder 232 */ 233 protected function locationsToFixQuery(Tree $tree, array $params): Builder 234 { 235 $query = DB::table('other') 236 ->where('o_type', '=', Location::RECORD_TYPE) 237 ->where('o_file', '=', $tree->id()); 238 239 if (isset($params['start'], $params['end'])) { 240 $query->whereBetween('o_id', [$params['start'], $params['end']]); 241 } 242 243 return $query; 244 } 245 246 /** 247 * XREFs of media records that might need fixing. 248 * 249 * @param Tree $tree 250 * @param array<string,string> $params 251 * 252 * @return Collection<int,string>|null 253 */ 254 protected function mediaToFix(/** @scrutinizer ignore-unused */ Tree $tree, /** @scrutinizer ignore-unused */ array $params): ?Collection 255 { 256 return null; 257 } 258 259 /** 260 * @param Tree $tree 261 * @param array<string,string> $params 262 * 263 * @return Builder 264 */ 265 protected function mediaToFixQuery(Tree $tree, array $params): Builder 266 { 267 $query = DB::table('media') 268 ->where('m_file', '=', $tree->id()); 269 270 if (isset($params['start'], $params['end'])) { 271 $query->whereBetween('m_id', [$params['start'], $params['end']]); 272 } 273 274 return $query; 275 } 276 277 /** 278 * XREFs of note records that might need fixing. 279 * 280 * @param Tree $tree 281 * @param array<string,string> $params 282 * 283 * @return Collection<int,string>|null 284 */ 285 protected function notesToFix(/** @scrutinizer ignore-unused */ Tree $tree, /** @scrutinizer ignore-unused */ array $params): ?Collection 286 { 287 return null; 288 } 289 290 /** 291 * @param Tree $tree 292 * @param array<string,string> $params 293 * 294 * @return Builder 295 */ 296 protected function notesToFixQuery(Tree $tree, array $params): Builder 297 { 298 $query = DB::table('other') 299 ->where('o_type', '=', Note::RECORD_TYPE) 300 ->where('o_file', '=', $tree->id()); 301 302 if (isset($params['start'], $params['end'])) { 303 $query->whereBetween('o_id', [$params['start'], $params['end']]); 304 } 305 306 return $query; 307 } 308 309 /** 310 * XREFs of repository records that might need fixing. 311 * 312 * @param Tree $tree 313 * @param array<string,string> $params 314 * 315 * @return Collection<int,string>|null 316 */ 317 protected function repositoriesToFix(/** @scrutinizer ignore-unused */ Tree $tree, /** @scrutinizer ignore-unused */ array $params): ?Collection 318 { 319 return null; 320 } 321 322 /** 323 * @param Tree $tree 324 * @param array<string,string> $params 325 * 326 * @return Builder 327 */ 328 protected function repositoriesToFixQuery(Tree $tree, array $params): Builder 329 { 330 $query = DB::table('other') 331 ->where('o_type', '=', Repository::RECORD_TYPE) 332 ->where('o_file', '=', $tree->id()); 333 334 if (isset($params['start'], $params['end'])) { 335 $query->whereBetween('o_id', [$params['start'], $params['end']]); 336 } 337 338 return $query; 339 } 340 341 /** 342 * XREFs of source records that might need fixing. 343 * 344 * @param Tree $tree 345 * @param array<string,string> $params 346 * 347 * @return Collection<int,string>|null 348 */ 349 protected function sourcesToFix(/** @scrutinizer ignore-unused */ Tree $tree, /** @scrutinizer ignore-unused */ array $params): ?Collection 350 { 351 return null; 352 } 353 354 /** 355 * @param Tree $tree 356 * @param array<string,string> $params 357 * 358 * @return Builder 359 */ 360 protected function sourcesToFixQuery(Tree $tree, array $params): Builder 361 { 362 $query = DB::table('sources') 363 ->where('s_file', '=', $tree->id()); 364 365 if (isset($params['start'], $params['end'])) { 366 $query->whereBetween('s_id', [$params['start'], $params['end']]); 367 } 368 369 return $query; 370 } 371 372 /** 373 * XREFs of submitter records that might need fixing. 374 * 375 * @param Tree $tree 376 * @param array<string,string> $params 377 * 378 * @return Collection<int,string>|null 379 */ 380 protected function submittersToFix(/** @scrutinizer ignore-unused */ Tree $tree, /** @scrutinizer ignore-unused */ array $params): ?Collection 381 { 382 return null; 383 } 384 385 /** 386 * @param Tree $tree 387 * @param array<string,string> $params 388 * 389 * @return Builder 390 */ 391 protected function submittersToFixQuery(Tree $tree, array $params): Builder 392 { 393 $query = DB::table('other') 394 ->where('o_type', '=', Submitter::RECORD_TYPE) 395 ->where('o_file', '=', $tree->id()); 396 397 if (isset($params['start'], $params['end'])) { 398 $query->whereBetween('o_id', [$params['start'], $params['end']]); 399 } 400 401 return $query; 402 } 403 404 /** 405 * Merge pending changes of a given type. We need to check all pending records. 406 * 407 * @param Collection<int,string> $records 408 * @param Tree $tree 409 * @param string $type 410 * 411 * @return Collection<int,object> 412 */ 413 private function mergePendingRecords(Collection $records, Tree $tree, string $type): Collection 414 { 415 $pending = DB::table('change') 416 ->where('gedcom_id', '=', $tree->id()) 417 ->where('status', '=', 'pending') 418 ->where(static function (Builder $query) use ($type): void { 419 $query 420 ->where('old_gedcom', 'LIKE', '%@ ' . $type . '%') 421 ->orWhere('new_gedcom', 'LIKE', '%@ ' . $type . '%'); 422 }) 423 ->pluck('xref'); 424 425 return $records 426 ->concat($pending) 427 ->map(static function (string $xref) use ($type): object { 428 return (object) ['xref' => $xref, 'type' => $type]; 429 }); 430 } 431} 432