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