Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi page types #16

Open
wants to merge 14 commits into
base: 1.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions localgov_subsites_extras.api.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

/**
* @file
* Hooks provided by the Locagov Subsites Extras module.
*/

use Drupal\node\NodeInterface;

/**
* @addtogroup hooks
* @{
*/

/**
* Alter the current node, before subsite determination.
*
* This alter hook is called before we walk the menu tree to determine if we're
* looking at a page in a subsite or not.
*
* It'll be passed the current node, acquired from routeMatch. If no node is
* found there, this hook will be passed null. This gives the opportunity to
* include non-node pages in a subsite if you wish.
*
* If you want to indicate that a subsite is not being viewed, set $node to
* NULL.
*
* Usually, this hook will be used to use a property of the node passed, such as
* a reference to another node (such as the parent node field for guides,
* directories, etc) to swop out the current node for a different node, which is
* in the menu and therefore part of a subsite.
*/
function hook_localgov_subsites_extras_current_node_alter(?NodeInterface &$node) {
if ($node instanceof NodeInterface && $node->hasField('field_parent')) {
$parent = $node->field_parent->entity;
if ($parent instanceof NodeInterface) {
$node = $parent;
}
}
}

/**
* @} End of "addtogroup hooks".
*/
37 changes: 37 additions & 0 deletions localgov_subsites_extras.module
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

declare(strict_types=1);

use Drupal\node\NodeInterface;

/**
* Implements hook_preprocess_node().
*
Expand Down Expand Up @@ -69,5 +71,40 @@ function localgov_subsites_extras_entity_bundle_create($entity_type_id, $bundle)
$type->setThirdPartySetting('menu_ui', 'available_menus', $availableMenus);
$type->save();
}
}

// phpcs:disable
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it better to make the code meet Drupal standards?

Copy link
Member Author

@rupertj rupertj May 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drupal's code style rules don't let you implement hooks for other modules like I've done here, as they insist on the function prefix being the module name. This is just a temporary thing anyway, as I'd like to move those hooks to the modules that provide those types & fields.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just taking a quick look, would an event and event subscriber be better here? And then the module can provide appropriate events for known localgov drupal content types until we can agree on a place to move it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've moved the hooks to their own file. Putting them there instead of in the module means that phpcs doesn't enforce the "function names must start with the module name rule", so I can re-enable phpcs on them.

I'm not against moving from hooks to an event. I don't think it's obviously better though. What do you reckon @Polynya?


// Ideally, we want this hook in localgov_directories.
// Wrap it for now so we can move it there cleanly.
if (!function_exists('localgov_directories_localgov_subsites_extras_current_node_alter')) {
/**
* Implements hook_localgov_subsites_extras_current_node_alter().
*/
function localgov_directories_localgov_subsites_extras_current_node_alter(?NodeInterface &$node) {
if ($node instanceof NodeInterface && $node->hasField('localgov_directory_channels')) {
$directoryChannel = $node->localgov_directory_channels->entity;
if ($directoryChannel instanceof NodeInterface) {
$node = $directoryChannel;
}
}
}
}

// Ideally, we want this hook in localgov_guides.
// Wrap it for now so we can move it there cleanly.
if (!function_exists('localgov_guides_localgov_subsites_extras_current_node_alter')) {
/**
* Implements hook_localgov_subsites_extras_current_node_alter().
*/
function localgov_guides_localgov_subsites_extras_current_node_alter(?NodeInterface &$node) {
if ($node instanceof NodeInterface && $node->hasField('localgov_guides_parent')) {
$guideParent = $node->localgov_guides_parent->entity;
if ($guideParent instanceof NodeInterface) {
$node = $guideParent;
}
}
}
}

