diff options
26 files changed, 643 insertions, 337 deletions
diff --git a/app/GedcomRecord.php b/app/GedcomRecord.php index a392dc779f..2d8ec9c934 100644 --- a/app/GedcomRecord.php +++ b/app/GedcomRecord.php @@ -548,177 +548,6 @@ class GedcomRecord } /** - * Find individuals linked to this record. - * - * @param string $link - * - * @return Collection<int,Individual> - */ - public function linkedIndividuals(string $link): Collection - { - return DB::table('individuals') - ->join('link', static function (JoinClause $join): void { - $join - ->on('l_file', '=', 'i_file') - ->on('l_from', '=', 'i_id'); - }) - ->where('i_file', '=', $this->tree->id()) - ->where('l_type', '=', $link) - ->where('l_to', '=', $this->xref) - ->select(['individuals.*']) - ->get() - ->map(Registry::individualFactory()->mapper($this->tree)) - ->filter(self::accessFilter()); - } - - /** - * Find families linked to this record. - * - * @param string $link - * - * @return Collection<int,Family> - */ - public function linkedFamilies(string $link): Collection - { - return DB::table('families') - ->join('link', static function (JoinClause $join): void { - $join - ->on('l_file', '=', 'f_file') - ->on('l_from', '=', 'f_id'); - }) - ->where('f_file', '=', $this->tree->id()) - ->where('l_type', '=', $link) - ->where('l_to', '=', $this->xref) - ->select(['families.*']) - ->get() - ->map(Registry::familyFactory()->mapper($this->tree)) - ->filter(self::accessFilter()); - } - - /** - * Find sources linked to this record. - * - * @param string $link - * - * @return Collection<int,Source> - */ - public function linkedSources(string $link): Collection - { - return DB::table('sources') - ->join('link', static function (JoinClause $join): void { - $join - ->on('l_file', '=', 's_file') - ->on('l_from', '=', 's_id'); - }) - ->where('s_file', '=', $this->tree->id()) - ->where('l_type', '=', $link) - ->where('l_to', '=', $this->xref) - ->select(['sources.*']) - ->get() - ->map(Registry::sourceFactory()->mapper($this->tree)) - ->filter(self::accessFilter()); - } - - /** - * Find media objects linked to this record. - * - * @param string $link - * - * @return Collection<int,Media> - */ - public function linkedMedia(string $link): Collection - { - return DB::table('media') - ->join('link', static function (JoinClause $join): void { - $join - ->on('l_file', '=', 'm_file') - ->on('l_from', '=', 'm_id'); - }) - ->where('m_file', '=', $this->tree->id()) - ->where('l_type', '=', $link) - ->where('l_to', '=', $this->xref) - ->select(['media.*']) - ->get() - ->map(Registry::mediaFactory()->mapper($this->tree)) - ->filter(self::accessFilter()); - } - - /** - * Find notes linked to this record. - * - * @param string $link - * - * @return Collection<int,Note> - */ - public function linkedNotes(string $link): Collection - { - return DB::table('other') - ->join('link', static function (JoinClause $join): void { - $join - ->on('l_file', '=', 'o_file') - ->on('l_from', '=', 'o_id'); - }) - ->where('o_file', '=', $this->tree->id()) - ->where('o_type', '=', Note::RECORD_TYPE) - ->where('l_type', '=', $link) - ->where('l_to', '=', $this->xref) - ->select(['other.*']) - ->get() - ->map(Registry::noteFactory()->mapper($this->tree)) - ->filter(self::accessFilter()); - } - - /** - * Find repositories linked to this record. - * - * @param string $link - * - * @return Collection<int,Repository> - */ - public function linkedRepositories(string $link): Collection - { - return DB::table('other') - ->join('link', static function (JoinClause $join): void { - $join - ->on('l_file', '=', 'o_file') - ->on('l_from', '=', 'o_id'); - }) - ->where('o_file', '=', $this->tree->id()) - ->where('o_type', '=', Repository::RECORD_TYPE) - ->where('l_type', '=', $link) - ->where('l_to', '=', $this->xref) - ->select(['other.*']) - ->get() - ->map(Registry::repositoryFactory()->mapper($this->tree)) - ->filter(self::accessFilter()); - } - - /** - * Find locations linked to this record. - * - * @param string $link - * - * @return Collection<int,Location> - */ - public function linkedLocations(string $link): Collection - { - return DB::table('other') - ->join('link', static function (JoinClause $join): void { - $join - ->on('l_file', '=', 'o_file') - ->on('l_from', '=', 'o_id'); - }) - ->where('o_file', '=', $this->tree->id()) - ->where('o_type', '=', Location::RECORD_TYPE) - ->where('l_type', '=', $link) - ->where('l_to', '=', $this->xref) - ->select(['other.*']) - ->get() - ->map(Registry::locationFactory()->mapper($this->tree)) - ->filter(self::accessFilter()); - } - - /** * Get all attributes (e.g. DATE or PLAC) from an event (e.g. BIRT or MARR). * This is used to display multiple events on the individual/family lists. * Multiple events can exist because of uncertainty in dates, dates in different @@ -1121,44 +950,6 @@ class GedcomRecord } /** - * Fetch XREFs of all records linked to a record - when deleting an object, we must - * also delete all links to it. - * - * @return array<GedcomRecord> - */ - public function linkingRecords(): array - { - $like = addcslashes($this->xref(), '\\%_'); - - $union = DB::table('change') - ->where('gedcom_id', '=', $this->tree()->id()) - ->where('new_gedcom', 'LIKE', '%@' . $like . '@%') - ->where('new_gedcom', 'NOT LIKE', '0 @' . $like . '@%') - ->whereIn('change_id', function (Builder $query): void { - $query->select(new Expression('MAX(change_id)')) - ->from('change') - ->where('gedcom_id', '=', $this->tree->id()) - ->where('status', '=', 'pending') - ->groupBy(['xref']); - }) - ->select(['xref']); - - $xrefs = DB::table('link') - ->where('l_file', '=', $this->tree()->id()) - ->where('l_to', '=', $this->xref()) - ->select(['l_from']) - ->union($union) - ->pluck('l_from'); - - return $xrefs->map(function (string $xref): GedcomRecord { - $record = Registry::gedcomRecordFactory()->make($xref, $this->tree); - assert($record instanceof GedcomRecord); - - return $record; - })->all(); - } - - /** * Each object type may have its own special rules, and re-implement this function. * * @param int $access_level diff --git a/app/Http/RequestHandlers/DeleteRecord.php b/app/Http/RequestHandlers/DeleteRecord.php index 1eda700238..2fc13e983d 100644 --- a/app/Http/RequestHandlers/DeleteRecord.php +++ b/app/Http/RequestHandlers/DeleteRecord.php @@ -26,6 +26,7 @@ use Fisharebest\Webtrees\Gedcom; use Fisharebest\Webtrees\I18N; use Fisharebest\Webtrees\Individual; use Fisharebest\Webtrees\Registry; +use Fisharebest\Webtrees\Services\LinkedRecordService; use Fisharebest\Webtrees\Validator; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -38,10 +39,20 @@ use function response; use function sprintf; /** - * Controller for edit forms and responses. + * Delete a record. */ class DeleteRecord implements RequestHandlerInterface { + private LinkedRecordService $linked_record_service; + + /** + * @param LinkedRecordService $linked_record_service + */ + public function __construct(LinkedRecordService $linked_record_service) + { + $this->linked_record_service = $linked_record_service; + } + /** * Delete a record. * @@ -58,7 +69,7 @@ class DeleteRecord implements RequestHandlerInterface if (Auth::isEditor($record->tree()) && $record->canShow() && $record->canEdit()) { // Delete links to this record - foreach ($record->linkingRecords() as $linker) { + foreach ($this->linked_record_service->allLinkedRecords($record) as $linker) { $old_gedcom = $linker->gedcom(); $new_gedcom = $this->removeLinks($old_gedcom, $record->xref()); if ($old_gedcom !== $new_gedcom) { diff --git a/app/Http/RequestHandlers/GedcomRecordPage.php b/app/Http/RequestHandlers/GedcomRecordPage.php index ec3a98754e..48339033ce 100644 --- a/app/Http/RequestHandlers/GedcomRecordPage.php +++ b/app/Http/RequestHandlers/GedcomRecordPage.php @@ -29,6 +29,8 @@ use Fisharebest\Webtrees\Media; use Fisharebest\Webtrees\Note; use Fisharebest\Webtrees\Registry; use Fisharebest\Webtrees\Repository; +use Fisharebest\Webtrees\Services\ClipboardService; +use Fisharebest\Webtrees\Services\LinkedRecordService; use Fisharebest\Webtrees\Source; use Fisharebest\Webtrees\Submission; use Fisharebest\Webtrees\Submitter; @@ -61,6 +63,20 @@ class GedcomRecordPage implements RequestHandlerInterface Submitter::class, ]; + private ClipboardService $clipboard_service; + + private LinkedRecordService $linked_record_service; + + /** + * @param ClipboardService $clipboard_service + * @param LinkedRecordService $linked_record_service + */ + public function __construct(ClipboardService $clipboard_service, LinkedRecordService $linked_record_service) + { + $this->clipboard_service = $clipboard_service; + $this->linked_record_service = $linked_record_service; + } + /** * Show a gedcom record's page. * @@ -80,13 +96,25 @@ class GedcomRecordPage implements RequestHandlerInterface return redirect($record->url()); } + $linked_families = $this->linked_record_service->linkedFamilies($record); + $linked_individuals = $this->linked_record_service->linkedIndividuals($record); + $linked_locations = $this->linked_record_service->linkedLocations($record); + $linked_media = $this->linked_record_service->linkedMedia($record); + $linked_notes = $this->linked_record_service->linkedNotes($record); + $linked_repositories = $this->linked_record_service->linkedRepositories($record); + $linked_sources = $this->linked_record_service->linkedSources($record); + $linked_submitters = $this->linked_record_service->linkedSubmitters($record); + return $this->viewResponse('record-page', [ - 'clipboard_facts' => new Collection(), - 'linked_families' => $record->linkedFamilies($record->tag()), - 'linked_individuals' => $record->linkedIndividuals($record->tag()), - 'linked_media_objects' => $record->linkedMedia($record->tag()), - 'linked_notes' => $record->linkedNotes($record->tag()), - 'linked_sources' => $record->linkedSources($record->tag()), + 'clipboard_facts' => $this->clipboard_service->pastableFacts($record), + 'linked_families' => $linked_families->isEmpty() ? null : $linked_families, + 'linked_individuals' => $linked_individuals->isEmpty() ? null : $linked_individuals, + 'linked_locations' => $linked_locations->isEmpty() ? null : $linked_locations, + 'linked_media_objects' => $linked_media->isEmpty() ? null : $linked_media, + 'linked_notes' => $linked_notes->isEmpty() ? null : $linked_notes, + 'linked_repositories' => $linked_repositories->isEmpty() ? null : $linked_repositories, + 'linked_sources' => $linked_sources->isEmpty() ? null : $linked_sources, + 'linked_submitters' => $linked_submitters->isEmpty() ? null : $linked_submitters, 'record' => $record, 'title' => $record->fullName(), 'tree' => $tree, diff --git a/app/Http/RequestHandlers/HeaderPage.php b/app/Http/RequestHandlers/HeaderPage.php index ae32286eab..2fd17f6154 100644 --- a/app/Http/RequestHandlers/HeaderPage.php +++ b/app/Http/RequestHandlers/HeaderPage.php @@ -23,6 +23,7 @@ use Fig\Http\Message\StatusCodeInterface; use Fisharebest\Webtrees\Auth; use Fisharebest\Webtrees\Http\ViewResponseTrait; use Fisharebest\Webtrees\Registry; +use Fisharebest\Webtrees\Services\LinkedRecordService; use Fisharebest\Webtrees\Validator; use Illuminate\Support\Collection; use Psr\Http\Message\ResponseInterface; @@ -62,8 +63,10 @@ class HeaderPage implements RequestHandlerInterface 'clipboard_facts' => new Collection(), 'linked_families' => null, 'linked_individuals' => null, + 'linked_locations' => null, 'linked_media_objects' => null, 'linked_notes' => null, + 'linked_repositories' => null, 'linked_sources' => null, 'meta_description' => '', 'meta_robots' => 'index,follow', diff --git a/app/Http/RequestHandlers/LocationPage.php b/app/Http/RequestHandlers/LocationPage.php index 4f53815e84..d21c000ead 100644 --- a/app/Http/RequestHandlers/LocationPage.php +++ b/app/Http/RequestHandlers/LocationPage.php @@ -23,6 +23,8 @@ use Fig\Http\Message\StatusCodeInterface; use Fisharebest\Webtrees\Auth; use Fisharebest\Webtrees\Http\ViewResponseTrait; use Fisharebest\Webtrees\Registry; +use Fisharebest\Webtrees\Services\ClipboardService; +use Fisharebest\Webtrees\Services\LinkedRecordService; use Fisharebest\Webtrees\Validator; use Illuminate\Support\Collection; use Psr\Http\Message\ResponseInterface; @@ -40,6 +42,20 @@ class LocationPage implements RequestHandlerInterface { use ViewResponseTrait; + private ClipboardService $clipboard_service; + + private LinkedRecordService $linked_record_service; + + /** + * @param ClipboardService $clipboard_service + * @param LinkedRecordService $linked_record_service + */ + public function __construct(ClipboardService $clipboard_service, LinkedRecordService $linked_record_service) + { + $this->clipboard_service = $clipboard_service; + $this->linked_record_service = $linked_record_service; + } + /** * @param ServerRequestInterface $request * @@ -59,12 +75,14 @@ class LocationPage implements RequestHandlerInterface } return $this->viewResponse('record-page', [ - 'clipboard_facts' => new Collection(), - 'linked_families' => $record->linkedFamilies($record->tag()), - 'linked_individuals' => $record->linkedIndividuals($record->tag()), + 'clipboard_facts' => $this->clipboard_service->pastableFacts($record), + 'linked_families' => $this->linked_record_service->linkedFamilies($record), + 'linked_individuals' => $this->linked_record_service->linkedIndividuals($record), + 'linked_locations' => $this->linked_record_service->linkedLocations($record), 'linked_media_objects' => null, 'linked_notes' => null, - 'linked_sources' => null, + 'linked_repositories' => null, + 'linked_sources' => $this->linked_record_service->linkedSources($record), 'record' => $record, 'title' => $record->fullName(), 'tree' => $tree, diff --git a/app/Http/RequestHandlers/ManageMediaData.php b/app/Http/RequestHandlers/ManageMediaData.php index c48cf0b132..852db8510d 100644 --- a/app/Http/RequestHandlers/ManageMediaData.php +++ b/app/Http/RequestHandlers/ManageMediaData.php @@ -25,6 +25,7 @@ use Fisharebest\Webtrees\Media; use Fisharebest\Webtrees\Mime; use Fisharebest\Webtrees\Registry; use Fisharebest\Webtrees\Services\DatatablesService; +use Fisharebest\Webtrees\Services\LinkedRecordService; use Fisharebest\Webtrees\Services\MediaFileService; use Fisharebest\Webtrees\Services\TreeService; use Illuminate\Database\Capsule\Manager as DB; @@ -58,6 +59,8 @@ class ManageMediaData implements RequestHandlerInterface { private DatatablesService $datatables_service; + private LinkedRecordService $linked_record_service; + private MediaFileService $media_file_service; private TreeService $tree_service; @@ -65,18 +68,21 @@ class ManageMediaData implements RequestHandlerInterface /** * MediaController constructor. * - * @param DatatablesService $datatables_service - * @param MediaFileService $media_file_service - * @param TreeService $tree_service + * @param DatatablesService $datatables_service + * @param LinkedRecordService $linked_record_service + * @param MediaFileService $media_file_service + * @param TreeService $tree_service */ public function __construct( DatatablesService $datatables_service, + LinkedRecordService $linked_record_service, MediaFileService $media_file_service, TreeService $tree_service ) { - $this->datatables_service = $datatables_service; - $this->media_file_service = $media_file_service; - $this->tree_service = $tree_service; + $this->datatables_service = $datatables_service; + $this->linked_record_service = $linked_record_service; + $this->media_file_service = $media_file_service; + $this->tree_service = $tree_service; } /** @@ -286,23 +292,28 @@ class ManageMediaData implements RequestHandlerInterface $html .= $element->value($media->getNote(), $media->tree()); $linked = []; - foreach ($media->linkedIndividuals('OBJE') as $link) { + + foreach ($this->linked_record_service->linkedIndividuals($media) as $link) { $linked[] = view('icons/individual') . '<a href="' . e($link->url()) . '">' . $link->fullName() . '</a>'; } - foreach ($media->linkedFamilies('OBJE') as $link) { + + foreach ($this->linked_record_service->linkedFamilies($media) as $link) { $linked[] = view('icons/family') . '<a href="' . e($link->url()) . '">' . $link->fullName() . '</a>'; } - foreach ($media->linkedSources('OBJE') as $link) { + + foreach ($this->linked_record_service->linkedSources($media) as $link) { $linked[] = view('icons/source') . '<a href="' . e($link->url()) . '">' . $link->fullName() . '</a>'; } - foreach ($media->linkedNotes('OBJE') as $link) { + + foreach ($this->linked_record_service->linkedNotes($media) as $link) { $linked[] = view('icons/note') . '<a href="' . e($link->url()) . '">' . $link->fullName() . '</a>'; } - foreach ($media->linkedRepositories('OBJE') as $link) { - // Invalid GEDCOM - you cannot link a REPO to an OBJE + + foreach ($this->linked_record_service->linkedRepositories($media) as $link) { $linked[] = view('icons/media') . '<a href="' . e($link->url()) . '">' . $link->fullName() . '</a>'; } - foreach ($media->linkedLocations('OBJE') as $link) { + + foreach ($this->linked_record_service->linkedMedia($media) as $link) { $linked[] = view('icons/location') . '<a href="' . e($link->url()) . '">' . $link->fullName() . '</a>'; } diff --git a/app/Http/RequestHandlers/MediaPage.php b/app/Http/RequestHandlers/MediaPage.php index 94c63f9e13..a51c4cb434 100644 --- a/app/Http/RequestHandlers/MediaPage.php +++ b/app/Http/RequestHandlers/MediaPage.php @@ -24,13 +24,12 @@ use Fisharebest\Webtrees\Auth; use Fisharebest\Webtrees\Http\ViewResponseTrait; use Fisharebest\Webtrees\Registry; use Fisharebest\Webtrees\Services\ClipboardService; +use Fisharebest\Webtrees\Services\LinkedRecordService; use Fisharebest\Webtrees\Validator; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; -use function assert; -use function is_string; use function redirect; /** @@ -42,14 +41,16 @@ class MediaPage implements RequestHandlerInterface private ClipboardService $clipboard_service; + private LinkedRecordService $linked_record_service; + /** - * MediaPage constructor. - * * @param ClipboardService $clipboard_service + * @param LinkedRecordService $linked_record_service */ - public function __construct(ClipboardService $clipboard_service) + public function __construct(ClipboardService $clipboard_service, LinkedRecordService $linked_record_service) { - $this->clipboard_service = $clipboard_service; + $this->clipboard_service = $clipboard_service; + $this->linked_record_service = $linked_record_service; } /** @@ -72,13 +73,20 @@ class MediaPage implements RequestHandlerInterface return redirect($record->url(), StatusCodeInterface::STATUS_MOVED_PERMANENTLY); } + $linked_families = $this->linked_record_service->linkedFamilies($record); + $linked_individuals = $this->linked_record_service->linkedIndividuals($record); + $linked_locations = $this->linked_record_service->linkedLocations($record); + $linked_notes = $this->linked_record_service->linkedNotes($record); + $linked_sources = $this->linked_record_service->linkedSources($record); + return $this->viewResponse('media-page', [ 'clipboard_facts' => $this->clipboard_service->pastableFacts($record), 'data_filesystem' => $data_filesystem, - 'linked_families' => $record->linkedFamilies('OBJE'), - 'linked_individuals' => $record->linkedIndividuals('OBJE'), - 'linked_notes' => $record->linkedNotes('OBJE'), - 'linked_sources' => $record->linkedSources('OBJE'), + 'linked_families' => $linked_families, + 'linked_individuals' => $linked_individuals, + 'linked_locations' => $linked_locations->isEmpty() ? null : $linked_locations, + 'linked_notes' => $linked_notes, + 'linked_sources' => $linked_sources, 'meta_description' => '', 'meta_robots' => 'index,follow', 'record' => $record, diff --git a/app/Http/RequestHandlers/MergeFactsAction.php b/app/Http/RequestHandlers/MergeFactsAction.php index 2c02774065..7fbde54bcc 100644 --- a/app/Http/RequestHandlers/MergeFactsAction.php +++ b/app/Http/RequestHandlers/MergeFactsAction.php @@ -24,6 +24,7 @@ use Fisharebest\Webtrees\Contracts\UserInterface; use Fisharebest\Webtrees\FlashMessages; use Fisharebest\Webtrees\I18N; use Fisharebest\Webtrees\Registry; +use Fisharebest\Webtrees\Services\LinkedRecordService; use Fisharebest\Webtrees\Validator; use Illuminate\Database\Capsule\Manager as DB; use Illuminate\Database\Query\Expression; @@ -43,6 +44,16 @@ use function str_replace; */ class MergeFactsAction implements RequestHandlerInterface { + private LinkedRecordService $linked_record_service; + + /** + * @param LinkedRecordService $linked_record_service + */ + public function __construct(LinkedRecordService $linked_record_service) + { + $this->linked_record_service = $linked_record_service; + } + /** * @param ServerRequestInterface $request * @@ -87,7 +98,7 @@ class MergeFactsAction implements RequestHandlerInterface } // Update records that link to the one we will be removing. - $linking_records = $record2->linkingRecords(); + $linking_records = $this->linked_record_service->allLinkedRecords($record2); foreach ($linking_records as $record) { if (!$record->isPendingDeletion()) { diff --git a/app/Http/RequestHandlers/NotePage.php b/app/Http/RequestHandlers/NotePage.php index 024eb7a002..ab24048c85 100644 --- a/app/Http/RequestHandlers/NotePage.php +++ b/app/Http/RequestHandlers/NotePage.php @@ -24,13 +24,12 @@ use Fisharebest\Webtrees\Auth; use Fisharebest\Webtrees\Http\ViewResponseTrait; use Fisharebest\Webtrees\Registry; use Fisharebest\Webtrees\Services\ClipboardService; +use Fisharebest\Webtrees\Services\LinkedRecordService; use Fisharebest\Webtrees\Validator; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; -use function assert; -use function is_string; use function redirect; /** @@ -42,14 +41,16 @@ class NotePage implements RequestHandlerInterface private ClipboardService $clipboard_service; + private LinkedRecordService $linked_record_service; + /** - * NotePage constructor. - * * @param ClipboardService $clipboard_service + * @param LinkedRecordService $linked_record_service */ - public function __construct(ClipboardService $clipboard_service) + public function __construct(ClipboardService $clipboard_service, LinkedRecordService $linked_record_service) { - $this->clipboard_service = $clipboard_service; + $this->clipboard_service = $clipboard_service; + $this->linked_record_service = $linked_record_service; } /** @@ -70,12 +71,23 @@ class NotePage implements RequestHandlerInterface return redirect($record->url(), StatusCodeInterface::STATUS_MOVED_PERMANENTLY); } + $linked_families = $this->linked_record_service->linkedFamilies($record); + $linked_individuals = $this->linked_record_service->linkedIndividuals($record); + $linked_locations = $this->linked_record_service->linkedLocations($record); + $linked_media = $this->linked_record_service->linkedMedia($record); + $linked_repositories = $this->linked_record_service->linkedRepositories($record); + $linked_sources = $this->linked_record_service->linkedSources($record); + $linked_submitters = $this->linked_record_service->linkedSubmitters($record); + return $this->viewResponse('note-page', [ 'clipboard_facts' => $this->clipboard_service->pastableFacts($record), - 'linked_families' => $record->linkedFamilies('NOTE'), - 'linked_individuals' => $record->linkedIndividuals('NOTE'), - 'linked_media_objects' => $record->linkedMedia('NOTE'), - 'linked_sources' => $record->linkedSources('NOTE'), + 'linked_families' => $linked_families, + 'linked_individuals' => $linked_individuals, + 'linked_locations' => $linked_locations->isEmpty() ? null : $linked_locations, + 'linked_media_objects' => $linked_media, + 'linked_repositories' => $linked_repositories, + 'linked_sources' => $linked_sources, + 'linked_submitters' => $linked_submitters, 'meta_description' => '', 'meta_robots' => 'index,follow', 'record' => $record, diff --git a/app/Http/RequestHandlers/RepositoryPage.php b/app/Http/RequestHandlers/RepositoryPage.php index ef919ac534..6e69912b20 100644 --- a/app/Http/RequestHandlers/RepositoryPage.php +++ b/app/Http/RequestHandlers/RepositoryPage.php @@ -24,6 +24,7 @@ use Fisharebest\Webtrees\Auth; use Fisharebest\Webtrees\Http\ViewResponseTrait; use Fisharebest\Webtrees\Registry; use Fisharebest\Webtrees\Services\ClipboardService; +use Fisharebest\Webtrees\Services\LinkedRecordService; use Fisharebest\Webtrees\Validator; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -40,14 +41,16 @@ class RepositoryPage implements RequestHandlerInterface private ClipboardService $clipboard_service; + private LinkedRecordService $linked_record_service; + /** - * RepositoryPage constructor. - * * @param ClipboardService $clipboard_service + * @param LinkedRecordService $linked_record_service */ - public function __construct(ClipboardService $clipboard_service) + public function __construct(ClipboardService $clipboard_service, LinkedRecordService $linked_record_service) { - $this->clipboard_service = $clipboard_service; + $this->clipboard_service = $clipboard_service; + $this->linked_record_service = $linked_record_service; } /** @@ -68,13 +71,17 @@ class RepositoryPage implements RequestHandlerInterface return redirect($record->url(), StatusCodeInterface::STATUS_MOVED_PERMANENTLY); } + $linked_sources = $this->linked_record_service->linkedSources($record); + return $this->viewResponse('record-page', [ 'clipboard_facts' => $this->clipboard_service->pastableFacts($record), 'linked_families' => null, 'linked_individuals' => null, + 'linked_locations' => null, 'linked_media_objects' => null, 'linked_notes' => null, - 'linked_sources' => $record->linkedSources('REPO'), + 'linked_repositories' => null, + 'linked_sources' => $linked_sources, 'meta_description' => '', 'meta_robots' => 'index,follow', 'record' => $record, diff --git a/app/Http/RequestHandlers/SourcePage.php b/app/Http/RequestHandlers/SourcePage.php index 4349a22af2..899cca5026 100644 --- a/app/Http/RequestHandlers/SourcePage.php +++ b/app/Http/RequestHandlers/SourcePage.php @@ -24,6 +24,7 @@ use Fisharebest\Webtrees\Auth; use Fisharebest\Webtrees\Http\ViewResponseTrait; use Fisharebest\Webtrees\Registry; use Fisharebest\Webtrees\Services\ClipboardService; +use Fisharebest\Webtrees\Services\LinkedRecordService; use Fisharebest\Webtrees\Validator; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -40,14 +41,16 @@ class SourcePage implements RequestHandlerInterface private ClipboardService $clipboard_service; + private LinkedRecordService $linked_record_service; + /** - * SourcePage constructor. - * * @param ClipboardService $clipboard_service + * @param LinkedRecordService $linked_record_service */ - public function __construct(ClipboardService $clipboard_service) + public function __construct(ClipboardService $clipboard_service, LinkedRecordService $linked_record_service) { - $this->clipboard_service = $clipboard_service; + $this->clipboard_service = $clipboard_service; + $this->linked_record_service = $linked_record_service; } /** @@ -68,12 +71,16 @@ class SourcePage implements RequestHandlerInterface return redirect($record->url(), StatusCodeInterface::STATUS_MOVED_PERMANENTLY); } + $linked_locations = $this->linked_record_service->linkedLocations($record); + return $this->viewResponse('record-page', [ 'clipboard_facts' => $this->clipboard_service->pastableFacts($record), - 'linked_families' => $record->linkedFamilies('SOUR'), - 'linked_individuals' => $record->linkedIndividuals('SOUR'), - 'linked_media_objects' => $record->linkedMedia('SOUR'), - 'linked_notes' => $record->linkedNotes('SOUR'), + 'linked_families' => $this->linked_record_service->linkedFamilies($record), + 'linked_individuals' => $this->linked_record_service->linkedIndividuals($record), + 'linked_locations' => $linked_locations->isEmpty() ? null : $linked_locations, + 'linked_media_objects' => $this->linked_record_service->linkedMedia($record), + 'linked_notes' => $this->linked_record_service->linkedNotes($record), + 'linked_repositories' => null, 'linked_sources' => null, 'meta_description' => '', 'meta_robots' => 'index,follow', diff --git a/app/Http/RequestHandlers/SubmissionPage.php b/app/Http/RequestHandlers/SubmissionPage.php index 146b4bff62..5eb7685660 100644 --- a/app/Http/RequestHandlers/SubmissionPage.php +++ b/app/Http/RequestHandlers/SubmissionPage.php @@ -23,6 +23,8 @@ use Fig\Http\Message\StatusCodeInterface; use Fisharebest\Webtrees\Auth; use Fisharebest\Webtrees\Http\ViewResponseTrait; use Fisharebest\Webtrees\Registry; +use Fisharebest\Webtrees\Services\ClipboardService; +use Fisharebest\Webtrees\Services\LinkedRecordService; use Fisharebest\Webtrees\Validator; use Illuminate\Support\Collection; use Psr\Http\Message\ResponseInterface; @@ -62,8 +64,10 @@ class SubmissionPage implements RequestHandlerInterface 'clipboard_facts' => new Collection(), 'linked_families' => null, 'linked_individuals' => null, + 'linked_locations' => null, 'linked_media_objects' => null, 'linked_notes' => null, + 'linked_repositories' => null, 'linked_sources' => null, 'meta_description' => '', 'meta_robots' => 'index,follow', diff --git a/app/Http/RequestHandlers/SubmitterPage.php b/app/Http/RequestHandlers/SubmitterPage.php index 1287b4d758..7f674c056d 100644 --- a/app/Http/RequestHandlers/SubmitterPage.php +++ b/app/Http/RequestHandlers/SubmitterPage.php @@ -23,6 +23,8 @@ use Fig\Http\Message\StatusCodeInterface; use Fisharebest\Webtrees\Auth; use Fisharebest\Webtrees\Http\ViewResponseTrait; use Fisharebest\Webtrees\Registry; +use Fisharebest\Webtrees\Services\ClipboardService; +use Fisharebest\Webtrees\Services\LinkedRecordService; use Fisharebest\Webtrees\Validator; use Illuminate\Support\Collection; use Psr\Http\Message\ResponseInterface; @@ -38,6 +40,20 @@ class SubmitterPage implements RequestHandlerInterface { use ViewResponseTrait; + private ClipboardService $clipboard_service; + + private LinkedRecordService $linked_record_service; + + /** + * @param ClipboardService $clipboard_service + * @param LinkedRecordService $linked_record_service + */ + public function __construct(ClipboardService $clipboard_service, LinkedRecordService $linked_record_service) + { + $this->clipboard_service = $clipboard_service; + $this->linked_record_service = $linked_record_service; + } + /** * @param ServerRequestInterface $request * @@ -57,11 +73,13 @@ class SubmitterPage implements RequestHandlerInterface } return $this->viewResponse('record-page', [ - 'clipboard_facts' => new Collection(), - 'linked_families' => $record->linkedFamilies('SUBM'), - 'linked_individuals' => $record->linkedIndividuals('SUBM'), + 'clipboard_facts' => $this->clipboard_service->pastableFacts($record), + 'linked_families' => $this->linked_record_service->linkedFamilies($record), + 'linked_individuals' => $this->linked_record_service->linkedIndividuals($record), + 'linked_locations' => null, 'linked_media_objects' => null, 'linked_notes' => null, + 'linked_repositories' => null, 'linked_sources' => null, 'meta_description' => '', 'meta_robots' => 'index,follow', diff --git a/app/Module/ClippingsCartModule.php b/app/Module/ClippingsCartModule.php index c860d03950..16a4e9a593 100644 --- a/app/Module/ClippingsCartModule.php +++ b/app/Module/ClippingsCartModule.php @@ -19,7 +19,6 @@ declare(strict_types=1); namespace Fisharebest\Webtrees\Module; -use Aura\Router\Route; use Fisharebest\Webtrees\Auth; use Fisharebest\Webtrees\Encodings\ANSEL; use Fisharebest\Webtrees\Encodings\ASCII; @@ -46,6 +45,7 @@ use Fisharebest\Webtrees\Note; use Fisharebest\Webtrees\Registry; use Fisharebest\Webtrees\Repository; use Fisharebest\Webtrees\Services\GedcomExportService; +use Fisharebest\Webtrees\Services\LinkedRecordService; use Fisharebest\Webtrees\Session; use Fisharebest\Webtrees\Source; use Fisharebest\Webtrees\Submitter; @@ -116,6 +116,8 @@ class ClippingsCartModule extends AbstractModule implements ModuleMenuInterface private GedcomExportService $gedcom_export_service; + private LinkedRecordService $linked_record_service; + private ResponseFactoryInterface $response_factory; private StreamFactoryInterface $stream_factory; @@ -124,15 +126,18 @@ class ClippingsCartModule extends AbstractModule implements ModuleMenuInterface * ClippingsCartModule constructor. * * @param GedcomExportService $gedcom_export_service + * @param LinkedRecordService $linked_record_service * @param ResponseFactoryInterface $response_factory * @param StreamFactoryInterface $stream_factory */ public function __construct( GedcomExportService $gedcom_export_service, + LinkedRecordService $linked_record_service, ResponseFactoryInterface $response_factory, StreamFactoryInterface $stream_factory ) { $this->gedcom_export_service = $gedcom_export_service; + $this->linked_record_service = $linked_record_service; $this->response_factory = $response_factory; $this->stream_factory = $stream_factory; } @@ -904,7 +909,7 @@ class ClippingsCartModule extends AbstractModule implements ModuleMenuInterface $this->addRepositoryToCart($repository); - foreach ($repository->linkedSources('REPO') as $source) { + foreach ($this->linked_record_service->linkedSources($repository) as $source) { $this->addSourceToCart($source); } @@ -961,10 +966,10 @@ class ClippingsCartModule extends AbstractModule implements ModuleMenuInterface $this->addSourceToCart($source); if ($option === self::ADD_LINKED_INDIVIDUALS) { - foreach ($source->linkedIndividuals('SOUR') as $individual) { + foreach ($this->linked_record_service->linkedIndividuals($source) as $individual) { $this->addIndividualToCart($individual); } - foreach ($source->linkedFamilies('SOUR') as $family) { + foreach ($this->linked_record_service->linkedFamilies($source) as $family) { $this->addFamilyToCart($family); } } diff --git a/app/Module/FixPrimaryTag.php b/app/Module/FixPrimaryTag.php index 9f3ed91a51..f850f6006d 100644 --- a/app/Module/FixPrimaryTag.php +++ b/app/Module/FixPrimaryTag.php @@ -23,6 +23,7 @@ use Fisharebest\Webtrees\Fact; use Fisharebest\Webtrees\GedcomRecord; use Fisharebest\Webtrees\I18N; use Fisharebest\Webtrees\Individual; +use Fisharebest\Webtrees\Services\LinkedRecordService; use Fisharebest\Webtrees\Tree; use Illuminate\Support\Collection; @@ -37,6 +38,16 @@ class FixPrimaryTag extends AbstractModule implements ModuleDataFixInterface { use ModuleDataFixTrait; + private LinkedRecordService $linked_record_service; + + /** + * @param LinkedRecordService $linked_record_service + */ + public function __construct(LinkedRecordService $linked_record_service) + { + $this->linked_record_service = $linked_record_service; + } + /** * How should this module be identified in the control panel, etc.? * @@ -104,7 +115,7 @@ class FixPrimaryTag extends AbstractModule implements ModuleDataFixInterface } $html .= '<ul>'; - foreach ($record->linkedIndividuals('OBJE') as $individual) { + foreach ($this->linked_record_service->linkedIndividuals($record) as $individual) { $html .= '<li>' . I18N::translate('Re-order media') . ' – <a href="' . e($individual->url()) . '">' . $individual->fullName() . '</a></li>'; } $html .= '</ul>'; @@ -129,7 +140,7 @@ class FixPrimaryTag extends AbstractModule implements ModuleDataFixInterface foreach ($facts as $fact) { $primary = strtoupper($fact->value()) !== 'N'; - foreach ($record->linkedIndividuals('OBJE') as $individual) { + foreach ($this->linked_record_service->linkedIndividuals($record) as $individual) { $this->updateMediaLinks($individual, $record->xref(), $primary); } diff --git a/app/Module/IndividualFactsTabModule.php b/app/Module/IndividualFactsTabModule.php index c6158d1fa5..d7313839bd 100644 --- a/app/Module/IndividualFactsTabModule.php +++ b/app/Module/IndividualFactsTabModule.php @@ -26,6 +26,7 @@ use Fisharebest\Webtrees\Family; use Fisharebest\Webtrees\I18N; use Fisharebest\Webtrees\Individual; use Fisharebest\Webtrees\Services\ClipboardService; +use Fisharebest\Webtrees\Services\LinkedRecordService; use Fisharebest\Webtrees\Services\ModuleService; use Illuminate\Support\Collection; @@ -43,20 +44,24 @@ class IndividualFactsTabModule extends AbstractModule implements ModuleTabInterf { use ModuleTabTrait; - private ModuleService $module_service; - private ClipboardService $clipboard_service; + private LinkedRecordService $linked_record_service; + + private ModuleService $module_service; + /** * IndividualFactsTabModule constructor. * - * @param ModuleService $module_service - * @param ClipboardService $clipboard_service + * @param ModuleService $module_service + * @param LinkedRecordService $linked_record_service + * @param ClipboardService $clipboard_service */ - public function __construct(ModuleService $module_service, ClipboardService $clipboard_service) + public function __construct(ModuleService $module_service, LinkedRecordService $linked_record_service, ClipboardService $clipboard_service) { - $this->module_service = $module_service; - $this->clipboard_service = $clipboard_service; + $this->clipboard_service = $clipboard_service; + $this->linked_record_service = $linked_record_service; + $this->module_service = $module_service; } /** @@ -879,10 +884,10 @@ class IndividualFactsTabModule extends AbstractModule implements ModuleTabInterf { $facts = []; - $asso1 = $person->linkedIndividuals('ASSO'); - $asso2 = $person->linkedIndividuals('_ASSO'); - $asso3 = $person->linkedFamilies('ASSO'); - $asso4 = $person->linkedFamilies('_ASSO'); + $asso1 = $this->linked_record_service->linkedIndividuals($person, 'ASSO'); + $asso2 = $this->linked_record_service->linkedIndividuals($person, '_ASSO'); + $asso3 = $this->linked_record_service->linkedFamilies($person, 'ASSO'); + $asso4 = $this->linked_record_service->linkedFamilies($person, '_ASSO'); $associates = $asso1->merge($asso2)->merge($asso3)->merge($asso4); diff --git a/app/Module/MediaListModule.php b/app/Module/MediaListModule.php index b3c980dc5c..7afe2c6323 100644 --- a/app/Module/MediaListModule.php +++ b/app/Module/MediaListModule.php @@ -19,13 +19,13 @@ declare(strict_types=1); namespace Fisharebest\Webtrees\Module; -use Aura\Router\RouterContainer; use Fig\Http\Message\RequestMethodInterface; use Fisharebest\Webtrees\Auth; -use Fisharebest\Webtrees\Registry; use Fisharebest\Webtrees\GedcomRecord; use Fisharebest\Webtrees\I18N; use Fisharebest\Webtrees\Media; +use Fisharebest\Webtrees\Registry; +use Fisharebest\Webtrees\Services\LinkedRecordService; use Fisharebest\Webtrees\Tree; use Fisharebest\Webtrees\Validator; use Illuminate\Database\Capsule\Manager as DB; @@ -37,10 +37,8 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; use function addcslashes; -use function app; use function array_combine; use function array_unshift; -use function assert; use function dirname; use function max; use function min; @@ -56,6 +54,16 @@ class MediaListModule extends AbstractModule implements ModuleListInterface, Req protected const ROUTE_URL = '/tree/{tree}/media-list'; + private LinkedRecordService $linked_record_service; + + /** + * @param LinkedRecordService $linked_record_service + */ + public function __construct(LinkedRecordService $linked_record_service) + { + $this->linked_record_service = $linked_record_service; + } + /** * Initialization. * @@ -199,21 +207,22 @@ class MediaListModule extends AbstractModule implements ModuleListInterface, Req $media_objects = $media_objects->slice(($page - 1) * $max, $max); return $this->viewResponse('modules/media-list/page', [ - 'count' => $count, - 'filter' => $filter, - 'folder' => $folder, - 'folders' => $folders, - 'format' => $format, - 'formats' => $formats, - 'max' => $max, - 'media_objects' => $media_objects, - 'page' => $page, - 'pages' => $pages, - 'subdirs' => $subdirs, - 'data_filesystem' => $data_filesystem, - 'module' => $this, - 'title' => I18N::translate('Media'), - 'tree' => $tree, + 'count' => $count, + 'filter' => $filter, + 'folder' => $folder, + 'folders' => $folders, + 'format' => $format, + 'formats' => $formats, + 'linked_record_service' => $this->linked_record_service, + 'max' => $max, + 'media_objects' => $media_objects, + 'page' => $page, + 'pages' => $pages, + 'subdirs' => $subdirs, + 'data_filesystem' => $data_filesystem, + 'module' => $this, + 'title' => I18N::translate('Media'), + 'tree' => $tree, ]); } diff --git a/app/Module/SlideShowModule.php b/app/Module/SlideShowModule.php index 45fe4f723c..0ccbecc3b1 100644 --- a/app/Module/SlideShowModule.php +++ b/app/Module/SlideShowModule.php @@ -22,6 +22,7 @@ namespace Fisharebest\Webtrees\Module; use Fisharebest\Webtrees\I18N; use Fisharebest\Webtrees\Media; use Fisharebest\Webtrees\Registry; +use Fisharebest\Webtrees\Services\LinkedRecordService; use Fisharebest\Webtrees\Tree; use Illuminate\Database\Capsule\Manager as DB; use Illuminate\Database\Query\JoinClause; @@ -49,6 +50,16 @@ class SlideShowModule extends AbstractModule implements ModuleBlockInterface // How long to show each slide (seconds) private const DELAY = 6; + private LinkedRecordService $linked_record_service; + + /** + * @param LinkedRecordService $linked_record_service + */ + public function __construct(LinkedRecordService $linked_record_service) + { + $this->linked_record_service = $linked_record_service; + } + /** * A sentence describing what this module does. * @@ -119,7 +130,7 @@ class SlideShowModule extends AbstractModule implements ModuleBlockInterface ->select('media.*') ->get() ->shuffle() - ->first(static function (object $row) use ($filter_links, $tree): bool { + ->first(function (object $row) use ($filter_links, $tree): bool { $media = Registry::mediaFactory()->make($row->m_id, $tree, $row->m_gedcom); assert($media instanceof Media); @@ -127,7 +138,7 @@ class SlideShowModule extends AbstractModule implements ModuleBlockInterface return false; } - foreach ($media->linkedIndividuals('OBJE') as $individual) { + foreach ($this->linked_record_service->linkedIndividuals($media) as $individual) { switch ($filter_links) { case self::LINK_ALL: return true; @@ -153,6 +164,9 @@ class SlideShowModule extends AbstractModule implements ModuleBlockInterface $content = view('modules/random_media/slide-show', [ 'block_id' => $block_id, 'delay' => self::DELAY, + 'linked_families' => $this->linked_record_service->linkedFamilies($random_media), + 'linked_individuals' => $this->linked_record_service->linkedIndividuals($random_media), + 'linked_sources' => $this->linked_record_service->linkedSources($random_media), 'media' => $random_media, 'media_file' => $random_media->firstImageFile(), 'show_controls' => $controls, diff --git a/app/Services/LinkedRecordService.php b/app/Services/LinkedRecordService.php new file mode 100644 index 0000000000..c104b83df6 --- /dev/null +++ b/app/Services/LinkedRecordService.php @@ -0,0 +1,281 @@ +<?php + +/** + * webtrees: online genealogy + * Copyright (C) 2021 webtrees development team + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +declare(strict_types=1); + +namespace Fisharebest\Webtrees\Services; + +use Fisharebest\Webtrees\Family; +use Fisharebest\Webtrees\GedcomRecord; +use Fisharebest\Webtrees\Individual; +use Fisharebest\Webtrees\Location; +use Fisharebest\Webtrees\Media; +use Fisharebest\Webtrees\Note; +use Fisharebest\Webtrees\Registry; +use Fisharebest\Webtrees\Repository; +use Fisharebest\Webtrees\Source; +use Fisharebest\Webtrees\Submitter; +use Illuminate\Database\Capsule\Manager as DB; +use Illuminate\Database\Query\Builder; +use Illuminate\Database\Query\Expression; +use Illuminate\Database\Query\JoinClause; +use Illuminate\Support\Collection; + +use function addcslashes; +use function assert; + +/** + * Find records linked to other records + */ +class LinkedRecordService +{ + /** + * Find all records linked to a record. + * + * @param GedcomRecord $record + * + * @return Collection<int,Family> + */ + public function allLinkedRecords(GedcomRecord $record): Collection + { + $like = addcslashes($record->xref(), '\\%_'); + + $union = DB::table('change') + ->where('gedcom_id', '=', $record->tree()->id()) + ->where('new_gedcom', 'LIKE', '%@' . $like . '@%') + ->where('new_gedcom', 'NOT LIKE', '0 @' . $like . '@%') + ->whereIn('change_id', function (Builder $query) use ($record): void { + $query + ->select(new Expression('MAX(change_id)')) + ->from('change') + ->where('gedcom_id', '=', $record->tree()->id()) + ->where('status', '=', 'pending') + ->groupBy(['xref']); + }) + ->select(['xref']); + + $xrefs = DB::table('link') + ->where('l_file', '=', $record->tree()->id()) + ->where('l_to', '=', $record->xref()) + ->select(['l_from']) + ->union($union) + ->pluck('l_from'); + + return $xrefs->map(static fn (string $xref) => Registry::gedcomRecordFactory()->make($xref, $record->tree())); + } + + /** + * Find families linked to a record. + * + * @param GedcomRecord $record + * @param string|null $link_type + * + * @return Collection<int,Family> + */ + public function linkedFamilies(GedcomRecord $record, string $link_type = null): Collection + { + $query = DB::table('families') + ->join('link', static function (JoinClause $join): void { + $join + ->on('l_file', '=', 'f_file') + ->on('l_from', '=', 'f_id'); + }) + ->where('f_file', '=', $record->tree()->id()) + ->where('l_to', '=', $record->xref()); + + if ($link_type !== null) { + $query->where('l_type', '=', $link_type); + } + + return $query + ->select(['families.*']) + ->get() + ->map(Registry::familyFactory()->mapper($record->tree())) + ->filter(GedcomRecord::accessFilter()); + } + + /** + * Find individuals linked to a record. + * + * @param GedcomRecord $record + * + * @return Collection<int,Individual> + */ + public function linkedIndividuals(GedcomRecord $record, string $link_type = null): Collection + { + $query = DB::table('individuals') + ->join('link', static function (JoinClause $join): void { + $join + ->on('l_file', '=', 'i_file') + ->on('l_from', '=', 'i_id'); + }) + ->where('i_file', '=', $record->tree()->id()) + ->where('l_to', '=', $record->xref()); + + if ($link_type !== null) { + $query->where('l_type', '=', $link_type); + } + + return $query + ->select(['individuals.*']) + ->get() + ->map(Registry::individualFactory()->mapper($record->tree())) + ->filter(GedcomRecord::accessFilter()); + } + + /** + * Find locations linked to a record. + * + * @param GedcomRecord $record + * + * @return Collection<int,Location> + */ + public function linkedLocations(GedcomRecord $record): Collection + { + return DB::table('other') + ->join('link', static function (JoinClause $join): void { + $join + ->on('l_file', '=', 'o_file') + ->on('l_from', '=', 'o_id'); + }) + ->where('o_file', '=', $record->tree()->id()) + ->where('o_type', '=', Location::RECORD_TYPE) + ->where('l_to', '=', $record->xref()) + ->select(['other.*']) + ->get() + ->map(Registry::locationFactory()->mapper($record->tree())) + ->filter(GedcomRecord::accessFilter()); + } + + /** + * Find media objects linked to a record. + * + * @param GedcomRecord $record + * + * @return Collection<int,Media> + */ + public function linkedMedia(GedcomRecord $record): Collection + { + return DB::table('media') + ->join('link', static function (JoinClause $join): void { + $join + ->on('l_file', '=', 'm_file') + ->on('l_from', '=', 'm_id'); + }) + ->where('m_file', '=', $record->tree()->id()) + ->where('l_to', '=', $record->xref()) + ->select(['media.*']) + ->get() + ->map(Registry::mediaFactory()->mapper($record->tree())) + ->filter(GedcomRecord::accessFilter()); + } + + /** + * Find notes linked to a record. + * + * @param GedcomRecord $record + * + * @return Collection<int,Note> + */ + public function linkedNotes(GedcomRecord $record): Collection + { + return DB::table('other') + ->join('link', static function (JoinClause $join): void { + $join + ->on('l_file', '=', 'o_file') + ->on('l_from', '=', 'o_id'); + }) + ->where('o_file', '=', $record->tree()->id()) + ->where('o_type', '=', Note::RECORD_TYPE) + ->where('l_to', '=', $record->xref()) + ->select(['other.*']) + ->get() + ->map(Registry::noteFactory()->mapper($record->tree())) + ->filter(GedcomRecord::accessFilter()); + } + + /** + * Find repositories linked to a record. + * + * @param GedcomRecord $record + * + * @return Collection<int,Repository> + */ + public function linkedRepositories(GedcomRecord $record): Collection + { + return DB::table('other') + ->join('link', static function (JoinClause $join): void { + $join + ->on('l_file', '=', 'o_file') + ->on('l_from', '=', 'o_id'); + }) + ->where('o_file', '=', $record->tree()->id()) + ->where('o_type', '=', Repository::RECORD_TYPE) + ->where('l_to', '=', $record->xref()) + ->select(['other.*']) + ->get() + ->map(Registry::repositoryFactory()->mapper($record->tree())) + ->filter(GedcomRecord::accessFilter()); + } + + /** + * Find sources linked to a record. + * + * @param GedcomRecord $record + * + * @return Collection<int,Source> + */ + public function linkedSources(GedcomRecord $record): Collection + { + return DB::table('sources') + ->join('link', static function (JoinClause $join): void { + $join + ->on('l_file', '=', 's_file') + ->on('l_from', '=', 's_id'); + }) + ->where('s_file', '=', $record->tree()->id()) + ->where('l_to', '=', $record->xref()) + ->select(['sources.*']) + ->get() + ->map(Registry::sourceFactory()->mapper($record->tree())) + ->filter(GedcomRecord::accessFilter()); + } + + /** + * Find submitters linked to a record. + * + * @param GedcomRecord $record + * + * @return Collection<int,Repository> + */ + public function linkedSubmitters(GedcomRecord $record): Collection + { + return DB::table('other') + ->join('link', static function (JoinClause $join): void { + $join + ->on('l_file', '=', 'o_file') + ->on('l_from', '=', 'o_id'); + }) + ->where('o_file', '=', $record->tree()->id()) + ->where('o_type', '=', Submitter::RECORD_TYPE) + ->where('l_to', '=', $record->xref()) + ->select(['other.*']) + ->get() + ->map(Registry::repositoryFactory()->mapper($record->tree())) + ->filter(GedcomRecord::accessFilter()); + } +} diff --git a/resources/views/media-page.phtml b/resources/views/media-page.phtml index b221f50118..441e976dcc 100644 --- a/resources/views/media-page.phtml +++ b/resources/views/media-page.phtml @@ -1,19 +1,25 @@ <?php +use Fisharebest\Webtrees\Family; +use Fisharebest\Webtrees\Individual; +use Fisharebest\Webtrees\Location; use Fisharebest\Webtrees\Media; +use Fisharebest\Webtrees\Note; +use Fisharebest\Webtrees\Source; use Fisharebest\Webtrees\Tree; use Illuminate\Support\Collection; use League\Flysystem\FilesystemOperator; /** - * @var Collection $clipboard_facts - * @var FilesystemOperator $data_filesystem - * @var Collection $linked_families - * @var Collection $linked_individuals - * @var Collection $linked_notes - * @var Collection $linked_sources - * @var Media $record - * @var Tree $tree + * @var Collection $clipboard_facts + * @var FilesystemOperator $data_filesystem + * @var Collection<Family> $linked_families + * @var Collection<Individual> $linked_individuals + * @var Collection<Location> $linked_locations + * @var Collection<Note> $linked_notes + * @var Collection<Source> $linked_sources + * @var Media $record + * @var Tree $tree */ ?> @@ -34,6 +40,7 @@ use League\Flysystem\FilesystemOperator; 'details' => view('media-page-details', ['clipboard_facts' => $clipboard_facts, 'data_filesystem' => $data_filesystem, 'record' => $record]), 'linked_families' => $linked_families, 'linked_individuals' => $linked_individuals, + 'linked_locations' => $linked_locations, 'linked_media_objects' => null, 'linked_notes' => $linked_notes, 'linked_sources' => $linked_sources, diff --git a/resources/views/modules/media-list/page.phtml b/resources/views/modules/media-list/page.phtml index f743723694..065c3c350c 100644 --- a/resources/views/modules/media-list/page.phtml +++ b/resources/views/modules/media-list/page.phtml @@ -6,6 +6,7 @@ use Fisharebest\Webtrees\Media; use Fisharebest\Webtrees\MediaFile; use Fisharebest\Webtrees\Module\ModuleListInterface; use Fisharebest\Webtrees\Registry; +use Fisharebest\Webtrees\Services\LinkedRecordService; use Fisharebest\Webtrees\Tree; use Illuminate\Support\Collection; use League\Flysystem\FilesystemOperator; @@ -18,6 +19,7 @@ use League\Flysystem\FilesystemOperator; * @var array<string> $folders * @var string $format * @var array<string> $formats + * @var LinkedRecordService $linked_record_service * @var int $max * @var Collection<int,Media> $media_objects * @var ModuleListInterface $module @@ -151,19 +153,19 @@ use League\Flysystem\FilesystemOperator; <?php endif ?> </div> <div class="card-footer"> - <?php foreach ($media_object->linkedIndividuals('OBJE') as $record) : ?> + <?php foreach ($linked_record_service->linkedIndividuals($media_object) as $record) : ?> <?= view('icons/individual') ?> <a href="<?= e($record->url()) ?>"><?= $record->fullName() ?></a> <br> <?php endforeach ?> - <?php foreach ($media_object->linkedFamilies('OBJE') as $record) : ?> + <?php foreach ($linked_record_service->linkedFamilies($media_object) as $record) : ?> <?= view('icons/family') ?> <a href="<?= e($record->url()) ?>"><?= $record->fullName() ?></a> <br> <?php endforeach ?> - <?php foreach ($media_object->linkedSources('OBJE') as $record) : ?> + <?php foreach ($linked_record_service->linkedSources($media_object) as $record) : ?> <?= view('icons/source') ?> <a href="<?= e($record->url()) ?>"><?= $record->fullName() ?></a> <br> diff --git a/resources/views/modules/random_media/slide-show.phtml b/resources/views/modules/random_media/slide-show.phtml index 54b531c88d..730117e87e 100644 --- a/resources/views/modules/random_media/slide-show.phtml +++ b/resources/views/modules/random_media/slide-show.phtml @@ -1,18 +1,25 @@ <?php +use Fisharebest\Webtrees\Family; use Fisharebest\Webtrees\I18N; +use Fisharebest\Webtrees\Individual; use Fisharebest\Webtrees\Media; use Fisharebest\Webtrees\MediaFile; +use Fisharebest\Webtrees\Source; use Fisharebest\Webtrees\Tree; +use Illuminate\Support\Collection; /** - * @var int $block_id - * @var int $delay - * @var bool $show_controls - * @var bool $start_automatically - * @var Media $media - * @var MediaFile $media_file - * @var Tree $tree + * @var int $block_id + * @var int $delay + * @var Collection<Family> $linked_families + * @var Collection<Individual> $linked_individuals + * @var Collection<Source> $linked_sources + * @var Media $media + * @var MediaFile $media_file + * @var bool $show_controls + * @var bool $start_automatically + * @var Tree $tree */ ?> @@ -51,7 +58,7 @@ use Fisharebest\Webtrees\Tree; </p> <ul class="fa-ul wt-slide-show-links"> - <?php foreach ($media->linkedIndividuals('OBJE') as $individual) : ?> + <?php foreach ($linked_individuals as $individual) : ?> <li> <span class="fa-li" title="<?= I18N::translate('Individual') ?>"><?= view('icons/individual') ?></span> <a href="<?= e($individual->url()) ?>" class="wt-slide-show-link"> @@ -60,7 +67,7 @@ use Fisharebest\Webtrees\Tree; </li> <?php endforeach ?> - <?php foreach ($media->linkedFamilies('OBJE') as $family) : ?> + <?php foreach ($linked_families as $family) : ?> <li> <span class="fa-li" title="<?= I18N::translate('Family') ?>"><?= view('icons/family') ?></span> <a href="<?= e($family->url()) ?>" class="wt-slide-show-link"> @@ -69,7 +76,7 @@ use Fisharebest\Webtrees\Tree; </li> <?php endforeach ?> - <?php foreach ($media->linkedSources('OBJE') as $source) : ?> + <?php foreach ($linked_sources as $source) : ?> <li> <span class="fa-li" title="<?= I18N::translate('Source') ?>"><?= view('icons/source') ?></span> <a href="<?= e($source->url()) ?>" class="wt-slide-show-link"> diff --git a/resources/views/note-page.phtml b/resources/views/note-page.phtml index 76a84da128..914e60820f 100644 --- a/resources/views/note-page.phtml +++ b/resources/views/note-page.phtml @@ -3,9 +3,12 @@ use Fisharebest\Webtrees\Fact; use Fisharebest\Webtrees\Family; use Fisharebest\Webtrees\Individual; +use Fisharebest\Webtrees\Location; use Fisharebest\Webtrees\Media; use Fisharebest\Webtrees\Note; +use Fisharebest\Webtrees\Repository; use Fisharebest\Webtrees\Source; +use Fisharebest\Webtrees\Submitter; use Fisharebest\Webtrees\Tree; use Illuminate\Support\Collection; @@ -14,8 +17,11 @@ use Illuminate\Support\Collection; * @var Collection<int,Fact> $clipboard_facts * @var Collection<int,Family> $linked_families * @var Collection<int,Individual> $linked_individuals + * @var Collection<int,Location> $linked_locations * @var Collection<int,Media> $linked_media_objects + * @var Collection<int,Repository> $linked_repositories * @var Collection<int,Source> $linked_sources + * @var Collection<int,Submitter> $linked_submitters * @var Tree $tree */ @@ -37,9 +43,12 @@ use Illuminate\Support\Collection; 'details' => view('note-page-details', ['clipboard_facts' => $clipboard_facts, 'record' => $record]), 'linked_families' => $linked_families, 'linked_individuals' => $linked_individuals, + 'linked_locations' => $linked_locations, 'linked_media_objects' => $linked_media_objects, 'linked_notes' => null, + 'linked_repositories' => $linked_repositories, 'linked_sources' => $linked_sources, + 'linked_submitters' => $linked_submitters, 'tree' => $tree, ]) ?> </div> diff --git a/resources/views/record-page-links.phtml b/resources/views/record-page-links.phtml index af6da071ab..6396ad7840 100644 --- a/resources/views/record-page-links.phtml +++ b/resources/views/record-page-links.phtml @@ -3,6 +3,7 @@ use Fisharebest\Webtrees\Family; use Fisharebest\Webtrees\I18N; use Fisharebest\Webtrees\Individual; +use Fisharebest\Webtrees\Location; use Fisharebest\Webtrees\Media; use Fisharebest\Webtrees\Note; use Fisharebest\Webtrees\Source; @@ -13,6 +14,7 @@ use Illuminate\Support\Collection; * @var string $details * @var ?Collection<int,Family> $linked_families * @var ?Collection<int,Individual> $linked_individuals + * @var ?Collection<int,Location> $linked_locations * @var ?Collection<int,Media> $linked_media_objects * @var ?Collection<int,Note> $linked_notes * @var ?Collection<int,Source> $linked_sources @@ -72,6 +74,15 @@ use Illuminate\Support\Collection; </a> </li> <?php endif ?> + + <?php if ($linked_locations instanceof Collection) : ?> + <li class="nav-item" role="presentation"> + <a class="nav-link" data-bs-toggle="tab" role="tab" href="#notes"> + <?= I18N::translate('Notes') ?> + <?= view('components/badge', ['count' => $linked_locations->count()]) ?> + </a> + </li> + <?php endif ?> </ul> <div class="tab-content"> @@ -108,4 +119,10 @@ use Illuminate\Support\Collection; <?= view('lists/notes-table', ['notes' => $linked_notes, 'tree' => $tree]) ?> </div> <?php endif ?> + + <?php if ($linked_locations instanceof Collection) : ?> + <div class="tab-pane fade" role="tabpanel" id="notes"> + <?= view('lists/locations-table', ['locations' => $linked_locations, 'tree' => $tree]) ?> + </div> + <?php endif ?> </div> diff --git a/resources/views/record-page.phtml b/resources/views/record-page.phtml index 225302b9f9..a4abdfd6b3 100644 --- a/resources/views/record-page.phtml +++ b/resources/views/record-page.phtml @@ -4,8 +4,10 @@ use Fisharebest\Webtrees\Fact; use Fisharebest\Webtrees\Family; use Fisharebest\Webtrees\GedcomRecord; use Fisharebest\Webtrees\Individual; +use Fisharebest\Webtrees\Location; use Fisharebest\Webtrees\Media; use Fisharebest\Webtrees\Note; +use Fisharebest\Webtrees\Repository; use Fisharebest\Webtrees\Source; use Fisharebest\Webtrees\Tree; use Illuminate\Support\Collection; @@ -14,8 +16,10 @@ use Illuminate\Support\Collection; * @var Collection<int,Fact>|null $clipboard_facts * @var Collection<int,Family>|null $linked_families * @var Collection<int,Individual>|null $linked_individuals + * @var Collection<int,Location>|null $linked_locations * @var Collection<int,Media>|null $linked_media_objects * @var Collection<int,Note>|null $linked_notes + * @var Collection<int,Repository>|null $linked_repositories * @var Collection<int,Source>|null $linked_sources * @var GedcomRecord $record * @var Tree $tree @@ -35,13 +39,15 @@ use Illuminate\Support\Collection; </div> <div class="wt-page-content"> - <?php if ($linked_families instanceof Collection || $linked_individuals instanceof Collection || $linked_media_objects instanceof Collection || $linked_notes instanceof Collection || $linked_sources instanceof Collection) : ?> + <?php if ($linked_families instanceof Collection || $linked_individuals instanceof Collection || $linked_locations instanceof Collection || $linked_media_objects instanceof Collection || $linked_notes instanceof Collection || $linked_repositories instanceof Collection || $linked_sources instanceof Collection) : ?> <?= view('record-page-links', [ 'details' => view('record-page-details', ['record' => $record]), 'linked_families' => $linked_families, 'linked_individuals' => $linked_individuals, + 'linked_locations' => $linked_locations, 'linked_media_objects' => $linked_media_objects, 'linked_notes' => $linked_notes, + 'linked_repositories' => $linked_repositories, 'linked_sources' => $linked_sources, 'tree' => $tree, ]) ?> diff --git a/tests/app/Http/RequestHandlers/ManageMediaDataTest.php b/tests/app/Http/RequestHandlers/ManageMediaDataTest.php index cc304777d5..f93fd0008d 100644 --- a/tests/app/Http/RequestHandlers/ManageMediaDataTest.php +++ b/tests/app/Http/RequestHandlers/ManageMediaDataTest.php @@ -23,6 +23,7 @@ use Fig\Http\Message\RequestMethodInterface; use Fig\Http\Message\StatusCodeInterface; use Fisharebest\Webtrees\Services\DatatablesService; use Fisharebest\Webtrees\Services\GedcomImportService; +use Fisharebest\Webtrees\Services\LinkedRecordService; use Fisharebest\Webtrees\Services\MediaFileService; use Fisharebest\Webtrees\Services\TreeService; use Fisharebest\Webtrees\TestCase; @@ -42,10 +43,11 @@ class ManageMediaDataTest extends TestCase public function testDataLocal(): void { $datatables_service = new DatatablesService(); - $media_file_service = new MediaFileService(); $gedcom_import_service = new GedcomImportService(); + $linked_record_service = new LinkedRecordService(); + $media_file_service = new MediaFileService(); $tree_service = new TreeService($gedcom_import_service); - $handler = new ManageMediaData($datatables_service, $media_file_service, $tree_service); + $handler = new ManageMediaData($datatables_service, $linked_record_service, $media_file_service, $tree_service); $request = self::createRequest(RequestMethodInterface::METHOD_GET, [ 'files' => 'local', 'media_folder' => '', @@ -65,10 +67,11 @@ class ManageMediaDataTest extends TestCase public function testDataExternal(): void { $datatables_service = new DatatablesService(); - $media_file_service = new MediaFileService(); $gedcom_import_service = new GedcomImportService(); + $linked_record_service = new LinkedRecordService(); + $media_file_service = new MediaFileService(); $tree_service = new TreeService($gedcom_import_service); - $handler = new ManageMediaData($datatables_service, $media_file_service, $tree_service); + $handler = new ManageMediaData($datatables_service, $linked_record_service, $media_file_service, $tree_service); $request = self::createRequest(RequestMethodInterface::METHOD_GET, [ 'files' => 'local', 'media_folder' => '', @@ -88,10 +91,11 @@ class ManageMediaDataTest extends TestCase public function testDataUnused(): void { $datatables_service = new DatatablesService(); - $media_file_service = new MediaFileService(); $gedcom_import_service = new GedcomImportService(); + $linked_record_service = new LinkedRecordService(); + $media_file_service = new MediaFileService(); $tree_service = new TreeService($gedcom_import_service); - $handler = new ManageMediaData($datatables_service, $media_file_service, $tree_service); + $handler = new ManageMediaData($datatables_service, $linked_record_service, $media_file_service, $tree_service); $request = self::createRequest(RequestMethodInterface::METHOD_GET, [ 'files' => 'local', 'media_folder' => '', |
