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)', + ), + )); } /**