// phpcs:enable
2 changes: 1 addition & 1 deletion localgov_subsites_extras.services.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
services:
localgov_subsites_extras.service:
class: Drupal\localgov_subsites_extras\Service\SubsiteService
arguments: ['@entity_type.manager', '@plugin.manager.menu.link', '@current_route_match', '@config.factory']
arguments: ['@config.factory', '@entity_type.manager', '@plugin.manager.menu.link', '@module_handler', '@current_route_match']
69 changes: 33 additions & 36 deletions src/Service/SubsiteService.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Menu\MenuLinkManagerInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\node\NodeInterface;
Expand All @@ -15,30 +16,34 @@
*/
class SubsiteService {

// Disable phpcs for a bit, so we don't have to add a load of stuff that's
// made redundant by type hints.
// phpcs:disable
private EntityTypeManagerInterface $entityTypeManager;
private MenuLinkManagerInterface $menuLinkService;
private RouteMatchInterface $routeMatch;
private ConfigFactory $configFactory;
/**
* Subsite homepage.
*
* @var \Drupal\node\NodeInterface|null
*/
private ?NodeInterface $subsiteHomePage;
private bool $searched = false;

/**
* Searched flag.
*
* @var bool
*/
private bool $searched = FALSE;

/**
* Subsite content types.
*
* @var array|null
*/
private ?array $subsiteTypes = [];
private ?string $themeField;

public function __construct(
EntityTypeManagerInterface $entityTypeManager,
MenuLinkManagerInterface $menuLinkService,
RouteMatchInterface $routeMatch,
ConfigFactory $configFactory
) {
$this->entityTypeManager = $entityTypeManager;
$this->menuLinkService = $menuLinkService;
$this->routeMatch = $routeMatch;
$this->configFactory = $configFactory;
}
// phpcs:enable
private ConfigFactory $configFactory,
private EntityTypeManagerInterface $entityTypeManager,
private MenuLinkManagerInterface $menuLinkService,
private ModuleHandlerInterface $moduleHandler,
private RouteMatchInterface $routeMatch,
) {}

