diff --git a/core/includes/unicode.inc b/core/includes/unicode.inc index 50b7fe0681906666d9ca8f31edd11764ed12b72f..1450b435fa1e6cc4ba2596de071bda48d1ca5dd8 100644 --- a/core/includes/unicode.inc +++ b/core/includes/unicode.inc @@ -73,7 +73,7 @@ '\x{A836}-\x{A839}\x{A874}-\x{A877}\x{A8CE}-\x{A8CF}\x{A8F8}-\x{A8FA}' . '\x{A92E}-\x{A92F}\x{A95F}\x{A9C1}-\x{A9CD}\x{A9DE}-\x{A9DF}' . '\x{AA5C}-\x{AA5F}\x{AA77}-\x{AA79}\x{AADE}-\x{AADF}\x{ABEB}' . - '\x{D800}-\x{F8FF}\x{FB29}\x{FD3E}-\x{FD3F}\x{FDFC}-\x{FDFD}' . + '\x{E000}-\x{F8FF}\x{FB29}\x{FD3E}-\x{FD3F}\x{FDFC}-\x{FDFD}' . '\x{FE10}-\x{FE19}\x{FE30}-\x{FE6B}\x{FEFF}-\x{FF0F}\x{FF1A}-\x{FF20}' . '\x{FF3B}-\x{FF40}\x{FF5B}-\x{FF65}\x{FFE0}-\x{FFFD}'); diff --git a/core/modules/aggregator/aggregator.admin.inc b/core/modules/aggregator/aggregator.admin.inc index 93319bfe797acbb68d8d78aeac6c2f8aaf6dbb84..a728920a660cb163683f3557c297f83b5ce96595 100644 --- a/core/modules/aggregator/aggregator.admin.inc +++ b/core/modules/aggregator/aggregator.admin.inc @@ -37,7 +37,7 @@ function aggregator_view() { ($feed->checked && $feed->refresh ? t('%time left', array('%time' => format_interval($feed->checked + $feed->refresh - REQUEST_TIME))) : t('never')), l(t('edit'), "admin/config/services/aggregator/edit/feed/$feed->fid"), l(t('remove items'), "admin/config/services/aggregator/remove/$feed->fid"), - l(t('update items'), "admin/config/services/aggregator/update/$feed->fid"), + l(t('update items'), "admin/config/services/aggregator/update/$feed->fid", array('query' => array('token' => drupal_get_token("aggregator/update/$feed->fid")))), ); } $output .= theme('table', array('header' => $header, 'rows' => $rows, 'empty' => t('No feeds available. <a href="@link">Add feed</a>.', array('@link' => url('admin/config/services/aggregator/add/feed'))))); @@ -84,7 +84,7 @@ function aggregator_form_feed($form, &$form_state, stdClass $feed = NULL) { $form['url'] = array('#type' => 'textfield', '#title' => t('URL'), '#default_value' => isset($feed->url) ? $feed->url : '', - '#maxlength' => 255, + '#maxlength' => NULL, '#description' => t('The fully-qualified URL of the feed.'), '#required' => TRUE, ); @@ -421,6 +421,9 @@ function _aggregator_parse_opml($opml) { * @see aggregator_menu() */ function aggregator_admin_refresh_feed($feed) { + if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], 'aggregator/update/' . $feed->fid)) { + return MENU_ACCESS_DENIED; + } aggregator_refresh($feed); drupal_goto('admin/config/services/aggregator'); } diff --git a/core/modules/aggregator/aggregator.install b/core/modules/aggregator/aggregator.install index eecd14fb27f1d66ff08d97160a8f820303a18043..ac4fce8befb0c53d74779a35ec7c75f1c985b455 100644 --- a/core/modules/aggregator/aggregator.install +++ b/core/modules/aggregator/aggregator.install @@ -130,10 +130,8 @@ function aggregator_schema() { 'description' => 'Title of the feed.', ), 'url' => array( - 'type' => 'varchar', - 'length' => 255, + 'type' => 'text', 'not null' => TRUE, - 'default' => '', 'description' => 'URL to the feed.', ), 'refresh' => array( @@ -155,10 +153,8 @@ function aggregator_schema() { 'description' => 'Time when this feed was queued for refresh, 0 if not queued.', ), 'link' => array( - 'type' => 'varchar', - 'length' => 255, + 'type' => 'text', 'not null' => TRUE, - 'default' => '', 'description' => 'The parent website of the feed; comes from the <link> element in the feed.', ), 'description' => array( @@ -202,13 +198,13 @@ function aggregator_schema() { ) ), 'primary key' => array('fid'), - 'unique keys' => array( - 'url' => array('url'), - 'title' => array('title'), - ), 'indexes' => array( + 'url' => array(array('url', 255)), 'queued' => array('queued'), ), + 'unique keys' => array( + 'title' => array('title'), + ), ); $schema['aggregator_item'] = array( @@ -233,10 +229,8 @@ function aggregator_schema() { 'description' => 'Title of the feed item.', ), 'link' => array( - 'type' => 'varchar', - 'length' => 255, + 'type' => 'text', 'not null' => TRUE, - 'default' => '', 'description' => 'Link to the feed item.', ), 'author' => array( @@ -258,9 +252,8 @@ function aggregator_schema() { 'description' => 'Posted date of the feed item, as a Unix timestamp.', ), 'guid' => array( - 'type' => 'varchar', - 'length' => 255, - 'not null' => FALSE, + 'type' => 'text', + 'not null' => TRUE, 'description' => 'Unique identifier for the feed item.', ) ), diff --git a/core/modules/aggregator/aggregator.module b/core/modules/aggregator/aggregator.module index bb66cbefc37d6b4d8bd7891b330f587f81d88117..81b8c7f334e61bd07d64084185482960f05f02ca 100644 --- a/core/modules/aggregator/aggregator.module +++ b/core/modules/aggregator/aggregator.module @@ -537,6 +537,7 @@ function aggregator_save_feed($edit) { 'url' => $edit['url'], 'refresh' => $edit['refresh'], 'block' => $edit['block'], + 'link' => '', 'description' => '', 'image' => '', )) @@ -573,15 +574,13 @@ function aggregator_remove($feed) { // Call hook_aggregator_remove() on all modules. module_invoke_all('aggregator_remove', $feed); // Reset feed. - db_merge('aggregator_feed') - ->key(array('fid' => $feed->fid)) + db_update('aggregator_feed') + ->condition('fid', $feed->fid) ->fields(array( 'checked' => 0, 'hash' => '', 'etag' => '', 'modified' => 0, - 'description' => $feed->description, - 'image' => $feed->image, )) ->execute(); } diff --git a/core/modules/aggregator/aggregator.test b/core/modules/aggregator/aggregator.test index 27c46737eb27ad8d358004bf09552af36adfc0e0..02deb4503444fff69f3fa8bffe68dd1d7979d2aa 100644 --- a/core/modules/aggregator/aggregator.test +++ b/core/modules/aggregator/aggregator.test @@ -92,8 +92,13 @@ class AggregatorTestCase extends DrupalWebTestCase { $this->drupalGet($feed->url); $this->assertResponse(200, t('!url is reachable.', array('!url' => $feed->url))); - // Refresh the feed (simulated link click). + // Attempt to access the update link directly without an access token. $this->drupalGet('admin/config/services/aggregator/update/' . $feed->fid); + $this->assertResponse(403); + + // Refresh the feed (simulated link click). + $this->drupalGet('admin/config/services/aggregator'); + $this->clickLink('update items'); // Ensure we have the right number of items. $result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid)); @@ -349,6 +354,35 @@ class AddFeedTestCase extends AggregatorTestCase { // Delete feed. $this->deleteFeed($feed); } + + /** + * Tests feeds with very long URLs. + */ + function testAddLongFeed() { + // Create a feed with a URL of > 255 characters. + $long_url = "https://www.google.com/search?ix=heb&sourceid=chrome&ie=UTF-8&q=angie+byron#sclient=psy-ab&hl=en&safe=off&source=hp&q=angie+byron&pbx=1&oq=angie+byron&aq=f&aqi=&aql=&gs_sm=3&gs_upl=0l0l0l10534l0l0l0l0l0l0l0l0ll0l0&bav=on.2,or.r_gc.r_pw.r_cp.,cf.osb&fp=a70b6b1f0abe28d8&biw=1629&bih=889&ix=heb"; + $feed = $this->createFeed($long_url); + + // Create a second feed of > 255 characters, where the only difference is + // after the 255th character. + $long_url_2 = "https://www.google.com/search?ix=heb&sourceid=chrome&ie=UTF-8&q=angie+byron#sclient=psy-ab&hl=en&safe=off&source=hp&q=angie+byron&pbx=1&oq=angie+byron&aq=f&aqi=&aql=&gs_sm=3&gs_upl=0l0l0l10534l0l0l0l0l0l0l0l0ll0l0&bav=on.2,or.r_gc.r_pw.r_cp.,cf.osb&fp=a70b6b1f0abe28d8&biw=1629&bih=889"; + $feed_2 = $this->createFeed($long_url_2); + + // Check feed data. + $this->assertTrue($this->uniqueFeed($feed->title, $feed->url), 'The first long URL feed is unique.'); + $this->assertTrue($this->uniqueFeed($feed_2->title, $feed_2->url), 'The second long URL feed is unique.'); + + // Check feed source. + $this->drupalGet('aggregator/sources/' . $feed->fid); + $this->assertResponse(200, 'Long URL feed source exists.'); + $this->assertText($feed->title, 'Page title'); + $this->drupalGet('aggregator/sources/' . $feed->fid . '/categorize'); + $this->assertResponse(200, 'Long URL feed categorization page exists.'); + + // Delete feeds. + $this->deleteFeed($feed); + $this->deleteFeed($feed_2); + } } class CategorizeFeedTestCase extends AggregatorTestCase { @@ -502,8 +536,8 @@ class UpdateFeedItemTestCase extends AggregatorTestCase { $this->assertRaw(t('The feed %name has been added.', array('%name' => $edit['title'])), t('The feed !name has been added.', array('!name' => $edit['title']))); $feed = db_query("SELECT * FROM {aggregator_feed} WHERE url = :url", array(':url' => $edit['url']))->fetchObject(); - $this->drupalGet('admin/config/services/aggregator/update/' . $feed->fid); + aggregator_refresh($feed); $before = db_query('SELECT timestamp FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(); // Sleep for 3 second. @@ -517,10 +551,9 @@ class UpdateFeedItemTestCase extends AggregatorTestCase { 'modified' => 0, )) ->execute(); - $this->drupalGet('admin/config/services/aggregator/update/' . $feed->fid); + aggregator_refresh($feed); $after = db_query('SELECT timestamp FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(); - $this->assertTrue($before === $after, t('Publish timestamp of feed item was not updated (!before === !after)', array('!before' => $before, '!after' => $after))); } } diff --git a/core/modules/book/book-node-export-html.tpl.php b/core/modules/book/book-node-export-html.tpl.php index 0c2c67cc213dd6eae1cff03f0e0586c78c4745fb..50c878f5b5b9976d8aadfa0bcb17b40122059a6b 100644 --- a/core/modules/book/book-node-export-html.tpl.php +++ b/core/modules/book/book-node-export-html.tpl.php @@ -18,8 +18,8 @@ * @ingroup themeable */ ?> -<div id="node-<?php print $node->nid; ?>" class="section-<?php print $depth; ?>"> +<article id="node-<?php print $node->nid; ?>" class="section-<?php print $depth; ?>"> <h1 class="book-heading"><?php print $title; ?></h1> <?php print $content; ?> <?php print $children; ?> -</div> +</article> diff --git a/core/modules/filter/filter.module b/core/modules/filter/filter.module index 3dc72a64300171abb26724befa04526796b993a9..77f63ef6f7fd945bff8aeb9c2a6cdb3738653da4 100644 --- a/core/modules/filter/filter.module +++ b/core/modules/filter/filter.module @@ -1128,7 +1128,7 @@ function filter_dom_serialize_escape_cdata_element($dom_document, $dom_element, * @ingroup themeable */ function theme_filter_tips_more_info() { - return '<p>' . l(t('More information about text formats'), 'filter/tips') . '</p>'; + return '<p>' . l(t('More information about text formats'), 'filter/tips', array('attributes' => array('target' => '_blank'))) . '</p>'; } /** diff --git a/core/modules/locale/locale.install b/core/modules/locale/locale.install index a75ee2e602df822c90758c54a18f27b470054978..aad91abbdff8ddcd5f71f011820368e822eeeb52 100644 --- a/core/modules/locale/locale.install +++ b/core/modules/locale/locale.install @@ -264,13 +264,25 @@ function locale_update_8001() { // storing data for blocks will need to update for themselves. $block_tables = array('block', 'block_node_type', 'block_role'); foreach ($block_tables as $table) { - db_update($table) - ->fields(array( - 'delta' => 'language_interface', - )) - ->condition('delta', 'language') - ->condition('module', 'locale') - ->execute(); + // Perform the update only if the language switcher block data is available. + $block_data = db_query_range('SELECT 1 FROM {' . $table . '} WHERE delta = :delta AND module = :module', 0, 1, array(':delta' => 'language', ':module' => 'locale')) + ->fetchField(); + if ($block_data) { + // If block information is rebuilt before performing the update, we might + // already have data for the new delta. In this case we need to remove it + // to avoid integrity constraint violation errors. + db_delete($table) + ->condition('delta', 'language_interface') + ->condition('module', 'locale') + ->execute(); + db_update($table) + ->fields(array( + 'delta' => 'language_interface', + )) + ->condition('delta', 'language') + ->condition('module', 'locale') + ->execute(); + } } } diff --git a/core/modules/openid/openid.inc b/core/modules/openid/openid.inc index e1e2861472a9618877c2695cc29660fa25fe3303..518dc8a18c30ec7667fbefc6933bc1458dcb0b8c 100644 --- a/core/modules/openid/openid.inc +++ b/core/modules/openid/openid.inc @@ -610,18 +610,31 @@ function _openid_get_params($str) { * @param $fallback_prefix * An optional prefix that will be used in case no prefix is found for the * target extension namespace. + * @param $only_signed + * Return only keys that are included in the message signature in openid.sig. + * Unsigned fields may have been modified or added by other parties than the + * OpenID Provider. + * * @return * An associative array containing all the parameters in the response message * that belong to the extension. The keys are stripped from their namespace * prefix. + * * @see http://openid.net/specs/openid-authentication-2_0.html#extensions */ -function openid_extract_namespace($response, $extension_namespace, $fallback_prefix = NULL) { +function openid_extract_namespace($response, $extension_namespace, $fallback_prefix = NULL, $only_signed = FALSE) { + $signed_keys = explode(',', $response['openid.signed']); + // Find the namespace prefix. $prefix = $fallback_prefix; foreach ($response as $key => $value) { if ($value == $extension_namespace && preg_match('/^openid\.ns\.([^.]+)$/', $key, $matches)) { $prefix = $matches[1]; + if ($only_signed && !in_array('ns.' . $matches[1], $signed_keys)) { + // The namespace was defined but was not signed as required. In this + // case we do not fall back to $fallback_prefix. + $prefix = NULL; + } break; } } @@ -634,7 +647,9 @@ function openid_extract_namespace($response, $extension_namespace, $fallback_pre foreach ($response as $key => $value) { if (preg_match('/^openid\.' . $prefix . '\.(.+)$/', $key, $matches)) { $local_key = $matches[1]; - $output[$local_key] = $value; + if (!$only_signed || in_array($prefix . '.' . $local_key, $signed_keys)) { + $output[$local_key] = $value; + } } } diff --git a/core/modules/openid/openid.module b/core/modules/openid/openid.module index edd73d39cc1dc9798e9e6f04e78aea07454ef7ea..d29d40578832ded4513aff161f2d3c351fc1bae0 100644 --- a/core/modules/openid/openid.module +++ b/core/modules/openid/openid.module @@ -185,10 +185,15 @@ function openid_form_user_register_form_alter(&$form, &$form_state) { $response = $_SESSION['openid']['response']; - // Extract Simple Registration keys from the response. - $sreg_values = openid_extract_namespace($response, OPENID_NS_SREG, 'sreg'); - // Extract Attribute Exchanges keys from the response. - $ax_values = openid_extract_namespace($response, OPENID_NS_AX, 'ax'); + // Extract Simple Registration keys from the response. We only include + // signed keys as required by OpenID Simple Registration Extension 1.0, + // section 4. + $sreg_values = openid_extract_namespace($response, OPENID_NS_SREG, 'sreg', TRUE); + // Extract Attribute Exchanges keys from the response. We only include + // signed keys. This is not required by the specification, but it is + // recommended by Google, see + // http://googlecode.blogspot.com/2011/05/security-advisory-to-websites-using.html + $ax_values = openid_extract_namespace($response, OPENID_NS_AX, 'ax', TRUE); if (!empty($sreg_values['nickname'])) { // Use the nickname returned by Simple Registration if available. diff --git a/core/modules/openid/openid.test b/core/modules/openid/openid.test index 5c6ca69e41e5d624c5d49c6d97472b3472fc9c7f..7f87b4b54514b3d6ca677c5dae59d61fae4f1a38 100644 --- a/core/modules/openid/openid.test +++ b/core/modules/openid/openid.test @@ -364,17 +364,49 @@ class OpenIDFunctionalTestCase extends OpenIDWebTestCase { // Use a User-supplied Identity that is the URL of an XRDS document. $identity = url('openid-test/yadis/xrds', array('absolute' => TRUE)); - // Do not sign all mandatory fields (e.g. assoc_handle). + // Respond with an invalid signature. + variable_set('openid_test_response', array('openid.sig' => 'this-is-an-invalid-signature')); + $this->submitLoginForm($identity); + $this->assertRaw('OpenID login failed.'); + + // Do not sign the mandatory field openid.assoc_handle. variable_set('openid_test_response', array('openid.signed' => 'op_endpoint,claimed_id,identity,return_to,response_nonce')); $this->submitLoginForm($identity); $this->assertRaw('OpenID login failed.'); - // Sign all mandatory fields and some custom fields. - variable_set('openid_test_response', array('openid.foo' => 'bar', 'openid.signed' => 'op_endpoint,claimed_id,identity,return_to,response_nonce,assoc_handle,foo')); + // Sign all mandatory fields and a custom field. + $keys_to_sign = array('op_endpoint', 'claimed_id', 'identity', 'return_to', 'response_nonce', 'assoc_handle', 'foo'); + $association = new stdClass(); + $association->mac_key = variable_get('mac_key'); + $response = array( + 'openid.op_endpoint' => url('openid-test/endpoint', array('absolute' => TRUE)), + 'openid.claimed_id' => $identity, + 'openid.identity' => $identity, + 'openid.return_to' => url('openid/authenticate', array('absolute' => TRUE)), + 'openid.response_nonce' => _openid_nonce(), + 'openid.assoc_handle' => 'openid-test', + 'openid.foo' => 123, + 'openid.signed' => implode(',', $keys_to_sign), + ); + $response['openid.sig'] = _openid_signature($association, $response, $keys_to_sign); + variable_set('openid_test_response', $response); $this->submitLoginForm($identity); $this->assertNoRaw('OpenID login failed.'); - } + $this->assertFieldByName('name', '', t('No username was supplied by provider.')); + $this->assertFieldByName('mail', '', t('No e-mail address was supplied by provider.')); + // Check that unsigned SREG fields are ignored. + $response = array( + 'openid.signed' => 'op_endpoint,claimed_id,identity,return_to,response_nonce,assoc_handle,sreg.nickname', + 'openid.sreg.nickname' => 'john', + 'openid.sreg.email' => 'john@example.com', + ); + variable_set('openid_test_response', $response); + $this->submitLoginForm($identity); + $this->assertNoRaw('OpenID login failed.'); + $this->assertFieldByName('name', 'john', t('Username was supplied by provider.')); + $this->assertFieldByName('mail', '', t('E-mail address supplied by provider was ignored.')); + } } /** @@ -707,4 +739,41 @@ class OpenIDUnitTest extends DrupalWebTestCase { $this->assertEqual(openid_normalize('http://example.com/path#fragment'), 'http://example.com/path', t('openid_normalize() correctly normalized a URL with a fragment.')); } + + /** + * Test openid_extract_namespace(). + */ + function testOpenidExtractNamespace() { + $response = array( + 'openid.sreg.nickname' => 'john', + 'openid.ns.ext1' => OPENID_NS_SREG, + 'openid.ext1.nickname' => 'george', + 'openid.ext1.email' => 'george@example.com', + 'openid.ns.ext2' => 'http://example.com/ns/ext2', + 'openid.ext2.foo' => '123', + 'openid.ext2.bar' => '456', + 'openid.signed' => 'sreg.nickname,ns.ext1,ext1.email,ext2.foo', + ); + + $values = openid_extract_namespace($response, 'http://example.com/ns/dummy', NULL, FALSE); + $this->assertEqual($values, array(), t('Nothing found for unused namespace.')); + + $values = openid_extract_namespace($response, 'http://example.com/ns/dummy', 'sreg', FALSE); + $this->assertEqual($values, array('nickname' => 'john'), t('Value found for fallback prefix.')); + + $values = openid_extract_namespace($response, OPENID_NS_SREG, 'sreg', FALSE); + $this->assertEqual($values, array('nickname' => 'george', 'email' => 'george@example.com'), t('Namespace takes precedence over fallback prefix.')); + + // ext1.email is signed, but ext1.nickname is not. + $values = openid_extract_namespace($response, OPENID_NS_SREG, 'sreg', TRUE); + $this->assertEqual($values, array('email' => 'george@example.com'), t('Unsigned namespaced fields ignored.')); + + $values = openid_extract_namespace($response, 'http://example.com/ns/ext2', 'sreg', FALSE); + $this->assertEqual($values, array('foo' => '123', 'bar' => '456'), t('Unsigned fields found.')); + + // ext2.foo and ext2.bar are ignored, because ns.ext2 is not signed. The + // fallback prefix is not used, because the namespace is specified. + $values = openid_extract_namespace($response, 'http://example.com/ns/ext2', 'sreg', TRUE); + $this->assertEqual($values, array(), t('Unsigned fields ignored.')); + } } diff --git a/core/modules/openid/tests/openid_test.module b/core/modules/openid/tests/openid_test.module index 11d32b7c80d3f67984e4f1bdf47334bba94c858f..5bd2f4ddf5bcd60748364b989457a23c607bd1b6 100644 --- a/core/modules/openid/tests/openid_test.module +++ b/core/modules/openid/tests/openid_test.module @@ -325,9 +325,7 @@ function _openid_test_endpoint_authenticate() { // Generate unique identifier for this authentication. $nonce = _openid_nonce(); - // Generate response containing the user's identity. The openid.sreg.xxx - // entries contain profile data stored by the OpenID Provider (see OpenID - // Simple Registration Extension 1.0). + // Generate response containing the user's identity. $response = variable_get('openid_test_response', array()) + array( 'openid.ns' => OPENID_NS_2_0, 'openid.mode' => 'id_res', @@ -337,14 +335,27 @@ function _openid_test_endpoint_authenticate() { 'openid.return_to' => $_REQUEST['openid_return_to'], 'openid.response_nonce' => $nonce, 'openid.assoc_handle' => 'openid-test', - 'openid.signed' => 'op_endpoint,claimed_id,identity,return_to,response_nonce,assoc_handle', ); + if (isset($response['openid.signed'])) { + $keys_to_sign = explode(',', $response['openid.signed']); + } + else { + // Unless openid.signed is explicitly defined, all keys are signed. + $keys_to_sign = array(); + foreach ($response as $key => $value) { + // Strip off the "openid." prefix. + $keys_to_sign[] = substr($key, 7); + } + $response['openid.signed'] = implode(',', $keys_to_sign); + } + // Sign the message using the MAC key that was exchanged during association. $association = new stdClass(); $association->mac_key = variable_get('mac_key'); - $keys_to_sign = explode(',', $response['openid.signed']); - $response['openid.sig'] = _openid_signature($association, $response, $keys_to_sign); + if (!isset($response['openid.sig'])) { + $response['openid.sig'] = _openid_signature($association, $response, $keys_to_sign); + } // Put the signed message into the query string of a URL supplied by the // Relying Party, and redirect the user. diff --git a/core/modules/system/system.base-rtl.css b/core/modules/system/system.base-rtl.css index c148995193911a319466a7f9504d41fcd53d5513..d01792e09c57cb312a732f876481afb5601a1d2c 100644 --- a/core/modules/system/system.base-rtl.css +++ b/core/modules/system/system.base-rtl.css @@ -33,8 +33,12 @@ */ .draggable a.tabledrag-handle { float: right; - margin: -0.4em -0.5em -0.4em 0; - padding: 0.42em 0.5em 0.42em 1.5em; + margin-right: -1em; + margin-left: 0; +} +a.tabledrag-handle .handle { + margin: -0.4em 0.5em; + padding: 0.42em 0.5em; } div.indentation { float: right; diff --git a/core/modules/system/system.base.css b/core/modules/system/system.base.css index 2fe1cba3c1323d89869ccf5786b333cf830c37f3..1723b33501d84de45ba98d211fd3061789c19a27 100644 --- a/core/modules/system/system.base.css +++ b/core/modules/system/system.base.css @@ -93,21 +93,22 @@ body.drag { cursor: move; float: left; /* LTR */ height: 1.7em; - margin: -0.4em 0 -0.4em -0.5em; /* LTR */ - padding: 0.42em 1.5em 0.42em 0.5em; /* LTR */ + margin-left: -1em; /* LTR */ + overflow: hidden; text-decoration: none; } a.tabledrag-handle:hover { text-decoration: none; } a.tabledrag-handle .handle { - background: url(../../misc/draggable.png) no-repeat 0 0; + background: url(../../misc/draggable.png) no-repeat 6px 9px; height: 13px; - margin-top: 4px; + margin: -0.4em 0.5em; /* LTR */ + padding: 0.42em 0.5em; /* LTR */ width: 13px; } a.tabledrag-handle-hover .handle { - background-position: 0 -20px; + background-position: 6px -11px; } div.indentation { float: left; /* LTR */