1<?php 2 3/** 4 * webtrees: online genealogy 5 * Copyright (C) 2023 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\DB; 23use Fisharebest\Webtrees\Family; 24use Fisharebest\Webtrees\GedcomRecord; 25use Fisharebest\Webtrees\Individual; 26use Fisharebest\Webtrees\Location; 27use Fisharebest\Webtrees\Media; 28use Fisharebest\Webtrees\Note; 29use Fisharebest\Webtrees\Repository; 30use Fisharebest\Webtrees\Source; 31use Fisharebest\Webtrees\Submitter; 32use Fisharebest\Webtrees\Tree; 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(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 fn(object $x, object $y) => $x->xref <=> $y->xref); 110 } 111 112 /** 113 * Does a record need updating? 114 * 115 * @param GedcomRecord $record 116 * @param array<string,string> $params 117 * 118 * @return bool 119 */ 120 public function doesRecordNeedUpdate(GedcomRecord $record, array $params): bool 121 { 122 return false; 123 } 124 125 /** 126 * Show the changes we would make 127 * 128 * @param GedcomRecord $record 129 * @param array<string,string> $params 130 * 131 * @return string 132 */ 133 public function previewUpdate(GedcomRecord $record, array $params): string 134 { 135 return $record->fullName(); 136 } 137 138 /** 139 * Fix a record 140 * 141 * @param GedcomRecord $record 142 * @param array<string,string> $params 143 * 144 * @return void 145 */ 146 public function updateRecord(GedcomRecord $record, array $params): void 147 { 148 } 149 150 /** 151 * XREFs of family records that might need fixing. 152 * 153 * @param Tree $tree 154 * @param array<string,string> $params 155 * 156 * @return Collection<int,string>|null 157 */ 158 protected function familiesToFix(Tree $tree, array $params): ?Collection 159 { 160 return null; 161 } 162 163 /** 164 * @param Tree $tree 165 * @param array<string,string> $params 166 * 167 * @return Builder 168 */ 169 protected function familiesToFixQuery(Tree $tree, array $params): Builder 170 { 171 $query = DB::table('families') 172 ->where('f_file', '=', $tree->id()); 173 174 if (isset($params['start'], $params['end'])) { 175 $query->whereBetween('f_id', [$params['start'], $params['end']]); 176 } 177 178 return $query; 179 } 180 181 /** 182 * XREFs of individual records that might need fixing. 183 * 184 * @param Tree $tree 185 * @param array<string,string> $params 186 * 187 * @return Collection<int,string>|null 188 */ 189 protected function individualsToFix(Tree $tree, array $params): ?Collection 190 { 191 return null; 192 } 193 194 /** 195 * @param Tree $tree 196 * @param array<string,string> $params 197 * 198 * @return Builder 199 */ 200 protected function individualsToFixQuery(Tree $tree, array $params): Builder 201 { 202 $query = DB::table('individuals') 203 ->where('i_file', '=', $tree->id()); 204 205 if (isset($params['start'], $params['end'])) { 206 $query->whereBetween('i_id', [$params['start'], $params['end']]); 207 } 208 209 return $query; 210 } 211 212 /** 213 * XREFs of location records that might need fixing. 214 * 215 * @param Tree $tree 216 * @param array<string,string> $params 217 * 218 * @return Collection<int,string>|null 219 */ 220 protected function locationsToFix(Tree $tree, array $params): ?Collection 221 { 222 return null; 223 } 224 225 /** 226 * @param Tree $tree 227 * @param array<string,string> $params 228 * 229 * @return Builder 230 */ 231 protected function locationsToFixQuery(Tree $tree, array $params): Builder 232 { 233 $query = DB::table('other') 234 ->where('o_type', '=', Location::RECORD_TYPE) 235 ->where('o_file', '=', $tree->id()); 236 237 if (isset($params['start'], $params['end'])) { 238 $query->whereBetween('o_id', [$params['start'], $params['end']]); 239 } 240 241 return $query; 242 } 243 244 /** 245 * XREFs of media records that might need fixing. 246 * 247 * @param Tree $tree 248 * @param array<string,string> $params 249 * 250 * @return Collection<int,string>|null 251 */ 252 protected function mediaToFix(Tree $tree, array $params): ?Collection 253 { 254 return null; 255 } 256 257 /** 258 * @param Tree $tree 259 * @param array<string,string> $params 260 * 261 * @return Builder 262 */ 263 protected function mediaToFixQuery(Tree $tree, array $params): Builder 264 { 265 $query = DB::table('media') 266 ->where('m_file', '=', $tree->id()); 267 268 if (isset($params['start'], $params['end'])) { 269 $query->whereBetween('m_id', [$params['start'], $params['end']]); 270 } 271 272 return $query; 273 } 274 275 /** 276 * XREFs of note records that might need fixing. 277 * 278 * @param Tree $tree 279 * @param array<string,string> $params 280 * 281 * @return Collection<int,string>|null 282 */ 283 protected function notesToFix(Tree $tree, array $params): ?Collection 284 { 285 return null; 286 } 287 288 /** 289 * @param Tree $tree 290 * @param array<string,string> $params 291 * 292 * @return Builder 293 */ 294 protected function notesToFixQuery(Tree $tree, array $params): Builder 295 { 296 $query = DB::table('other') 297 ->where('o_type', '=', Note::RECORD_TYPE) 298 ->where('o_file', '=', $tree->id()); 299 300 if (isset($params['start'], $params['end'])) { 301 $query->whereBetween('o_id', [$params['start'], $params['end']]); 302 } 303 304 return $query; 305 } 306 307 /** 308 * XREFs of repository records that might need fixing. 309 * 310 * @param Tree $tree 311 * @param array<string,string> $params 312 * 313 * @return Collection<int,string>|null 314 */ 315 protected function repositoriesToFix(Tree $tree, array $params): ?Collection 316 { 317 return null; 318 } 319 320 /** 321 * @param Tree $tree 322 * @param array<string,string> $params 323 * 324 * @return Builder 325 */ 326 protected function repositoriesToFixQuery(Tree $tree, array $params): Builder 327 { 328 $query = DB::table('other') 329 ->where('o_type', '=', Repository::RECORD_TYPE) 330 ->where('o_file', '=', $tree->id()); 331 332 if (isset($params['start'], $params['end'])) { 333 $query->whereBetween('o_id', [$params['start'], $params['end']]); 334 } 335 336 return $query; 337 } 338 339 /** 340 * XREFs of source records that might need fixing. 341 * 342 * @param Tree $tree 343 * @param array<string,string> $params 344 * 345 * @return Collection<int,string>|null 346 */ 347 protected function sourcesToFix(Tree $tree, array $params): ?Collection 348 { 349 return null; 350 } 351 352 /** 353 * @param Tree $tree 354 * @param array<string,string> $params 355 * 356 * @return Builder 357 */ 358 protected function sourcesToFixQuery(Tree $tree, array $params): Builder 359 { 360 $query = DB::table('sources') 361 ->where('s_file', '=', $tree->id()); 362 363 if (isset($params['start'], $params['end'])) { 364 $query->whereBetween('s_id', [$params['start'], $params['end']]); 365 } 366 367 return $query; 368 } 369 370 /** 371 * XREFs of submitter records that might need fixing. 372 * 373 * @param Tree $tree 374 * @param array<string,string> $params 375 * 376 * @return Collection<int,string>|null 377 */ 378 protected function submittersToFix(Tree $tree, array $params): ?Collection 379 { 380 return null; 381 } 382 383 /** 384 * @param Tree $tree 385 * @param array<string,string> $params 386 * 387 * @return Builder 388 */ 389 protected function submittersToFixQuery(Tree $tree, array $params): Builder 390 { 391 $query = DB::table('other') 392 ->where('o_type', '=', Submitter::RECORD_TYPE) 393 ->where('o_file', '=', $tree->id()); 394 395 if (isset($params['start'], $params['end'])) { 396 $query->whereBetween('o_id', [$params['start'], $params['end']]); 397 } 398 399 return $query; 400 } 401 402 /** 403 * Merge pending changes of a given type. We need to check all pending records. 404 * 405 * @param Collection<int,string> $records 406 * @param Tree $tree 407 * @param string $type 408 * 409 * @return Collection<int,object> 410 */ 411 private function mergePendingRecords(Collection $records, Tree $tree, string $type): Collection 412 { 413 $pending = DB::table('change') 414 ->where('gedcom_id', '=', $tree->id()) 415 ->where('status', '=', 'pending') 416 ->where(static function (Builder $query) use ($type): void { 417 $query 418 ->where('old_gedcom', 'LIKE', '%@ ' . $type . '\n%') 419 ->orWhere('new_gedcom', 'LIKE', '%@ ' . $type . '\n%'); 420 }) 421 ->pluck('xref'); 422 423 return $records 424 ->concat($pending) 425 ->map(static fn(string $xref): object => (object) ['xref' => $xref, 'type' => $type]); 426 } 427} 428