diff --git CHANGELOG.txt CHANGELOG.txt
index 87e7d1a..2c9da6b 100644
--- CHANGELOG.txt
+++ CHANGELOG.txt
@@ -140,6 +140,14 @@ 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 that advertises a short link as an
+ alternative to third-party services.
+ * drupal_set_html_head now stores structured data for link, meta, and
+ 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..499aeff 100644
--- includes/batch.inc
+++ includes/batch.inc
@@ -190,7 +190,14 @@ 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(array(
+ '#type' => 'head_element',
+ '#value' => 'meta',
+ '#attributes' => array(
+ 'http-equiv' => 'Refresh',
+ 'content' => '0; URL=' . $url,
+ ),
+ ));
return theme('progress_bar', $percentage, $message);
}
diff --git includes/common.inc includes/common.inc
index 063174e..7cf7d89 100644
--- includes/common.inc
+++ includes/common.inc
@@ -250,12 +250,24 @@ 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 $data
+ * Optional renderable array, of type 'head_element' or 'head_title'.
+ * @param $key
+ * Optional key for identifying the element in hook_html_head_alter().
+ *
+ * @return
+ * An array of all the store head elements.
*/
-function drupal_add_html_head($data = NULL) {
- $stored_head = &drupal_static(__FUNCTION__, '');
+function drupal_add_html_head($data = NULL, $key = NULL) {
+ $stored_head = &drupal_static(__FUNCTION__, array());
- if (!is_null($data)) {
- $stored_head .= $data . "\n";
+ if (isset($data)) {
+ if (isset($key)) {
+ $stored_head[$key] = $data;
+ } else {
+ $stored_head[] = $data;
+ }
}
return $stored_head;
}
@@ -264,8 +276,22 @@ 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();
+
+ $meta_element = array(
+ '#type' => 'head_element',
+ '#value' => 'meta',
+ '#attributes' => array(
+ 'http-equiv' => 'Content-Type',
+ 'content' => 'text/html; charset=utf-8',
+ ),
+ );
+ array_unshift($head_elements, $meta_element);
+
+ drupal_alter('html_head', $head_elements);
+
+ // Return structured data for later rendering.
+ return drupal_render($head_elements);
}
/**
@@ -2503,9 +2529,44 @@ function base_path() {
* Add a tag to the page's HEAD.
*
* This function can be called as long the HTML header hasn't been sent.
+ *
+ * @param $attributes
+ * Associative array of element attributes.
*/
-function drupal_add_link($attributes) {
- drupal_add_html_head('\n");
+function drupal_add_link($attributes, $header = FALSE) {
+ $head_element = array(
+ '#type' => 'head_element',
+ '#value' => 'link',
+ '#attributes' => $attributes,
+ );
+
+ if ($header) {
+ $head_element['#attached']['drupal_set_link_header'][] = array($attributes);
+ }
+
+ // Add LINK element under HTML HEAD element
+ drupal_add_html_head($head_element);
+}
+
+/**
+ * Add a HTTP Link: header.
+ *
+ * @param $attributes
+ * Associative array of element attributes.
+ */
+function drupal_set_link_header($attributes) {
+ // From drupal_attributes function
+ foreach ($attributes as $attribute => &$data) {
+ if ($attribute != 'href') {
+ if (is_array($data)) {
+ $data = implode(' ', $data);
+ }
+ $data = $attribute . '="' . check_plain($data) . '"';
+ }
+ }
+ $href = '<' . $attributes['href'] . '> ';
+ unset($attributes['href']);
+ drupal_set_header('Link', $href . implode('; ', $attributes));
}
/**
diff --git includes/theme.inc includes/theme.inc
index 9c646a0..8a8e94d 100644
--- includes/theme.inc
+++ includes/theme.inc
@@ -2040,7 +2040,11 @@ 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_link(array(
+ 'rel' => 'shortcut icon',
+ 'href' => check_url($favicon),
+ 'type' => $type,
+ ));
}
// Set up layout variable.
@@ -2214,7 +2218,11 @@ 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_link(array(
+ 'rel' => 'shortcut icon',
+ 'href' => check_url($favicon),
+ 'type' => $type,
+ ));
}
global $theme;
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..5dd5104 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 content.
+ drupal_add_link(array('rel' => 'canonical', 'href' => url('node/' . $node->nid)), TRUE);
+ drupal_add_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..9a424c8 100644
--- modules/openid/tests/openid_test.module
+++ modules/openid/tests/openid_test.module
@@ -97,7 +97,14 @@ function openid_test_yadis_x_xrds_location() {
* Menu callback; regular HTML page with element.
*/
function openid_test_yadis_http_equiv() {
- drupal_add_html_head('');
+ drupal_add_html_head(array(
+ '#type' => 'head_element',
+ '#value' => 'meta',
+ '#attributes' => array(
+ 'http-equiv' => 'X-XRDS-Location',
+ 'content' => url('openid-test/yadis/xrds', array('absolute' => TRUE)),
+ ),
+ ));
return t('This page includes a <meta equiv=...> element containing the URL of an XRDS document.');
}
@@ -105,7 +112,10 @@ 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('');
+ drupal_add_link(array(
+ 'rel' => 'openid.server',
+ 'href' => url('openid-test/endpoint', array('absolute' => TRUE)),
+ ));
return t('This page includes a <link rel=...> element containing the URL of an OpenID Provider Endpoint.');
}
@@ -113,7 +123,10 @@ function openid_test_html_openid1() {
* Menu callback; regular HTML page with OpenID 2.0 element.
*/
function openid_test_html_openid2() {
- drupal_add_html_head('');
+ drupal_add_link(array(
+ 'rel' => 'openid2.provider',
+ 'href' => url('openid-test/endpoint', array('absolute' => TRUE)),
+ ));
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..5845472 100644
--- modules/simpletest/tests/browser_test.module
+++ modules/simpletest/tests/browser_test.module
@@ -61,7 +61,14 @@ 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(array(
+ '#type' => 'head_element',
+ '#value' => 'meta',
+ '#attributes' => array(
+ 'http-equiv' => 'Refresh',
+ 'content' => '0; URL=' . $url,
+ )
+ ));
return '';
}
echo 'Refresh successful';
diff --git modules/system/system.module modules/system/system.module
index 3eb3656..26087dd 100644
--- modules/system/system.module
+++ modules/system/system.module
@@ -194,6 +194,9 @@ function system_theme() {
'system_powered_by' => array(
'arguments' => array('image_path' => NULL),
),
+ 'head_element' => array(
+ 'arguments' => array('element' => NULL),
+ ),
'meta_generator_html' => array(
'arguments' => array('version' => NULL),
),
@@ -486,6 +489,10 @@ function system_elements() {
'#theme' => array('hidden'),
);
+ $type['head_element'] = array(
+ '#theme' => 'head_element',
+ );
+
return $type;
}
@@ -3022,6 +3029,14 @@ function theme_system_compact_link() {
return $output;
}
+/**
+ * Send Drupal and the major version number in the HTTP headers.
+ *
+ * @ingroup themeable
+ */
+function theme_head_element(array $element) {
+ return '<' . $element['#value'] . drupal_attributes($element['#attributes']) ." />\n";
+}
/**
* Send Drupal and the major version number in the META GENERATOR HTML.
@@ -3029,7 +3044,14 @@ function theme_system_compact_link() {
* @ingroup themeable
*/
function theme_meta_generator_html($version = VERSION) {
- drupal_add_html_head('');
+ drupal_add_html_head(array(
+ '#type' => 'head_element',
+ '#value' => 'meta',
+ '#attributes' => array(
+ 'name' => 'Generator',
+ 'content' => 'Drupal ' . $version . ' (http://drupal.org)',
+ ),
+ ));
}
/**