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(). */