/**
* Get the subsite homepage node if we're in a subsite.
Expand All @@ -63,13 +68,13 @@ public function getHomePage(): ?NodeInterface {
*/
public function getCurrentSubsiteTheme(): ?string {

$this->themeField = $this->configFactory->get('localgov_subsites_extras.settings')->get('theme_field');
$themeField = $this->configFactory->get('localgov_subsites_extras.settings')->get('theme_field');

// If the current node is part of a subsite, $subsiteHomePage will be the
// subsite's homepage node. If it's not, it'll be null.
$subsiteHomePage = $this->getHomePage();
if ($subsiteHomePage) {
return $subsiteHomePage->get($this->themeField)->value;
return $subsiteHomePage->get($themeField)->value;
}

return NULL;
Expand All @@ -85,7 +90,7 @@ private function isSubsiteType(NodeInterface $node): bool {
/**
* Walks up the menu tree to look for a subsite homepage node.
*/
private function walkMenuTree(NodeInterface $node) {
private function walkMenuTree(NodeInterface $node): ?NodeInterface {

if ($this->isSubsiteType($node)) {
return $node;
Expand All @@ -96,10 +101,9 @@ private function walkMenuTree(NodeInterface $node) {
if (!empty($result)) {
$menuLink = reset($result);
$parentMenuLinkID = $menuLink->getParent();

if ($parentMenuLinkID) {
$parentNode = $this->loadNodeForMenuLink($parentMenuLinkID);
return $this->walkMenuTree($parentNode);
return $parentNode ? $this->walkMenuTree($parentNode) : NULL;
}
}
return NULL;
Expand All @@ -108,7 +112,7 @@ private function walkMenuTree(NodeInterface $node) {
/**
* Loads the node for the supplied menu link ID.
*/
private function loadNodeForMenuLink($menuLinkContentID) {
private function loadNodeForMenuLink($menuLinkContentID): ?NodeInterface {
$menuLink = $this->menuLinkService->createInstance($menuLinkContentID);
$pluginDefinition = $menuLink->getPluginDefinition();

Expand Down Expand Up @@ -140,6 +144,8 @@ private function findHomePage(?NodeInterface $node = NULL): ?NodeInterface {
}
}

$this->moduleHandler->alter('localgov_subsites_extras_current_node', $node);

if (!$node instanceof NodeInterface) {
return NULL;
}
Expand All @@ -149,16 +155,7 @@ private function findHomePage(?NodeInterface $node = NULL): ?NodeInterface {
$this->subsiteTypes = $subsiteTypes;
}

$subsiteHomePage = $this->walkMenuTree($node);

// @todo Move this out to an event or hook or something.
if (empty($subsiteHomePage) && $node->getType() === 'localgov_directories_page') {
/** @var \Drupal\node\NodeInterface $directoryChannel */
$directoryChannel = $node->localgov_directory_channels->entity;
$subsiteHomePage = $this->walkMenuTree($directoryChannel);
}

return $subsiteHomePage;
return $this->walkMenuTree($node);
}

}
85 changes: 56 additions & 29 deletions tests/src/Functional/SubsiteTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace Drupal\Tests\localgov_subsites_extras\Functional;

use Drupal\menu_link_content\Entity\MenuLinkContent;
use Drupal\node\Entity\Node;
use Drupal\Tests\BrowserTestBase;

/**
Expand All @@ -22,49 +21,77 @@ class SubsiteTest extends BrowserTestBase {
protected static $modules = [
'localgov_subsites',
'localgov_subsites_extras',
'localgov_guides',
];

/**
* Test that we can set up a subsite using this module.
* Creates a menu link to the given node in the subsites menu.
*/
public function testLoadAdminView() {
protected function createMenuLinkForNode($node, $parentLink = NULL) {
$properties = [
'link' => [['uri' => 'entity:node/' . $node->id()]],
'title' => $node->label(),
'menu_name' => 'subsites',
];
if ($parentLink instanceof MenuLinkContent) {
$properties['parent'] = 'menu_link_content:' . $parentLink->uuid();
}
$menuLink = MenuLinkContent::create($properties);
$menuLink->save();
return $menuLink;
}

$user = $this->createUser([], 'admintestuser', TRUE);
/**
* Test that we can set up a subsite using this module.
*
* Structure is a single subsite page under a subsite overview.
*/
public function testSubsitePage() {

// "theme_a" is the only default value in a fresh install of
// localgov_subsites.
$parentNode = Node::create([
// "theme_a" is the only value in a fresh install of localgov_subsites.
$parentNode = $this->createNode([
'type' => 'localgov_subsites_overview',
'title' => $this->randomMachineName(),
'uid' => $user->id(),
'status' => 1,
'localgov_subsites_theme' => 'theme_a',
]);
$parentNode->save();
$parentMenuLink = $this->createMenuLinkForNode($parentNode);

$parentMenuLink = MenuLinkContent::create([
'link' => [['uri' => 'entity:node/' . $parentNode->id()]],
'title' => $parentNode->label(),
'menu_name' => 'subsites',
$childNode = $this->createNode([
'type' => 'localgov_subsites_page',
]);
$parentMenuLink->save();
$this->createMenuLinkForNode($childNode, $parentMenuLink);

$childNode = Node::create([
'type' => 'localgov_subsites_page',
'title' => $this->randomMachineName(),
'uid' => $user->id(),
'status' => 1,
$this->drupalGet('/node/' . $childNode->id());

// Check the class for the color scheme is on the body of the child node.
$this->assertSession()->elementAttributeContains('xpath', '/body', 'class', 'subsite-extra--color-theme_a');
}

/**
* Test that we can set up a guide in a subsite using this module.
*
* Structure is a single guide page under a guide overview under a subsite
* overview.
*/
public function testGuidePage() {

// "theme_a" is the only value in a fresh install of localgov_subsites.
$subsiteNode = $this->createNode([
'type' => 'localgov_subsites_overview',
'localgov_subsites_theme' => 'theme_a',
]);
$childNode->save();
$subsiteMenuLink = $this->createMenuLinkForNode($subsiteNode);

MenuLinkContent::create([
'link' => [['uri' => 'entity:node/' . $childNode->id()]],
'title' => $childNode->label(),
'menu_name' => 'subsites',
'parent' => 'menu_link_content:' . $parentMenuLink->uuid(),
])->save();
$guideOverviewNode = $this->createNode([
'type' => 'localgov_guides_overview',
]);
$this->createMenuLinkForNode($guideOverviewNode, $subsiteMenuLink);

$this->drupalGet('/node/' . $childNode->id());
$guidePageNode = $this->createNode([
'type' => 'localgov_guides_page',
'localgov_guides_parent' => $guideOverviewNode->id(),
]);

$this->drupalGet('/node/' . $guidePageNode->id());

// Check the class for the color scheme is on the body of the child node.
$this->assertSession()->elementAttributeContains('xpath', '/body', 'class', 'subsite-extra--color-theme_a');
Expand Down