Index: modules/openid/tests/openid_test.module =================================================================== RCS file: /cvs/drupal/drupal/modules/openid/tests/openid_test.module,v retrieving revision 1.3 diff -u -r1.3 openid_test.module --- modules/openid/tests/openid_test.module 10 Jun 2009 20:13:20 -0000 1.3 +++ modules/openid/tests/openid_test.module 6 Sep 2009 21:20:02 -0000 @@ -97,24 +97,54 @@ * Menu callback; regular HTML page with element. */ function openid_test_yadis_http_equiv() { - drupal_add_html_head(''); - return t('This page includes a <meta equiv=...> element containing the URL of an XRDS document.'); + $build[] = array( + '#type' => 'markup', + '#markup' => t('This page includes a <meta equiv=...> element containing the URL of an XRDS document.'), + '#attached' => array( + 'drupal_add_html_head' => array(array(array( + 'tag' => 'meta', + 'attributes' => array( + 'http-equiv' => 'X-XRDS-Location', + 'content' => url('openid-test/yadis/xrds', array('absolute' => TRUE)), + ), + ))), + ), + ); + return $build; } /** * Menu callback; regular HTML page with OpenID 1.0 element. */ function openid_test_html_openid1() { - drupal_add_html_head(''); - return t('This page includes a <link rel=...> element containing the URL of an OpenID Provider Endpoint.'); + $build[] = array( + '#type' => 'markup', + '#markup' => t('This page includes a <link rel=...> element containing the URL of an OpenID Provider Endpoint.'), + '#attached' => array( + 'drupal_add_link' => array(array(array( + 'rel' => 'openid.server', + 'href' => url('openid-test/endpoint', array('absolute' => TRUE)), + ), + ))), + ); + return $build; } /** * Menu callback; regular HTML page with OpenID 2.0 element. */ function openid_test_html_openid2() { - drupal_add_html_head(''); - return t('This page includes a <link rel=...> element containing the URL of an OpenID Provider Endpoint.'); + $build[] = array( + '#type' => 'markup', + '#markup' => t('This page includes a <link rel=...> element containing the URL of an OpenID Provider Endpoint.'), + '#attached' => array( + 'drupal_add_link' => array(array(array( + 'rel' => 'openid2.provider', + 'href' => url('openid-test/endpoint', array('absolute' => TRUE)), + ), + ))), + ); + return $build; } /** Index: CHANGELOG.txt =================================================================== RCS file: /cvs/drupal/drupal/CHANGELOG.txt,v retrieving revision 1.340 diff -u -r1.340 CHANGELOG.txt --- CHANGELOG.txt 5 Sep 2009 06:03:29 -0000 1.340 +++ CHANGELOG.txt 6 Sep 2009 21:18:48 -0000 @@ -140,6 +140,14 @@ - 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. Index: includes/theme.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/theme.inc,v retrieving revision 1.519 diff -u -r1.519 theme.inc --- includes/theme.inc 31 Aug 2009 19:50:17 -0000 1.519 +++ includes/theme.inc 6 Sep 2009 21:19:34 -0000 @@ -1941,6 +1941,20 @@ } /** + * Return a tag to be placed in the HEAD section of a page. + * + * @param $element + * An associative array containing the properties of the element: + * #tag: The tag name, for example 'title, 'meta', 'link'. + * #attributes: an array of atttributes. + * @return + * A string containing the tag generated. + */ +function theme_head_tag(array $element) { + return '<' . $element['#tag'] . drupal_attributes($element['#attributes']) ." />\n"; +} + +/** * @} End of "defgroup themeable". */ @@ -2040,7 +2054,11 @@ 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 +2232,11 @@ 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; Index: includes/batch.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/batch.inc,v retrieving revision 1.37 diff -u -r1.37 batch.inc --- includes/batch.inc 26 Aug 2009 15:00:17 -0000 1.37 +++ includes/batch.inc 6 Sep 2009 21:18:51 -0000 @@ -190,7 +190,13 @@ } $url = url($batch['url'], array('query' => array('id' => $batch['id'], 'op' => $new_op))); - drupal_add_html_head(''); + drupal_add_html_head(array( + 'tag' => 'meta', + 'attributes' => array( + 'http-equiv' => 'Refresh', + 'content' => '0; URL=' . $url, + ), + )); return theme('progress_bar', $percentage, $message); } Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.984 diff -u -r1.984 common.inc --- includes/common.inc 5 Sep 2009 15:05:01 -0000 1.984 +++ includes/common.inc 6 Sep 2009 21:19:20 -0000 @@ -247,15 +247,24 @@ } /** - * Add output to the head tag of the HTML page. + * Add data to the head tag of the HTML page. * * This function can be called as long the headers aren't sent. + * + * @param $data + * An associative array of data with the following keys. + * - 'key': Optional key for identifying the data in hook_html_head_alter(). + * - 'tag': The tag to be added to the HEAD section of the page. + * - 'attributes': An array of tag attributes. + * + * @return + * An array of all the stored head data. */ function drupal_add_html_head($data = NULL) { - $stored_head = &drupal_static(__FUNCTION__, ''); + $stored_head = &drupal_static(__FUNCTION__, array()); - if (!is_null($data)) { - $stored_head .= $data . "\n"; + if (isset($data)) { + $stored_head[] = $data; } return $stored_head; } @@ -264,8 +273,68 @@ * 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_data = drupal_add_html_head(); + $head_elements = array(); + foreach ($head_data as $data) { + $element = array( + '#type' => 'head_tag', + '#tag' => isset($data['tag']) ? $data['tag'] : 'meta', + '#attributes' => isset($data['attributes']) ? $data['attributes'] : array(), + ); + // Attach HTTP headers. + if (isset($data['headers'])) { + $element['#attached']['drupal_set_header'] = $data['headers']; + } + // If a key is provided, use it for identifying the element + // in hook_html_head_alter(). + if (isset($data['key'])) { + $head_elements[$data['key']] = $element; + } + $head_elements[][] = $element; + } + + // Add default head elements. + $head_elements += drupal_html_head_defaults(); + + // Allow modules to alter the head elements. + drupal_alter('html_head', $head_elements); + + // Return the rendered data for later inclusion in the page. + return drupal_render($head_elements); +} + +/** + * Return an array of head tag elements added by default to all pages generated + * by Drupal. + */ +function drupal_html_head_defaults() { + // Set the default content type. + $defaults['content_type'] = array( + '#type' => 'head_tag', + '#tag' => 'meta', + '#attributes' => array( + 'http-equiv' => 'Content-Type', + 'content' => 'text/html; charset=utf-8', + ), + ); + + // Send Drupal and the major version number in the META GENERATOR HTML + // and HTTP headers. + list($version, ) = explode('.', VERSION); + $defaults['generator'] = array( + '#type' => 'head_tag', + '#tag' => 'meta', + '#attributes' => array( + 'name' => 'Generator', + 'content' => 'Drupal ' . $version . ' (http://drupal.org)', + ), + '#attached' => array( + 'drupal_set_header' => array( + array('X-Generator', 'Drupal ' . $version . ' (http://drupal.org)'), + ), + ), + ); + return $defaults; } /** @@ -2503,9 +2572,44 @@ * 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 attributes. + * + * @see drupal_add_html_head() + * @see drupal_attributes() */ function drupal_add_link($attributes) { - drupal_add_html_head('\n"); + $data['tag'] = 'link'; + $data['attributes'] = $attributes; + if (!empty($attributes['header'])) { + $data['headers'] = array( + drupal_set_link_header($attributes) + ); + } + // Add tag under HTML HEAD. + drupal_add_html_head($data); +} + +/** + * Generate 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']); + return array('Link', $href . implode('; ', $attributes)); } /** @@ -4715,6 +4819,9 @@ 'vertical_tabs' => array( 'arguments' => array('element' => NULL), ), + 'head_tag' => array( + 'arguments' => array('element' => NULL), + ), ); } Index: modules/node/node.module =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.module,v retrieving revision 1.1119 diff -u -r1.1119 node.module --- modules/node/node.module 5 Sep 2009 06:53:01 -0000 1.1119 +++ modules/node/node.module 6 Sep 2009 21:20:00 -0000 @@ -1955,6 +1955,18 @@ */ 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), + 'header' => TRUE, + )); + drupal_add_link(array( + 'rel' => 'shortlink', + 'href' => url('node/' . $node->nid, array('alias' => TRUE)), + 'header' => TRUE, + )); return node_show($node); } Index: modules/system/system.module =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.module,v retrieving revision 1.785 diff -u -r1.785 system.module --- modules/system/system.module 5 Sep 2009 15:05:04 -0000 1.785 +++ modules/system/system.module 6 Sep 2009 21:20:19 -0000 @@ -194,12 +194,6 @@ '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), @@ -486,6 +480,10 @@ '#theme' => array('hidden'), ); + $type['head_tag'] = array( + '#theme' => 'head_tag', + ); + return $type; } @@ -3022,25 +3020,6 @@ 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(). */ Index: modules/comment/comment.module =================================================================== RCS file: /cvs/drupal/drupal/modules/comment/comment.module,v retrieving revision 1.767 diff -u -r1.767 comment.module --- modules/comment/comment.module 5 Sep 2009 15:05:02 -0000 1.767 +++ modules/comment/comment.module 6 Sep 2009 21:19:46 -0000 @@ -349,9 +349,6 @@ $_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); } Index: modules/simpletest/tests/browser_test.module =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/browser_test.module,v retrieving revision 1.1 diff -u -r1.1 browser_test.module --- modules/simpletest/tests/browser_test.module 17 Aug 2009 06:08:47 -0000 1.1 +++ modules/simpletest/tests/browser_test.module 6 Sep 2009 21:20:02 -0000 @@ -61,7 +61,13 @@ 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( + 'tag' => 'meta', + 'attributes' => array( + 'http-equiv' => 'Refresh', + 'content' => '0; URL=' . $url, + ), + )); return ''; } echo 'Refresh successful';