diff --git a/README.md b/README.md index 3de594f..5fbb4c9 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,9 @@ Tide is a Drupal 8 distribution focused on delivering an API first, headless Dru [![Pull Requests](https://img.shields.io/github/issues-pr/dpc-sdp/tide_page.svg)](https://github.com/dpc-sdp/tide_site/pulls) ## What is in this package -- Fiedls for `Site` taxonomy vocabulary -- JSONAPI module integration +- Fields for `Site` taxonomy vocabulary +- JSONAPI and Tide API module integration +- Preview Links support (with Tide API) - Simple Sitemap integration configuration ## Installation diff --git a/modules/tide_site_preview/config/optional/block.block.seven_tide_site_preview_links.yml b/modules/tide_site_preview/config/optional/block.block.seven_tide_site_preview_links.yml new file mode 100644 index 0000000..97ac74c --- /dev/null +++ b/modules/tide_site_preview/config/optional/block.block.seven_tide_site_preview_links.yml @@ -0,0 +1,20 @@ +langcode: en +status: true +dependencies: + module: + - tide_site_preview + theme: + - seven +id: seven_tide_site_preview_links +theme: seven +region: highlighted +weight: -3 +provider: null +plugin: tide_site_preview_links_block +settings: + id: tide_site_preview_links_block + label: 'Click the links below to preview this revision on frontend sites' + provider: tide_site_preview + label_display: visible + open_new_window: 1 +visibility: { } diff --git a/modules/tide_site_preview/css/tide_site_preview.preview_links.css b/modules/tide_site_preview/css/tide_site_preview.preview_links.css new file mode 100644 index 0000000..a39255e --- /dev/null +++ b/modules/tide_site_preview/css/tide_site_preview.preview_links.css @@ -0,0 +1,14 @@ +/** + * @file + * Theme styles for the tide site preview links. + */ +.block-tide-site-preview-links-block { + border: 1px dashed #bbb; + margin: 2em 0; + background: #fff; + padding-left: 1em; +} + +.tide-site-preview-link .site-name::after { + content: ':'; +} diff --git a/modules/tide_site_preview/src/Plugin/Block/PreviewLinksBlock.php b/modules/tide_site_preview/src/Plugin/Block/PreviewLinksBlock.php new file mode 100644 index 0000000..3de2470 --- /dev/null +++ b/modules/tide_site_preview/src/Plugin/Block/PreviewLinksBlock.php @@ -0,0 +1,326 @@ +siteHelper = $site_helper; + $this->routeMatch = $route_match; + $this->entityTypeManager = $entity_type_manager; + $this->getCurrentNode(); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('tide_site.helper'), + $container->get('current_route_match'), + $container->get('entity_type.manager') + ); + } + + /** + * {@inheritdoc} + */ + public function blockForm($form, FormStateInterface $form_state) { + $form = parent::blockForm($form, $form_state); + + $config = $this->getConfiguration(); + $form['open_new_window'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Open preview links in a new window or browser tab'), + '#default_value' => $config['open_new_window'] ?? TRUE, + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function blockSubmit($form, FormStateInterface $form_state) { + parent::blockSubmit($form, $form_state); + $values = $form_state->getValues(); + $this->configuration['open_new_window'] = $values['open_new_window']; + } + + /** + * {@inheritdoc} + */ + public function blockAccess(AccountInterface $account) { + return AccessResult::allowedIf($account->hasPermission('view tide_site preview links')); + } + + /** + * {@inheritdoc} + */ + public function build() { + if (!$this->currentNode || !$this->isValidRoute()) { + return []; + } + + $preview_urls = []; + // Load all sites of the current node. + $sites = $this->siteHelper->getEntitySites($this->currentNode, TRUE); + if (!empty($sites['ids'])) { + // Generate the preview URLs on all sites. + foreach ($sites['ids'] as $site_id) { + $site = $this->siteHelper->getSiteById($site_id); + if ($site) { + $section = NULL; + if (!empty($sites['sections'][$site_id])) { + $section = $this->siteHelper->getSiteById($sites['sections'][$site_id]); + } + $preview_urls[$site_id] = $this->buildFrontendPreviewLink($this->currentNode, $site, $section); + } + } + } + + // Prepend the preview URL of the primary site to the Preview Links. + $primary_site = $this->siteHelper->getEntityPrimarySite($this->currentNode); + if ($primary_site) { + $primary_site_section = NULL; + if (!empty($sites['sections'][$primary_site->id()])) { + $primary_site_section = $this->siteHelper->getSiteById($sites['sections'][$primary_site->id()]); + } + $primary_preview_url = $this->buildFrontendPreviewLink($this->currentNode, $primary_site, $primary_site_section); + unset($preview_urls[$primary_site->id()]); + array_unshift($preview_urls, $primary_preview_url); + } + + $config = $this->getConfiguration(); + $build = [ + '#theme' => 'tide_site_preview_links', + '#node' => $this->currentNode, + '#open_in_new_window' => !(empty($config['open_new_window'])), + '#preview_links' => $preview_urls, + '#attached' => [ + 'library' => ['tide_site_preview/preview-links'], + ], + ]; + + return $build; + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + $contexts = parent::getCacheContexts(); + if ($this->currentNode) { + $contexts = Cache::mergeTags($contexts, $this->currentNode->getCacheContexts()); + } + return Cache::mergeContexts($contexts, [ + 'url', + 'url.query_args', + 'user.roles', + ]); + } + + /** + * {@inheritdoc} + */ + public function getCacheTags() { + $tags = parent::getCacheTags(); + if ($this->currentNode) { + $tags = Cache::mergeTags($tags, $this->currentNode->getCacheTags()); + } + return $tags; + } + + /** + * Check if the current route is valid for this block. + * + * @return bool + * TRUE if the route is Node view or Node revision view. + */ + protected function isValidRoute() : bool { + // Only display on Node view and Node Revision view. + $valid_routes = [ + 'entity.node.revision', + 'entity.node.latest_version', + 'entity.node.canonical', + ]; + $route_name = $this->routeMatch->getRouteName(); + return in_array($route_name, $valid_routes); + } + + /** + * Get the current node. + * + * @return \Drupal\node\NodeInterface|null + * The node. + */ + protected function getCurrentNode() : ?NodeInterface { + if ($this->currentNode) { + return $this->currentNode; + } + + $route_name = $this->routeMatch->getRouteName(); + // Load the node from URL. + /** @var \Drupal\node\NodeInterface $node */ + $node = $this->routeMatch->getParameter('node'); + if ($route_name === 'entity.node.revision') { + try { + $vid = $this->routeMatch->getParameter('node_revision'); + $node = $this->entityTypeManager->getStorage('node') + ->loadRevision($vid); + } + catch (Exception $exception) { + watchdog_exception('tide_site_preview', $exception); + $node = NULL; + } + } + $this->currentNode = $node; + + return $this->currentNode; + } + + /** + * Build the frontend preview link array of a node. + * + * @param \Drupal\node\NodeInterface $node + * The node. + * @param \Drupal\taxonomy\TermInterface $site + * The site of the preview link. + * @param \Drupal\taxonomy\TermInterface|null $section + * The section of the preview link. + * + * @return array + * The preview link array with following keys: + * * #site: The site object. + * * #section: The section object. + * * name: The site/section name. + * * url: The absolute URL of the preview link. + */ + protected function buildFrontendPreviewLink(NodeInterface $node, TermInterface $site, TermInterface $section = NULL) : array { + $config = $this->getConfiguration(); + $url_options = [ + 'attributes' => !(empty($config['open_new_window'])) ? ['target' => '_blank'] : [], + ]; + if ($section) { + $url_options['query']['section'] = $section->id(); + } + + $preview_link = [ + '#site' => $site, + '#section' => $section, + 'name' => $site->getName(), + ]; + if ($section && $section->id() !== $site->id()) { + $preview_link['name'] = $site->getName() . ' - ' . $section->getName(); + } + $site_base_url = $this->siteHelper->getSiteBaseUrl($site); + if ($node->isPublished() && $node->isDefaultRevision()) { + unset($url_options['query']['section']); + $preview_link['url'] = $this->getNodeFrontendUrl($node, $site_base_url, $url_options); + } + else { + $revision_id = $node->getLoadedRevisionId(); + $is_latest_revision = $node->isLatestRevision(); + $content_type = $node->bundle(); + $url = !empty($site_base_url) ? ($site_base_url . '/preview/' . $content_type . '/' . $node->uuid() . '/' . ($is_latest_revision ? 'latest' : $revision_id)) : ''; + $preview_link['url'] = (!empty($url) && !empty($url_options)) ? Url::fromUri($url, $url_options) : ''; + } + + return $preview_link; + } + + /** + * Get the frontend URL of a node. + * + * @param \Drupal\node\NodeInterface $node + * The node. + * @param string $site_base_url + * The base URL of the frontend. + * @param array $url_options + * The extra options. + * + * @return \Drupal\Core\Url|string + * The Url. + */ + protected function getNodeFrontendUrl(NodeInterface $node, $site_base_url = '', array $url_options = []) { + try { + $url = $node->toUrl('canonical', [ + 'absolute' => TRUE, + 'base_url' => $site_base_url, + ] + $url_options); + + $pattern = '/^\/site\-(\d+)\//'; + if ($site_base_url) { + $pattern = '/' . preg_quote($site_base_url, '/') . '\/site\-(\d+)\//'; + } + $clean_url = preg_replace($pattern, $site_base_url . '/', $url->toString()); + return $clean_url ? Url::fromUri($clean_url, $url_options) : $url; + } + catch (Exception $exception) { + watchdog_exception('tide_site_preview', $exception); + } + return ''; + } + +} diff --git a/modules/tide_site_preview/templates/tide-site-preview-links.html.twig b/modules/tide_site_preview/templates/tide-site-preview-links.html.twig new file mode 100644 index 0000000..5003345 --- /dev/null +++ b/modules/tide_site_preview/templates/tide-site-preview-links.html.twig @@ -0,0 +1,23 @@ +{# +/** + * @file + * Default theme implementation for the preview links. + * + * Available variables: + * - node: The node of the preview links. + * - open_in_new_window: whether to open all links in a new window. + * - preview_links: The preview links array, each item has: + * - name: The site/section name of the link. + * - url: The absolute URL of the preview link. + * - link: The pregenerated anchor link. + * + * @ingroup themeable + */ +#} + diff --git a/modules/tide_site_preview/tide_site_preview.info.yml b/modules/tide_site_preview/tide_site_preview.info.yml new file mode 100644 index 0000000..7533f25 --- /dev/null +++ b/modules/tide_site_preview/tide_site_preview.info.yml @@ -0,0 +1,12 @@ +name: 'Tide Site - Preview links' +description: Provides frontend preview links for content managed with Tide Site. +type: module +package: Tide +core: 8.x +dependencies: + - dpc-sdp:tide_site + - dpc-sdp:tide_api +config_devel: + install: { } + optional: + - block.block.seven_tide_site_preview_links diff --git a/modules/tide_site_preview/tide_site_preview.install b/modules/tide_site_preview/tide_site_preview.install new file mode 100644 index 0000000..8ae5e4e --- /dev/null +++ b/modules/tide_site_preview/tide_site_preview.install @@ -0,0 +1,23 @@ +grantPermission('view tide_site preview links')->save(); + } + } +} diff --git a/modules/tide_site_preview/tide_site_preview.libraries.yml b/modules/tide_site_preview/tide_site_preview.libraries.yml new file mode 100644 index 0000000..6590a41 --- /dev/null +++ b/modules/tide_site_preview/tide_site_preview.libraries.yml @@ -0,0 +1,5 @@ +preview-links: + version: 1.x + css: + theme: + css/tide_site_preview.preview_links.css: {} diff --git a/modules/tide_site_preview/tide_site_preview.module b/modules/tide_site_preview/tide_site_preview.module new file mode 100644 index 0000000..f3a412b --- /dev/null +++ b/modules/tide_site_preview/tide_site_preview.module @@ -0,0 +1,31 @@ + [ + 'variables' => [ + 'node' => NULL, + 'preview_links' => [], + ], + ], + ]; +} + +/** + * Implements template_preprocess_HOOK(). + */ +function template_preprocess_tide_site_preview_links(&$variables) { + foreach ($variables['preview_links'] as &$preview_link) { + $preview_link['link'] = !empty($preview_link['url']) ? Link::fromTextAndUrl($preview_link['url']->toString(), $preview_link['url']) : ''; + } +} diff --git a/modules/tide_site_preview/tide_site_preview.permissions.yml b/modules/tide_site_preview/tide_site_preview.permissions.yml new file mode 100644 index 0000000..d2085eb --- /dev/null +++ b/modules/tide_site_preview/tide_site_preview.permissions.yml @@ -0,0 +1,3 @@ +view tide_site preview links: + title: 'View frontend preview links' + description: 'View preview links on frontend sites for a node.' diff --git a/src/AliasManager.php b/src/AliasManager.php index 9c1f5fe..014fe58 100644 --- a/src/AliasManager.php +++ b/src/AliasManager.php @@ -39,7 +39,7 @@ public function getAliasByPath($path, $langcode = NULL) { // Remove the site prefix from path alias when responding from // JSONAPI entity resource with site parameter. $request = \Drupal::request(); - $is_jsonapi = $request->attributes->get('_is_jsonapi', FALSE); + $is_jsonapi = !empty($request->attributes) ? $request->attributes->get('_is_jsonapi', FALSE) : FALSE; if ($is_jsonapi) { $site_id = $request->get('site'); if ($site_id) {