diff --git a/docs/development/module/services.md b/docs/development/module/services.md index 79cc18d59..58690db6a 100644 --- a/docs/development/module/services.md +++ b/docs/development/module/services.md @@ -38,7 +38,7 @@ asset. Returns a log entity, or `NULL` if no logs were found. `setIntrinsicGeometry($asset, $wkt)` - Set an asset's intrinsic geometry, given a string in Well-Known Text format. -`getAssetsByLocation($location)` - Get assets that are in a location. +`getAssetsByLocation($locations)` - Get assets that are in locations. **Example usage**: diff --git a/modules/asset/group/src/GroupAssetLocation.php b/modules/asset/group/src/GroupAssetLocation.php index a6d4e82bb..9f5343917 100644 --- a/modules/asset/group/src/GroupAssetLocation.php +++ b/modules/asset/group/src/GroupAssetLocation.php @@ -110,11 +110,11 @@ class GroupAssetLocation extends AssetLocation implements AssetLocationInterface /** * {@inheritdoc} */ - public function getAssetsByLocation(AssetInterface $location): array { + public function getAssetsByLocation(array $locations): array { // First delegate to the parent function to get assets in the location. /** @var \Drupal\asset\Entity\AssetInterface[] $assets */ - $assets = parent::getAssetsByLocation($location); + $assets = parent::getAssetsByLocation($locations); // Recursively load all group members and add them to the list of assets. $groups = array_filter($assets, function (AssetInterface $asset) { @@ -123,22 +123,27 @@ class GroupAssetLocation extends AssetLocation implements AssetLocationInterface $members = $this->groupMembership->getGroupMembers($groups, TRUE); $assets = array_merge($assets, $members); + // Get location ids. + $location_ids = array_map(function (AssetInterface $location) { + return $location->id(); + }, $locations); + // It is possible for a group member asset to be in a different location // than the group, if it has a movement log that is more recent than the // group's. So iterate through all the assets and remove any that are not in - // the location. The asset may be in multiple locations (including this - // one), so we only want to remove it if none of its locations match. - foreach ($assets as $key => $asset) { - $match = FALSE; - foreach ($this->getLocation($asset) as $asset_location) { - if ($asset_location->id() == $location->id()) { - $match = TRUE; - } - } - if (!$match) { - unset($assets[$key]); - } - } + // one of the specified locations. The asset may be in multiple locations + // (including this one), so we only want to remove it if none of its + // locations match. + $assets = array_filter($assets, function (AssetInterface $asset) use ($location_ids) { + + // Get asset location ids. + $asset_location_ids = array_map(function (AssetInterface $location) { + return $location->id(); + }, $this->getLocation($asset)); + + // Only include the asset if it is in one of the specified locations. + return !empty(array_intersect($location_ids, $asset_location_ids)); + }); // Return the assets. return $assets; diff --git a/modules/asset/group/tests/src/Kernel/GroupTest.php b/modules/asset/group/tests/src/Kernel/GroupTest.php index c42af4c15..b5dc20c55 100644 --- a/modules/asset/group/tests/src/Kernel/GroupTest.php +++ b/modules/asset/group/tests/src/Kernel/GroupTest.php @@ -307,7 +307,7 @@ class GroupTest extends KernelTestBase { $second_pasture->save(); // Confirm that new locations are empty. - $this->assertEmpty($this->assetLocation->getAssetsByLocation($first_pasture), 'New locations are empty.'); + $this->assertEmpty($this->assetLocation->getAssetsByLocation([$first_pasture]), 'New locations are empty.'); // Create a log that moves the animal to the first pasture. /** @var \Drupal\log\Entity\LogInterface $second_log */ @@ -323,8 +323,8 @@ class GroupTest extends KernelTestBase { // Confirm that the animal is located in the first pasture. $this->assertEquals($this->logLocation->getLocation($second_log), $this->assetLocation->getLocation($animal), 'Asset location is determined by asset membership log.'); $this->assertEquals($this->logLocation->getGeometry($second_log), $this->assetLocation->getGeometry($animal), 'Asset geometry is determined by asset membership log.'); - $this->assertEquals(1, count($this->assetLocation->getAssetsByLocation($first_pasture)), 'Locations have assets that are moved to them.'); - $this->assertEmpty($this->assetLocation->getAssetsByLocation($second_pasture), 'Locations that do not have assets moved to them are unaffected.'); + $this->assertEquals(1, count($this->assetLocation->getAssetsByLocation([$first_pasture])), 'Locations have assets that are moved to them.'); + $this->assertEmpty($this->assetLocation->getAssetsByLocation([$second_pasture]), 'Locations that do not have assets moved to them are unaffected.'); // Assert that the animal's cache tags were invalidated. $this->assertEntityTestCache($animal, FALSE); @@ -346,8 +346,8 @@ class GroupTest extends KernelTestBase { // Confirm that the animal is located in the second pasture. $this->assertEquals($this->logLocation->getLocation($third_log), $this->assetLocation->getLocation($animal), 'Asset location is determined by group membership log.'); $this->assertEquals($this->logLocation->getGeometry($third_log), $this->assetLocation->getGeometry($animal), 'Asset geometry is determined by group membership log.'); - $this->assertEmpty($this->assetLocation->getAssetsByLocation($first_pasture), 'A group movement removes assets from their previous location.'); - $this->assertEquals(2, count($this->assetLocation->getAssetsByLocation($second_pasture)), 'A group movement adds assets to their new location.'); + $this->assertEmpty($this->assetLocation->getAssetsByLocation([$first_pasture]), 'A group movement removes assets from their previous location.'); + $this->assertEquals(2, count($this->assetLocation->getAssetsByLocation([$second_pasture])), 'A group movement adds assets to their new location.'); // Assert that the animal's cache tags were invalidated. $this->assertEntityTestCache($animal, FALSE); @@ -369,8 +369,8 @@ class GroupTest extends KernelTestBase { // Confirm that the animal location was unset. $this->assertEquals($this->logLocation->getLocation($fourth_log), $this->assetLocation->getLocation($animal), 'Asset location can be unset by group membership log.'); $this->assertEquals($this->logLocation->getGeometry($fourth_log), $this->assetLocation->getGeometry($animal), 'Asset geometry can be unset by group membership log.'); - $this->assertEmpty($this->assetLocation->getAssetsByLocation($first_pasture), 'Unsetting group location removes member assets from all locations.'); - $this->assertEmpty($this->assetLocation->getAssetsByLocation($second_pasture), 'Unsetting group location removes member assets from all locations.'); + $this->assertEmpty($this->assetLocation->getAssetsByLocation([$first_pasture]), 'Unsetting group location removes member assets from all locations.'); + $this->assertEmpty($this->assetLocation->getAssetsByLocation([$second_pasture]), 'Unsetting group location removes member assets from all locations.'); // Assert that the animal's cache tags were invalidated. $this->assertEntityTestCache($animal, FALSE); @@ -393,8 +393,8 @@ class GroupTest extends KernelTestBase { // logs now. $this->assertEquals($this->logLocation->getLocation($second_log), $this->assetLocation->getLocation($animal), 'Asset location is determined by asset membership log.'); $this->assertEquals($this->logLocation->getGeometry($second_log), $this->assetLocation->getGeometry($animal), 'Asset geometry is determined by asset membership log.'); - $this->assertEquals(1, count($this->assetLocation->getAssetsByLocation($first_pasture)), 'Unsetting group membership adds assets to their previous location.'); - $this->assertEmpty($this->assetLocation->getAssetsByLocation($second_pasture), 'Unsetting group membership removes member assets from the group location.'); + $this->assertEquals(1, count($this->assetLocation->getAssetsByLocation([$first_pasture])), 'Unsetting group membership adds assets to their previous location.'); + $this->assertEmpty($this->assetLocation->getAssetsByLocation([$second_pasture]), 'Unsetting group membership removes member assets from the group location.'); // Assert that the animal's cache tags were invalidated. $this->assertEntityTestCache($animal, FALSE); @@ -412,8 +412,8 @@ class GroupTest extends KernelTestBase { // Confirm that the animal is located in the second pasture. $this->assertEquals($this->logLocation->getLocation($third_log), $this->assetLocation->getLocation($animal), 'Asset location is determined by group membership log.'); $this->assertEquals($this->logLocation->getGeometry($third_log), $this->assetLocation->getGeometry($animal), 'Asset geometry is determined by group membership log.'); - $this->assertEmpty($this->assetLocation->getAssetsByLocation($first_pasture), 'A group movement removes assets from their previous location.'); - $this->assertEquals(2, count($this->assetLocation->getAssetsByLocation($second_pasture)), 'A group movement adds assets to their new location.'); + $this->assertEmpty($this->assetLocation->getAssetsByLocation([$first_pasture]), 'A group movement removes assets from their previous location.'); + $this->assertEquals(2, count($this->assetLocation->getAssetsByLocation([$second_pasture])), 'A group movement adds assets to their new location.'); // Assert that the animal's cache tags were invalidated. $this->assertEntityTestCache($animal, FALSE); diff --git a/modules/core/location/src/AssetLocation.php b/modules/core/location/src/AssetLocation.php index d25bfa69b..460a38cde 100644 --- a/modules/core/location/src/AssetLocation.php +++ b/modules/core/location/src/AssetLocation.php @@ -227,10 +227,19 @@ class AssetLocation implements AssetLocationInterface { /** * {@inheritdoc} */ - public function getAssetsByLocation(AssetInterface $location): array { - if (empty($location->id())) { + public function getAssetsByLocation(array $locations): array { + + // Get location ids. + $location_ids = array_map(function (AssetInterface $location) { + return $location->id(); + }, $locations); + + // Bail if there are no location ids. + if (empty($location_ids)) { return []; } + + // Build query for assets in locations. $query = " -- Select asset IDs from the asset base table. SELECT a.id @@ -261,13 +270,13 @@ class AssetLocation implements AssetLocationInterface { -- Limit results to completed movement logs to the desired location that -- took place before the given timestamp. - WHERE (lfd.is_movement = 1) AND (lfd.status = 'done') AND (lfd.timestamp <= :timestamp) AND (ll.location_target_id = :location_id) + WHERE (lfd.is_movement = 1) AND (lfd.status = 'done') AND (lfd.timestamp <= :timestamp) AND (ll.location_target_id IN (:location_ids[])) -- Exclude records with future log entries. AND lfd2.id IS NULL"; $args = [ ':timestamp' => $this->time->getRequestTime(), - ':location_id' => $location->id(), + ':location_ids[]' => $location_ids, ]; $result = $this->database->query($query, $args)->fetchAll(); $asset_ids = []; diff --git a/modules/core/location/src/AssetLocationInterface.php b/modules/core/location/src/AssetLocationInterface.php index a2c53edad..e89c5d667 100644 --- a/modules/core/location/src/AssetLocationInterface.php +++ b/modules/core/location/src/AssetLocationInterface.php @@ -98,14 +98,14 @@ interface AssetLocationInterface { public function setIntrinsicGeometry(AssetInterface $asset, string $wkt): void; /** - * Get assets that are in a location. + * Get assets that are in locations. * - * @param \Drupal\asset\Entity\AssetInterface $location - * The location asset entity. + * @param \Drupal\asset\Entity\AssetInterface[] $locations + * An array of location assets to lookup. * - * @return array + * @return \Drupal\asset\Entity\AssetInterface[] * Returns an array of assets. */ - public function getAssetsByLocation(AssetInterface $location): array; + public function getAssetsByLocation(array $locations): array; } diff --git a/modules/core/location/src/Plugin/views/argument/AssetLocation.php b/modules/core/location/src/Plugin/views/argument/AssetLocation.php index ccb916271..1021078ed 100644 --- a/modules/core/location/src/Plugin/views/argument/AssetLocation.php +++ b/modules/core/location/src/Plugin/views/argument/AssetLocation.php @@ -81,7 +81,7 @@ class AssetLocation extends ArgumentPluginBase { // See https://www.drupal.org/project/farm/issues/3217168 for more info. /** @var \Drupal\asset\Entity\AssetInterface $location */ $location = $this->entityTypeManager->getStorage('asset')->load($this->argument); - $assets = $this->assetLocation->getAssetsByLocation($location); + $assets = $this->assetLocation->getAssetsByLocation([$location]); $asset_ids = []; foreach ($assets as $asset) { $asset_ids[] = $asset->id(); diff --git a/modules/core/location/tests/src/Kernel/LocationTest.php b/modules/core/location/tests/src/Kernel/LocationTest.php index 566cd175b..4f9df05c3 100644 --- a/modules/core/location/tests/src/Kernel/LocationTest.php +++ b/modules/core/location/tests/src/Kernel/LocationTest.php @@ -348,8 +348,7 @@ class LocationTest extends KernelTestBase { public function testLocationAssets() { // Locations have no assets. - $this->assertEmpty($this->assetLocation->getAssetsByLocation($this->locations[0])); - $this->assertEmpty($this->assetLocation->getAssetsByLocation($this->locations[1])); + $this->assertEmpty($this->assetLocation->getAssetsByLocation([$this->locations[0], $this->locations[1]])); // Create an asset and move it to the first location. /** @var \Drupal\asset\Entity\AssetInterface $first_asset */ @@ -370,8 +369,9 @@ class LocationTest extends KernelTestBase { $first_log->save(); // First location has one asset, second has none. - $this->assertEquals(1, count($this->assetLocation->getAssetsByLocation($this->locations[0]))); - $this->assertEmpty($this->assetLocation->getAssetsByLocation($this->locations[1])); + $this->assertEquals(1, count($this->assetLocation->getAssetsByLocation([$this->locations[0]]))); + $this->assertEmpty($this->assetLocation->getAssetsByLocation([$this->locations[1]])); + $this->assertEquals(1, count($this->assetLocation->getAssetsByLocation([$this->locations[0], $this->locations[1]]))); // Create a second asset and move it to the second location. /** @var \Drupal\asset\Entity\AssetInterface $second_asset */ @@ -392,8 +392,9 @@ class LocationTest extends KernelTestBase { $second_log->save(); // Both locations have one asset. - $this->assertEquals(1, count($this->assetLocation->getAssetsByLocation($this->locations[0]))); - $this->assertEquals(1, count($this->assetLocation->getAssetsByLocation($this->locations[1]))); + $this->assertEquals(1, count($this->assetLocation->getAssetsByLocation([$this->locations[0]]))); + $this->assertEquals(1, count($this->assetLocation->getAssetsByLocation([$this->locations[1]]))); + $this->assertEquals(2, count($this->assetLocation->getAssetsByLocation([$this->locations[0], $this->locations[1]]))); // Create a third log that moves both assets to the first location. /** @var \Drupal\log\Entity\LogInterface $third_log */ @@ -410,8 +411,9 @@ class LocationTest extends KernelTestBase { $third_log->save(); // First location has two assets, second has none. - $this->assertEquals(2, count($this->assetLocation->getAssetsByLocation($this->locations[0]))); - $this->assertEmpty($this->assetLocation->getAssetsByLocation($this->locations[1])); + $this->assertEquals(2, count($this->assetLocation->getAssetsByLocation([$this->locations[0]]))); + $this->assertEmpty($this->assetLocation->getAssetsByLocation([$this->locations[1]])); + $this->assertEquals(2, count($this->assetLocation->getAssetsByLocation([$this->locations[0], $this->locations[1]]))); // Create a fourth log that moves first asset to the second location. /** @var \Drupal\log\Entity\LogInterface $fourth_log */ @@ -427,8 +429,9 @@ class LocationTest extends KernelTestBase { $fourth_log->save(); // Both locations have one asset. - $this->assertEquals(1, count($this->assetLocation->getAssetsByLocation($this->locations[0]))); - $this->assertEquals(1, count($this->assetLocation->getAssetsByLocation($this->locations[1]))); + $this->assertEquals(1, count($this->assetLocation->getAssetsByLocation([$this->locations[0]]))); + $this->assertEquals(1, count($this->assetLocation->getAssetsByLocation([$this->locations[1]]))); + $this->assertEquals(2, count($this->assetLocation->getAssetsByLocation([$this->locations[0], $this->locations[1]]))); // Create a fifth log that moves first asset to the both locations. /** @var \Drupal\log\Entity\LogInterface $fifth_log */ @@ -447,8 +450,9 @@ class LocationTest extends KernelTestBase { $fifth_log->save(); // First location has two asset, second location has one asset. - $this->assertEquals(2, count($this->assetLocation->getAssetsByLocation($this->locations[0]))); - $this->assertEquals(1, count($this->assetLocation->getAssetsByLocation($this->locations[1]))); + $this->assertEquals(2, count($this->assetLocation->getAssetsByLocation([$this->locations[0]]))); + $this->assertEquals(1, count($this->assetLocation->getAssetsByLocation([$this->locations[1]]))); + $this->assertEquals(2, count($this->assetLocation->getAssetsByLocation([$this->locations[0], $this->locations[1]]))); } }