diff --git CHANGELOG.txt CHANGELOG.txt
index 87e7d1a..35a5eae 100644
--- CHANGELOG.txt
+++ CHANGELOG.txt
@@ -140,6 +140,13 @@ Drupal 7.0, xxxx-xx-xx (development version)
- Added RDF support:
* Modules can declare RDF namespaces which are serialized in the tag
for RDFa support.
+- Search engine optimization and web linking:
+ * Added a rel="canonical" link on node and comment pages to prevent
+ duplicate content indexing by search engines.
+ * Added default a rel="shortlink" link on node and comment pages
+ that advertises a short link as an alternative to third-party services.
+ * drupal_set_html_head now stores structured data for link, meta, and
+ other elements, allowing it to be altered by modules before rendering.
- Field API:
* Custom data fields may be attached to nodes, users, comments and taxonomy
terms.
diff --git includes/batch.inc includes/batch.inc
index 289626e..7cd4b80 100644
--- includes/batch.inc
+++ includes/batch.inc
@@ -190,7 +190,7 @@ function _batch_progress_page_nojs() {
}
$url = url($batch['url'], array('query' => array('id' => $batch['id'], 'op' => $new_op)));
- drupal_add_html_head('');
+ drupal_add_html_head('batch_meta_refresh', array('#markup' => ''));
return theme('progress_bar', $percentage, $message);
}
diff --git includes/common.inc includes/common.inc
index 063174e..1a27343 100644
--- includes/common.inc
+++ includes/common.inc
@@ -250,12 +250,21 @@ function drupal_get_rdf_namespaces() {
* Add output to the head tag of the HTML page.
*
* This function can be called as long the headers aren't sent.
+ *
+ * @param $key
+ * Key for identifying the element in hook_html_head_alter(). Required if
+ * $data is not NULL.
+ * @param $data
+ * Optional renderable array.
+ *
+ * @return
+ * An array of all the stored head elements.
*/
-function drupal_add_html_head($data = NULL) {
- $stored_head = &drupal_static(__FUNCTION__, '');
+function drupal_add_html_head($key = NULL, $data = NULL) {
+ $stored_head = &drupal_static(__FUNCTION__, array());
- if (!is_null($data)) {
- $stored_head .= $data . "\n";
+ if (isset($data) && isset($key)) {
+ $stored_head[$key] = $data;
}
return $stored_head;
}
@@ -264,8 +273,29 @@ function drupal_add_html_head($data = NULL) {
* Retrieve output to be displayed in the head tag of the HTML page.
*/
function drupal_get_html_head() {
- $output = "\n";
- return $output . drupal_add_html_head();
+ $head_elements = drupal_add_html_head();
+ // Get the major version.
+ list($version, ) = explode('.', VERSION);
+ // Add default elements. Make sure the Content-Type comes first.
+ $meta_elements['system_meta_content_type'] = array(
+ '#markup' => '',
+ );
+ // Send Drupal and the major version number in the META GENERATOR HTML.
+ $meta_elements['system_meta_generator'] = array(
+ '#markup' => '',
+ );
+ // Send Drupal and the major version number in the HTTP headers.
+ $meta_elements['system_meta_generator']['#attached']['drupal_set_header'][] = array('X-Generator', 'Drupal ' . $version . ' (http://drupal.org)');
+ // Make sure the Content-Type comes first.
+ $head_elements = array_merge($meta_elements, $head_elements);
+ // Add a CR to each element for nicer printing.
+ foreach ($head_elements as $key => $element) {
+ if (!isset($element['#suffix'])) {
+ $head_elements[$key]['#suffix'] = "\n";
+ }
+ }
+ drupal_alter('html_head', $head_elements);
+ return drupal_render($head_elements);
}
/**
@@ -290,11 +320,13 @@ function drupal_add_feed($url = NULL, $title = '') {
if (!is_null($url) && !isset($stored_feed_links[$url])) {
$stored_feed_links[$url] = theme('feed_icon', $url, $title);
-
- drupal_add_link(array('rel' => 'alternate',
- 'type' => 'application/rss+xml',
- 'title' => $title,
- 'href' => $url));
+ $attributes = array(
+ 'rel' => 'alternate',
+ 'type' => 'application/rss+xml',
+ 'title' => $title,
+ 'href' => $url,
+ );
+ drupal_add_link('system_feed_' . $url, $attributes);
}
return $stored_feed_links;
}
@@ -2300,10 +2332,13 @@ function url($path = NULL, array $options = array()) {
*
* @param $attributes
* An associative array of HTML attributes.
+ * @param $separator
+ * Optional string for separating attributes. Defaults to a space.
+ *
* @return
* An HTML string ready for insertion in a tag.
*/
-function drupal_attributes(array $attributes = array()) {
+function drupal_attributes(array $attributes = array(), $separator = ' ') {
foreach ($attributes as $attribute => &$data) {
if (is_array($data)) {
$data = implode(' ', $data);
@@ -2500,12 +2535,29 @@ function base_path() {
}
/**
- * Add a tag to the page's HEAD.
+ * Add a LINK tag to the page's HEAD.
*
* This function can be called as long the HTML header hasn't been sent.
+ *
+ * @param $key
+ * A unique string key for identifying this link.
+ * @param $attributes
+ * Associative array of element attributes.
+ * @param $header
+ * Optional flag to determine if a HTTP Link: header should be sent.
*/
-function drupal_add_link($attributes) {
- drupal_add_html_head('\n");
+function drupal_add_link($key, $attributes, $header = FALSE) {
+ $head_element = array(
+ '#markup' => '',
+ );
+
+ if ($header) {
+ // Set a HTTP Link: header also.
+ $href = '<' . check_plain($attributes['href']) . '>';
+ unset($attributes['href']);
+ $head_element['#attached']['drupal_set_header'][] = array('Link', $href . drupal_attributes($attributes, '; '), TRUE);
+ }
+ drupal_add_html_head($key, $head_element);
}
/**
diff --git includes/theme.inc includes/theme.inc
index 1c5a80f..701245a 100644
--- includes/theme.inc
+++ includes/theme.inc
@@ -2093,7 +2093,7 @@ function template_preprocess_page(&$variables) {
if (theme_get_setting('toggle_favicon')) {
$favicon = theme_get_setting('favicon');
$type = theme_get_setting('favicon_mimetype');
- drupal_add_html_head('');
+ drupal_add_html_head('favicon_link', array('#markup' => ''));
}
// Set up layout variable.
@@ -2267,7 +2267,7 @@ function template_preprocess_maintenance_page(&$variables) {
if (theme_get_setting('toggle_favicon')) {
$favicon = theme_get_setting('favicon');
$type = theme_get_setting('favicon_mimetype');
- drupal_add_html_head('');
+ drupal_add_html_head('favicon_link', array('#markup' => ''));
}
global $theme;
diff --git modules/book/book.module modules/book/book.module
index 9f2fabe..75bdc39 100644
--- modules/book/book.module
+++ modules/book/book.module
@@ -914,21 +914,21 @@ function template_preprocess_book_navigation(&$variables) {
if ($prev = book_prev($book_link)) {
$prev_href = url($prev['href']);
- drupal_add_link(array('rel' => 'prev', 'href' => $prev_href));
+ drupal_add_link('book_prev_link', array('rel' => 'prev', 'href' => $prev_href));
$variables['prev_url'] = $prev_href;
$variables['prev_title'] = check_plain($prev['title']);
}
if ($book_link['plid'] && $parent = book_link_load($book_link['plid'])) {
$parent_href = url($parent['href']);
- drupal_add_link(array('rel' => 'up', 'href' => $parent_href));
+ drupal_add_link('book_up_link', array('rel' => 'up', 'href' => $parent_href));
$variables['parent_url'] = $parent_href;
$variables['parent_title'] = check_plain($parent['title']);
}
if ($next = book_next($book_link)) {
$next_href = url($next['href']);
- drupal_add_link(array('rel' => 'next', 'href' => $next_href));
+ drupal_add_link('book_next_link', array('rel' => 'next', 'href' => $next_href));
$variables['next_url'] = $next_href;
$variables['next_title'] = check_plain($next['title']);
}
diff --git modules/comment/comment.module modules/comment/comment.module
index f048a8b..ae33b30 100644
--- modules/comment/comment.module
+++ modules/comment/comment.module
@@ -349,9 +349,6 @@ function comment_permalink($comment) {
$_GET['q'] = 'node/' . $node->nid;
$_GET['page'] = $page;
- // Set the node path as the canonical URL to prevent duplicate content.
- drupal_add_link(array('rel' => 'canonical', 'href' => url('node/' . $node->nid)));
-
// Return the node view, this will show the correct comment in context.
return menu_execute_active_handler('node/' . $node->nid);
}
diff --git modules/node/node.module modules/node/node.module
index 0ae1104..5bdbcf4 100644
--- modules/node/node.module
+++ modules/node/node.module
@@ -1955,6 +1955,10 @@ function node_page_default() {
*/
function node_page_view($node) {
drupal_set_title($node->title);
+ // Set the node path as the canonical URL to prevent duplicate indexing.
+ drupal_add_link('node_canonical_link', array('rel' => 'canonical', 'href' => url('node/' . $node->nid)), TRUE);
+ // Set the non-aliased path as a default shortlink.
+ drupal_add_link('node_shortlink_link', array('rel' => 'shortlink', 'href' => url('node/' . $node->nid, array('alias' => TRUE))), TRUE);
return node_show($node);
}
diff --git modules/openid/tests/openid_test.module modules/openid/tests/openid_test.module
index b101b50..7b8a6cf 100644
--- modules/openid/tests/openid_test.module
+++ modules/openid/tests/openid_test.module
@@ -97,7 +97,8 @@ function openid_test_yadis_x_xrds_location() {
* Menu callback; regular HTML page with element.
*/
function openid_test_yadis_http_equiv() {
- drupal_add_html_head('');
+ $url = url('openid-test/yadis/xrds', array('absolute' => TRUE));
+ drupal_add_html_head('openid_test_yadis_xrds', array('#markup' => ''));
return t('This page includes a <meta equiv=...> element containing the URL of an XRDS document.');
}
@@ -105,7 +106,11 @@ function openid_test_yadis_http_equiv() {
* Menu callback; regular HTML page with OpenID 1.0 element.
*/
function openid_test_html_openid1() {
- drupal_add_html_head('');
+ $attributes = array(
+ 'rel' => 'openid.server',
+ 'href' => url('openid-test/endpoint', array('absolute' => TRUE)),
+ );
+ drupal_add_link('openid_test_html_openid1', $attributes);
return t('This page includes a <link rel=...> element containing the URL of an OpenID Provider Endpoint.');
}
@@ -113,7 +118,11 @@ function openid_test_html_openid1() {
* Menu callback; regular HTML page with OpenID 2.0 element.
*/
function openid_test_html_openid2() {
- drupal_add_html_head('');
+ $attributes = array(
+ 'rel' => 'openid2.provider',
+ 'href' => url('openid-test/endpoint', array('absolute' => TRUE)),
+ );
+ drupal_add_link('openid_test_html_openid2', $attributes);
return t('This page includes a <link rel=...> element containing the URL of an OpenID Provider Endpoint.');
}
diff --git modules/simpletest/tests/browser_test.module modules/simpletest/tests/browser_test.module
index ea2ea8f..2fdf7ab 100644
--- modules/simpletest/tests/browser_test.module
+++ modules/simpletest/tests/browser_test.module
@@ -61,7 +61,7 @@ function browser_test_print_post_form_submit($form, &$form_state) {
function browser_test_refresh_meta() {
if (!isset($_GET['refresh'])) {
$url = url('browser_test/refresh/meta', array('absolute' => TRUE, 'query' => 'refresh=true'));
- drupal_add_html_head('');
+ drupal_add_html_head('browser_test_meta_refresh', array('#markup' => ''));
return '';
}
echo 'Refresh successful';
diff --git modules/system/system.api.php modules/system/system.api.php
index df2ff32..995844d 100644
--- modules/system/system.api.php
+++ modules/system/system.api.php
@@ -2213,6 +2213,27 @@ function hook_drupal_goto_alter(array $args) {
}
/**
+ * Alter XHTML HEAD tags before they are rendered by drupal_get_html_head().
+ *
+ * Elements available to be altered are only those added using drupal_add_link()
+ * or drupal_add_html_head(). CSS and JS files are handled using
+ * drupal_add_css() and drupal_add_js(), so the head links for those files will
+ * not appear in the $head_elements array.
+ *
+ * @param $head_elements
+ * An array of renderable elements. Typically they will have a #markup
+ * key, but might have a #type if they are not simple markup.
+ */
+function hook_html_head_alter(&$head_elements) {
+ foreach($head_elements as $key => $element) {
+ if ($key == 'node_canonical_link') {
+ // I want a custom canonical url.
+ $head_elements[$key]['#markup'] = mymodule_canonical_link();
+ }
+ }
+}
+
+/**
* Alter MIME type mappings used to determine MIME type from a file extension.
*
* This hook is run when file_mimetype_mapping() is called. It is used to
diff --git modules/system/system.module modules/system/system.module
index 3eb3656..2034439 100644
--- modules/system/system.module
+++ modules/system/system.module
@@ -194,12 +194,6 @@ function system_theme() {
'system_powered_by' => array(
'arguments' => array('image_path' => NULL),
),
- 'meta_generator_html' => array(
- 'arguments' => array('version' => NULL),
- ),
- 'meta_generator_header' => array(
- 'arguments' => array('version' => NULL),
- ),
'system_compact_link' => array(),
'system_run_cron_image' => array(
'arguments' => array('image_path' => NULL),
@@ -1496,22 +1490,6 @@ function system_init() {
}
/**
- * Implement MODULE_preprocess_HOOK().
- */
-function system_preprocess_page(&$variables) {
- // Get the major version
- list($version, ) = explode('.', VERSION);
-
- // Emit the META tag in the HTML HEAD section
- theme('meta_generator_html', $version);
-
- // Emit the HTTP Header too
- theme('meta_generator_header', $version);
-
- $variables['head'] = drupal_get_html_head();
-}
-
-/**
* Implement hook_user_form().
*/
function system_user_form(&$edit, $account, $category) {
@@ -3022,25 +3000,6 @@ function theme_system_compact_link() {
return $output;
}
-
-/**
- * Send Drupal and the major version number in the META GENERATOR HTML.
- *
- * @ingroup themeable
- */
-function theme_meta_generator_html($version = VERSION) {
- drupal_add_html_head('');
-}
-
-/**
- * Send Drupal and the major version number in the HTTP headers.
- *
- * @ingroup themeable
- */
-function theme_meta_generator_header($version = VERSION) {
- drupal_set_header('X-Generator', 'Drupal ' . $version . ' (http://drupal.org)');
-}
-
/**
* Implement hook_image_toolkits().
*/