amp.module in Accelerated Mobile Pages (AMP) 8.3
Same filename and directory in other branches
Provides functionality for handling AMP.
File
amp.moduleView source
<?php
/**
* @file
* Provides functionality for handling AMP.
*/
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\node\NodeInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
/**
* Implements hook_help().
*/
function amp_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
// Main module help for the amp module.
case 'help.page.amp':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('Accelerated Mobile Pages (AMP) integration. Read about it online at <a href=":url">AMP Documentation</a>.', [
':url' => 'https://www.drupal.org/docs/8/modules/accelerated-mobile-pages-amp',
]) . '</p>';
return $output;
default:
}
}
/**
* Implements hook_preprocess_html().
*/
function amp_preprocess_html(&$variables) {
$amp_context = \Drupal::service('router.amp_context');
if ($amp_context
->isAmpRoute()) {
foreach ($variables['page']['#attached']['html_head'] as $key => $value) {
if ($value[1] === 'viewport') {
$value[0]['#attributes']['content'] = 'width=device-width,minimum-scale=1,initial-scale=1';
$variables['page']['#attached']['html_head'][$key] = $value;
}
// Remove Big Pipe's meta tag, which is invalid AMP markup.
// We are generating placeholders on the server so should not be needed.
if ($key == 'big_pipe_detect_nojs') {
unset($variables['page']['#attached']['html_head'][$key]);
}
}
// Remove RDF and Metatag properties incompatible with AMP specification.
$attribute_list = [
'prefix',
'xmlns:dc',
'xmlns:og',
'xmlns:article',
'xmlns:book',
'xmlns:product',
'xmlns:profile',
'xmlns:video',
'itemtype',
'itemscope',
];
if (isset($variables['html_attributes'])) {
foreach ($attribute_list as $attribute_item) {
if (isset($variables['html_attributes'][$attribute_item])) {
unset($variables['html_attributes'][$attribute_item]);
}
}
}
}
}
/**
* Implements hook_page_attachments().
*/
function amp_page_attachments(array &$attachments) {
// Pages with development messages should not be stored in Google's AMP cache.
// We prevent caching by making the page invalid.
// @see https://www.ampproject.org/docs/fundamentals/how_cached#can-i-opt-out-of-caching?
$ampService = \Drupal::service('amp.utilities');
if ($ampService
->isDevPage()) {
$tag = [
'#tag' => 'base',
'#attributes' => [
'name' => 'invalid-do-not-cache',
'content' => 'invalid-do-not-cache',
],
];
$attachments['#attached']['html_head'][] = [
$tag,
'no-cache',
];
}
}
/**
* Implements hook_amp_rules_alter().
*
* @see Drupal\amp\Service\DrupalParsedValidatorRules::updatedRules()
*/
function amp_amp_rules_alter(&$rules) {
foreach ($rules->tags as $delta => $tag) {
// Multiple amp-sidebar elements are now allowed.
if ($tag->tag_name == 'amp-sidebar') {
$rules->tags[$delta]->unique = 'false';
break;
}
}
}
/**
* Implements hook_entity_view_mode_alter().
*/
function amp_entity_view_mode_alter(&$view_mode, EntityInterface $entity, $context) {
$amp_context = \Drupal::service('router.amp_context');
// If on AMP route, and in full view mode, switch to AMP view mode.
if ($amp_context
->isAmpRoute(NULL, $entity) && $view_mode == 'full') {
$view_mode = 'amp';
}
}
/**
* Implements hook_entity_view_alter().
*/
function amp_entity_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
// Check if entity is a non-new node in either full or AMP view mode.
$canonical_links = [
'canonical',
'canonical_url',
];
$protected_links = $canonical_links + [
'shortlink',
];
/**
* @var NodeInterface $entity
*/
if ($entity instanceof NodeInterface && !$entity
->isNew() && in_array($build['#view_mode'], [
'full',
'amp',
])) {
// Get a list of available view modes for the current entity.
$view_modes = \Drupal::service('entity_display.repository')
->getViewModeOptionsByBundle('node', $entity
->bundle());
// Double-check that the AMP view mode is enabled for this node type.
if (isset($view_modes['amp'])) {
$build['#cache']['contexts'][] = 'url.query_args:amp';
$absolute_canonical = $entity
->toUrl('canonical', [
'absolute' => TRUE,
])
->toString();
if (!empty($build['#attached']['html_head_link'])) {
foreach ($build['#attached']['html_head_link'] as $key => $config) {
if ($build['#view_mode'] === 'amp') {
if (!in_array($config[0]['rel'], $protected_links)) {
unset($build['#attached']['html_head_link'][$key]);
}
}
elseif ($build['#view_mode'] === 'full') {
if (in_array($config[0]['rel'], $canonical_links) && !empty($config[0]['href'])) {
// Check if the canonical link is absolute and external. Do not
// expose AMP for those.
$current_canonical = $config[0]['href'];
if (UrlHelper::isExternal($current_canonical) && !UrlHelper::externalIsLocal($current_canonical, \Drupal::service('router.request_context')
->getCompleteBaseUrl())) {
continue;
}
$amp_href = \Drupal::service('amp.query_parameters')
->add($absolute_canonical);
$build['#attached']['html_head_link'][] = [
[
'rel' => 'amphtml',
'href' => $amp_href,
],
TRUE,
];
$build['#cache']['contexts'][] = 'url.site';
}
}
}
}
}
}
}
/**
* Implements hook_theme().
*/
function amp_theme() {
$theme = [
'amp_iframe' => [
'variables' => [
'iframe' => NULL,
],
],
'amp_ad' => [
'variables' => [
'type' => NULL,
'attributes' => [],
],
],
'amp_analytics' => [
'variables' => [
'account' => NULL,
'attributes' => [],
],
],
'block__amp_system_branding_block' => [
'render element' => 'elements',
'base hook' => 'block',
],
'amp_custom_style' => [
'render element' => 'element',
],
'amp_video' => [
'variables' => [
'attributes' => [],
],
],
'amp_image_carousel' => [
'variables' => [
'items' => [],
'attributes' => [],
],
],
'amp_views_carousel' => [
'variables' => [
'view' => NULL,
'options' => NULL,
'rows' => NULL,
'title' => NULL,
'attributes' => [],
],
'file' => 'amp.theme.inc',
],
'amp_sidebar' => [
'render element' => 'element',
'id' => NULL,
'tabindex' => NULL,
'attributes' => [],
'content_attributes' => [],
'file' => 'amp.theme.inc',
],
'amp_sidebar_toggle' => [
'variables' => [
'sidebarid' => NULL,
'tabindex' => NULL,
'title' => NULL,
'attributes' => [],
],
],
'amp_social_share' => [
'variables' => [
'providers' => [],
'app_id' => '',
],
],
'amp_social_post_theme' => [
'render element' => 'element',
'variables' => [
'url' => NULL,
'provider' => NULL,
'placeholder' => NULL,
'attributes' => [],
],
],
'amp_pixel' => [
'variables' => [
'domain' => NULL,
'query_string' => NULL,
'subs' => [
'AMPDOC_HOST' => [
'active' => FALSE,
],
'AMPDOC_URL' => [
'active' => FALSE,
],
'CANONICAL_HOST' => [
'active' => FALSE,
],
'CANONICAL_PATH' => [
'active' => FALSE,
],
'CANONICAL_URL' => [
'active' => FALSE,
],
'SOURCE_URL' => [
'active' => FALSE,
],
'SOURCE_HOST' => [
'active' => FALSE,
],
'DOCUMENT_CHARSET' => [
'active' => FALSE,
],
'DOCUMENT_REFERRER' => [
'active' => FALSE,
],
'TITLE' => [
'active' => FALSE,
],
'VIEWER' => [
'active' => FALSE,
],
'CONTENT_LOAD_TIME' => [
'active' => FALSE,
],
'DOMAIN_LOOKUP_TIME' => [
'active' => FALSE,
],
'DOM_INTERACTIVE_TIME' => [
'active' => FALSE,
],
'PAGE_DOWNLOAD_TIME' => [
'active' => FALSE,
],
'PAGE_LOAD_TIME' => [
'active' => FALSE,
],
'REDIRECT_TIME' => [
'active' => FALSE,
],
'SERVER_RESPONSE_TIME' => [
'active' => FALSE,
],
'TCP_CONNECT_TIME' => [
'active' => FALSE,
],
'AVAILABLE_SCREEN_HEIGHT' => [
'active' => FALSE,
],
'AVAILABLE_SCREEN_WIDTH' => [
'active' => FALSE,
],
'BROWSER_LANGUAGE' => [
'active' => FALSE,
],
'SCREEN_COLOR_DEPTH' => [
'active' => FALSE,
],
'VIEWPORT_HEIGHT' => [
'active' => FALSE,
],
'VIEWPORT_WIDTH' => [
'active' => FALSE,
],
'PAGE_VIEW_ID' => [
'active' => FALSE,
],
'RANDOM' => [
'active' => FALSE,
],
'TIMESTAMP' => [
'active' => FALSE,
],
'TOTAL_ENGAGED_TIME' => [
'active' => FALSE,
],
],
],
],
];
return $theme;
}
/**
* Implements hook_theme_suggestions_HOOK_alter().
*
* Add amp-image template suggestion, but only for images that have the AMP
* layout attribute. This will output these images as <amp-image></amp-image>.
* We want to control this from the module so the components will work on
* non-AMP pages, not just AMP pages using AMP theme. The logical name would
* be amp_image but that name has been used by the image formatter and is in
* production, it's too hard to replace it with a different implementation.
*/
function amp_theme_suggestions_image_alter(array &$suggestions, array $variables) {
if (!empty($variables['attributes']['layout'])) {
$suggestions[] = 'amp_image_wrapper';
}
}
/**
* Implements hook_theme_registry_alter().
*
* We still want the image_theme to process the image, just output it
* in a different template, and only when it used in an AMP component.
* A theme could do this with just theme_suggestions_alter(), but if a module
* provides an alternate template it won't be discovered automatically by the
* theme system. Using theme_register_alter() we can force the theme system to
* discover the alternate template by providing it in a theme we will never
* actually use directly, then it will be available for the suggestion.
*/
function amp_theme_registry_alter(&$theme_registry) {
$theme_registry['amp_image_wrapper'] = $theme_registry['image'];
$theme_registry['amp_image_wrapper']['template'] = 'amp-image-wrapper';
$theme_registry['amp_image_wrapper']['path'] = drupal_get_path('module', 'amp') . '/templates';
}
/**
* Implements hook_page_bottom() for page bottom.
*/
function amp_page_bottom(array &$page_bottom) {
$amp_context = \Drupal::service('router.amp_context');
if ($amp_context
->isAmpRoute()) {
$config = \Drupal::config('amp.analytics.settings');
$google_analytics_id = $config
->get('google_analytics_id');
if (!empty($google_analytics_id)) {
$amp_analytics = [
'#type' => 'amp_analytics',
'#attributes' => [
'type' => 'googleanalytics',
],
'#account' => $google_analytics_id,
];
$page_bottom['amp_analytics'] = $amp_analytics;
}
$amp_gtm_id = $config
->get('amp_gtm_id');
if (!empty($amp_gtm_id)) {
$amp_gtm = [
'#type' => 'amp_analytics',
'#attributes' => [
'config' => 'https://www.googletagmanager.com/amp.json?id=' . $amp_gtm_id . '>m.url=SOURCE_URL',
'data-credentials' => 'include',
],
];
$page_bottom['amp_gtm'] = $amp_gtm;
}
if ($config
->get('amp_pixel')) {
$domain = $config
->get('amp_pixel_domain_name');
$query_string = $config
->get('amp_pixel_query_string');
if (!empty($domain) && !empty($query_string)) {
$subs_random = $config
->get('amp_pixel_random_number');
$subs = [
'RANDOM' => [
'active' => $subs_random ? TRUE : FALSE,
],
];
$amp_pixel = [
'#theme' => 'amp_pixel',
'#domain' => $domain,
'#query_string' => $query_string,
'#subs' => $subs,
];
$page_bottom['amp_pixel'] = $amp_pixel;
}
}
}
}
/**
* Implements hook_form_BASE_FORM_ID_alter().
*/
function amp_form_node_form_alter(&$form, FormStateInterface $form_state, $form_id) {
// Check if the content is AMP enabled.
$amp_entity_type_service = \Drupal::service('amp.entity_type');
$type = $form_state
->getFormObject()
->getEntity()
->bundle();
if (!$amp_entity_type_service
->isAmpEnabledType($type)) {
return;
}
// Exit if the AMP submit button has been disabled.
if (!\Drupal::config('amp.settings')
->get('show_extra_save_buttons')) {
return;
}
// Add another option to go to the AMP page after saving.
$form['actions']['save_view_amp'] = [
'#type' => 'submit',
'#value' => t('Save and view AMP page'),
'#submit' => [
'::submitForm',
'::save',
],
'#access' => TRUE,
'#weight' => 10,
];
// Add a submit handler to redirect to the AMP page.
$form['actions']['save_view_amp']['#submit'][] = 'amp_node_form_submit';
$form['actions']['save_view_amp_with_warn']['#submit'][] = 'amp_node_form_submit_with_warn';
}
/**
* Submit handler for viewing the AMP page.
*/
function amp_node_form_submit(&$form, FormStateInterface $form_state) {
$path = $form_state
->getValue('path');
// Redirect to the alias if it exists, otherwise use the node URL.
$url = !empty($path[0]['alias']) ? $path[0]['alias'] : $path[0]['source'];
if (isset($url)) {
$amp_url = \Drupal::service('amp.query_parameters')
->add($url);
$response = new RedirectResponse($amp_url);
$response
->send();
}
}
/**
* Implements hook_form_alter().
*/
function amp_form_alter(&$form, FormStateInterface $form_state, $form_id) {
if ($form_id == 'entity_view_display_edit_form') {
$form['actions']['submit']['#submit'][] = 'amp_view_modes_submit';
}
}
/**
* Submit handler for enabling or disabling AMP view modes.
*/
function amp_view_modes_submit(&$form, FormStateInterface $form_state) {
$new_values = [];
$old_values = [];
if (isset($form_state
->getValues()['display_modes_custom'])) {
$new_values = array_filter($form_state
->getValues()['display_modes_custom']);
}
if (isset($form_state
->getCompleteForm()['modes']['display_modes_custom']['#default_value'])) {
$old_values = $form_state
->getCompleteForm()['modes']['display_modes_custom']['#default_value'];
}
$removed = array_diff($old_values, $new_values);
$added = array_diff($new_values, $old_values);
if (is_array($removed) && in_array('amp', $removed) && ($type = $form['#bundle'])) {
// If the AMP view was removed, clear cache of AMP-enabled content.
\Drupal::cache()
->delete('amp_enabled_types');
\Drupal::service('cache_tags.invalidator')
->invalidateTags([
'amp_types',
]);
}
if (is_array($added) && in_array('amp', $added)) {
// If the AMP view was added, clear cache of AMP-enabled content.
\Drupal::cache()
->delete('amp_enabled_types');
\Drupal::service('cache_tags.invalidator')
->invalidateTags([
'amp_types',
]);
}
}
/**
* Implements hook_preprocess_HOOK() for block templates.
*/
function amp_preprocess_block(&$variables) {
switch ($variables['base_plugin_id']) {
case 'amp_system_branding_block':
$variables['site_logo'] = '';
if ($variables['content']['site_logo']['#access'] && $variables['content']['site_logo']['#uri']) {
$variables['site_logo'] = $variables['content']['site_logo']['#uri'];
$variables['logo_width'] = $variables['content']['logo_width'];
$variables['logo_height'] = $variables['content']['logo_height'];
}
$variables['site_name'] = '';
if ($variables['content']['site_name']['#access'] && $variables['content']['site_name']['#markup']) {
$variables['site_name'] = $variables['content']['site_name']['#markup'];
}
$variables['site_slogan'] = '';
if ($variables['content']['site_slogan']['#access'] && $variables['content']['site_slogan']['#markup']) {
$variables['site_slogan'] = [
'#markup' => $variables['content']['site_slogan']['#markup'],
];
}
break;
}
}
Functions
Name | Description |
---|---|
amp_amp_rules_alter | Implements hook_amp_rules_alter(). |
amp_entity_view_alter | Implements hook_entity_view_alter(). |
amp_entity_view_mode_alter | Implements hook_entity_view_mode_alter(). |
amp_form_alter | Implements hook_form_alter(). |
amp_form_node_form_alter | Implements hook_form_BASE_FORM_ID_alter(). |
amp_help | Implements hook_help(). |
amp_node_form_submit | Submit handler for viewing the AMP page. |
amp_page_attachments | Implements hook_page_attachments(). |
amp_page_bottom | Implements hook_page_bottom() for page bottom. |
amp_preprocess_block | Implements hook_preprocess_HOOK() for block templates. |
amp_preprocess_html | Implements hook_preprocess_html(). |
amp_theme | Implements hook_theme(). |
amp_theme_registry_alter | Implements hook_theme_registry_alter(). |
amp_theme_suggestions_image_alter | Implements hook_theme_suggestions_HOOK_alter(). |
amp_view_modes_submit | Submit handler for enabling or disabling AMP view modes. |