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

VACMS-9885 site previewer #16138

Merged
merged 14 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from 6 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
1 change: 1 addition & 0 deletions config/sync/feature_toggle.features.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ features:
feature_health_connect_number: FEATURE_HEALTH_CONNECT_NUMBER
feature_event_outreach_checkbox: FEATURE_EVENT_OUTREACH_CHECKBOX
feature_event_outreach_checkbox_all: FEATURE_EVENT_OUTREACH_CHECKBOX_ALL
feature_next_story_preview: FEATURE_NEXT_STORY_PREVIEW
12 changes: 12 additions & 0 deletions config/sync/next.next_entity_type_config.node.news_story.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
uuid: 67c41c39-6232-472c-807a-088084bb6b6f
langcode: en
status: true
dependencies: { }
id: node.news_story
site_resolver: site_selector
configuration:
sites:
next_build_preview_server: next_build_preview_server
field_name: { }
revalidator: ''
revalidator_configuration: { }
11 changes: 11 additions & 0 deletions config/sync/next.next_entity_type_config.node.story_listing.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
uuid: b7fce347-c71d-4af8-ad58-3916f96d9829
langcode: en
status: true
dependencies: { }
id: node.story_listing
site_resolver: site_selector
configuration:
sites:
next_build_preview_server: next_build_preview_server
revalidator: ''
revalidator_configuration: { }
4 changes: 2 additions & 2 deletions config/sync/next.settings.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
_core:
default_config_hash: xRrZztTZj9Lor1bWUC6p2aB3S29Vg8m94UrOwc5AZps
langcode: en
site_previewer: iframe
site_previewer: link
site_previewer_configuration:
width: 100%
sync_route: false
sync_route: 0
sync_route_skip_routes: ''
preview_url_generator: simple_oauth
preview_url_generator_configuration:
Expand Down
48 changes: 0 additions & 48 deletions docroot/modules/custom/va_gov_backend/va_gov_backend.module
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ use Drupal\paragraphs\Entity\Paragraph;
use Drupal\taxonomy\TermInterface;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
use Drupal\va_gov_build_trigger\Form\PreviewForm;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Mime\MimeTypeGuesserInterface;

Expand Down Expand Up @@ -971,53 +970,6 @@ function va_gov_backend_preprocess_page_title(&$variables) {
}
}

/**
* Add va_gov preview button to view node on the front end.
*/
function va_gov_backend_preprocess_page(&$variables) {

$node = \Drupal::routeMatch()->getParameter('node');
if ($node instanceof NodeInterface) {
// It's a node.
$exclusion_types_from_config = \Drupal::service('va_gov_backend.exclusion_types')->getExcludedTypes();
$list_types = [
// List pages don't play nicely with preview.
'event_listing',
'health_services_listing,',
'leadership_listing',
'locations_listing',
'press_releases_listing',
'publication_listing',
'story_listing',
];
$exclusion_types = array_merge(array_values($exclusion_types_from_config), array_values($list_types));
$last_saved_by_an_editor = $node->get('field_last_saved_by_an_editor')->value;
$variables['page']['last_saved_by_an_editor'] = $last_saved_by_an_editor ? \Drupal::service('date.formatter')->format($last_saved_by_an_editor, 'custom', 'F j Y g:ia') : 'Unknown';
// Exclude staff pages without bios.
if ($node->bundle() === 'person_profile' && $node->field_complete_biography_create->value === '0') {
$exclusion_types[] = 'person_profile';
}
// Make sure we aren't on the node form or an excluded type.
$route_name = \Drupal::routeMatch()->getRouteName();
$language = \Drupal::languageManager()->getCurrentLanguage()->getId();
if (($route_name !== 'entity.node.edit_form') &&
(!in_array($node->bundle(), $exclusion_types)) &&
($language === 'en')) {
// Make sure we aren't on /training-guide.
$current_uri = \Drupal::request()->getRequestUri();
if ($current_uri !== '/training-guide') {
$node = \Drupal::routeMatch()->getParameter('node');
$nid = $node->id();
$host = \Drupal::request()->getHost();
$preview_form = new PreviewForm();
$url = $preview_form->getEnvironment($host, $nid);
$button = '<a class="button button--primary js-form-submit form-submit node-preview-button" rel="noopener" target="_blank" href="' . $url . '">Preview</a>';
$variables['page']['sidebar_second']['#markup'] = $button;
}
}
}
}

