diff --git a/.travis.yml b/.travis.yml index 5f0f82b6..82b526f8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,6 +28,7 @@ install: script: - $SCRIPT_DIR/travis_scripts.sh - $SCRIPT_DIR/run-tests.sh "islandora" + - $SCRIPT_DIR/run-tests.sh "islandora_breadcrumbs" notifications: irc: diff --git a/modules/islandora_breadcrumbs/config/install/islandora.breadcrumbs.yml b/modules/islandora_breadcrumbs/config/install/islandora.breadcrumbs.yml new file mode 100644 index 00000000..38f8be38 --- /dev/null +++ b/modules/islandora_breadcrumbs/config/install/islandora.breadcrumbs.yml @@ -0,0 +1,3 @@ +maxDepth: -1 +includeSelf: FALSE +referenceField: field_member_of diff --git a/modules/islandora_breadcrumbs/config/schema/islandora_breadcrumbs.schema.yml b/modules/islandora_breadcrumbs/config/schema/islandora_breadcrumbs.schema.yml new file mode 100644 index 00000000..7a26de0c --- /dev/null +++ b/modules/islandora_breadcrumbs/config/schema/islandora_breadcrumbs.schema.yml @@ -0,0 +1,12 @@ +islandora.breadcrumbs: + type: config_object + mapping: + maxDepth: + type: integer + label: 'Max Depth' + includeSelf: + type: boolean + label: 'Include Self' + referenceField: + type: string + label: 'Reference Field' diff --git a/modules/islandora_breadcrumbs/islandora_breadcrumbs.info.yml b/modules/islandora_breadcrumbs/islandora_breadcrumbs.info.yml new file mode 100644 index 00000000..085b38bf --- /dev/null +++ b/modules/islandora_breadcrumbs/islandora_breadcrumbs.info.yml @@ -0,0 +1,7 @@ +name: 'Islandora Breadcrumbs' +type: module +description: 'Builds breadcrumbs based on field_member_of relationships.' +core: 8.x +package: Islandora +dependencies: + - islandora diff --git a/modules/islandora_breadcrumbs/islandora_breadcrumbs.services.yml b/modules/islandora_breadcrumbs/islandora_breadcrumbs.services.yml new file mode 100644 index 00000000..af9efa06 --- /dev/null +++ b/modules/islandora_breadcrumbs/islandora_breadcrumbs.services.yml @@ -0,0 +1,6 @@ +services: + islandora_breadcrumbs.breadcrumb: + class: Drupal\islandora_breadcrumbs\IslandoraBreadcrumbBuilder + arguments: ['@config.factory'] + tags: + - { name: breadcrumb_builder, priority: 100 } diff --git a/modules/islandora_breadcrumbs/src/IslandoraBreadcrumbBuilder.php b/modules/islandora_breadcrumbs/src/IslandoraBreadcrumbBuilder.php new file mode 100644 index 00000000..763c523a --- /dev/null +++ b/modules/islandora_breadcrumbs/src/IslandoraBreadcrumbBuilder.php @@ -0,0 +1,96 @@ +config = $config_factory->get('islandora.breadcrumbs'); + } + + /** + * {@inheritdoc} + */ + public function applies(RouteMatchInterface $attributes) { + $parameters = $attributes->getParameters()->all(); + if (!empty($parameters['node'])) { + return ($parameters['node']->hasField($this->config->get('referenceField')) && + !$parameters['node']->get($this->config->get('referenceField'))->isEmpty()); + } + } + + /** + * {@inheritdoc} + */ + public function build(RouteMatchInterface $route_match) { + + $node = $route_match->getParameter('node'); + $breadcrumb = new Breadcrumb(); + + $chain = []; + $this->walkMembership($node, $chain); + + if (!$this->config->get('includeSelf')) { + array_pop($chain); + } + $breadcrumb->addCacheableDependency($node); + + // Add membership chain to the breadcrumb. + foreach ($chain as $chainlink) { + $breadcrumb->addCacheableDependency($chainlink); + $breadcrumb->addLink($chainlink->toLink()); + } + $breadcrumb->addCacheContexts(['route']); + return $breadcrumb; + } + + /** + * Follows chain of field_member_of links. + * + * We pass crumbs by reference to enable checking for looped chains. + */ + protected function walkMembership(EntityInterface $entity, &$crumbs) { + // Avoid infinate loops, return if we've seen this before. + foreach ($crumbs as $crumb) { + if ($crumb->uuid == $entity->uuid) { + return; + } + } + + // Add this item onto the pile. + array_unshift($crumbs, $entity); + + if ($this->config->get('maxDepth') > 0 && count($crumbs) >= $this->config->get('maxDepth')) { + return; + } + + // Find the next in the chain, if there are any. + if ($entity->hasField($this->config->get('referenceField')) && + !$entity->get($this->config->get('referenceField'))->isEmpty()) { + $this->walkMembership($entity->get($this->config->get('referenceField'))->entity, $crumbs); + } + } + +} diff --git a/modules/islandora_breadcrumbs/tests/src/Functional/BreadcrumbsTest.php b/modules/islandora_breadcrumbs/tests/src/Functional/BreadcrumbsTest.php new file mode 100644 index 00000000..4ba101fe --- /dev/null +++ b/modules/islandora_breadcrumbs/tests/src/Functional/BreadcrumbsTest.php @@ -0,0 +1,89 @@ +nodeA = $this->container->get('entity_type.manager')->getStorage('node')->create([ + 'type' => $this->testType->id(), + 'title' => 'Node A', + ]); + $this->nodeA->save(); + + $this->nodeB = $this->container->get('entity_type.manager')->getStorage('node')->create([ + 'type' => $this->testType->id(), + 'title' => 'Node B', + ]); + $this->nodeB->set('field_member_of', [$this->nodeA->id()]); + $this->nodeB->save(); + + $this->nodeC = $this->container->get('entity_type.manager')->getStorage('node')->create([ + 'type' => $this->testType->id(), + 'title' => 'Node C', + ]); + $this->nodeC->set('field_member_of', [$this->nodeB->id()]); + $this->nodeC->save(); + + $this->nodeD = $this->container->get('entity_type.manager')->getStorage('node')->create([ + 'type' => $this->testType->id(), + 'title' => 'Node D', + ]); + $this->nodeD->set('field_member_of', [$this->nodeC->id()]); + $this->nodeD->save(); + } + + /** + * @covers \Drupal\islandora_breadcrumbs\IslandoraBreadcrumbBuilder::applies + */ + public function testDefaults() { + $breadcrumbs = [ + $this->nodeC->toUrl()->toString() => $this->nodeC->label(), + $this->nodeB->toUrl()->toString() => $this->nodeB->label(), + $this->nodeA->toUrl()->toString() => $this->nodeA->label(), + ]; + $this->assertBreadcrumb($this->nodeD->toUrl()->toString(), $breadcrumbs); + + // Create a reference loop. + $this->nodeA->set('field_member_of', [$this->nodeD->id()]); + $this->nodeA->save(); + + // We should still escape it and have the same trail as before. + $this->assertBreadcrumb($this->nodeD->toUrl()->toString(), $breadcrumbs); + } + +}