/**
* Callback handler for workbench_access_assign_user form - invalidate cache.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
<?php

namespace Drupal\va_gov_preview\EventSubscriber;

use Drupal\Core\Entity\EntityTypeManager;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\feature_toggle\FeatureStatus;
use Drupal\next\NextEntityTypeManagerInterface;
use Drupal\next\NextSettingsManagerInterface;
use Drupal\node\NodeInterface;
use Drupal\preprocess_event_dispatcher\Event\PagePreprocessEvent;
use Drupal\va_gov_build_trigger\Form\PreviewForm;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
* VA.gov Lovell Entity Event Subscriber.
*/
class PreviewEventSubscriber implements EventSubscriberInterface {

use StringTranslationTrait;

/**
* The Feature toggle name for outreach checkbox.
*/
const NEXT_PREVIEW_FEATURE_NAME = 'feature_next_story_preview';

/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityTypeManager
*/
private $entityTypeManager;

/**
* The route match interface.
*
* @var \Drupal\Core\Routing\RouteMatchInterface
*/
protected $routeMatch;

/**
* The next entity type manager.
*
* @var \Drupal\next\NextEntityTypeManagerInterface
*/
protected NextEntityTypeManagerInterface $nextEntityTypeManager;

/**
* The next settings manager.
*
* @var \Drupal\next\NextSettingsManagerInterface
*/
protected NextSettingsManagerInterface $nextSettingsManager;

/**
* TRUE if the next preview checkbox feature toggle is enabled.
*
* @var bool
*/
private bool $nextPreviewEnabled;

/**
* Constructs the EventSubscriber object.
*
* @param \Drupal\Core\Entity\EntityTypeManager $entityTypeManager
* The string entity type service.
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* Interface for classes representing the result of routing.
* @param \Drupal\next\NextEntityTypeManagerInterface $next_entity_type_manager
* Interface for retrieving all connected Next.js sites.
* @param \Drupal\next\NextSettingsManagerInterface $next_settings_manager
* Interface for retrieving specific Next.js site settings.
* @param \Drupal\feature_toggle\FeatureStatus $feature_status
* Interface for checking CMS feature flags.
*/
public function __construct(
tjheffner marked this conversation as resolved.
Show resolved Hide resolved
EntityTypeManager $entityTypeManager,
RouteMatchInterface $route_match,
NextEntityTypeManagerInterface $next_entity_type_manager,
NextSettingsManagerInterface $next_settings_manager,
FeatureStatus $feature_status,
) {
$this->entityTypeManager = $entityTypeManager;
$this->routeMatch = $route_match;
$this->nextEntityTypeManager = $next_entity_type_manager;
$this->nextSettingsManager = $next_settings_manager;
$this->next_preview_enabled = $feature_status->getStatus(self::NEXT_PREVIEW_FEATURE_NAME);
tjheffner marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
return [
PagePreprocessEvent::name() => 'preprocessPage',
];
}

/**
* Preprocess a node page to display the preview button.
*
* @param \Drupal\preprocess_event_dispatcher\Event\PagePreprocessEvent $event
* Event.
*/
public function preprocessPage(PagePreprocessEvent $event): void {
/** @var \Drupal\preprocess_event_dispatcher\Variables\PageEventVariables $variables */
$variables = $event->getVariables();
$node = $variables->getNode();
$vars = &$variables->getRootVariablesByReference();

if ($node === NULL) {
return;
}

$route_name = $this->routeMatch->getRouteName();
$language = \Drupal::languageManager()->getCurrentLanguage()->getId();
tjheffner marked this conversation as resolved.
Show resolved Hide resolved

// Make sure we aren't on the node form or an excluded type.
if (($route_name !== 'entity.node.edit_form') && ($language === 'en')) {
$last_saved_by_an_editor = $node->get('field_last_saved_by_an_editor')->value;
$vars['page']['last_saved_by_an_editor'] = $last_saved_by_an_editor ? \Drupal::service('date.formatter')->format($last_saved_by_an_editor, 'custom', 'F j Y g:ia') : 'Unknown';
tjheffner marked this conversation as resolved.
Show resolved Hide resolved

$button = $this->generatePreviewButton($node);
$vars['page']['sidebar_second']['#markup'] = $button;
}
}

/**
* Generate a preview button for a node.
*
* @param \Drupal\node\NodeInterface $node
* Node.
*/
protected function generatePreviewButton(NodeInterface $node): string|null {
// Needs to come first because listing types are allowed here.
if ($this->next_preview_enabled && $this->checkNextEnabledTypes($node->bundle())) {
tjheffner marked this conversation as resolved.
Show resolved Hide resolved
$url = $this->generateNextBuildPreviewLink($node);
}
// Otherwise return default preview experience.
else {
if ($this->checkExcludedTypes($node)) {
return NULL;
}
// Make sure we aren't on /training-guide.
$current_uri = \Drupal::request()->getRequestUri();
tjheffner marked this conversation as resolved.
Show resolved Hide resolved
if ($current_uri === '/training-guide') {
return NULL;
}

$node = $this->routeMatch->getParameter('node');
$nid = $node->id();
$host = \Drupal::request()->getHost();
tjheffner marked this conversation as resolved.
Show resolved Hide resolved
$preview_form = new PreviewForm();
$url = $preview_form->getEnvironment($host, $nid);
}
return '<a class="button button--primary js-form-submit form-submit node-preview-button" rel="noopener" target="_blank" href="' . $url . '">' . $this->t('Preview') . '</a>';
}

/**
* Next-build enabled preview requires certain config entities to exist.
*
* @param string $type
* The node type.
*
* @return bool
* TRUE if the node type has a corresponding next config entity.
*/
protected function checkNextEnabledTypes(string $type): bool {
// Replace this array with a config check.
$enabled_types = ['news_story', 'story_listing'];
return in_array($type, $enabled_types);
}

/**
* Existing preview functionality has some conditions.
*
* @param \Drupal\node\NodeInterface $node
* The node type.
*
* @return bool
* TRUE if the node type is excluded from preview.
*/
protected function checkExcludedTypes(NodeInterface $node): bool {
$exclusion_types_from_config = \Drupal::service('va_gov_backend.exclusion_types')->getExcludedTypes();
tjheffner marked this conversation as resolved.
Show resolved Hide resolved
$list_types = [
// List pages don't play nicely with preview.
'event_listing',
'health_services_listing,',
'leadership_listing',
'locations_listing',
'press_releases_listing',
'publication_listing',
'story_listing',
];
$exclusion_types = array_merge(array_values($exclusion_types_from_config), array_values($list_types));
// Exclude staff pages without bios.
if ($node->bundle() === 'person_profile' && $node->get('field_complete_biography_create')->value === '0') {
$exclusion_types[] = 'person_profile';
}

return (in_array($node->bundle(), $exclusion_types));
}

/**
* Generate a preview link targeting an associated next-build server.
*
* @param \Drupal\node\NodeInterface $node
* Node.
*
* @return string
* Preview link with required URL query params.
*/
protected function generateNextBuildPreviewLink(NodeInterface $node): string {
$sites = $this->nextEntityTypeManager->getSitesForEntity($node);

$url = $sites['next_build_preview_server']->getPreviewUrlForEntity($node);

return $url->toString();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php
Copy link
Contributor Author

@tjheffner tjheffner Nov 15, 2023

Choose a reason for hiding this comment

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

noting this plugin isn't actually used by the PreviewEventSubscriber, but it does provide an drastically-less-experience-altering alternative to the full-page iframe takeover of a node view page that the default plugin provided by drupal/next does.

next.settings.yml has been configured to use it by default, but it should realistically never render to a page in this manner, because we render buttons that handle both versions of preview ourselves in the PreviewEventSubscriber.


namespace Drupal\va_gov_preview\Plugin\Next\SitePreviewer;

use Drupal\Core\Entity\EntityInterface;
use Drupal\next\Plugin\SitePreviewerBase;

/**
* Provides a link to the preview page.
*
* @SitePreviewer(
* id = "link",
* label = "Link to preview",
* description = "Displays a link to the preview page."
* )
*/
class Link extends SitePreviewerBase {

/**
* {@inheritdoc}
*/
public function render(EntityInterface $entity, array $sites) {
$build = [];

foreach ($sites as $site) {
$build[] = [
'#type' => 'link',
'#title' => $this->t('Preview'),
'#url' => $site->getPreviewUrlForEntity($entity),
'#attributes' => [
'class' => ['button', 'button--primary', 'node-preview-button'],
'target' => '_blank',
],
];
}

return $build;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ type: module
core_version_requirement: ^9 || ^10
dependencies:
- drupal:serialization
- drupal:hook_event_dispatcher
- va_gov_build_trigger
Loading
Loading