' . t('Use this page to create a new custom block.') . '
'; } @@ -189,6 +190,7 @@ function _block_themes_access($theme) { * @param $theme * The theme whose blocks are being configured. If not set, the default theme * is assumed. + * * @return * The theme that should be used for the block configuration page, or NULL * to indicate that the default theme should be used. @@ -283,8 +285,7 @@ function block_page_build(&$page) { // Append region description if we are rendering the regions demo page. $item = menu_get_item(); if ($item['path'] == 'admin/structure/block/demo/' . $theme) { - $visible_regions = array_keys(system_region_list($theme, REGIONS_VISIBLE)); - foreach ($visible_regions as $region) { + foreach (system_region_list($theme, REGIONS_VISIBLE, FALSE) as $region) { $description = ' -
-
- ',
+ 'allowed_html' => '
-
-
-
',
'filter_html_help' => 1,
'filter_html_nofollow' => 0,
);
@@ -1184,6 +1209,10 @@ class FilterUnitTestCase extends DrupalUnitTestCase {
$f = _filter_html('
', $filter);
$this->assertNoNormalized($f, 'onerror', 'HTML filter should remove empty on* attributes on default.');
+
+ // Custom tags are supported and should be allowed through.
+ $f = _filter_html(' ', $filter);
+ $this->assertNormalized($f, 'test-element', 'HTML filter should allow custom elements.');
}
/**
@@ -1269,6 +1298,7 @@ class FilterUnitTestCase extends DrupalUnitTestCase {
// Create a e-mail that is too long.
$long_email = str_repeat('a', 254) . '@example.com';
$too_long_email = str_repeat('b', 255) . '@example.com';
+ $email_with_plus_sign = 'one+two@example.com';
// Filter selection/pattern matching.
@@ -1282,12 +1312,13 @@ http://example.com or www.example.com
),
// MAILTO URLs.
'
-person@example.com or mailto:person2@example.com or ' . $long_email . ' but not ' . $too_long_email . '
+person@example.com or mailto:person2@example.com or ' . $email_with_plus_sign . ' or ' . $long_email . ' but not ' . $too_long_email . '
' => array(
'person@example.com' => TRUE,
'mailto:person2@example.com' => TRUE,
'' . $long_email . '' => TRUE,
'' . $too_long_email . '' => FALSE,
+ '' . $email_with_plus_sign . '' => TRUE,
),
// URI parts and special characters.
'
@@ -1979,3 +2010,26 @@ class FilterSettingsTestCase extends DrupalWebTestCase {
}
}
}
+
+/**
+ * Tests DOMDocument serialization.
+ */
+class FilterDOMSerializeTestCase extends DrupalWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Serialization',
+ 'description' => 'Test serialization of DOMDocument objects.',
+ 'group' => 'Filter',
+ );
+ }
+
+ /**
+ * Tests empty DOMDocument object.
+ */
+ function testFilterEmptyDOMSerialization() {
+ $document = new DOMDocument();
+ $result = filter_dom_serialize($document);
+ $this->assertEqual('', $result);
+ }
+}
diff --git a/modules/forum/forum.info b/modules/forum/forum.info
index b7d518c..39250e4 100644
--- a/modules/forum/forum.info
+++ b/modules/forum/forum.info
@@ -9,8 +9,8 @@ files[] = forum.test
configure = admin/structure/forum
stylesheets[all][] = forum.css
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/help/help.api.php b/modules/help/help.api.php
deleted file mode 100644
index f7d9c08..0000000
--- a/modules/help/help.api.php
+++ /dev/null
@@ -1,63 +0,0 @@
-' . t('Blocks are boxes of content rendered into an area, or region, of a web page. The default theme Bartik, for example, implements the regions "Sidebar first", "Sidebar second", "Featured", "Content", "Header", "Footer", etc., and a block may appear in any one of these areas. The blocks administration page provides a drag-and-drop interface for assigning a block to a region, and for controlling the order of blocks within regions.', array('@blocks' => url('admin/structure/block'))) . '';
-
- // Help for another path in the block module
- case 'admin/structure/block':
- return '' . t('This page provides a drag-and-drop interface for assigning a block to a region, and for controlling the order of blocks within regions. Since not all themes implement the same regions, or display regions in the same way, blocks are positioned on a per-theme basis. Remember that your changes will not be saved until you click the Save blocks button at the bottom of the page.') . '
';
- }
-}
-
-/**
- * @} End of "addtogroup hooks".
- */
diff --git a/modules/help/help.info b/modules/help/help.info
index 8e4b239..3b48122 100644
--- a/modules/help/help.info
+++ b/modules/help/help.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
files[] = help.test
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/image/image.info b/modules/image/image.info
index 1e17ac1..0b14b66 100644
--- a/modules/image/image.info
+++ b/modules/image/image.info
@@ -7,8 +7,8 @@ dependencies[] = file
files[] = image.test
configure = admin/config/media/image-styles
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/image/image.module b/modules/image/image.module
index fac8de9..dab8836 100644
--- a/modules/image/image.module
+++ b/modules/image/image.module
@@ -64,7 +64,7 @@ function image_help($path, $arg) {
$effect = image_effect_definition_load($arg[7]);
return isset($effect['help']) ? ('' . $effect['help'] . '
') : NULL;
case 'admin/config/media/image-styles/edit/%/effects/%':
- $effect = ($arg[5] == 'add') ? image_effect_definition_load($arg[6]) : image_effect_load($arg[6], $arg[4]);
+ $effect = ($arg[5] == 'add') ? image_effect_definition_load($arg[6]) : image_effect_load($arg[7], $arg[5]);
return isset($effect['help']) ? ('' . $effect['help'] . '
') : NULL;
}
}
@@ -801,6 +801,8 @@ function image_style_options($include_empty = TRUE, $output = CHECK_PLAIN) {
*
* @param $style
* The image style
+ * @param $scheme
+ * The file scheme, for example 'public' for public files.
*/
function image_style_deliver($style, $scheme) {
$args = func_get_args();
@@ -833,8 +835,8 @@ function image_style_deliver($style, $scheme) {
file_download($scheme, file_uri_target($derivative_uri));
}
else {
- $headers = module_invoke_all('file_download', $image_uri);
- if (in_array(-1, $headers) || empty($headers)) {
+ $headers = file_download_headers($image_uri);
+ if (empty($headers)) {
return MENU_ACCESS_DENIED;
}
if (count($headers)) {
diff --git a/modules/image/image.test b/modules/image/image.test
index 3591979..0c26ffa 100644
--- a/modules/image/image.test
+++ b/modules/image/image.test
@@ -32,7 +32,7 @@ class ImageFieldTestCase extends DrupalWebTestCase {
function setUp() {
parent::setUp('image');
- $this->admin_user = $this->drupalCreateUser(array('access content', 'access administration pages', 'administer site configuration', 'administer content types', 'administer nodes', 'create article content', 'edit any article content', 'delete any article content', 'administer image styles'));
+ $this->admin_user = $this->drupalCreateUser(array('access content', 'access administration pages', 'administer site configuration', 'administer content types', 'administer nodes', 'create article content', 'edit any article content', 'delete any article content', 'administer image styles', 'administer fields'));
$this->drupalLogin($this->admin_user);
}
@@ -77,6 +77,24 @@ class ImageFieldTestCase extends DrupalWebTestCase {
return field_create_instance($instance);
}
+ /**
+ * Create a random style.
+ *
+ * @return array
+ * A list containing the details of the generated image style.
+ */
+ function createRandomStyle() {
+ $style_name = strtolower($this->randomName(10));
+ $style_label = $this->randomString();
+ image_style_save(array('name' => $style_name, 'label' => $style_label));
+ $style_path = 'admin/config/media/image-styles/edit/' . $style_name;
+ return array(
+ 'name' => $style_name,
+ 'label' => $style_label,
+ 'path' => $style_path,
+ );
+ }
+
/**
* Upload an image to a node.
*
@@ -183,6 +201,22 @@ class ImageStylesPathAndUrlTestCase extends DrupalWebTestCase {
$this->assertResponse(404, 'Accessing an image style URL with a source image that does not exist provides a 404 error response.');
}
+ /**
+ * Test that we do not pass an array to drupal_add_http_header.
+ */
+ function testImageContentTypeHeaders() {
+ $files = $this->drupalGetTestFiles('image');
+ $file = array_shift($files);
+ // Copy the test file to private folder.
+ $private_file = file_copy($file, 'private://', FILE_EXISTS_RENAME);
+ // Tell image_module_test module to return the headers we want to test.
+ variable_set('image_module_test_invalid_headers', $private_file->uri);
+ // Invoke image_style_deliver so it will try to set headers.
+ $generated_url = image_style_url($this->style_name, $private_file->uri);
+ $this->drupalGet($generated_url);
+ variable_del('image_module_test_invalid_headers');
+ }
+
/**
* Test image_style_url().
*/
@@ -251,7 +285,7 @@ class ImageStylesPathAndUrlTestCase extends DrupalWebTestCase {
$this->assertEqual($this->drupalGetHeader('Content-Length'), $generated_image_info['file_size'], 'Expected Content-Length was reported.');
if ($scheme == 'private') {
$this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', 'Expires header was sent.');
- $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'no-cache, must-revalidate, post-check=0, pre-check=0', 'Cache-Control header was set to prevent caching.');
+ $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'no-cache, must-revalidate', 'Cache-Control header was set to prevent caching.');
$this->assertEqual($this->drupalGetHeader('X-Image-Owned-By'), 'image_module_test', 'Expected custom header has been added.');
// Make sure that a second request to the already existing derivate works
@@ -469,6 +503,58 @@ class ImageEffectsUnitTest extends ImageToolkitTestCase {
}
}
+/**
+ * Tests the administrative user interface.
+ */
+class ImageAdminUiTestCase extends ImageFieldTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Administrative user interface',
+ 'description' => 'Tests the forms used in the administrative user interface.',
+ 'group' => 'Image',
+ );
+ }
+
+ function setUp() {
+ parent::setUp(array('image'));
+ }
+
+ /**
+ * Test if the help text is available on the add effect form.
+ */
+ function testAddEffectHelpText() {
+ // Create a random image style.
+ $style = $this->createRandomStyle();
+
+ // Open the add effect form and check for the help text.
+ $this->drupalGet($style['path'] . '/add/image_crop');
+ $this->assertText(t('Cropping will remove portions of an image to make it the specified dimensions.'), 'The image style effect help text was displayed on the add effect page.');
+ }
+
+ /**
+ * Test if the help text is available on the edit effect form.
+ */
+ function testEditEffectHelpText() {
+ // Create a random image style.
+ $random_style = $this->createRandomStyle();
+
+ // Add the crop effect to the image style.
+ $edit = array();
+ $edit['data[width]'] = 20;
+ $edit['data[height]'] = 20;
+ $this->drupalPost($random_style['path'] . '/add/image_crop', $edit, t('Add effect'));
+
+ // Open the edit effect form and check for the help text.
+ drupal_static_reset('image_styles');
+ $style = image_style_load($random_style['name']);
+
+ foreach ($style['effects'] as $ieid => $effect) {
+ $this->drupalGet($random_style['path'] . '/effects/' . $ieid);
+ $this->assertText(t('Cropping will remove portions of an image to make it the specified dimensions.'), 'The image style effect help text was displayed on the edit effect page.');
+ }
+ }
+}
+
/**
* Tests creation, deletion, and editing of image styles and effects.
*/
diff --git a/modules/image/tests/image_module_test.info b/modules/image/tests/image_module_test.info
index d382a73..d2131dd 100644
--- a/modules/image/tests/image_module_test.info
+++ b/modules/image/tests/image_module_test.info
@@ -6,8 +6,8 @@ core = 7.x
files[] = image_module_test.module
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/image/tests/image_module_test.module b/modules/image/tests/image_module_test.module
index 8a322fb..fc66d9b 100644
--- a/modules/image/tests/image_module_test.module
+++ b/modules/image/tests/image_module_test.module
@@ -9,6 +9,9 @@ function image_module_test_file_download($uri) {
if (variable_get('image_module_test_file_download', FALSE) == $uri) {
return array('X-Image-Owned-By' => 'image_module_test');
}
+ if (variable_get('image_module_test_invalid_headers', FALSE) == $uri) {
+ return array('Content-Type' => 'image/png');
+ }
}
/**
diff --git a/modules/locale/locale.admin.inc b/modules/locale/locale.admin.inc
index e813962..acf6eb2 100644
--- a/modules/locale/locale.admin.inc
+++ b/modules/locale/locale.admin.inc
@@ -1194,7 +1194,7 @@ function locale_translate_edit_form_submit($form, &$form_state) {
$translation = db_query("SELECT translation FROM {locales_target} WHERE lid = :lid AND language = :language", array(':lid' => $lid, ':language' => $key))->fetchField();
if (!empty($value)) {
// Only update or insert if we have a value to use.
- if (!empty($translation)) {
+ if (is_string($translation)) {
db_update('locales_target')
->fields(array(
'translation' => $value,
diff --git a/modules/locale/locale.info b/modules/locale/locale.info
index 7431c7b..6e05794 100644
--- a/modules/locale/locale.info
+++ b/modules/locale/locale.info
@@ -6,8 +6,8 @@ core = 7.x
files[] = locale.test
configure = admin/config/regional/language
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/locale/locale.test b/modules/locale/locale.test
index 9086587..6fcf06f 100644
--- a/modules/locale/locale.test
+++ b/modules/locale/locale.test
@@ -393,6 +393,16 @@ class LocaleTranslationFunctionalTest extends DrupalWebTestCase {
// The indicator should not be here.
$this->assertNoRaw($language_indicator, 'String is translated.');
+ // Verify that a translation set which has an empty target string can be
+ // updated without any database error.
+ db_update('locales_target')
+ ->fields(array('translation' => ''))
+ ->condition('language', $langcode, '=')
+ ->condition('lid', $lid, '=')
+ ->execute();
+ $this->drupalPost('admin/config/regional/translate/edit/' . $lid, $edit, t('Save translations'));
+ $this->assertText(t('The string has been saved.'), 'The string has been saved.');
+
// Try to edit a non-existent string and ensure we're redirected correctly.
// Assuming we don't have 999,999 strings already.
$random_lid = 999999;
@@ -2237,6 +2247,37 @@ class LocaleContentFunctionalTest extends DrupalWebTestCase {
$this->drupalLogout();
}
+
+ /**
+ * Verifies that nodes may be created with different languages.
+ */
+ function testNodeCreationWithLanguage() {
+ // Create an admin user and log them in.
+ $perms = array(
+ // Standard node permissions.
+ 'create page content',
+ 'administer content types',
+ 'administer nodes',
+ 'bypass node access',
+ // Locale.
+ 'administer languages',
+ );
+ $web_user = $this->drupalCreateUser($perms);
+ $this->drupalLogin($web_user);
+
+ // Create some test nodes using different langcodes.
+ foreach (array(LANGUAGE_NONE, 'en', 'fr') as $langcode) {
+ $node_args = array(
+ 'type' => 'page',
+ 'promote' => 1,
+ 'language' => $langcode,
+ );
+ $node = $this->drupalCreateNode($node_args);
+ $node_reloaded = node_load($node->nid, NULL, TRUE);
+ $this->assertEqual($node_reloaded->language, $langcode, format_string('The language code of the node was successfully set to @langcode.', array('@langcode' => $langcode)));
+ }
+ }
+
}
/**
@@ -2629,6 +2670,68 @@ class LocaleUrlRewritingTest extends DrupalWebTestCase {
$this->drupalGet("$prefix/$path");
$this->assertResponse(404, $message2);
}
+
+ /**
+ * Check URL rewriting when using a domain name and a non-standard port.
+ */
+ function testDomainNameNegotiationPort() {
+ $language_domain = 'example.fr';
+ $edit = array(
+ 'locale_language_negotiation_url_part' => 1,
+ );
+ $this->drupalPost('admin/config/regional/language/configure/url', $edit, t('Save configuration'));
+ $edit = array(
+ 'prefix' => '',
+ 'domain' => $language_domain
+ );
+ $this->drupalPost('admin/config/regional/language/edit/fr', $edit, t('Save language'));
+
+ // Enable domain configuration.
+ variable_set('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN);
+
+ // Reset static caching.
+ drupal_static_reset('language_list');
+ drupal_static_reset('language_url_outbound_alter');
+ drupal_static_reset('language_url_rewrite_url');
+
+ // In case index.php is part of the URLs, we need to adapt the asserted
+ // URLs as well.
+ $index_php = strpos(url('', array('absolute' => TRUE)), 'index.php') !== FALSE;
+
+ // Remember current HTTP_HOST.
+ $http_host = $_SERVER['HTTP_HOST'];
+
+ // Fake a different port.
+ $_SERVER['HTTP_HOST'] .= ':88';
+
+ // Create an absolute French link.
+ $languages = language_list();
+ $language = $languages['fr'];
+ $url = url('', array(
+ 'absolute' => TRUE,
+ 'language' => $language
+ ));
+
+ $expected = 'http://example.fr:88/';
+ $expected .= $index_php ? 'index.php/' : '';
+
+ $this->assertEqual($url, $expected, 'The right port is used.');
+
+ // If we set the port explicitly in url(), it should not be overriden.
+ $url = url('', array(
+ 'absolute' => TRUE,
+ 'language' => $language,
+ 'base_url' => $GLOBALS['base_url'] . ':90',
+ ));
+
+ $expected = 'http://example.fr:90/';
+ $expected .= $index_php ? 'index.php/' : '';
+
+ $this->assertEqual($url, $expected, 'A given port is not overriden.');
+
+ // Restore HTTP_HOST.
+ $_SERVER['HTTP_HOST'] = $http_host;
+ }
}
/**
@@ -3141,3 +3244,46 @@ class LocaleCSSAlterTest extends DrupalWebTestCase {
$this->assertRaw('@import url("' . $base_url . '/modules/system/system.messages.css' . $query_string . '");' . "\n" . '@import url("' . $base_url . '/modules/system/system.messages-rtl.css' . $query_string . '");' . "\n", 'CSS: system.messages-rtl.css is added directly after system.messages.css.');
}
}
+
+/**
+ * Tests locale translation safe string handling.
+ */
+class LocaleStringIsSafeTest extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Test if a string is safe',
+ 'description' => 'Tests locale translation safe string handling.',
+ 'group' => 'Locale',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('locale');
+ }
+
+ /**
+ * Tests for locale_string_is_safe().
+ */
+ public function testLocaleStringIsSafe() {
+ // Check a translatable string without HTML.
+ $string = 'Hello world!';
+ $result = locale_string_is_safe($string);
+ $this->assertTrue($result);
+
+ // Check a translatable string which includes trustable HTML.
+ $string = 'Hello world!';
+ $result = locale_string_is_safe($string);
+ $this->assertTrue($result);
+
+ // Check an untranslatable string which includes untrustable HTML (according
+ // to the locale_string_is_safe() function definition).
+ $string = 'Hello
!';
+ $result = locale_string_is_safe($string);
+ $this->assertFalse($result);
+
+ // Check a translatable string which includes a token in an href attribute.
+ $string = 'Hi user';
+ $result = locale_string_is_safe($string);
+ $this->assertTrue($result);
+ }
+}
diff --git a/modules/locale/tests/locale_test.info b/modules/locale/tests/locale_test.info
index 5391d65..5ea5dbc 100644
--- a/modules/locale/tests/locale_test.info
+++ b/modules/locale/tests/locale_test.info
@@ -5,8 +5,8 @@ package = Testing
version = VERSION
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/menu/menu.admin.inc b/modules/menu/menu.admin.inc
index 66bd6f3..a24703c 100644
--- a/modules/menu/menu.admin.inc
+++ b/modules/menu/menu.admin.inc
@@ -281,6 +281,7 @@ function menu_edit_item($form, &$form_state, $type, $item, $menu) {
$form['link_title'] = array(
'#type' => 'textfield',
'#title' => t('Menu link title'),
+ '#maxlength' => 255,
'#default_value' => $item['link_title'],
'#description' => t('The text to be used for this link in the menu.'),
'#required' => TRUE,
@@ -305,7 +306,7 @@ function menu_edit_item($form, &$form_state, $type, $item, $menu) {
'#title' => t('Path'),
'#maxlength' => 255,
'#default_value' => $path,
- '#description' => t('The path for this menu link. This can be an internal Drupal path such as %add-node or an external URL such as %drupal. Enter %front to link to the front page.', array('%front' => '', '%add-node' => 'node/add', '%drupal' => 'http://drupal.org')),
+ '#description' => t('The path for this menu link. This can be an internal path such as %add-node or an external URL such as %example. Enter %front to link to the front page.', array('%front' => '', '%add-node' => 'node/add', '%example' => 'http://example.com')),
'#required' => TRUE,
);
$form['actions']['delete'] = array(
diff --git a/modules/menu/menu.info b/modules/menu/menu.info
index 4c8f89f..2c4681c 100644
--- a/modules/menu/menu.info
+++ b/modules/menu/menu.info
@@ -6,8 +6,8 @@ core = 7.x
files[] = menu.test
configure = admin/structure/menu
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/menu/menu.module b/modules/menu/menu.module
index dc8f015..27b1675 100644
--- a/modules/menu/menu.module
+++ b/modules/menu/menu.module
@@ -674,6 +674,7 @@ function menu_form_node_form_alter(&$form, $form_state) {
$form['menu']['link']['link_title'] = array(
'#type' => 'textfield',
'#title' => t('Menu link title'),
+ '#maxlength' => 255,
'#default_value' => $link['link_title'],
);
diff --git a/modules/menu/menu.test b/modules/menu/menu.test
index a9bdb5f..bb792ee 100644
--- a/modules/menu/menu.test
+++ b/modules/menu/menu.test
@@ -72,6 +72,17 @@ class MenuTestCase extends DrupalWebTestCase {
$saved_item = menu_link_load($item['mlid']);
$this->assertEqual($description, $saved_item['options']['attributes']['title'], 'Saving an existing link updates the description (title attribute)');
$this->resetMenuLink($item, $old_title);
+
+ // Test that the page title is correct when a local task appears in a
+ // top-level menu item. See https://www.drupal.org/node/1973262.
+ $item = $this->addMenuLink(0, 'user/register', 'user-menu');
+ $this->drupalGet('user/password');
+ $this->assertNoTitle('Home | Drupal');
+ $this->drupalLogout();
+ $this->drupalGet('user/register');
+ $this->assertTitle($item['link_title'] . ' | Drupal');
+ $this->drupalGet('user');
+ $this->assertNoTitle('Home | Drupal');
}
/**
@@ -637,7 +648,12 @@ class MenuNodeTestCase extends DrupalWebTestCase {
);
$this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type'));
- // Create a node.
+ // Verify that the menu link title on the node add form has the correct
+ // maxlength.
+ $this->drupalGet('node/add/page');
+ $this->assertPattern('//', 'Menu link title field has correct maxlength in node add form.');
+
+ // Create a node with menu link disabled.
$node_title = $this->randomName();
$language = LANGUAGE_NONE;
$edit = array(
@@ -673,6 +689,10 @@ class MenuNodeTestCase extends DrupalWebTestCase {
$this->drupalGet('node/' . $node->nid . '/edit');
$this->assertOptionSelected('edit-menu-weight', 17, 'Menu weight correct in edit form');
+ // Verify that the menu link title on the node edit form has the correct
+ // maxlength.
+ $this->assertPattern('//', 'Menu link title field has correct maxlength in node edit form.');
+
// Edit the node and remove the menu link.
$edit = array(
'menu[enabled]' => FALSE,
diff --git a/modules/node/content_types.inc b/modules/node/content_types.inc
index 55af667..c451dc7 100644
--- a/modules/node/content_types.inc
+++ b/modules/node/content_types.inc
@@ -11,7 +11,7 @@
function node_overview_types() {
$types = node_type_get_types();
$names = node_type_get_names();
- $field_ui = module_exists('field_ui');
+ $field_ui = module_exists('field_ui') && user_access('administer fields');
$header = array(t('Name'), array('data' => t('Operations'), 'colspan' => $field_ui ? '4' : '2'));
$rows = array();
diff --git a/modules/node/node.admin.inc b/modules/node/node.admin.inc
index 35f4c1d..eead4ea 100644
--- a/modules/node/node.admin.inc
+++ b/modules/node/node.admin.inc
@@ -329,6 +329,8 @@ function _node_mass_update_helper($nid, $updates) {
}
/**
+ * Implements callback_batch_operation().
+ *
* Executes a batch operation for node_mass_update().
*
* @param array $nodes
@@ -367,7 +369,9 @@ function _node_mass_update_batch_process($nodes, $updates, &$context) {
}
/**
- * Menu callback: Reports the status of batch operation for node_mass_update().
+ * Implements callback_batch_finished().
+ *
+ * Reports the status of batch operation for node_mass_update().
*
* @param bool $success
* A boolean indicating whether the batch mass update operation successfully
@@ -504,14 +508,17 @@ function node_admin_nodes() {
$options = array();
foreach ($nodes as $node) {
$langcode = entity_language('node', $node);
- $l_options = $langcode != LANGUAGE_NONE && isset($languages[$langcode]) ? array('language' => $languages[$langcode]) : array();
+ $uri = entity_uri('node', $node);
+ if ($langcode != LANGUAGE_NONE && isset($languages[$langcode])) {
+ $uri['options']['language'] = $languages[$langcode];
+ }
$options[$node->nid] = array(
'title' => array(
'data' => array(
'#type' => 'link',
'#title' => $node->title,
- '#href' => 'node/' . $node->nid,
- '#options' => $l_options,
+ '#href' => $uri['path'],
+ '#options' => $uri['options'],
'#suffix' => ' ' . theme('mark', array('type' => node_mark($node->nid, $node->changed))),
),
),
diff --git a/modules/node/node.api.php b/modules/node/node.api.php
index 9a4d095..c8176a7 100644
--- a/modules/node/node.api.php
+++ b/modules/node/node.api.php
@@ -950,7 +950,7 @@ function hook_node_info() {
* 'recent', or 'comments'. The values should be arrays themselves, with the
* following keys available:
* - title: (required) The human readable name of the ranking mechanism.
- * - join: (optional) The part of a query string to join to any additional
+ * - join: (optional) An array with information to join any additional
* necessary table. This is not necessary if the table required is already
* joined to by the base query, such as for the {node} table. Other tables
* should use the full table name as an alias to avoid naming collisions.
@@ -974,7 +974,12 @@ function hook_ranking() {
'title' => t('Average vote'),
// Note that we use i.sid, the search index's search item id, rather than
// n.nid.
- 'join' => 'LEFT JOIN {vote_node_data} vote_node_data ON vote_node_data.nid = i.sid',
+ 'join' => array(
+ 'type' => 'LEFT',
+ 'table' => 'vote_node_data',
+ 'alias' => 'vote_node_data',
+ 'on' => 'vote_node_data.nid = i.sid',
+ ),
// The highest possible score should be 1, and the lowest possible score,
// always 0, should be 0.
'score' => 'vote_node_data.average / CAST(%f AS DECIMAL)',
@@ -1079,19 +1084,9 @@ function hook_delete($node) {
* @ingroup node_api_hooks
*/
function hook_prepare($node) {
- $file = file_save_upload($field_name, _image_filename($file->filename, NULL, TRUE));
- if ($file) {
- if (!image_get_info($file->uri)) {
- form_set_error($field_name, t('Uploaded file is not a valid image'));
- return;
- }
+ if (!isset($node->mymodule_value)) {
+ $node->mymodule_value = 'foo';
}
- else {
- return;
- }
- $node->images['_original'] = $file->uri;
- _image_build_derivatives($node, TRUE);
- $node->new_file = TRUE;
}
/**
diff --git a/modules/node/node.info b/modules/node/node.info
index c91b28a..63a0c4b 100644
--- a/modules/node/node.info
+++ b/modules/node/node.info
@@ -9,8 +9,8 @@ required = TRUE
configure = admin/structure/types
stylesheets[all][] = node.css
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/node/node.install b/modules/node/node.install
index 76c2aec..3c4e7c2 100644
--- a/modules/node/node.install
+++ b/modules/node/node.install
@@ -410,6 +410,7 @@ function node_schema() {
'nid' => array(
'description' => 'The {node}.nid that was read.',
'type' => 'int',
+ 'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
@@ -933,6 +934,33 @@ function node_update_7014() {
db_add_index('node', 'language', array('language'));
}
+/**
+ * Enable node types that may have been erroneously disabled in Drupal 7.36.
+ */
+function node_update_7015() {
+ db_update('node_type')
+ ->fields(array('disabled' => 0))
+ ->condition('base', 'node_content')
+ ->execute();
+}
+
+/**
+ * Change {history}.nid to an unsigned int in order to match {node}.nid.
+ */
+function node_update_7016() {
+ db_drop_primary_key('history');
+ db_drop_index('history', 'nid');
+ db_change_field('history', 'nid', 'nid', array(
+ 'description' => 'The {node}.nid that was read.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ));
+ db_add_primary_key('history', array('uid', 'nid'));
+ db_add_index('history', 'nid', array('nid'));
+}
+
/**
* @} End of "addtogroup updates-7.x-extra".
*/
diff --git a/modules/node/node.module b/modules/node/node.module
index fd848e2..1d88834 100644
--- a/modules/node/node.module
+++ b/modules/node/node.module
@@ -740,9 +740,11 @@ function _node_types_build($rebuild = FALSE) {
$type_db = $type_object->type;
// Original disabled value.
$disabled = $type_object->disabled;
- // Check for node types either from disabled modules or otherwise not defined
- // and mark as disabled.
- if (empty($type_object->custom) && empty($_node_types->types[$type_db])) {
+ // Check for node types from disabled modules and mark their types for removal.
+ // Types defined by the node module in the database (rather than by a separate
+ // module using hook_node_info) have a base value of 'node_content'. The isset()
+ // check prevents errors on old (pre-Drupal 7) databases.
+ if (isset($type_object->base) && $type_object->base != 'node_content' && empty($_node_types->types[$type_db])) {
$type_object->disabled = TRUE;
}
if (isset($_node_types->types[$type_db])) {
@@ -2951,7 +2953,10 @@ function node_search_validate($form, &$form_state) {
* system. When adding a node listing to your module, be sure to use a dynamic
* query created by db_select() and add a tag of "node_access". This will allow
* modules dealing with node access to ensure only nodes to which the user has
- * access are retrieved, through the use of hook_query_TAG_alter().
+ * access are retrieved, through the use of hook_query_TAG_alter(). Tagging a
+ * query with "node_access" does not check the published/unpublished status of
+ * nodes, so the base query is responsible for ensuring that unpublished nodes
+ * are not displayed to inappropriate users.
*
* Note: Even a single module returning NODE_ACCESS_DENY from hook_node_access()
* will block access to the node. Therefore, implementers should take care to
@@ -3667,6 +3672,8 @@ function node_access_rebuild($batch_mode = FALSE) {
}
/**
+ * Implements callback_batch_operation().
+ *
* Performs batch operation for node_access_rebuild().
*
* This is a multistep operation: we go through all nodes by packs of 20. The
@@ -3681,7 +3688,7 @@ function _node_access_rebuild_batch_operation(&$context) {
// Initiate multistep processing.
$context['sandbox']['progress'] = 0;
$context['sandbox']['current_node'] = 0;
- $context['sandbox']['max'] = db_query('SELECT COUNT(DISTINCT nid) FROM {node}')->fetchField();
+ $context['sandbox']['max'] = db_query('SELECT COUNT(nid) FROM {node}')->fetchField();
}
// Process the next 20 nodes.
@@ -3705,6 +3712,8 @@ function _node_access_rebuild_batch_operation(&$context) {
}
/**
+ * Implements callback_batch_finished().
+ *
* Performs post-processing for node_access_rebuild().
*
* @param bool $success
diff --git a/modules/node/node.pages.inc b/modules/node/node.pages.inc
index cc3908e..72b0ea7 100644
--- a/modules/node/node.pages.inc
+++ b/modules/node/node.pages.inc
@@ -396,7 +396,6 @@ function node_preview($node) {
$cloned_node->changed = REQUEST_TIME;
$nodes = array($cloned_node->nid => $cloned_node);
- field_attach_prepare_view('node', $nodes, 'full');
// Display a preview of the node.
if (!form_get_errors()) {
diff --git a/modules/node/node.test b/modules/node/node.test
index 5c9118e..e8eb459 100644
--- a/modules/node/node.test
+++ b/modules/node/node.test
@@ -457,10 +457,70 @@ class PagePreviewTestCase extends DrupalWebTestCase {
}
function setUp() {
- parent::setUp();
+ parent::setUp(array('taxonomy', 'node'));
$web_user = $this->drupalCreateUser(array('edit own page content', 'create page content'));
$this->drupalLogin($web_user);
+
+ // Add a vocabulary so we can test different view modes.
+ $vocabulary = (object) array(
+ 'name' => $this->randomName(),
+ 'description' => $this->randomName(),
+ 'machine_name' => drupal_strtolower($this->randomName()),
+ 'help' => '',
+ 'nodes' => array('page' => 'page'),
+ );
+ taxonomy_vocabulary_save($vocabulary);
+
+ $this->vocabulary = $vocabulary;
+
+ // Add a term to the vocabulary.
+ $term = (object) array(
+ 'name' => $this->randomName(),
+ 'description' => $this->randomName(),
+ // Use the first available text format.
+ 'format' => db_query_range('SELECT format FROM {filter_format}', 0, 1)->fetchField(),
+ 'vid' => $this->vocabulary->vid,
+ 'vocabulary_machine_name' => $vocabulary->machine_name,
+ );
+ taxonomy_term_save($term);
+
+ $this->term = $term;
+
+ // Set up a field and instance.
+ $this->field_name = drupal_strtolower($this->randomName());
+ $this->field = array(
+ 'field_name' => $this->field_name,
+ 'type' => 'taxonomy_term_reference',
+ 'settings' => array(
+ 'allowed_values' => array(
+ array(
+ 'vocabulary' => $this->vocabulary->machine_name,
+ 'parent' => '0',
+ ),
+ ),
+ )
+ );
+
+ field_create_field($this->field);
+ $this->instance = array(
+ 'field_name' => $this->field_name,
+ 'entity_type' => 'node',
+ 'bundle' => 'page',
+ 'widget' => array(
+ 'type' => 'options_select',
+ ),
+ // Hide on full display but render on teaser.
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'hidden',
+ ),
+ 'teaser' => array(
+ 'type' => 'taxonomy_term_reference_link',
+ ),
+ ),
+ );
+ field_create_instance($this->instance);
}
/**
@@ -470,21 +530,26 @@ class PagePreviewTestCase extends DrupalWebTestCase {
$langcode = LANGUAGE_NONE;
$title_key = "title";
$body_key = "body[$langcode][0][value]";
+ $term_key = "{$this->field_name}[$langcode]";
// Fill in node creation form and preview node.
$edit = array();
$edit[$title_key] = $this->randomName(8);
$edit[$body_key] = $this->randomName(16);
+ $edit[$term_key] = $this->term->tid;
$this->drupalPost('node/add/page', $edit, t('Preview'));
- // Check that the preview is displaying the title and body.
+ // Check that the preview is displaying the title, body, and term.
$this->assertTitle(t('Preview | Drupal'), 'Basic page title is preview.');
$this->assertText($edit[$title_key], 'Title displayed.');
$this->assertText($edit[$body_key], 'Body displayed.');
+ $this->assertText($this->term->name, 'Term displayed.');
- // Check that the title and body fields are displayed with the correct values.
+ // Check that the title, body, and term fields are displayed with the
+ // correct values.
$this->assertFieldByName($title_key, $edit[$title_key], 'Title field displayed.');
$this->assertFieldByName($body_key, $edit[$body_key], 'Body field displayed.');
+ $this->assertFieldByName($term_key, $edit[$term_key], 'Term field displayed.');
}
/**
@@ -494,6 +559,7 @@ class PagePreviewTestCase extends DrupalWebTestCase {
$langcode = LANGUAGE_NONE;
$title_key = "title";
$body_key = "body[$langcode][0][value]";
+ $term_key = "{$this->field_name}[$langcode]";
// Force revision on "Basic page" content.
variable_set('node_options_page', array('status', 'revision'));
@@ -501,17 +567,21 @@ class PagePreviewTestCase extends DrupalWebTestCase {
$edit = array();
$edit[$title_key] = $this->randomName(8);
$edit[$body_key] = $this->randomName(16);
+ $edit[$term_key] = $this->term->tid;
$edit['log'] = $this->randomName(32);
$this->drupalPost('node/add/page', $edit, t('Preview'));
- // Check that the preview is displaying the title and body.
+ // Check that the preview is displaying the title, body, and term.
$this->assertTitle(t('Preview | Drupal'), 'Basic page title is preview.');
$this->assertText($edit[$title_key], 'Title displayed.');
$this->assertText($edit[$body_key], 'Body displayed.');
+ $this->assertText($this->term->name, 'Term displayed.');
- // Check that the title and body fields are displayed with the correct values.
+ // Check that the title, body, and term fields are displayed with the
+ // correct values.
$this->assertFieldByName($title_key, $edit[$title_key], 'Title field displayed.');
$this->assertFieldByName($body_key, $edit[$body_key], 'Body field displayed.');
+ $this->assertFieldByName($term_key, $edit[$term_key], 'Term field displayed.');
// Check that the log field has the correct value.
$this->assertFieldByName('log', $edit['log'], 'Log field displayed.');
@@ -1448,7 +1518,7 @@ class NodeTypeTestCase extends DrupalWebTestCase {
* Tests editing a node type using the UI.
*/
function testNodeTypeEditing() {
- $web_user = $this->drupalCreateUser(array('bypass node access', 'administer content types'));
+ $web_user = $this->drupalCreateUser(array('bypass node access', 'administer content types', 'administer fields'));
$this->drupalLogin($web_user);
$instance = field_info_instance('node', 'body', 'page');
@@ -2698,8 +2768,8 @@ class NodeAccessFieldTestCase extends NodeWebTestCase {
node_access_rebuild();
// Create some users.
- $this->admin_user = $this->drupalCreateUser(array('access content', 'bypass node access'));
- $this->content_admin_user = $this->drupalCreateUser(array('access content', 'administer content types'));
+ $this->admin_user = $this->drupalCreateUser(array('access content', 'bypass node access', 'administer fields'));
+ $this->content_admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer fields'));
// Add a custom field to the page content type.
$this->field_name = drupal_strtolower($this->randomName() . '_field_name');
@@ -2916,3 +2986,36 @@ class NodePageCacheTest extends NodeWebTestCase {
$this->assertResponse(404);
}
}
+
+/**
+ * Tests that multi-byte UTF-8 characters are stored and retrieved correctly.
+ */
+class NodeMultiByteUtf8Test extends NodeWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Multi-byte UTF-8',
+ 'description' => 'Test that multi-byte UTF-8 characters are stored and retrieved correctly.',
+ 'group' => 'Node',
+ );
+ }
+
+ /**
+ * Tests that multi-byte UTF-8 characters are stored and retrieved correctly.
+ */
+ public function testMultiByteUtf8() {
+ $connection = Database::getConnection();
+ // On MySQL, this test will only run if 'charset' is set to 'utf8mb4' in
+ // settings.php.
+ if (!($connection->utf8mb4IsSupported() && $connection->utf8mb4IsActive())) {
+ return;
+ }
+ $title = '🐙';
+ $this->assertTrue(drupal_strlen($title, 'utf-8') < strlen($title), 'Title has multi-byte characters.');
+ $node = $this->drupalCreateNode(array('title' => $title));
+ $this->drupalGet('node/' . $node->nid);
+ $result = $this->xpath('//h1[@id="page-title"]');
+ $this->assertEqual(trim((string) $result[0]), $title, 'The passed title was returned.');
+ }
+
+}
diff --git a/modules/node/tests/node_access_test.info b/modules/node/tests/node_access_test.info
index 69362c9..7354c04 100644
--- a/modules/node/tests/node_access_test.info
+++ b/modules/node/tests/node_access_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/node/tests/node_test.info b/modules/node/tests/node_test.info
index aa658ea..afc1a9e 100644
--- a/modules/node/tests/node_test.info
+++ b/modules/node/tests/node_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/node/tests/node_test_exception.info b/modules/node/tests/node_test_exception.info
index 4289481..7d0eb60 100644
--- a/modules/node/tests/node_test_exception.info
+++ b/modules/node/tests/node_test_exception.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/openid/openid.info b/modules/openid/openid.info
index 3acd5dc..cc25d81 100644
--- a/modules/openid/openid.info
+++ b/modules/openid/openid.info
@@ -5,8 +5,8 @@ package = Core
core = 7.x
files[] = openid.test
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/openid/openid.module b/modules/openid/openid.module
index a28f452..a52dbc3 100644
--- a/modules/openid/openid.module
+++ b/modules/openid/openid.module
@@ -365,14 +365,20 @@ function openid_complete($response = array()) {
// to the OpenID Provider, we need to do discovery on the returned
// identififer to make sure that the provider is authorized to
// respond on behalf of this.
- if ($response_claimed_id != $claimed_id) {
+ if ($response_claimed_id != $claimed_id || $response_claimed_id != $response['openid.identity']) {
$discovery = openid_discovery($response['openid.claimed_id']);
+ $uris = array();
if ($discovery && !empty($discovery['services'])) {
- $uris = array();
foreach ($discovery['services'] as $discovered_service) {
- if (in_array('http://specs.openid.net/auth/2.0/server', $discovered_service['types']) || in_array('http://specs.openid.net/auth/2.0/signon', $discovered_service['types'])) {
- $uris[] = $discovered_service['uri'];
+ if (!in_array('http://specs.openid.net/auth/2.0/server', $discovered_service['types']) && !in_array('http://specs.openid.net/auth/2.0/signon', $discovered_service['types'])) {
+ continue;
}
+ // The OP-Local Identifier (if different than the Claimed
+ // Identifier) must be present in the XRDS document.
+ if ($response_claimed_id != $response['openid.identity'] && (!isset($discovered_service['identity']) || $discovered_service['identity'] != $response['openid.identity'])) {
+ continue;
+ }
+ $uris[] = $discovered_service['uri'];
}
}
if (!in_array($service['uri'], $uris)) {
diff --git a/modules/openid/openid.test b/modules/openid/openid.test
index 41af3f8..d0708e0 100644
--- a/modules/openid/openid.test
+++ b/modules/openid/openid.test
@@ -94,7 +94,7 @@ class OpenIDFunctionalTestCase extends OpenIDWebTestCase {
$identity = url('openid-test/yadis/xrds/dummy-user', array('absolute' => TRUE, 'fragment' => $this->randomName()));
// Tell openid_test.module to respond with this identifier. If the fragment
// part is present in the identifier, it should be retained.
- variable_set('openid_test_response', array('openid.claimed_id' => $identity));
+ variable_set('openid_test_response', array('openid.claimed_id' => $identity, 'openid.identity' => openid_normalize($identity)));
$this->addIdentity(url('openid-test/yadis/xrds/server', array('absolute' => TRUE)), 2, 'http://specs.openid.net/auth/2.0/identifier_select', $identity);
variable_set('openid_test_response', array());
@@ -680,11 +680,11 @@ class OpenIDTestCase extends DrupalWebTestCase {
* Test _openid_dh_XXX_to_XXX() functions.
*/
function testConversion() {
- $this->assertEqual(_openid_dh_long_to_base64('12345678901234567890123456789012345678901234567890'), 'CHJ/Y2mq+DyhUCZ0evjH8ZbOPwrS', '_openid_dh_long_to_base64() returned expected result.');
- $this->assertEqual(_openid_dh_base64_to_long('BsH/g8Nrpn2dtBSdu/sr1y8hxwyx'), '09876543210987654321098765432109876543210987654321', '_openid_dh_base64_to_long() returned expected result.');
+ $this->assertIdentical(_openid_dh_long_to_base64('12345678901234567890123456789012345678901234567890'), 'CHJ/Y2mq+DyhUCZ0evjH8ZbOPwrS', '_openid_dh_long_to_base64() returned expected result.');
+ $this->assertIdentical(_openid_dh_base64_to_long('BsH/g8Nrpn2dtBSdu/sr1y8hxwyx'), '9876543210987654321098765432109876543210987654321', '_openid_dh_base64_to_long() returned expected result.');
- $this->assertEqual(_openid_dh_long_to_binary('12345678901234567890123456789012345678901234567890'), "\x08r\x7fci\xaa\xf8<\xa1P&tz\xf8\xc7\xf1\x96\xce?\x0a\xd2", '_openid_dh_long_to_binary() returned expected result.');
- $this->assertEqual(_openid_dh_binary_to_long("\x06\xc1\xff\x83\xc3k\xa6}\x9d\xb4\x14\x9d\xbb\xfb+\xd7/!\xc7\x0c\xb1"), '09876543210987654321098765432109876543210987654321', '_openid_dh_binary_to_long() returned expected result.');
+ $this->assertIdentical(_openid_dh_long_to_binary('12345678901234567890123456789012345678901234567890'), "\x08r\x7fci\xaa\xf8<\xa1P&tz\xf8\xc7\xf1\x96\xce?\x0a\xd2", '_openid_dh_long_to_binary() returned expected result.');
+ $this->assertIdentical(_openid_dh_binary_to_long("\x06\xc1\xff\x83\xc3k\xa6}\x9d\xb4\x14\x9d\xbb\xfb+\xd7/!\xc7\x0c\xb1"), '9876543210987654321098765432109876543210987654321', '_openid_dh_binary_to_long() returned expected result.');
}
/**
diff --git a/modules/openid/tests/openid_test.info b/modules/openid/tests/openid_test.info
index c828275..922fdcc 100644
--- a/modules/openid/tests/openid_test.info
+++ b/modules/openid/tests/openid_test.info
@@ -6,8 +6,8 @@ core = 7.x
dependencies[] = openid
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/openid/tests/openid_test.module b/modules/openid/tests/openid_test.module
index bcf9f42..3d6e292 100644
--- a/modules/openid/tests/openid_test.module
+++ b/modules/openid/tests/openid_test.module
@@ -150,6 +150,7 @@ function openid_test_yadis_xrds() {
http://specs.openid.net/auth/2.0/server
' . url('openid-test/endpoint', array('absolute' => TRUE)) . '
+ ' . url('openid-test/yadis/xrds/server', array('absolute' => TRUE)) . '
';
}
elseif (arg(3) == 'delegate') {
diff --git a/modules/overlay/overlay-parent.js b/modules/overlay/overlay-parent.js
index 7452a51..efb2637 100644
--- a/modules/overlay/overlay-parent.js
+++ b/modules/overlay/overlay-parent.js
@@ -350,7 +350,7 @@ Drupal.overlay.setFocusBefore = function ($element, document) {
* TRUE if the URL represents an administrative link, FALSE otherwise.
*/
Drupal.overlay.isAdminLink = function (url) {
- if (Drupal.overlay.isExternalLink(url)) {
+ if (!Drupal.urlIsLocal(url)) {
return false;
}
@@ -378,6 +378,8 @@ Drupal.overlay.isAdminLink = function (url) {
/**
* Determine whether a link is external to the site.
*
+ * Deprecated. Use Drupal.urlIsLocal() instead.
+ *
* @param url
* The URL to be tested.
*
@@ -385,8 +387,28 @@ Drupal.overlay.isAdminLink = function (url) {
* TRUE if the URL is external to the site, FALSE otherwise.
*/
Drupal.overlay.isExternalLink = function (url) {
- var re = RegExp('^((f|ht)tps?:)?//(?!' + window.location.host + ')');
- return re.test(url);
+ return !Drupal.urlIsLocal(url);
+};
+
+/**
+ * Constructs an internal URL (relative to this site) from the provided path.
+ *
+ * For example, if the provided path is 'admin' and the site is installed at
+ * http://example.com/drupal, this function will return '/drupal/admin'.
+ *
+ * @param path
+ * The internal path, without any leading slash.
+ *
+ * @return
+ * The internal URL derived from the provided path, or null if a valid
+ * internal path cannot be constructed (for example, if an attempt to create
+ * an external link is detected).
+ */
+Drupal.overlay.getInternalUrl = function (path) {
+ var url = Drupal.settings.basePath + path;
+ if (Drupal.urlIsLocal(url)) {
+ return url;
+ }
};
/**
@@ -577,7 +599,7 @@ Drupal.overlay.eventhandlerOverrideLink = function (event) {
// If the link contains the overlay-restore class and the overlay-context
// state is set, also update the parent window's location.
var parentLocation = ($target.hasClass('overlay-restore') && typeof $.bbq.getState('overlay-context') == 'string')
- ? Drupal.settings.basePath + $.bbq.getState('overlay-context')
+ ? this.getInternalUrl($.bbq.getState('overlay-context'))
: null;
href = this.fragmentizeLink($target.get(0), parentLocation);
// Only override default behavior when left-clicking and user is not
@@ -657,11 +679,15 @@ Drupal.overlay.eventhandlerOperateByURLFragment = function (event) {
}
// Get the overlay URL from the current URL fragment.
+ var internalUrl = null;
var state = $.bbq.getState('overlay');
if (state) {
+ internalUrl = this.getInternalUrl(state);
+ }
+ if (internalUrl) {
// Append render variable, so the server side can choose the right
// rendering and add child frame code to the page if needed.
- var url = $.param.querystring(Drupal.settings.basePath + state, { render: 'overlay' });
+ var url = $.param.querystring(internalUrl, { render: 'overlay' });
this.open(url);
this.resetActiveClass(this.getPath(Drupal.settings.basePath + state));
diff --git a/modules/overlay/overlay.info b/modules/overlay/overlay.info
index f113064..63ca90f 100644
--- a/modules/overlay/overlay.info
+++ b/modules/overlay/overlay.info
@@ -4,8 +4,8 @@ package = Core
version = VERSION
core = 7.x
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/overlay/overlay.module b/modules/overlay/overlay.module
index 7b2fc93..7e54734 100644
--- a/modules/overlay/overlay.module
+++ b/modules/overlay/overlay.module
@@ -78,6 +78,20 @@ function overlay_theme() {
);
}
+/**
+ * Implements hook_form_alter().
+ */
+function overlay_form_alter(&$form, &$form_state) {
+ // Add a hidden element to prevent dropping out of the overlay when a form is
+ // submitted inside the overlay using a GET method.
+ if (isset($form['#method']) && $form['#method'] == 'get' && isset($_REQUEST['render']) && $_REQUEST['render'] == 'overlay' && !isset($form['render'])) {
+ $form['render'] = array(
+ '#type' => 'hidden',
+ '#value' => 'overlay',
+ );
+ }
+}
+
/**
* Implements hook_form_FORM_ID_alter().
*/
diff --git a/modules/path/path.info b/modules/path/path.info
index 8e2b63b..9f4503b 100644
--- a/modules/path/path.info
+++ b/modules/path/path.info
@@ -6,8 +6,8 @@ core = 7.x
files[] = path.test
configure = admin/config/search/path
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/path/path.module b/modules/path/path.module
index 81c7bb2..4614b0f 100644
--- a/modules/path/path.module
+++ b/modules/path/path.module
@@ -185,7 +185,7 @@ function path_form_element_validate($element, &$form_state, $complete_form) {
* Implements hook_node_insert().
*/
function path_node_insert($node) {
- if (isset($node->path)) {
+ if (isset($node->path) && isset($node->path['alias'])) {
$path = $node->path;
$path['alias'] = trim($path['alias']);
// Only save a non-empty alias.
@@ -205,9 +205,9 @@ function path_node_insert($node) {
function path_node_update($node) {
if (isset($node->path)) {
$path = $node->path;
- $path['alias'] = trim($path['alias']);
+ $path['alias'] = isset($path['alias']) ? trim($path['alias']) : '';
// Delete old alias if user erased it.
- if (!empty($path['pid']) && empty($path['alias'])) {
+ if (!empty($path['pid']) && !$path['alias']) {
path_delete($path['pid']);
}
path_node_insert($node);
diff --git a/modules/php/php.info b/modules/php/php.info
index aea13e3..a977a9a 100644
--- a/modules/php/php.info
+++ b/modules/php/php.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
files[] = php.test
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/poll/poll.info b/modules/poll/poll.info
index bd0cd89..67158a1 100644
--- a/modules/poll/poll.info
+++ b/modules/poll/poll.info
@@ -6,8 +6,8 @@ core = 7.x
files[] = poll.test
stylesheets[all][] = poll.css
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/poll/poll.module b/modules/poll/poll.module
index d3d64b1..336e445 100644
--- a/modules/poll/poll.module
+++ b/modules/poll/poll.module
@@ -191,7 +191,6 @@ function poll_node_info() {
'base' => 'poll',
'description' => t('A poll is a question with a set of possible responses. A poll, once created, automatically provides a simple running count of the number of votes received for each response.'),
'title_label' => t('Question'),
- 'has_body' => FALSE,
)
);
}
@@ -632,9 +631,6 @@ function poll_delete($node) {
* The node object to load.
*/
function poll_block_latest_poll_view($node) {
- global $user;
- $output = '';
-
// This is necessary for shared objects because PHP doesn't copy objects, but
// passes them by reference. So when the objects are cached it can result in
// the wrong output being displayed on subsequent calls. The cloning and
@@ -675,9 +671,6 @@ function poll_block_latest_poll_view($node) {
* Implements hook_view().
*/
function poll_view($node, $view_mode) {
- global $user;
- $output = '';
-
if (!empty($node->allowvotes) && empty($node->show_results)) {
$node->content['poll_view_voting'] = drupal_get_form('poll_view_voting', $node);
}
@@ -695,7 +688,7 @@ function poll_view($node, $view_mode) {
function poll_teaser($node) {
$teaser = NULL;
if (is_array($node->choice)) {
- foreach ($node->choice as $k => $choice) {
+ foreach ($node->choice as $choice) {
if ($choice['chtext'] != '') {
$teaser .= '* ' . check_plain($choice['chtext']) . "\n";
}
diff --git a/modules/profile/profile.info b/modules/profile/profile.info
index 174bbcd..61f6f4d 100644
--- a/modules/profile/profile.info
+++ b/modules/profile/profile.info
@@ -11,8 +11,8 @@ configure = admin/config/people/profile
; See user_system_info_alter().
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/profile/profile.test b/modules/profile/profile.test
index 42a1a42..1892471 100644
--- a/modules/profile/profile.test
+++ b/modules/profile/profile.test
@@ -339,12 +339,22 @@ class ProfileTestAutocomplete extends ProfileTestCase {
$this->setProfileField($field, $field['value']);
// Set some html for what we want to see in the page output later.
- $autocomplete_html = '';
- $field_html = '';
+ // Autocomplete always uses non-clean URLs.
+ $current_clean_url = isset($GLOBALS['conf']['clean_url']) ? $GLOBALS['conf']['clean_url'] : NULL;
+ $GLOBALS['conf']['clean_url'] = 0;
+ $autocomplete_url = url('profile/autocomplete/' . $field['fid'], array('absolute' => TRUE, 'script' => 'index.php'));
+ $GLOBALS['conf']['clean_url'] = $current_clean_url;
+ $autocomplete_id = drupal_html_id('edit-' . $field['form_name'] . '-autocomplete');
+ $autocomplete_html = '';
// Check that autocompletion html is found on the user's profile edit page.
$this->drupalGet('user/' . $this->admin_user->uid . '/edit/' . $category);
$this->assertRaw($autocomplete_html, 'Autocomplete found.');
+ $this->assertFieldByXPath(
+ '//input[@type="text" and @name="' . $field['form_name'] . '" and contains(@class, "form-autocomplete")]',
+ '',
+ 'Text input field found'
+ );
$this->assertRaw('misc/autocomplete.js', 'Autocomplete JavaScript found.');
$this->assertRaw('class="form-text form-autocomplete"', 'Autocomplete form element class found.');
diff --git a/modules/rdf/rdf.info b/modules/rdf/rdf.info
index 8626a8e..b0d4a64 100644
--- a/modules/rdf/rdf.info
+++ b/modules/rdf/rdf.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
files[] = rdf.test
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/rdf/tests/rdf_test.info b/modules/rdf/tests/rdf_test.info
index a59f44d..898fdf5 100644
--- a/modules/rdf/tests/rdf_test.info
+++ b/modules/rdf/tests/rdf_test.info
@@ -4,9 +4,10 @@ package = Testing
version = VERSION
core = 7.x
hidden = TRUE
+dependencies[] = blog
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/search/search.api.php b/modules/search/search.api.php
index 62d53b8..8c17bb4 100644
--- a/modules/search/search.api.php
+++ b/modules/search/search.api.php
@@ -30,8 +30,9 @@
*
* @return
* Array with optional keys:
- * - title: Title for the tab on the search page for this module. Defaults
- * to the module name if not given.
+ * - title: Title for the tab on the search page for this module. Title must
+ * be untranslated. Outside of this return array, pass the title through the
+ * t() function to register it as a translatable string.
* - path: Path component after 'search/' for searching with this module.
* Defaults to the module name if not given.
* - conditions_callback: An implementation of callback_search_conditions().
@@ -39,6 +40,9 @@
* @ingroup search
*/
function hook_search_info() {
+ // Make the title translatable.
+ t('Content');
+
return array(
'title' => 'Content',
'path' => 'node',
diff --git a/modules/search/search.extender.inc b/modules/search/search.extender.inc
index 72cea64..4074256 100644
--- a/modules/search/search.extender.inc
+++ b/modules/search/search.extender.inc
@@ -409,10 +409,10 @@ class SearchQuery extends SelectQueryExtender {
* used. However, if at least one call to addScore() has taken place, the
* keyword relevance score is not automatically added.
*
- * Also note that if you call orderBy() directly on the query, search scores
- * will not automatically be used to order search results. Your orderBy()
- * expression can reference 'calculated_score', which will be the total
- * calculated score value.
+ * Note that you must use this method to add ordering to your searches, and
+ * not call orderBy() directly, when using the SearchQuery extender. This is
+ * because of the two-pass system the SearchQuery class uses to normalize
+ * scores.
*
* @param $score
* The score expression, which should evaluate to a number between 0 and 1.
diff --git a/modules/search/search.info b/modules/search/search.info
index 5fae76a..389d284 100644
--- a/modules/search/search.info
+++ b/modules/search/search.info
@@ -8,8 +8,8 @@ files[] = search.test
configure = admin/config/search/settings
stylesheets[all][] = search.css
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/search/search.pages.inc b/modules/search/search.pages.inc
index 9dd00a6..2123dd7 100644
--- a/modules/search/search.pages.inc
+++ b/modules/search/search.pages.inc
@@ -49,7 +49,7 @@ function search_view($module = NULL, $keys = '') {
// which will get us back to this page callback. In other words, the search
// form submits with POST but redirects to GET. This way we can keep
// the search query URL clean as a whistle.
- if (empty($_POST['form_id']) || $_POST['form_id'] != 'search_form') {
+ if (empty($_POST['form_id']) || ($_POST['form_id'] != 'search_form' && $_POST['form_id'] != 'search_block_form')) {
$conditions = NULL;
if (isset($info['conditions_callback']) && function_exists($info['conditions_callback'])) {
// Build an optional array of more search conditions.
diff --git a/modules/search/search.test b/modules/search/search.test
index 5f16db3..913d198 100644
--- a/modules/search/search.test
+++ b/modules/search/search.test
@@ -666,6 +666,24 @@ class SearchBlockTestCase extends DrupalWebTestCase {
url('search/node/', array('absolute' => TRUE)),
'Redirected to correct url.'
);
+
+ // Test that after entering a too-short keyword in the form, you can then
+ // search again with a longer keyword. First test using the block form.
+ $terms = array('search_block_form' => 'a');
+ $this->drupalPost('node', $terms, t('Search'));
+ $this->assertText('You must include at least one positive keyword with 3 characters or more');
+ $terms = array('search_block_form' => 'foo');
+ $this->drupalPost(NULL, $terms, t('Search'));
+ $this->assertNoText('You must include at least one positive keyword with 3 characters or more');
+ $this->assertText('Your search yielded no results');
+
+ // Same test again, using the search page form for the second search this time.
+ $terms = array('search_block_form' => 'a');
+ $this->drupalPost('node', $terms, t('Search'));
+ $terms = array('keys' => 'foo');
+ $this->drupalPost(NULL, $terms, t('Search'));
+ $this->assertNoText('You must include at least one positive keyword with 3 characters or more');
+ $this->assertText('Your search yielded no results');
}
}
@@ -2029,10 +2047,11 @@ class SearchNodeAccessTest extends DrupalWebTestCase {
}
/**
- * Tests that search returns results with punctuation in the search phrase.
+ * Tests that search works with punctuation and HTML entities.
*/
function testPhraseSearchPunctuation() {
$node = $this->drupalCreateNode(array('body' => array(LANGUAGE_NONE => array(array('value' => "The bunny's ears were fuzzy.")))));
+ $node2 = $this->drupalCreateNode(array('body' => array(LANGUAGE_NONE => array(array('value' => 'Dignissim Aliquam & Quieligo meus natu quae quia te. Damnum© erat— neo pneum. Facilisi feugiat ibidem ratis.')))));
// Update the search index.
module_invoke_all('update_index');
@@ -2045,6 +2064,17 @@ class SearchNodeAccessTest extends DrupalWebTestCase {
$edit = array('keys' => '"bunny\'s"');
$this->drupalPost('search/node', $edit, t('Search'));
$this->assertText($node->title);
+
+ // Search for "&" and verify entities are not broken up in the output.
+ $edit = array('keys' => '&');
+ $this->drupalPost('search/node', $edit, t('Search'));
+ $this->assertNoRaw('&');
+ $this->assertText('You must include at least one positive keyword');
+
+ $edit = array('keys' => '&');
+ $this->drupalPost('search/node', $edit, t('Search'));
+ $this->assertNoRaw('&');
+ $this->assertText('You must include at least one positive keyword');
}
}
diff --git a/modules/search/tests/search_embedded_form.info b/modules/search/tests/search_embedded_form.info
index 9b04667..b9c5c22 100644
--- a/modules/search/tests/search_embedded_form.info
+++ b/modules/search/tests/search_embedded_form.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/search/tests/search_extra_type.info b/modules/search/tests/search_extra_type.info
index c1fd775..b409794 100644
--- a/modules/search/tests/search_extra_type.info
+++ b/modules/search/tests/search_extra_type.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/search/tests/search_node_tags.info b/modules/search/tests/search_node_tags.info
index 96461af..687269a 100644
--- a/modules/search/tests/search_node_tags.info
+++ b/modules/search/tests/search_node_tags.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/shortcut/shortcut.info b/modules/shortcut/shortcut.info
index 7e65529..c5a7b28 100644
--- a/modules/shortcut/shortcut.info
+++ b/modules/shortcut/shortcut.info
@@ -6,8 +6,8 @@ core = 7.x
files[] = shortcut.test
configure = admin/config/user-interface/shortcut
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/simpletest/drupal_web_test_case.php b/modules/simpletest/drupal_web_test_case.php
index 271efff..08452f3 100644
--- a/modules/simpletest/drupal_web_test_case.php
+++ b/modules/simpletest/drupal_web_test_case.php
@@ -853,6 +853,13 @@ class DrupalWebTestCase extends DrupalTestCase {
*/
protected $cookieFile = NULL;
+ /**
+ * The cookies of the page currently loaded in the internal browser.
+ *
+ * @var array
+ */
+ protected $cookies = array();
+
/**
* Additional cURL options.
*
@@ -942,7 +949,6 @@ class DrupalWebTestCase extends DrupalTestCase {
protected function drupalCreateNode($settings = array()) {
// Populate defaults array.
$settings += array(
- 'body' => array(LANGUAGE_NONE => array(array())),
'title' => $this->randomName(8),
'comment' => 2,
'changed' => REQUEST_TIME,
@@ -957,6 +963,12 @@ class DrupalWebTestCase extends DrupalTestCase {
'language' => LANGUAGE_NONE,
);
+ // Add the body after the language is defined so that it may be set
+ // properly.
+ $settings += array(
+ 'body' => array($settings['language'] => array(array())),
+ );
+
// Use the original node's created time for existing nodes.
if (isset($settings['created']) && !isset($settings['date'])) {
$settings['date'] = format_date($settings['created'], 'custom', 'Y-m-d H:i:s O');
@@ -1015,9 +1027,7 @@ class DrupalWebTestCase extends DrupalTestCase {
'description' => '',
'help' => '',
'title_label' => 'Title',
- 'body_label' => 'Body',
'has_title' => 1,
- 'has_body' => 1,
);
// Imposed values for a custom type.
$forced = array(
@@ -1067,7 +1077,7 @@ class DrupalWebTestCase extends DrupalTestCase {
$lines = array(16, 256, 1024, 2048, 20480);
$count = 0;
foreach ($lines as $line) {
- simpletest_generate_file('text-' . $count++, 64, $line);
+ simpletest_generate_file('text-' . $count++, 64, $line, 'text');
}
// Copy other test files from simpletest.
@@ -1695,8 +1705,10 @@ class DrupalWebTestCase extends DrupalTestCase {
$GLOBALS['conf']['language_default'] = $this->originalLanguageDefault;
}
- // Close the CURL handler.
+ // Close the CURL handler and reset the cookies array so test classes
+ // containing multiple tests are not polluted.
$this->curlClose();
+ $this->cookies = array();
}
/**
@@ -2221,6 +2233,7 @@ class DrupalWebTestCase extends DrupalTestCase {
// Submit the POST request.
$return = drupal_json_decode($this->drupalPost(NULL, $edit, array('path' => $ajax_path, 'triggering_element' => $triggering_element), $options, $headers, $form_html_id, $extra_post));
+ $this->assertIdentical($this->drupalGetHeader('X-Drupal-Ajax-Token'), '1', 'Ajax response header found.');
// Change the page content by applying the returned commands.
if (!empty($ajax_settings) && !empty($return)) {
@@ -2257,8 +2270,13 @@ class DrupalWebTestCase extends DrupalTestCase {
if ($wrapperNode) {
// ajax.js adds an enclosing DIV to work around a Safari bug.
$newDom = new DOMDocument();
+ // DOM can load HTML soup. But, HTML soup can throw warnings,
+ // suppress them.
$newDom->loadHTML('' . $command['data'] . '');
- $newNode = $dom->importNode($newDom->documentElement->firstChild->firstChild, TRUE);
+ // Suppress warnings thrown when duplicate HTML IDs are
+ // encountered. This probably means we are replacing an element
+ // with the same ID.
+ $newNode = @$dom->importNode($newDom->documentElement->firstChild->firstChild, TRUE);
$method = isset($command['method']) ? $command['method'] : $ajax_settings['method'];
// The "method" is a jQuery DOM manipulation function. Emulate
// each one using PHP's DOMNode API.
@@ -2580,6 +2598,11 @@ class DrupalWebTestCase extends DrupalTestCase {
*
* @param $xpath
* The xpath string to use in the search.
+ * @param array $arguments
+ * An array of arguments with keys in the form ':name' matching the
+ * placeholders in the query. The values may be either strings or numeric
+ * values.
+ *
* @return
* The return value of the xpath search. For details on the xpath string
* format and return values see the SimpleXML documentation,
@@ -2751,7 +2774,7 @@ class DrupalWebTestCase extends DrupalTestCase {
$path = substr($path, $length);
}
// Ensure that we have an absolute path.
- if ($path[0] !== '/') {
+ if (empty($path) || $path[0] !== '/') {
$path = '/' . $path;
}
// Finally, prepend the $base_url.
diff --git a/modules/simpletest/files/css_test_files/css_input_with_import.css b/modules/simpletest/files/css_test_files/css_input_with_import.css
index 87afcb3..484db83 100644
--- a/modules/simpletest/files/css_test_files/css_input_with_import.css
+++ b/modules/simpletest/files/css_test_files/css_input_with_import.css
@@ -1,5 +1,7 @@
+@import url("http://example.com/style.css");
+@import url("//example.com/style.css");
@import "import1.css";
@import "import2.css";
diff --git a/modules/simpletest/files/css_test_files/css_input_with_import.css.optimized.css b/modules/simpletest/files/css_test_files/css_input_with_import.css.optimized.css
index a05f939..a2af7b3 100644
--- a/modules/simpletest/files/css_test_files/css_input_with_import.css.optimized.css
+++ b/modules/simpletest/files/css_test_files/css_input_with_import.css.optimized.css
@@ -1,4 +1,4 @@
-ul,select{font:1em/160% Verdana,sans-serif;color:#494949;}.ui-icon{background-image:url(images/icon.png);}.data .double-quote{background-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");}.data .single-quote{background-image:url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAH//Z');}.data .no-quote{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=);}
+@import url("http://example.com/style.css");@import url("//example.com/style.css");ul,select{font:1em/160% Verdana,sans-serif;color:#494949;}.ui-icon{background-image:url(images/icon.png);}.data .double-quote{background-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");}.data .single-quote{background-image:url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAH//Z');}.data .no-quote{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAEAQAAAAAo/mtHAAAAIElEQVQIHWMRnWHwcRNLN8NZ7QYWwT8PlBlYsgqVBRsAankIMw5MtnoAAAAASUVORK5CYII=);}
p,select{font:1em/160% Verdana,sans-serif;color:#494949;}
body{margin:0;padding:0;background:#edf5fa;font:76%/170% Verdana,sans-serif;color:#494949;}.this .is .a .test{font:1em/100% Verdana,sans-serif;color:#494949;}.this
.is
diff --git a/modules/simpletest/files/css_test_files/css_input_with_import.css.unoptimized.css b/modules/simpletest/files/css_test_files/css_input_with_import.css.unoptimized.css
index b8c7778..bc3c7b6 100644
--- a/modules/simpletest/files/css_test_files/css_input_with_import.css.unoptimized.css
+++ b/modules/simpletest/files/css_test_files/css_input_with_import.css.unoptimized.css
@@ -1,5 +1,7 @@
+@import url("http://example.com/style.css");
+@import url("//example.com/style.css");
ul, select {
font: 1em/160% Verdana, sans-serif;
diff --git a/modules/simpletest/files/image-test-no-transparency.gif b/modules/simpletest/files/image-test-no-transparency.gif
new file mode 100644
index 0000000..15ae777
Binary files /dev/null and b/modules/simpletest/files/image-test-no-transparency.gif differ
diff --git a/modules/simpletest/simpletest.info b/modules/simpletest/simpletest.info
index 48f4e21..188d9ef 100644
--- a/modules/simpletest/simpletest.info
+++ b/modules/simpletest/simpletest.info
@@ -11,6 +11,7 @@ configure = admin/config/development/testing/settings
files[] = tests/actions.test
files[] = tests/ajax.test
files[] = tests/batch.test
+files[] = tests/boot.test
files[] = tests/bootstrap.test
files[] = tests/cache.test
files[] = tests/common.test
@@ -56,8 +57,8 @@ files[] = tests/upgrade/update.trigger.test
files[] = tests/upgrade/update.field.test
files[] = tests/upgrade/update.user.test
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/simpletest/simpletest.module b/modules/simpletest/simpletest.module
index 91f0f90..cf83047 100644
--- a/modules/simpletest/simpletest.module
+++ b/modules/simpletest/simpletest.module
@@ -154,7 +154,7 @@ function simpletest_run_tests($test_list, $reporter = 'drupal') {
}
/**
- * Batch operation callback.
+ * Implements callback_batch_operation().
*/
function _simpletest_batch_operation($test_list_init, $test_id, &$context) {
simpletest_classloader_register();
@@ -205,6 +205,9 @@ function _simpletest_batch_operation($test_list_init, $test_id, &$context) {
$context['finished'] = 1 - $size / $max;
}
+/**
+ * Implements callback_batch_finished().
+ */
function _simpletest_batch_finished($success, $results, $operations, $elapsed) {
if ($success) {
drupal_set_message(t('The test run finished in @elapsed.', array('@elapsed' => $elapsed)));
@@ -371,7 +374,10 @@ function simpletest_test_get_all() {
// If this test class requires a non-existing module, skip it.
if (!empty($info['dependencies'])) {
foreach ($info['dependencies'] as $module) {
- if (!drupal_get_filename('module', $module)) {
+ // Pass FALSE as fourth argument so no error gets created for
+ // the missing file.
+ $found_module = drupal_get_filename('module', $module, NULL, FALSE);
+ if (!$found_module) {
continue 2;
}
}
@@ -509,25 +515,25 @@ function simpletest_registry_files_alter(&$files, $modules) {
* Generate test file.
*/
function simpletest_generate_file($filename, $width, $lines, $type = 'binary-text') {
- $size = $width * $lines - $lines;
-
- // Generate random text
$text = '';
- for ($i = 0; $i < $size; $i++) {
- switch ($type) {
- case 'text':
- $text .= chr(rand(32, 126));
- break;
- case 'binary':
- $text .= chr(rand(0, 31));
- break;
- case 'binary-text':
- default:
- $text .= rand(0, 1);
- break;
+ for ($i = 0; $i < $lines; $i++) {
+ // Generate $width - 1 characters to leave space for the "\n" character.
+ for ($j = 0; $j < $width - 1; $j++) {
+ switch ($type) {
+ case 'text':
+ $text .= chr(rand(32, 126));
+ break;
+ case 'binary':
+ $text .= chr(rand(0, 31));
+ break;
+ case 'binary-text':
+ default:
+ $text .= rand(0, 1);
+ break;
+ }
}
+ $text .= "\n";
}
- $text = wordwrap($text, $width - 1, "\n", TRUE) . "\n"; // Add \n for symmetrical file.
// Create filename.
file_put_contents('public://' . $filename . '.txt', $text);
diff --git a/modules/simpletest/simpletest.test b/modules/simpletest/simpletest.test
index f22ef95..5d1c718 100644
--- a/modules/simpletest/simpletest.test
+++ b/modules/simpletest/simpletest.test
@@ -322,6 +322,14 @@ class SimpleTestFunctionalTest extends DrupalWebTestCase {
* Test internal testing framework browser.
*/
class SimpleTestBrowserTestCase extends DrupalWebTestCase {
+
+ /**
+ * A flag indicating whether a cookie has been set in a test.
+ *
+ * @var bool
+ */
+ protected static $cookieSet = FALSE;
+
public static function getInfo() {
return array(
'name' => 'SimpleTest browser',
@@ -380,6 +388,46 @@ EOF;
$urls = $this->xpath('//a[text()=:text]', array(':text' => 'A second "even more weird" link, in memory of George O\'Malley'));
$this->assertEqual($urls[0]['href'], 'link2', 'Match with mixed single and double quotes.');
}
+
+ /**
+ * Tests that cookies set during a request are available for testing.
+ */
+ public function testCookies() {
+ // Check that the $this->cookies property is populated when a user logs in.
+ $user = $this->drupalCreateUser();
+ $edit = array('name' => $user->name, 'pass' => $user->pass_raw);
+ $this->drupalPost('', $edit, t('Log in'));
+ $this->assertEqual(count($this->cookies), 1, 'A cookie is set when the user logs in.');
+
+ // Check that the name and value of the cookie match the request data.
+ $cookie_header = $this->drupalGetHeader('set-cookie', TRUE);
+
+ // The name and value are located at the start of the string, separated by
+ // an equals sign and ending in a semicolon.
+ preg_match('/^([^=]+)=([^;]+)/', $cookie_header, $matches);
+ $name = $matches[1];
+ $value = $matches[2];
+
+ $this->assertTrue(array_key_exists($name, $this->cookies), 'The cookie name is correct.');
+ $this->assertEqual($value, $this->cookies[$name]['value'], 'The cookie value is correct.');
+
+ // Set a flag indicating that a cookie has been set in this test.
+ // @see SimpleTestBrowserTestCase::testCookieDoesNotBleed().
+ self::$cookieSet = TRUE;
+ }
+
+ /**
+ * Tests that the cookies from a previous test do not bleed into a new test.
+ *
+ * @see SimpleTestBrowserTestCase::testCookies().
+ */
+ public function testCookieDoesNotBleed() {
+ // In order for this test to be effective it should always run after the
+ // testCookies() test.
+ $this->assertTrue(self::$cookieSet, 'Tests have been executed in the expected order.');
+ $this->assertEqual(count($this->cookies), 0, 'No cookies are present at the start of a new test.');
+ }
+
}
class SimpleTestMailCaptureTestCase extends DrupalWebTestCase {
diff --git a/modules/simpletest/tests/actions_loop_test.info b/modules/simpletest/tests/actions_loop_test.info
index 023dd08..977bbe0 100644
--- a/modules/simpletest/tests/actions_loop_test.info
+++ b/modules/simpletest/tests/actions_loop_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/simpletest/tests/ajax_forms_test.info b/modules/simpletest/tests/ajax_forms_test.info
index c55e026..824c266 100644
--- a/modules/simpletest/tests/ajax_forms_test.info
+++ b/modules/simpletest/tests/ajax_forms_test.info
@@ -5,8 +5,8 @@ package = Testing
version = VERSION
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/simpletest/tests/ajax_test.info b/modules/simpletest/tests/ajax_test.info
index 579d08b..8d3dae7 100644
--- a/modules/simpletest/tests/ajax_test.info
+++ b/modules/simpletest/tests/ajax_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/simpletest/tests/batch_test.callbacks.inc b/modules/simpletest/tests/batch_test.callbacks.inc
index 75e6655..6564413 100644
--- a/modules/simpletest/tests/batch_test.callbacks.inc
+++ b/modules/simpletest/tests/batch_test.callbacks.inc
@@ -7,6 +7,8 @@
*/
/**
+ * Implements callback_batch_operation().
+ *
* Simple batch operation.
*/
function _batch_test_callback_1($id, $sleep, &$context) {
@@ -20,6 +22,8 @@ function _batch_test_callback_1($id, $sleep, &$context) {
}
/**
+ * Implements callback_batch_operation().
+ *
* Multistep batch operation.
*/
function _batch_test_callback_2($start, $total, $sleep, &$context) {
@@ -53,6 +57,8 @@ function _batch_test_callback_2($start, $total, $sleep, &$context) {
}
/**
+ * Implements callback_batch_operation().
+ *
* Simple batch operation.
*/
function _batch_test_callback_5($id, $sleep, &$context) {
@@ -68,6 +74,8 @@ function _batch_test_callback_5($id, $sleep, &$context) {
}
/**
+ * Implements callback_batch_operation().
+ *
* Batch operation setting up its own batch.
*/
function _batch_test_nested_batch_callback() {
@@ -76,6 +84,8 @@ function _batch_test_nested_batch_callback() {
}
/**
+ * Implements callback_batch_finished().
+ *
* Common 'finished' callbacks for batches 1 to 4.
*/
function _batch_test_finished_helper($batch_id, $success, $results, $operations) {
@@ -99,6 +109,8 @@ function _batch_test_finished_helper($batch_id, $success, $results, $operations)
}
/**
+ * Implements callback_batch_finished().
+ *
* 'finished' callback for batch 0.
*/
function _batch_test_finished_0($success, $results, $operations) {
@@ -106,6 +118,8 @@ function _batch_test_finished_0($success, $results, $operations) {
}
/**
+ * Implements callback_batch_finished().
+ *
* 'finished' callback for batch 1.
*/
function _batch_test_finished_1($success, $results, $operations) {
@@ -113,6 +127,8 @@ function _batch_test_finished_1($success, $results, $operations) {
}
/**
+ * Implements callback_batch_finished().
+ *
* 'finished' callback for batch 2.
*/
function _batch_test_finished_2($success, $results, $operations) {
@@ -120,6 +136,8 @@ function _batch_test_finished_2($success, $results, $operations) {
}
/**
+ * Implements callback_batch_finished().
+ *
* 'finished' callback for batch 3.
*/
function _batch_test_finished_3($success, $results, $operations) {
@@ -127,6 +145,8 @@ function _batch_test_finished_3($success, $results, $operations) {
}
/**
+ * Implements callback_batch_finished().
+ *
* 'finished' callback for batch 4.
*/
function _batch_test_finished_4($success, $results, $operations) {
@@ -134,6 +154,8 @@ function _batch_test_finished_4($success, $results, $operations) {
}
/**
+ * Implements callback_batch_finished().
+ *
* 'finished' callback for batch 5.
*/
function _batch_test_finished_5($success, $results, $operations) {
diff --git a/modules/simpletest/tests/batch_test.info b/modules/simpletest/tests/batch_test.info
index 5061393..07c8bd4 100644
--- a/modules/simpletest/tests/batch_test.info
+++ b/modules/simpletest/tests/batch_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/simpletest/tests/boot.test b/modules/simpletest/tests/boot.test
new file mode 100644
index 0000000..562b082
--- /dev/null
+++ b/modules/simpletest/tests/boot.test
@@ -0,0 +1,38 @@
+ 'Early bootstrap test',
+ 'description' => 'Confirm that calling module_implements() during early bootstrap does not pollute the module_implements() cache.',
+ 'group' => 'System',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('boot_test_1', 'boot_test_2');
+ }
+
+ /**
+ * Test hook_boot() on both regular and "early exit" pages.
+ */
+ public function testHookBoot() {
+ $paths = array('', 'early_exit');
+ foreach ($paths as $path) {
+ // Empty the module_implements() caches.
+ module_implements(NULL, FALSE, TRUE);
+ // Do a request to the front page, which will call module_implements()
+ // during hook_boot().
+ $this->drupalGet($path);
+ // Reset the static cache so we get implementation data from the persistent
+ // cache.
+ drupal_static_reset();
+ // Make sure we get a full list of all modules implementing hook_help().
+ $modules = module_implements('help');
+ $this->assertTrue(in_array('boot_test_2', $modules));
+ }
+ }
+}
diff --git a/modules/simpletest/tests/boot_test_1.info b/modules/simpletest/tests/boot_test_1.info
new file mode 100644
index 0000000..474c4c4
--- /dev/null
+++ b/modules/simpletest/tests/boot_test_1.info
@@ -0,0 +1,12 @@
+name = Early bootstrap tests
+description = A support module for hook_boot testing.
+core = 7.x
+package = Testing
+version = VERSION
+hidden = TRUE
+
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
+project = "drupal"
+datestamp = "1475694174"
+
diff --git a/modules/simpletest/tests/boot_test_1.module b/modules/simpletest/tests/boot_test_1.module
new file mode 100644
index 0000000..a452e28
--- /dev/null
+++ b/modules/simpletest/tests/boot_test_1.module
@@ -0,0 +1,21 @@
+proxy_ip;
+ $_SERVER['HTTP_X_FORWARDED_FOR'] = $this->proxy_ip;
+ drupal_static_reset('ip_address');
+ $this->assertTrue(
+ ip_address() == $this->proxy_ip,
+ 'Visiting from trusted proxy got proxy IP address.'
+ );
+
// Multi-tier architecture with comma separated values in header.
$_SERVER['REMOTE_ADDR'] = $this->proxy_ip;
$_SERVER['HTTP_X_FORWARDED_FOR'] = implode(', ', array($this->untrusted_ip, $this->forwarded_ip, $this->proxy2_ip));
@@ -152,7 +161,7 @@ class BootstrapPageCacheTestCase extends DrupalWebTestCase {
$this->drupalLogin($user);
$this->drupalGet('', array(), array('If-Modified-Since: ' . $last_modified, 'If-None-Match: ' . $etag));
$this->assertResponse(200, 'Conditional request returned 200 OK for authenticated user.');
- $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Absense of Page was not cached.');
+ $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Absence of Page was not cached.');
$this->assertFalse($this->drupalGetHeader('ETag'), 'ETag HTTP headers are not present for logged in users.');
$this->assertFalse($this->drupalGetHeader('Last-Modified'), 'Last-Modified HTTP headers are not present for logged in users.');
}
@@ -191,7 +200,7 @@ class BootstrapPageCacheTestCase extends DrupalWebTestCase {
$this->drupalGet('system-test/set-header', array('query' => array('name' => 'Foo', 'value' => 'bar')));
$this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Caching was bypassed.');
$this->assertTrue(strpos($this->drupalGetHeader('Vary'), 'Cookie') === FALSE, 'Vary: Cookie header was not sent.');
- $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'no-cache, must-revalidate, post-check=0, pre-check=0', 'Cache-Control header was sent.');
+ $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'no-cache, must-revalidate', 'Cache-Control header was sent.');
$this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', 'Expires header was sent.');
$this->assertEqual($this->drupalGetHeader('Foo'), 'bar', 'Custom header was sent.');
@@ -313,6 +322,10 @@ class BootstrapAutoloadTestCase extends DrupalWebTestCase {
$this->assertTrue(drupal_autoload_interface('drupalautoloadtestinterface'), 'drupal_autoload_interface() recognizes DrupalAutoloadTestInterface in lower case.');
// Test class autoloader.
$this->assertTrue(drupal_autoload_class('drupalautoloadtestclass'), 'drupal_autoload_class() recognizes DrupalAutoloadTestClass in lower case.');
+ // Test trait autoloader.
+ if (version_compare(PHP_VERSION, '5.4') >= 0) {
+ $this->assertTrue(drupal_autoload_trait('drupalautoloadtesttrait'), 'drupal_autoload_trait() recognizes DrupalAutoloadTestTrait in lower case.');
+ }
}
}
@@ -375,12 +388,19 @@ class BootstrapGetFilenameTestCase extends DrupalUnitTestCase {
public static function getInfo() {
return array(
- 'name' => 'Get filename test',
- 'description' => 'Test that drupal_get_filename() works correctly when the file is not found in the database.',
+ 'name' => 'Get filename test (without the system table)',
+ 'description' => 'Test that drupal_get_filename() works correctly when the database is not available.',
'group' => 'Bootstrap',
);
}
+ /**
+ * The last file-related error message triggered by the filename test.
+ *
+ * Used by BootstrapGetFilenameTestCase::testDrupalGetFilename().
+ */
+ protected $getFilenameTestTriggeredError;
+
/**
* Test that drupal_get_filename() works correctly when the file is not found in the database.
*/
@@ -410,6 +430,203 @@ class BootstrapGetFilenameTestCase extends DrupalUnitTestCase {
// automatically check there for 'script' files, just as it does for (e.g.)
// 'module' files in modules.
$this->assertIdentical(drupal_get_filename('script', 'test'), 'scripts/test.script', t('Retrieve test script location.'));
+
+ // When searching for a module that does not exist, drupal_get_filename()
+ // should return NULL and trigger an appropriate error message.
+ $this->getFilenameTestTriggeredError = NULL;
+ set_error_handler(array($this, 'fileNotFoundErrorHandler'));
+ $non_existing_module = $this->randomName();
+ $this->assertNull(drupal_get_filename('module', $non_existing_module), 'Searching for a module that does not exist returns NULL.');
+ $this->assertTrue(strpos($this->getFilenameTestTriggeredError, format_string('The following module is missing from the file system: %name', array('%name' => $non_existing_module))) === 0, 'Searching for an item that does not exist triggers the correct error.');
+ restore_error_handler();
+
+ // Check that the result is stored in the file system scan cache.
+ $file_scans = _drupal_file_scan_cache();
+ $this->assertIdentical($file_scans['module'][$non_existing_module], FALSE, 'Searching for a module that does not exist creates a record in the missing and moved files static variable.');
+
+ // Performing the search again in the same request still should not find
+ // the file, but the error message should not be repeated (therefore we do
+ // not override the error handler here).
+ $this->assertNull(drupal_get_filename('module', $non_existing_module), 'Searching for a module that does not exist returns NULL during the second search.');
+ }
+
+ /**
+ * Skips handling of "file not found" errors.
+ */
+ public function fileNotFoundErrorHandler($error_level, $message, $filename, $line, $context) {
+ // Skip error handling if this is a "file not found" error.
+ if (strpos($message, 'is missing from the file system:') !== FALSE || strpos($message, 'has moved within the file system:') !== FALSE) {
+ $this->getFilenameTestTriggeredError = $message;
+ return;
+ }
+ _drupal_error_handler($error_level, $message, $filename, $line, $context);
+ }
+}
+
+/**
+ * Test drupal_get_filename() in the context of a full Drupal installation.
+ */
+class BootstrapGetFilenameWebTestCase extends DrupalWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Get filename test (full installation)',
+ 'description' => 'Test that drupal_get_filename() works correctly in the context of a full Drupal installation.',
+ 'group' => 'Bootstrap',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('system_test');
+ }
+
+ /**
+ * The last file-related error message triggered by the filename test.
+ *
+ * Used by BootstrapGetFilenameWebTestCase::testDrupalGetFilename().
+ */
+ protected $getFilenameTestTriggeredError;
+
+ /**
+ * Test that drupal_get_filename() works correctly with a full Drupal site.
+ */
+ function testDrupalGetFilename() {
+ // Search for a module that exists in the file system and the {system}
+ // table and make sure that it is found.
+ $this->assertIdentical(drupal_get_filename('module', 'node'), 'modules/node/node.module', 'Module found at expected location.');
+
+ // Search for a module that does not exist in either the file system or the
+ // {system} table. Make sure that an appropriate error is triggered and
+ // that the module winds up in the static and persistent cache.
+ $this->getFilenameTestTriggeredError = NULL;
+ set_error_handler(array($this, 'fileNotFoundErrorHandler'));
+ $non_existing_module = $this->randomName();
+ $this->assertNull(drupal_get_filename('module', $non_existing_module), 'Searching for a module that does not exist returns NULL.');
+ $this->assertTrue(strpos($this->getFilenameTestTriggeredError, format_string('The following module is missing from the file system: %name', array('%name' => $non_existing_module))) === 0, 'Searching for a module that does not exist triggers the correct error.');
+ restore_error_handler();
+ $file_scans = _drupal_file_scan_cache();
+ $this->assertIdentical($file_scans['module'][$non_existing_module], FALSE, 'Searching for a module that does not exist creates a record in the missing and moved files static variable.');
+ drupal_file_scan_write_cache();
+ $cache = cache_get('_drupal_file_scan_cache', 'cache_bootstrap');
+ $this->assertIdentical($cache->data['module'][$non_existing_module], FALSE, 'Searching for a module that does not exist creates a record in the missing and moved files persistent cache.');
+
+ // Simulate moving a module to a location that does not match the location
+ // in the {system} table and perform similar tests as above.
+ db_update('system')
+ ->fields(array('filename' => 'modules/simpletest/tests/fake_location/module_test.module'))
+ ->condition('name', 'module_test')
+ ->condition('type', 'module')
+ ->execute();
+ $this->getFilenameTestTriggeredError = NULL;
+ set_error_handler(array($this, 'fileNotFoundErrorHandler'));
+ $this->assertIdentical(drupal_get_filename('module', 'module_test'), 'modules/simpletest/tests/module_test.module', 'Searching for a module that has moved finds the module at its new location.');
+ $this->assertTrue(strpos($this->getFilenameTestTriggeredError, format_string('The following module has moved within the file system: %name', array('%name' => 'module_test'))) === 0, 'Searching for a module that has moved triggers the correct error.');
+ restore_error_handler();
+ $file_scans = _drupal_file_scan_cache();
+ $this->assertIdentical($file_scans['module']['module_test'], 'modules/simpletest/tests/module_test.module', 'Searching for a module that has moved creates a record in the missing and moved files static variable.');
+ drupal_file_scan_write_cache();
+ $cache = cache_get('_drupal_file_scan_cache', 'cache_bootstrap');
+ $this->assertIdentical($cache->data['module']['module_test'], 'modules/simpletest/tests/module_test.module', 'Searching for a module that has moved creates a record in the missing and moved files persistent cache.');
+
+ // Simulate a module that exists in the {system} table but does not exist
+ // in the file system and perform similar tests as above.
+ $non_existing_module = $this->randomName();
+ db_update('system')
+ ->fields(array('name' => $non_existing_module))
+ ->condition('name', 'module_test')
+ ->condition('type', 'module')
+ ->execute();
+ $this->getFilenameTestTriggeredError = NULL;
+ set_error_handler(array($this, 'fileNotFoundErrorHandler'));
+ $this->assertNull(drupal_get_filename('module', $non_existing_module), 'Searching for a module that exists in the system table but not in the file system returns NULL.');
+ $this->assertTrue(strpos($this->getFilenameTestTriggeredError, format_string('The following module is missing from the file system: %name', array('%name' => $non_existing_module))) === 0, 'Searching for a module that exists in the system table but not in the file system triggers the correct error.');
+ restore_error_handler();
+ $file_scans = _drupal_file_scan_cache();
+ $this->assertIdentical($file_scans['module'][$non_existing_module], FALSE, 'Searching for a module that exists in the system table but not in the file system creates a record in the missing and moved files static variable.');
+ drupal_file_scan_write_cache();
+ $cache = cache_get('_drupal_file_scan_cache', 'cache_bootstrap');
+ $this->assertIdentical($cache->data['module'][$non_existing_module], FALSE, 'Searching for a module that exists in the system table but not in the file system creates a record in the missing and moved files persistent cache.');
+
+ // Simulate a module that exists in the file system but not in the {system}
+ // table and perform similar tests as above.
+ db_delete('system')
+ ->condition('name', 'common_test')
+ ->condition('type', 'module')
+ ->execute();
+ system_list_reset();
+ $this->getFilenameTestTriggeredError = NULL;
+ set_error_handler(array($this, 'fileNotFoundErrorHandler'));
+ $this->assertIdentical(drupal_get_filename('module', 'common_test'), 'modules/simpletest/tests/common_test.module', 'Searching for a module that does not exist in the system table finds the module at its actual location.');
+ $this->assertTrue(strpos($this->getFilenameTestTriggeredError, format_string('The following module has moved within the file system: %name', array('%name' => 'common_test'))) === 0, 'Searching for a module that does not exist in the system table triggers the correct error.');
+ restore_error_handler();
+ $file_scans = _drupal_file_scan_cache();
+ $this->assertIdentical($file_scans['module']['common_test'], 'modules/simpletest/tests/common_test.module', 'Searching for a module that does not exist in the system table creates a record in the missing and moved files static variable.');
+ drupal_file_scan_write_cache();
+ $cache = cache_get('_drupal_file_scan_cache', 'cache_bootstrap');
+ $this->assertIdentical($cache->data['module']['common_test'], 'modules/simpletest/tests/common_test.module', 'Searching for a module that does not exist in the system table creates a record in the missing and moved files persistent cache.');
+ }
+
+ /**
+ * Skips handling of "file not found" errors.
+ */
+ public function fileNotFoundErrorHandler($error_level, $message, $filename, $line, $context) {
+ // Skip error handling if this is a "file not found" error.
+ if (strpos($message, 'is missing from the file system:') !== FALSE || strpos($message, 'has moved within the file system:') !== FALSE) {
+ $this->getFilenameTestTriggeredError = $message;
+ return;
+ }
+ _drupal_error_handler($error_level, $message, $filename, $line, $context);
+ }
+
+ /**
+ * Test that watchdog messages about missing files are correctly recorded.
+ */
+ public function testWatchdog() {
+ // Search for a module that does not exist in either the file system or the
+ // {system} table. Make sure that an appropriate warning is recorded in the
+ // logs.
+ $non_existing_module = $this->randomName();
+ $query_parameters = array(
+ ':type' => 'php',
+ ':severity' => WATCHDOG_WARNING,
+ );
+ $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND severity = :severity', $query_parameters)->fetchField(), 0, 'No warning message appears in the logs before searching for a module that does not exist.');
+ // Trigger the drupal_get_filename() call. This must be done via a request
+ // to a separate URL since the watchdog() will happen in a shutdown
+ // function, and so that SimpleTest can be told to ignore (and not fail as
+ // a result of) the expected PHP warnings generated during this process.
+ variable_set('system_test_drupal_get_filename_test_module_name', $non_existing_module);
+ $this->drupalGet('system-test/drupal-get-filename');
+ $message_variables = db_query('SELECT variables FROM {watchdog} WHERE type = :type AND severity = :severity', $query_parameters)->fetchCol();
+ $this->assertEqual(count($message_variables), 1, 'A single warning message appears in the logs after searching for a module that does not exist.');
+ $variables = reset($message_variables);
+ $variables = unserialize($variables);
+ $this->assertTrue(isset($variables['!message']) && strpos($variables['!message'], format_string('The following module is missing from the file system: %name', array('%name' => $non_existing_module))) !== FALSE, 'The warning message that appears in the logs after searching for a module that does not exist contains the expected text.');
+ }
+
+ /**
+ * Test that drupal_get_filename() does not break recursive rebuilds.
+ */
+ public function testRecursiveRebuilds() {
+ // Ensure that the drupal_get_filename() call due to a missing module does
+ // not break the data returned by an attempted recursive rebuild. The code
+ // path which is tested is as follows:
+ // - Call drupal_get_schema().
+ // - Within a hook_schema() implementation, trigger a drupal_get_filename()
+ // search for a nonexistent module.
+ // - In the watchdog() call that results from that, trigger
+ // drupal_get_schema() again.
+ // Without some kind of recursion protection, this could cause the second
+ // drupal_get_schema() call to return incomplete results. This test ensures
+ // that does not happen.
+ $non_existing_module = $this->randomName();
+ variable_set('system_test_drupal_get_filename_test_module_name', $non_existing_module);
+ $this->drupalGet('system-test/drupal-get-filename-with-schema-rebuild');
+ $original_drupal_get_schema_tables = variable_get('system_test_drupal_get_filename_with_schema_rebuild_original_tables');
+ $final_drupal_get_schema_tables = variable_get('system_test_drupal_get_filename_with_schema_rebuild_final_tables');
+ $this->assertTrue(!empty($original_drupal_get_schema_tables));
+ $this->assertTrue(!empty($final_drupal_get_schema_tables));
+ $this->assertEqual($original_drupal_get_schema_tables, $final_drupal_get_schema_tables);
}
}
diff --git a/modules/simpletest/tests/common.test b/modules/simpletest/tests/common.test
index 0f0347f..0f991c3 100644
--- a/modules/simpletest/tests/common.test
+++ b/modules/simpletest/tests/common.test
@@ -372,6 +372,65 @@ class CommonURLUnitTest extends DrupalWebTestCase {
}
}
+/**
+ * Tests url_is_external().
+ */
+class UrlIsExternalUnitTest extends DrupalUnitTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'External URL checking',
+ 'description' => 'Performs tests on url_is_external().',
+ 'group' => 'System',
+ );
+ }
+
+ /**
+ * Tests if each URL is external or not.
+ */
+ function testUrlIsExternal() {
+ foreach ($this->examples() as $path => $expected) {
+ $this->assertIdentical(url_is_external($path), $expected, $path);
+ }
+ }
+
+ /**
+ * Provides data for testUrlIsExternal().
+ *
+ * @return array
+ * An array of test data, keyed by a path, with the expected value where
+ * TRUE is external, and FALSE is not external.
+ */
+ protected function examples() {
+ return array(
+ // Simple external URLs.
+ 'http://example.com' => TRUE,
+ 'https://example.com' => TRUE,
+ 'http://drupal.org/foo/bar?foo=bar&bar=baz&baz#foo' => TRUE,
+ '//drupal.org' => TRUE,
+ // Some browsers ignore or strip leading control characters.
+ "\x00//www.example.com" => TRUE,
+ "\x08//www.example.com" => TRUE,
+ "\x1F//www.example.com" => TRUE,
+ "\n//www.example.com" => TRUE,
+ // JSON supports decoding directly from UTF-8 code points.
+ json_decode('"\u00AD"') . "//www.example.com" => TRUE,
+ json_decode('"\u200E"') . "//www.example.com" => TRUE,
+ json_decode('"\uE0020"') . "//www.example.com" => TRUE,
+ json_decode('"\uE000"') . "//www.example.com" => TRUE,
+ // Backslashes should be normalized to forward.
+ '\\\\example.com' => TRUE,
+ // Local URLs.
+ 'node' => FALSE,
+ '/system/ajax' => FALSE,
+ '?q=foo:bar' => FALSE,
+ 'node/edit:me' => FALSE,
+ '/drupal.org' => FALSE,
+ '' => FALSE,
+ );
+ }
+}
+
/**
* Tests for check_plain(), filter_xss(), format_string(), and check_url().
*/
@@ -888,6 +947,31 @@ class DrupalHTMLIdentifierTestCase extends DrupalUnitTestCase {
// Verify that invalid characters (including non-breaking space) are stripped from the identifier.
$this->assertIdentical(drupal_clean_css_identifier('invalid !"#$%&\'()*+,./:;<=>?@[\\]^`{|}~ identifier', array()), 'invalididentifier', 'Strip invalid characters.');
+
+ // Verify that double underscores are replaced in the identifier by default.
+ $identifier = 'css__identifier__with__double__underscores';
+ $expected = 'css--identifier--with--double--underscores';
+ $this->assertIdentical(drupal_clean_css_identifier($identifier), $expected, 'Verify double underscores are replaced with double hyphens by default.');
+
+ // Verify that double underscores are preserved in the identifier if the
+ // variable allow_css_double_underscores is set to TRUE.
+ $this->setAllowCSSDoubleUnderscores(TRUE);
+ $this->assertIdentical(drupal_clean_css_identifier($identifier), $identifier, 'Verify double underscores are preserved if the allow_css_double_underscores set to TRUE.');
+
+ // To avoid affecting other test cases, set the variable
+ // allow_css_double_underscores to FALSE which is the default value.
+ $this->setAllowCSSDoubleUnderscores(FALSE);
+ }
+
+ /**
+ * Set the variable allow_css_double_underscores and reset the cache.
+ *
+ * @param $value bool
+ * A new value to be set to allow_css_double_underscores.
+ */
+ function setAllowCSSDoubleUnderscores($value) {
+ $GLOBALS['conf']['allow_css_double_underscores'] = $value;
+ drupal_static_reset('drupal_clean_css_identifier:allow_css_double_underscores');
}
/**
@@ -942,6 +1026,7 @@ class CascadingStylesheetsUnitTest extends DrupalUnitTestCase {
* - Proper URLs in imported files. (https://drupal.org/node/265719)
* - Retain pseudo-selectors. (https://drupal.org/node/460448)
* - Don't adjust data URIs. (https://drupal.org/node/2142441)
+ * - Files imported from external URLs. (https://drupal.org/node/2014851)
*/
function testLoadCssBasic() {
// Array of files to test living in 'simpletest/files/css_test_files/'.
@@ -1194,7 +1279,7 @@ class DrupalSetContentTestCase extends DrupalWebTestCase {
function testRegions() {
global $theme_key;
- $block_regions = array_keys(system_region_list($theme_key));
+ $block_regions = system_region_list($theme_key, REGIONS_ALL, FALSE);
$delimiter = $this->randomName(32);
$values = array();
// Set some random content for each region available.
@@ -1255,6 +1340,15 @@ class DrupalGotoTest extends DrupalWebTestCase {
$this->assertText('drupal_goto', 'Drupal goto redirect succeeded.');
$this->assertEqual($this->getUrl(), url('common-test/drupal_goto', array('query' => array('foo' => '123'), 'absolute' => TRUE)), 'Drupal goto redirected to expected URL.');
+ // Test that calling drupal_goto() on the current path is not dangerous.
+ variable_set('common_test_redirect_current_path', TRUE);
+ $this->drupalGet('', array('query' => array('q' => 'http://www.example.com/')));
+ $headers = $this->drupalGetHeaders(TRUE);
+ list(, $status) = explode(' ', $headers[0][':status'], 3);
+ $this->assertEqual($status, 302, 'Expected response code was sent.');
+ $this->assertNotEqual($this->getUrl(), 'http://www.example.com/', 'Drupal goto did not redirect to external URL.');
+ $this->assertTrue(strpos($this->getUrl(), url('', array('absolute' => TRUE))) === 0, 'Drupal redirected to itself.');
+ variable_del('common_test_redirect_current_path');
// Test that drupal_goto() respects ?destination=xxx. Use an complicated URL
// to test that the path is encoded and decoded properly.
$destination = 'common-test/drupal_goto/destination?foo=%2525&bar=123';
@@ -2116,7 +2210,7 @@ class DrupalRenderTestCase extends DrupalWebTestCase {
}
/**
- * Tests caching of an empty render item.
+ * Tests caching of render items.
*/
function testDrupalRenderCache() {
// Force a request via GET.
@@ -2142,6 +2236,59 @@ class DrupalRenderTestCase extends DrupalWebTestCase {
drupal_render($element);
$this->assertFalse(isset($element['#printed']), 'Cache hit');
+ // Test that user 1 does not share the cache with other users who have the
+ // same roles, even when DRUPAL_CACHE_PER_ROLE is used.
+ $user1 = user_load(1);
+ $first_authenticated_user = $this->drupalCreateUser();
+ $second_authenticated_user = $this->drupalCreateUser();
+ $user1->roles = array_intersect_key($user1->roles, array(DRUPAL_AUTHENTICATED_RID => TRUE));
+ user_save($user1);
+ // Load all the accounts again, to make sure we have complete account
+ // objects.
+ $user1 = user_load(1);
+ $first_authenticated_user = user_load($first_authenticated_user->uid);
+ $second_authenticated_user = user_load($second_authenticated_user->uid);
+ $this->assertEqual($user1->roles, $first_authenticated_user->roles, 'User 1 has the same roles as an authenticated user.');
+ // Impersonate user 1 and render content that only user 1 should have
+ // permission to see.
+ $original_user = $GLOBALS['user'];
+ $original_session_state = drupal_save_session();
+ drupal_save_session(FALSE);
+ $GLOBALS['user'] = $user1;
+ $test_element = array(
+ '#cache' => array(
+ 'keys' => array('test'),
+ 'granularity' => DRUPAL_CACHE_PER_ROLE,
+ ),
+ );
+ $element = $test_element;
+ $element['#markup'] = 'content for user 1';
+ $output = drupal_render($element);
+ $this->assertEqual($output, 'content for user 1');
+ // Verify the cache is working by rendering the same element but with
+ // different markup passed in; the result should be the same.
+ $element = $test_element;
+ $element['#markup'] = 'should not be used';
+ $output = drupal_render($element);
+ $this->assertEqual($output, 'content for user 1');
+ // Verify that the first authenticated user does not see the same content
+ // as user 1.
+ $GLOBALS['user'] = $first_authenticated_user;
+ $element = $test_element;
+ $element['#markup'] = 'content for authenticated users';
+ $output = drupal_render($element);
+ $this->assertEqual($output, 'content for authenticated users');
+ // Verify that the second authenticated user shares the cache with the
+ // first authenticated user.
+ $GLOBALS['user'] = $second_authenticated_user;
+ $element = $test_element;
+ $element['#markup'] = 'should not be used';
+ $output = drupal_render($element);
+ $this->assertEqual($output, 'content for authenticated users');
+ // Restore the original logged-in user.
+ $GLOBALS['user'] = $original_user;
+ drupal_save_session($original_session_state);
+
// Restore the previous request method.
$_SERVER['REQUEST_METHOD'] = $request_method;
}
diff --git a/modules/simpletest/tests/common_test.info b/modules/simpletest/tests/common_test.info
index c267f1d..c324d8a 100644
--- a/modules/simpletest/tests/common_test.info
+++ b/modules/simpletest/tests/common_test.info
@@ -7,8 +7,8 @@ stylesheets[all][] = common_test.css
stylesheets[print][] = common_test.print.css
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/simpletest/tests/common_test.module b/modules/simpletest/tests/common_test.module
index 674a494..2eb8cd5 100644
--- a/modules/simpletest/tests/common_test.module
+++ b/modules/simpletest/tests/common_test.module
@@ -92,6 +92,15 @@ function common_test_drupal_goto_alter(&$path, &$options, &$http_response_code)
}
}
+/**
+ * Implements hook_init().
+ */
+function common_test_init() {
+ if (variable_get('common_test_redirect_current_path', FALSE)) {
+ drupal_goto(current_path());
+ }
+}
+
/**
* Print destination query parameter.
*/
diff --git a/modules/simpletest/tests/common_test_cron_helper.info b/modules/simpletest/tests/common_test_cron_helper.info
index bf61551..619b87e 100644
--- a/modules/simpletest/tests/common_test_cron_helper.info
+++ b/modules/simpletest/tests/common_test_cron_helper.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/simpletest/tests/database_test.info b/modules/simpletest/tests/database_test.info
index fb55d8e..34571d0 100644
--- a/modules/simpletest/tests/database_test.info
+++ b/modules/simpletest/tests/database_test.info
@@ -5,8 +5,8 @@ package = Testing
version = VERSION
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/simpletest/tests/database_test.test b/modules/simpletest/tests/database_test.test
index 9c533be..59d2e5d 100644
--- a/modules/simpletest/tests/database_test.test
+++ b/modules/simpletest/tests/database_test.test
@@ -1414,10 +1414,47 @@ class DatabaseSelectTestCase extends DatabaseTestCase {
}
$query = (string)$query;
- $expected = "/* Testing query comments SELECT nid FROM {node}; -- */ SELECT test.name AS name, test.age AS age\nFROM \n{test} test";
+ $expected = "/* Testing query comments * / SELECT nid FROM {node}; -- */ SELECT test.name AS name, test.age AS age\nFROM \n{test} test";
$this->assertEqual($num_records, 4, 'Returned the correct number of rows.');
$this->assertEqual($query, $expected, 'The flattened query contains the sanitised comment string.');
+
+ $connection = Database::getConnection();
+ foreach ($this->makeCommentsProvider() as $test_set) {
+ list($expected, $comments) = $test_set;
+ $this->assertEqual($expected, $connection->makeComment($comments));
+ }
+ }
+
+ /**
+ * Provides expected and input values for testVulnerableComment().
+ */
+ function makeCommentsProvider() {
+ return array(
+ array(
+ '/* */ ',
+ array(''),
+ ),
+ // Try and close the comment early.
+ array(
+ '/* Exploit * / DROP TABLE node; -- */ ',
+ array('Exploit */ DROP TABLE node; --'),
+ ),
+ // Variations on comment closing.
+ array(
+ '/* Exploit * / * / DROP TABLE node; -- */ ',
+ array('Exploit */*/ DROP TABLE node; --'),
+ ),
+ array(
+ '/* Exploit * * // DROP TABLE node; -- */ ',
+ array('Exploit **// DROP TABLE node; --'),
+ ),
+ // Try closing the comment in the second string which is appended.
+ array(
+ '/* Exploit * / DROP TABLE node; --; Another try * / DROP TABLE node; -- */ ',
+ array('Exploit */ DROP TABLE node; --', 'Another try */ DROP TABLE node; --'),
+ ),
+ );
}
/**
diff --git a/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info b/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info
index 46bc0a2..910d27a 100644
--- a/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info
+++ b/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.info
@@ -7,8 +7,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.module b/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.module
index 37aa94e..edd5d77 100644
--- a/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.module
+++ b/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test.module
@@ -4,3 +4,19 @@
* @file
* Test module to check code registry.
*/
+
+/**
+ * Implements hook_registry_files_alter().
+ */
+function drupal_autoload_test_registry_files_alter(&$files, $modules) {
+ foreach ($modules as $module) {
+ // Add the drupal_autoload_test_trait.sh file to the registry when PHP 5.4+
+ // is being used.
+ if ($module->name == 'drupal_autoload_test' && version_compare(PHP_VERSION, '5.4') >= 0) {
+ $files["$module->dir/drupal_autoload_test_trait.sh"] = array(
+ 'module' => $module->name,
+ 'weight' => $module->weight,
+ );
+ }
+ }
+}
diff --git a/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test_trait.sh b/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test_trait.sh
new file mode 100644
index 0000000..69ce9ec
--- /dev/null
+++ b/modules/simpletest/tests/drupal_autoload_test/drupal_autoload_test_trait.sh
@@ -0,0 +1,16 @@
+drupalCreateUser();
-
// Create a file with a size of 1000 bytes, and quotas of only 1 byte.
$file = new stdClass();
$file->filesize = 1000;
@@ -498,9 +491,6 @@ class FileValidatorTest extends DrupalWebTestCase {
$this->assertEqual(count($errors), 1, 'Error for the user being over their limit.', 'File');
$errors = file_validate_size($file, 1, 1);
$this->assertEqual(count($errors), 2, 'Errors for both the file and their limit.', 'File');
-
- $user = $original_user;
- drupal_save_session(TRUE);
}
}
diff --git a/modules/simpletest/tests/file_test.info b/modules/simpletest/tests/file_test.info
index dae842c..605af1d 100644
--- a/modules/simpletest/tests/file_test.info
+++ b/modules/simpletest/tests/file_test.info
@@ -6,8 +6,8 @@ core = 7.x
files[] = file_test.module
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/simpletest/tests/filter_test.info b/modules/simpletest/tests/filter_test.info
index 1a45d8d..9e0a598 100644
--- a/modules/simpletest/tests/filter_test.info
+++ b/modules/simpletest/tests/filter_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/simpletest/tests/form.test b/modules/simpletest/tests/form.test
index 0bf6c8c..6bf2d9e 100644
--- a/modules/simpletest/tests/form.test
+++ b/modules/simpletest/tests/form.test
@@ -994,6 +994,26 @@ class FormsElementsTableSelectFunctionalTest extends DrupalWebTestCase {
$this->assertTrue(isset($errors['tableselect']), 'Option checker disallows invalid values for radio buttons.');
}
+ /**
+ * Test presence of ajax functionality
+ */
+ function testAjax() {
+ $rows = array('row1', 'row2', 'row3');
+ // Test checkboxes (#multiple == TRUE).
+ foreach ($rows as $row) {
+ $element = 'tableselect[' . $row . ']';
+ $edit = array($element => TRUE);
+ $result = $this->drupalPostAJAX('form_test/tableselect/multiple-true', $edit, $element);
+ $this->assertFalse(empty($result), t('Ajax triggers on checkbox for @row.', array('@row' => $row)));
+ }
+ // Test radios (#multiple == FALSE).
+ $element = 'tableselect';
+ foreach ($rows as $row) {
+ $edit = array($element => $row);
+ $result = $this->drupalPostAjax('form_test/tableselect/multiple-false', $edit, $element);
+ $this->assertFalse(empty($result), t('Ajax triggers on radio for @row.', array('@row' => $row)));
+ }
+ }
/**
* Helper function for the option check test to submit a form while collecting errors.
@@ -2099,3 +2119,36 @@ class HTMLIdTestCase extends DrupalWebTestCase {
$this->assertNoDuplicateIds('There are no duplicate IDs');
}
}
+
+/**
+ * Tests for form textarea.
+ */
+class FormTextareaTestCase extends DrupalUnitTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Form textarea',
+ 'description' => 'Tests form textarea related functions.',
+ 'group' => 'Form API',
+ );
+ }
+
+ /**
+ * Tests that textarea value is properly set.
+ */
+ public function testValueCallback() {
+ $element = array();
+ $form_state = array();
+ $test_cases = array(
+ array(NULL, FALSE),
+ array(NULL, NULL),
+ array('', array('test')),
+ array('test', 'test'),
+ array('123', 123),
+ );
+ foreach ($test_cases as $test_case) {
+ list($expected, $input) = $test_case;
+ $this->assertIdentical($expected, form_type_textarea_value($element, $input, $form_state));
+ }
+ }
+}
diff --git a/modules/simpletest/tests/form_test.info b/modules/simpletest/tests/form_test.info
index 2dbcd7a..75ee2ea 100644
--- a/modules/simpletest/tests/form_test.info
+++ b/modules/simpletest/tests/form_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/simpletest/tests/form_test.module b/modules/simpletest/tests/form_test.module
index 602b409..4fd708f 100644
--- a/modules/simpletest/tests/form_test.module
+++ b/modules/simpletest/tests/form_test.module
@@ -589,11 +589,17 @@ function _form_test_tableselect_form_builder($form, $form_state, $element_proper
$form['tableselect'] = $element_properties;
$form['tableselect'] += array(
+ '#prefix' => '',
+ '#suffix' => '',
'#type' => 'tableselect',
'#header' => $header,
'#options' => $options,
'#multiple' => FALSE,
'#empty' => t('Empty text.'),
+ '#ajax' => array(
+ 'callback' => '_form_test_tableselect_ajax_callback',
+ 'wrapper' => 'tableselect-wrapper',
+ ),
);
$form['submit'] = array(
@@ -697,6 +703,13 @@ function _form_test_vertical_tabs_form($form, &$form_state) {
return $form;
}
+/**
+* Ajax callback that returns the form element.
+*/
+function _form_test_tableselect_ajax_callback($form, &$form_state) {
+ return $form['tableselect'];
+}
+
/**
* A multistep form for testing the form storage.
*
diff --git a/modules/simpletest/tests/image.test b/modules/simpletest/tests/image.test
index 8497022..7ca1d3a 100644
--- a/modules/simpletest/tests/image.test
+++ b/modules/simpletest/tests/image.test
@@ -207,9 +207,11 @@ class ImageToolkitGdTestCase extends DrupalWebTestCase {
protected $green = array(0, 255, 0, 0);
protected $blue = array(0, 0, 255, 0);
protected $yellow = array(255, 255, 0, 0);
- protected $fuchsia = array(255, 0, 255, 0); // Used as background colors.
- protected $transparent = array(0, 0, 0, 127);
protected $white = array(255, 255, 255, 0);
+ protected $transparent = array(0, 0, 0, 127);
+ // Used as rotate background colors.
+ protected $fuchsia = array(255, 0, 255, 0);
+ protected $rotate_transparent = array(255, 255, 255, 127);
protected $width = 40;
protected $height = 20;
@@ -275,6 +277,7 @@ class ImageToolkitGdTestCase extends DrupalWebTestCase {
$files = array(
'image-test.png',
'image-test.gif',
+ 'image-test-no-transparency.gif',
'image-test.jpg',
);
@@ -334,13 +337,6 @@ class ImageToolkitGdTestCase extends DrupalWebTestCase {
// Systems using non-bundled GD2 don't have imagerotate. Test if available.
if (function_exists('imagerotate')) {
$operations += array(
- 'rotate_5' => array(
- 'function' => 'rotate',
- 'arguments' => array(5, 0xFF00FF), // Fuchsia background.
- 'width' => 42,
- 'height' => 24,
- 'corners' => array_fill(0, 4, $this->fuchsia),
- ),
'rotate_90' => array(
'function' => 'rotate',
'arguments' => array(90, 0xFF00FF), // Fuchsia background.
@@ -348,13 +344,6 @@ class ImageToolkitGdTestCase extends DrupalWebTestCase {
'height' => 40,
'corners' => array($this->fuchsia, $this->red, $this->green, $this->blue),
),
- 'rotate_transparent_5' => array(
- 'function' => 'rotate',
- 'arguments' => array(5),
- 'width' => 42,
- 'height' => 24,
- 'corners' => array_fill(0, 4, $this->transparent),
- ),
'rotate_transparent_90' => array(
'function' => 'rotate',
'arguments' => array(90),
@@ -363,6 +352,49 @@ class ImageToolkitGdTestCase extends DrupalWebTestCase {
'corners' => array($this->transparent, $this->red, $this->green, $this->blue),
),
);
+ // As of PHP version 5.5, GD uses a different algorithm to rotate images
+ // than version 5.4 and below, resulting in different dimensions.
+ // See https://bugs.php.net/bug.php?id=65148.
+ // For the 40x20 test images, the dimensions resulting from rotation will
+ // be 1 pixel smaller in both width and height in PHP 5.5 and above.
+ // @todo: If and when the PHP bug gets solved, add an upper limit
+ // version check.
+ if (version_compare(PHP_VERSION, '5.5', '>=')) {
+ $operations += array(
+ 'rotate_5' => array(
+ 'function' => 'rotate',
+ 'arguments' => array(5, 0xFF00FF), // Fuchsia background.
+ 'width' => 41,
+ 'height' => 23,
+ 'corners' => array_fill(0, 4, $this->fuchsia),
+ ),
+ 'rotate_transparent_5' => array(
+ 'function' => 'rotate',
+ 'arguments' => array(5),
+ 'width' => 41,
+ 'height' => 23,
+ 'corners' => array_fill(0, 4, $this->rotate_transparent),
+ ),
+ );
+ }
+ else {
+ $operations += array(
+ 'rotate_5' => array(
+ 'function' => 'rotate',
+ 'arguments' => array(5, 0xFF00FF), // Fuchsia background.
+ 'width' => 42,
+ 'height' => 24,
+ 'corners' => array_fill(0, 4, $this->fuchsia),
+ ),
+ 'rotate_transparent_5' => array(
+ 'function' => 'rotate',
+ 'arguments' => array(5),
+ 'width' => 42,
+ 'height' => 24,
+ 'corners' => array_fill(0, 4, $this->rotate_transparent),
+ ),
+ );
+ }
}
// Systems using non-bundled GD2 don't have imagefilter. Test if available.
@@ -430,6 +462,11 @@ class ImageToolkitGdTestCase extends DrupalWebTestCase {
}
// Now check each of the corners to ensure color correctness.
foreach ($values['corners'] as $key => $corner) {
+ // The test gif that does not have transparency has yellow where the
+ // others have transparent.
+ if ($file === 'image-test-no-transparency.gif' && $corner === $this->transparent) {
+ $corner = $this->yellow;
+ }
// Get the location of the corner.
switch ($key) {
case 0:
diff --git a/modules/simpletest/tests/image_test.info b/modules/simpletest/tests/image_test.info
index 483bf1d..4f29188 100644
--- a/modules/simpletest/tests/image_test.info
+++ b/modules/simpletest/tests/image_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/simpletest/tests/mail.test b/modules/simpletest/tests/mail.test
index 70a43cb..3e40e13 100644
--- a/modules/simpletest/tests/mail.test
+++ b/modules/simpletest/tests/mail.test
@@ -441,7 +441,7 @@ class DrupalHtmlToTextTestCase extends DrupalWebTestCase {
* is 1000 characters."
*/
function testVeryLongLineWrap() {
- $input = 'Drupal
' . str_repeat('x', 2100) . '>
Drupal';
+ $input = 'Drupal
' . str_repeat('x', 2100) . '
Drupal';
$output = drupal_html_to_text($input);
// This awkward construct comes from includes/mail.inc lines 8-13.
$eol = variable_get('mail_line_endings', MAIL_LINE_ENDINGS);
@@ -455,7 +455,6 @@ class DrupalHtmlToTextTestCase extends DrupalWebTestCase {
$maximum_line_length = max($maximum_line_length, strlen($line . $eol));
}
$verbose = 'Maximum line length found was ' . $maximum_line_length . ' octets.';
- // @todo This should assert that $maximum_line_length <= 1000.
- $this->pass($verbose);
+ $this->assertTrue($maximum_line_length <= 1000, $verbose);
}
}
diff --git a/modules/simpletest/tests/menu_test.info b/modules/simpletest/tests/menu_test.info
index 45e0a1e..68a1386 100644
--- a/modules/simpletest/tests/menu_test.info
+++ b/modules/simpletest/tests/menu_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/simpletest/tests/module.test b/modules/simpletest/tests/module.test
index 371339f..eea3b51 100644
--- a/modules/simpletest/tests/module.test
+++ b/modules/simpletest/tests/module.test
@@ -302,3 +302,45 @@ class ModuleUninstallTestCase extends DrupalWebTestCase {
$this->assertEqual(0, $count, 'Permissions were all removed.');
}
}
+
+class ModuleImplementsAlterTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Module implements alter',
+ 'description' => 'Tests hook_module_implements_alter().',
+ 'group' => 'Module',
+ );
+ }
+
+ /**
+ * Tests hook_module_implements_alter() adding an implementation.
+ */
+ function testModuleImplementsAlter() {
+ module_enable(array('module_test'), FALSE);
+ $this->assertTrue(module_exists('module_test'), 'Test module is enabled.');
+
+ // Assert that module_test.module is now included.
+ $this->assertTrue(function_exists('module_test_permission'),
+ 'The file module_test.module was successfully included.');
+
+ $modules = module_implements('permission');
+ $this->assertTrue(in_array('module_test', $modules), 'module_test implements hook_permission.');
+
+ $modules = module_implements('module_implements_alter');
+ $this->assertTrue(in_array('module_test', $modules), 'module_test implements hook_module_implements_alter().');
+
+ // Assert that module_test.implementations.inc is not included yet.
+ $this->assertFalse(function_exists('module_test_altered_test_hook'),
+ 'The file module_test.implementations.inc is not included yet.');
+
+ // Assert that module_test_module_implements_alter(*, 'altered_test_hook')
+ // has added an implementation
+ $this->assertTrue(in_array('module_test', module_implements('altered_test_hook')),
+ 'module_test implements hook_altered_test_hook().');
+
+ // Assert that module_test.implementations.inc was included as part of the process.
+ $this->assertTrue(function_exists('module_test_altered_test_hook'),
+ 'The file module_test.implementations.inc was included.');
+ }
+
+}
diff --git a/modules/simpletest/tests/module_test.implementations.inc b/modules/simpletest/tests/module_test.implementations.inc
new file mode 100644
index 0000000..63c866e
--- /dev/null
+++ b/modules/simpletest/tests/module_test.implementations.inc
@@ -0,0 +1,10 @@
+assertResponse(200);
}
+ /**
+ * Tests that empty session IDs do not cause unrelated sessions to load.
+ */
+ public function testEmptySessionId() {
+ global $is_https;
+
+ if ($is_https) {
+ $secure_session_name = session_name();
+ }
+ else {
+ $secure_session_name = 'S' . session_name();
+ }
+
+ // Enable mixed mode for HTTP and HTTPS.
+ variable_set('https', TRUE);
+
+ $admin_user = $this->drupalCreateUser(array('access administration pages'));
+ $standard_user = $this->drupalCreateUser(array('access content'));
+
+ // First log in as the admin user on HTTP.
+ // We cannot use $this->drupalLogin() here because we need to use the
+ // special http.php URLs.
+ $edit = array(
+ 'name' => $admin_user->name,
+ 'pass' => $admin_user->pass_raw
+ );
+ $this->drupalGet('user');
+ $form = $this->xpath('//form[@id="user-login"]');
+ $form[0]['action'] = $this->httpUrl('user');
+ $this->drupalPost(NULL, $edit, t('Log in'));
+
+ $this->curlClose();
+
+ // Now start a session for the standard user on HTTPS.
+ $edit = array(
+ 'name' => $standard_user->name,
+ 'pass' => $standard_user->pass_raw
+ );
+ $this->drupalGet('user');
+ $form = $this->xpath('//form[@id="user-login"]');
+ $form[0]['action'] = $this->httpsUrl('user');
+ $this->drupalPost(NULL, $edit, t('Log in'));
+
+ // Make the secure session cookie blank.
+ curl_setopt($this->curlHandle, CURLOPT_COOKIE, "$secure_session_name=");
+ $this->drupalGet($this->httpsUrl('user'));
+ $this->assertNoText($admin_user->name, 'User is not logged in as admin');
+ $this->assertNoText($standard_user->name, "The user's own name is not displayed because the invalid session cookie has logged them out.");
+ }
+
/**
* Test that there exists a session with two specific session IDs.
*
diff --git a/modules/simpletest/tests/session_test.info b/modules/simpletest/tests/session_test.info
index ece374e..da76cac 100644
--- a/modules/simpletest/tests/session_test.info
+++ b/modules/simpletest/tests/session_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/simpletest/tests/system_dependencies_test.info b/modules/simpletest/tests/system_dependencies_test.info
index 48c2077..e5fa625 100644
--- a/modules/simpletest/tests/system_dependencies_test.info
+++ b/modules/simpletest/tests/system_dependencies_test.info
@@ -6,8 +6,8 @@ core = 7.x
hidden = TRUE
dependencies[] = _missing_dependency
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info b/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info
index 57b2300..4c0fd9e 100644
--- a/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info
+++ b/modules/simpletest/tests/system_incompatible_core_version_dependencies_test.info
@@ -6,8 +6,8 @@ core = 7.x
hidden = TRUE
dependencies[] = system_incompatible_core_version_test
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/simpletest/tests/system_incompatible_core_version_test.info b/modules/simpletest/tests/system_incompatible_core_version_test.info
index 5fc1451..01d0bda 100644
--- a/modules/simpletest/tests/system_incompatible_core_version_test.info
+++ b/modules/simpletest/tests/system_incompatible_core_version_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 5.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info b/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info
index 7c5fa2b..d453ed1 100644
--- a/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info
+++ b/modules/simpletest/tests/system_incompatible_module_version_dependencies_test.info
@@ -7,8 +7,8 @@ hidden = TRUE
; system_incompatible_module_version_test declares version 1.0
dependencies[] = system_incompatible_module_version_test (>2.0)
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/simpletest/tests/system_incompatible_module_version_test.info b/modules/simpletest/tests/system_incompatible_module_version_test.info
index 136dc55..9e59bc8 100644
--- a/modules/simpletest/tests/system_incompatible_module_version_test.info
+++ b/modules/simpletest/tests/system_incompatible_module_version_test.info
@@ -5,8 +5,8 @@ version = 1.0
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/simpletest/tests/system_project_namespace_test.info b/modules/simpletest/tests/system_project_namespace_test.info
new file mode 100644
index 0000000..3a51de4
--- /dev/null
+++ b/modules/simpletest/tests/system_project_namespace_test.info
@@ -0,0 +1,13 @@
+name = "System project namespace test"
+description = "Support module for testing project namespace dependencies."
+package = Testing
+version = VERSION
+core = 7.x
+hidden = TRUE
+dependencies[] = drupal:filter
+
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
+project = "drupal"
+datestamp = "1475694174"
+
diff --git a/modules/simpletest/tests/system_project_namespace_test.module b/modules/simpletest/tests/system_project_namespace_test.module
new file mode 100644
index 0000000..b3d9bbc
--- /dev/null
+++ b/modules/simpletest/tests/system_project_namespace_test.module
@@ -0,0 +1 @@
+ MENU_CALLBACK,
);
+ $items['system-test/drupal-get-filename'] = array(
+ 'title' => 'Test drupal_get_filename()',
+ 'page callback' => 'system_test_drupal_get_filename',
+ 'access callback' => TRUE,
+ 'type' => MENU_CALLBACK,
+ );
+
+ $items['system-test/drupal-get-filename-with-schema-rebuild'] = array(
+ 'title' => 'Test drupal_get_filename() with a schema rebuild',
+ 'page callback' => 'system_test_drupal_get_filename_with_schema_rebuild',
+ 'access callback' => TRUE,
+ 'type' => MENU_CALLBACK,
+ );
+
return $items;
}
@@ -296,6 +310,9 @@ function system_test_system_info_alter(&$info, $file, $type) {
}
}
+ if ($file->name == 'system_project_namespace_test') {
+ $info['hidden'] = FALSE;
+ }
// Make the system_dependencies_test visible by default.
if ($file->name == 'system_dependencies_test') {
$info['hidden'] = FALSE;
@@ -479,3 +496,76 @@ function system_test_request_destination() {
// information.
exit;
}
+
+/**
+ * Page callback to run drupal_get_filename() on a particular module.
+ */
+function system_test_drupal_get_filename() {
+ // Prevent SimpleTest from failing as a result of the expected PHP warnings
+ // this function causes. Any warnings will be recorded in the database logs
+ // for examination by the tests.
+ define('SIMPLETEST_COLLECT_ERRORS', FALSE);
+
+ $module_name = variable_get('system_test_drupal_get_filename_test_module_name');
+ drupal_get_filename('module', $module_name);
+
+ return '';
+}
+
+/**
+ * Page callback to run drupal_get_filename() and do a schema rebuild.
+ */
+function system_test_drupal_get_filename_with_schema_rebuild() {
+ // Prevent SimpleTest from failing as a result of the expected PHP warnings
+ // this function causes.
+ define('SIMPLETEST_COLLECT_ERRORS', FALSE);
+
+ // Record the original database tables from drupal_get_schema().
+ variable_set('system_test_drupal_get_filename_with_schema_rebuild_original_tables', array_keys(drupal_get_schema(NULL, TRUE)));
+
+ // Trigger system_test_schema() and system_test_watchdog() to perform an
+ // attempted recursive rebuild when drupal_get_schema() is called. See
+ // BootstrapGetFilenameWebTestCase::testRecursiveRebuilds().
+ variable_set('system_test_drupal_get_filename_attempt_recursive_rebuild', TRUE);
+ drupal_get_schema(NULL, TRUE);
+
+ return '';
+}
+
+/**
+ * Implements hook_watchdog().
+ */
+function system_test_watchdog($log_entry) {
+ // If an attempted recursive schema rebuild has been triggered by
+ // system_test_drupal_get_filename_with_schema_rebuild(), perform the rebuild
+ // in response to the missing file message triggered by system_test_schema().
+ if (!variable_get('system_test_drupal_get_filename_attempt_recursive_rebuild')) {
+ return;
+ }
+ if ($log_entry['type'] != 'php' || $log_entry['severity'] != WATCHDOG_WARNING) {
+ return;
+ }
+ $module_name = variable_get('system_test_drupal_get_filename_test_module_name');
+ if (!isset($log_entry['variables']['!message']) || strpos($log_entry['variables']['!message'], format_string('The following module is missing from the file system: %name', array('%name' => $module_name))) === FALSE) {
+ return;
+ }
+ variable_set('system_test_drupal_get_filename_with_schema_rebuild_final_tables', array_keys(drupal_get_schema()));
+}
+
+/**
+ * Implements hook_module_implements_alter().
+ */
+function system_test_module_implements_alter(&$implementations, $hook) {
+ // For BootstrapGetFilenameWebTestCase::testRecursiveRebuilds() to work
+ // correctly, this module's hook_schema() implementation cannot be either the
+ // first implementation (since that would trigger a potential recursive
+ // rebuild before anything is in the drupal_get_schema() cache) or the last
+ // implementation (since that would trigger a potential recursive rebuild
+ // after the cache is already complete). So put it somewhere in the middle.
+ if ($hook == 'schema') {
+ $group = $implementations['system_test'];
+ unset($implementations['system_test']);
+ $count = count($implementations);
+ $implementations = array_merge(array_slice($implementations, 0, $count / 2, TRUE), array('system_test' => $group), array_slice($implementations, $count / 2, NULL, TRUE));
+ }
+}
diff --git a/modules/simpletest/tests/taxonomy_test.info b/modules/simpletest/tests/taxonomy_test.info
index 2e204b5..d5e1235 100644
--- a/modules/simpletest/tests/taxonomy_test.info
+++ b/modules/simpletest/tests/taxonomy_test.info
@@ -6,8 +6,8 @@ core = 7.x
hidden = TRUE
dependencies[] = taxonomy
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/simpletest/tests/theme_test.info b/modules/simpletest/tests/theme_test.info
index 6e0a5c1..57fc7f4 100644
--- a/modules/simpletest/tests/theme_test.info
+++ b/modules/simpletest/tests/theme_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info b/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info
index cf301ad..e473e69 100644
--- a/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info
+++ b/modules/simpletest/tests/themes/test_basetheme/test_basetheme.info
@@ -6,8 +6,8 @@ hidden = TRUE
settings[basetheme_only] = base theme value
settings[subtheme_override] = base theme value
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info b/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info
index f2fcf5c..2527b61 100644
--- a/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info
+++ b/modules/simpletest/tests/themes/test_subtheme/test_subtheme.info
@@ -6,8 +6,8 @@ hidden = TRUE
settings[subtheme_override] = subtheme value
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/simpletest/tests/themes/test_theme/test_theme.info b/modules/simpletest/tests/themes/test_theme/test_theme.info
index b4fed8a..b11f6c1 100644
--- a/modules/simpletest/tests/themes/test_theme/test_theme.info
+++ b/modules/simpletest/tests/themes/test_theme/test_theme.info
@@ -17,8 +17,8 @@ stylesheets[all][] = system.base.css
settings[theme_test_setting] = default value
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/simpletest/tests/update_script_test.info b/modules/simpletest/tests/update_script_test.info
index 7d6e4a6..f0ada88 100644
--- a/modules/simpletest/tests/update_script_test.info
+++ b/modules/simpletest/tests/update_script_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/simpletest/tests/update_script_test.install b/modules/simpletest/tests/update_script_test.install
index 6955ef1..4024fb4 100644
--- a/modules/simpletest/tests/update_script_test.install
+++ b/modules/simpletest/tests/update_script_test.install
@@ -31,6 +31,19 @@ function update_script_test_requirements($phase) {
'severity' => REQUIREMENT_ERROR,
);
break;
+ case REQUIREMENT_INFO:
+ $requirements['update_script_test_stop'] = array(
+ 'title' => 'Update script test stop',
+ 'value' => 'Error',
+ 'description' => 'This is a requirements error provided by the update_script_test module to stop the page redirect for the info.',
+ 'severity' => REQUIREMENT_ERROR,
+ );
+ $requirements['update_script_test'] = array(
+ 'title' => 'Update script test',
+ 'description' => 'This is a requirements info provided by the update_script_test module.',
+ 'severity' => REQUIREMENT_INFO,
+ );
+ break;
}
}
diff --git a/modules/simpletest/tests/update_test_1.info b/modules/simpletest/tests/update_test_1.info
index fb345e7..79b2a83 100644
--- a/modules/simpletest/tests/update_test_1.info
+++ b/modules/simpletest/tests/update_test_1.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/simpletest/tests/update_test_2.info b/modules/simpletest/tests/update_test_2.info
index fb345e7..79b2a83 100644
--- a/modules/simpletest/tests/update_test_2.info
+++ b/modules/simpletest/tests/update_test_2.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/simpletest/tests/update_test_3.info b/modules/simpletest/tests/update_test_3.info
index fb345e7..79b2a83 100644
--- a/modules/simpletest/tests/update_test_3.info
+++ b/modules/simpletest/tests/update_test_3.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/simpletest/tests/upgrade/drupal-6.filled.database.php b/modules/simpletest/tests/upgrade/drupal-6.filled.database.php
index a916281..10b9040 100644
--- a/modules/simpletest/tests/upgrade/drupal-6.filled.database.php
+++ b/modules/simpletest/tests/upgrade/drupal-6.filled.database.php
@@ -19919,7 +19919,7 @@ db_insert('vocabulary')->fields(array(
'vid' => '1',
'name' => 'vocabulary 1 (i=0)',
'description' => 'description of vocabulary 1 (i=0)',
- 'help' => '',
+ 'help' => 'help for vocabulary 1 (i=0)',
'relations' => '1',
'hierarchy' => '0',
'multiple' => '0',
@@ -19932,7 +19932,7 @@ db_insert('vocabulary')->fields(array(
'vid' => '2',
'name' => 'vocabulary 2 (i=1)',
'description' => 'description of vocabulary 2 (i=1)',
- 'help' => '',
+ 'help' => 'help for vocabulary 2 (i=1)',
'relations' => '1',
'hierarchy' => '1',
'multiple' => '1',
@@ -19945,7 +19945,7 @@ db_insert('vocabulary')->fields(array(
'vid' => '3',
'name' => 'vocabulary 3 (i=2)',
'description' => 'description of vocabulary 3 (i=2)',
- 'help' => '',
+ 'help' => 'help for vocabulary 3 (i=2)',
'relations' => '1',
'hierarchy' => '2',
'multiple' => '0',
@@ -19958,7 +19958,7 @@ db_insert('vocabulary')->fields(array(
'vid' => '4',
'name' => 'vocabulary 4 (i=3)',
'description' => 'description of vocabulary 4 (i=3)',
- 'help' => '',
+ 'help' => 'help for vocabulary 4 (i=3)',
'relations' => '1',
'hierarchy' => '0',
'multiple' => '1',
@@ -19971,7 +19971,7 @@ db_insert('vocabulary')->fields(array(
'vid' => '5',
'name' => 'vocabulary 5 (i=4)',
'description' => 'description of vocabulary 5 (i=4)',
- 'help' => '',
+ 'help' => 'help for vocabulary 5 (i=4)',
'relations' => '1',
'hierarchy' => '1',
'multiple' => '0',
@@ -19984,7 +19984,7 @@ db_insert('vocabulary')->fields(array(
'vid' => '6',
'name' => 'vocabulary 6 (i=5)',
'description' => 'description of vocabulary 6 (i=5)',
- 'help' => '',
+ 'help' => 'help for vocabulary 6 (i=5)',
'relations' => '1',
'hierarchy' => '2',
'multiple' => '1',
@@ -19997,7 +19997,7 @@ db_insert('vocabulary')->fields(array(
'vid' => '7',
'name' => 'vocabulary 7 (i=6)',
'description' => 'description of vocabulary 7 (i=6)',
- 'help' => '',
+ 'help' => 'help for vocabulary 7 (i=6)',
'relations' => '1',
'hierarchy' => '0',
'multiple' => '0',
@@ -20010,7 +20010,7 @@ db_insert('vocabulary')->fields(array(
'vid' => '8',
'name' => 'vocabulary 8 (i=7)',
'description' => 'description of vocabulary 8 (i=7)',
- 'help' => '',
+ 'help' => 'help for vocabulary 8 (i=7)',
'relations' => '1',
'hierarchy' => '1',
'multiple' => '1',
@@ -20023,7 +20023,7 @@ db_insert('vocabulary')->fields(array(
'vid' => '9',
'name' => 'vocabulary 9 (i=8)',
'description' => 'description of vocabulary 9 (i=8)',
- 'help' => '',
+ 'help' => 'help for vocabulary 9 (i=8)',
'relations' => '1',
'hierarchy' => '2',
'multiple' => '0',
@@ -20036,7 +20036,7 @@ db_insert('vocabulary')->fields(array(
'vid' => '10',
'name' => 'vocabulary 10 (i=9)',
'description' => 'description of vocabulary 10 (i=9)',
- 'help' => '',
+ 'help' => 'help for vocabulary 10 (i=9)',
'relations' => '1',
'hierarchy' => '0',
'multiple' => '1',
@@ -20049,7 +20049,7 @@ db_insert('vocabulary')->fields(array(
'vid' => '11',
'name' => 'vocabulary 11 (i=10)',
'description' => 'description of vocabulary 11 (i=10)',
- 'help' => '',
+ 'help' => 'help for vocabulary 11 (i=10)',
'relations' => '1',
'hierarchy' => '1',
'multiple' => '0',
@@ -20062,7 +20062,7 @@ db_insert('vocabulary')->fields(array(
'vid' => '12',
'name' => 'vocabulary 12 (i=11)',
'description' => 'description of vocabulary 12 (i=11)',
- 'help' => '',
+ 'help' => 'help for vocabulary 12 (i=11)',
'relations' => '1',
'hierarchy' => '2',
'multiple' => '1',
@@ -20075,7 +20075,7 @@ db_insert('vocabulary')->fields(array(
'vid' => '13',
'name' => 'vocabulary 13 (i=12)',
'description' => 'description of vocabulary 13 (i=12)',
- 'help' => '',
+ 'help' => 'help for vocabulary 13 (i=12)',
'relations' => '1',
'hierarchy' => '0',
'multiple' => '0',
@@ -20088,7 +20088,7 @@ db_insert('vocabulary')->fields(array(
'vid' => '14',
'name' => 'vocabulary 14 (i=13)',
'description' => 'description of vocabulary 14 (i=13)',
- 'help' => '',
+ 'help' => 'help for vocabulary 14 (i=13)',
'relations' => '1',
'hierarchy' => '1',
'multiple' => '1',
@@ -20101,7 +20101,7 @@ db_insert('vocabulary')->fields(array(
'vid' => '15',
'name' => 'vocabulary 15 (i=14)',
'description' => 'description of vocabulary 15 (i=14)',
- 'help' => '',
+ 'help' => 'help for vocabulary 15 (i=14)',
'relations' => '1',
'hierarchy' => '2',
'multiple' => '0',
@@ -20114,7 +20114,7 @@ db_insert('vocabulary')->fields(array(
'vid' => '16',
'name' => 'vocabulary 16 (i=15)',
'description' => 'description of vocabulary 16 (i=15)',
- 'help' => '',
+ 'help' => 'help for vocabulary 16 (i=15)',
'relations' => '1',
'hierarchy' => '0',
'multiple' => '1',
@@ -20127,7 +20127,7 @@ db_insert('vocabulary')->fields(array(
'vid' => '17',
'name' => 'vocabulary 17 (i=16)',
'description' => 'description of vocabulary 17 (i=16)',
- 'help' => '',
+ 'help' => 'help for vocabulary 17 (i=16)',
'relations' => '1',
'hierarchy' => '1',
'multiple' => '0',
@@ -20140,7 +20140,7 @@ db_insert('vocabulary')->fields(array(
'vid' => '18',
'name' => 'vocabulary 18 (i=17)',
'description' => 'description of vocabulary 18 (i=17)',
- 'help' => '',
+ 'help' => 'help for vocabulary 18 (i=17)',
'relations' => '1',
'hierarchy' => '2',
'multiple' => '1',
@@ -20153,7 +20153,7 @@ db_insert('vocabulary')->fields(array(
'vid' => '19',
'name' => 'vocabulary 19 (i=18)',
'description' => 'description of vocabulary 19 (i=18)',
- 'help' => '',
+ 'help' => 'help for vocabulary 19 (i=18)',
'relations' => '1',
'hierarchy' => '0',
'multiple' => '0',
@@ -20166,7 +20166,7 @@ db_insert('vocabulary')->fields(array(
'vid' => '20',
'name' => 'vocabulary 20 (i=19)',
'description' => 'description of vocabulary 20 (i=19)',
- 'help' => '',
+ 'help' => 'help for vocabulary 20 (i=19)',
'relations' => '1',
'hierarchy' => '1',
'multiple' => '1',
@@ -20179,7 +20179,7 @@ db_insert('vocabulary')->fields(array(
'vid' => '21',
'name' => 'vocabulary 21 (i=20)',
'description' => 'description of vocabulary 21 (i=20)',
- 'help' => '',
+ 'help' => 'help for vocabulary 21 (i=20)',
'relations' => '1',
'hierarchy' => '2',
'multiple' => '0',
@@ -20192,7 +20192,7 @@ db_insert('vocabulary')->fields(array(
'vid' => '22',
'name' => 'vocabulary 22 (i=21)',
'description' => 'description of vocabulary 22 (i=21)',
- 'help' => '',
+ 'help' => 'help for vocabulary 22 (i=21)',
'relations' => '1',
'hierarchy' => '0',
'multiple' => '1',
@@ -20205,7 +20205,7 @@ db_insert('vocabulary')->fields(array(
'vid' => '23',
'name' => 'vocabulary 23 (i=22)',
'description' => 'description of vocabulary 23 (i=22)',
- 'help' => '',
+ 'help' => 'help for vocabulary 23 (i=22)',
'relations' => '1',
'hierarchy' => '1',
'multiple' => '0',
@@ -20218,7 +20218,7 @@ db_insert('vocabulary')->fields(array(
'vid' => '24',
'name' => 'vocabulary 24 (i=23)',
'description' => 'description of vocabulary 24 (i=23)',
- 'help' => '',
+ 'help' => 'help for vocabulary 24 (i=23)',
'relations' => '1',
'hierarchy' => '2',
'multiple' => '1',
diff --git a/modules/simpletest/tests/upgrade/drupal-6.upload.database.php b/modules/simpletest/tests/upgrade/drupal-6.upload.database.php
index 46ebe2c..3fd602a 100644
--- a/modules/simpletest/tests/upgrade/drupal-6.upload.database.php
+++ b/modules/simpletest/tests/upgrade/drupal-6.upload.database.php
@@ -127,6 +127,38 @@ db_insert('files')->fields(array(
'status' => '1',
'timestamp' => '1285708958',
))
+// On some Drupal 6 sites, more than one file can have the same filepath. See
+// https://www.drupal.org/node/1260938.
+->values(array(
+ 'fid' => '12',
+ 'uid' => '1',
+ 'filename' => 'duplicate-name.png',
+ 'filepath' => 'sites/default/files/duplicate-name.png',
+ 'filemime' => 'image/png',
+ 'filesize' => '314',
+ 'status' => '1',
+ 'timestamp' => '1285708958',
+))
+->values(array(
+ 'fid' => '13',
+ 'uid' => '1',
+ 'filename' => 'duplicate-name.png',
+ 'filepath' => 'sites/default/files/duplicate-name.png',
+ 'filemime' => 'image/png',
+ 'filesize' => '315',
+ 'status' => '1',
+ 'timestamp' => '1285708958',
+))
+->values(array(
+ 'fid' => '14',
+ 'uid' => '1',
+ 'filename' => 'duplicate-name.png',
+ 'filepath' => 'sites/default/files/duplicate-name.png',
+ 'filemime' => 'image/png',
+ 'filesize' => '316',
+ 'status' => '1',
+ 'timestamp' => '1285708958',
+))
->execute();
db_insert('node')->fields(array(
@@ -196,6 +228,23 @@ db_insert('node')->fields(array(
'sticky' => '0',
'tnid' => '0',
'translate' => '0',
+))
+->values(array(
+ 'nid' => '41',
+ 'vid' => '55',
+ 'type' => 'page',
+ 'language' => '',
+ 'title' => 'node title 41 revision 55',
+ 'uid' => '1',
+ 'status' => '1',
+ 'created' => '1285709012',
+ 'changed' => '1285709012',
+ 'comment' => '0',
+ 'promote' => '0',
+ 'moderate' => '0',
+ 'sticky' => '0',
+ 'tnid' => '0',
+ 'translate' => '0',
))
->execute();
@@ -253,6 +302,28 @@ db_insert('node_revisions')->fields(array(
'log' => '',
'timestamp' => '1285709012',
'format' => '1',
+))
+->values(array(
+ 'nid' => '41',
+ 'vid' => '54',
+ 'uid' => '1',
+ 'title' => 'node title 41 revision 54',
+ 'body' => "Attachments:\r\nduplicate-name.png",
+ 'teaser' => "Attachments:\r\nduplicate-name.png",
+ 'log' => '',
+ 'timestamp' => '1285709012',
+ 'format' => '1',
+))
+->values(array(
+ 'nid' => '41',
+ 'vid' => '55',
+ 'uid' => '1',
+ 'title' => 'node title 41 revision 55',
+ 'body' => "Attachments:\r\nduplicate-name.png\r\nduplicate-name.png",
+ 'teaser' => "Attachments:\r\nduplicate-name.png\r\nduplicate-name.png",
+ 'log' => '',
+ 'timestamp' => '1285709012',
+ 'format' => '1',
))
->execute();
@@ -415,6 +486,30 @@ db_insert('upload')->fields(array(
'list' => '1',
'weight' => '0',
))
+->values(array(
+ 'fid' => '12',
+ 'nid' => '41',
+ 'vid' => '54',
+ 'description' => 'duplicate-name.png',
+ 'list' => '1',
+ 'weight' => '0',
+))
+->values(array(
+ 'fid' => '13',
+ 'nid' => '41',
+ 'vid' => '55',
+ 'description' => 'first description',
+ 'list' => '0',
+ 'weight' => '0',
+))
+->values(array(
+ 'fid' => '14',
+ 'nid' => '41',
+ 'vid' => '55',
+ 'description' => 'second description',
+ 'list' => '1',
+ 'weight' => '0',
+))
->execute();
// Add series of entries for invalid node vids to the {upload} table.
@@ -431,7 +526,7 @@ for ($i = 30; $i < 250; $i += 2) {
->values(array(
'fid' => $i,
'nid' => '40',
- 'vid' => 24 + $i,
+ 'vid' => 26 + $i,
'description' => 'crazy-basename.png',
'list' => '1',
'weight' => '0',
@@ -440,7 +535,7 @@ for ($i = 30; $i < 250; $i += 2) {
->values(array(
'fid' => 2,
'nid' => '40',
- 'vid' => 24 + $i + 1,
+ 'vid' => 26 + $i + 1,
'description' => 'crazy-basename.png',
'list' => '1',
'weight' => '0',
diff --git a/modules/simpletest/tests/upgrade/upgrade.taxonomy.test b/modules/simpletest/tests/upgrade/upgrade.taxonomy.test
index 58a4d5c..51402ed 100644
--- a/modules/simpletest/tests/upgrade/upgrade.taxonomy.test
+++ b/modules/simpletest/tests/upgrade/upgrade.taxonomy.test
@@ -74,9 +74,10 @@ class UpgradePathTaxonomyTestCase extends UpgradePathTestCase {
$this->assertEqual($voc_keys, $inst_keys, 'Node type page has instances for every vocabulary.');
// Ensure instance variables are getting through.
- foreach ($instances as $instance) {
- $this->assertTrue(isset($instance['required']), 'The required setting was preserved during the upgrade path.');
- $this->assertTrue($instance['description'], 'The description was preserved during the upgrade path');
+ foreach (array_unique($instances) as $instance) {
+ $field_instance = field_info_instance('node', $instance, 'page');
+ $this->assertTrue(isset($field_instance['required']), 'The required setting was preserved during the upgrade path.');
+ $this->assertTrue($field_instance['description'], 'The description was preserved during the upgrade path');
}
// Node type 'story' was not explicitly in $vocabulary->nodes but
diff --git a/modules/simpletest/tests/upgrade/upgrade.upload.test b/modules/simpletest/tests/upgrade/upgrade.upload.test
index be352bd..dfa94a0 100644
--- a/modules/simpletest/tests/upgrade/upgrade.upload.test
+++ b/modules/simpletest/tests/upgrade/upgrade.upload.test
@@ -64,12 +64,35 @@ class UploadUpgradePathTestCase extends UpgradePathTestCase {
}
$this->assertIdentical($filenames, $recorded_filenames, 'The uploaded files are present in the same order after the upgrade.');
}
+
// Test for the file with repeating basename to only have the streaming
// path replaced.
$node = node_load(40, 53);
$repeated_basename_file = $node->upload[LANGUAGE_NONE][4];
$this->assertEqual($repeated_basename_file['uri'], 'private://drupal-6/file/directory/path/crazy-basename.png', "The file with the repeated basename path only had the stream portion replaced");
+ // Ensure that filepaths are deduplicated.
+ $node0 = node_load(41, 54);
+ $node1 = node_load(41, 55);
+ // Ensure that both revisions point to the same file ID.
+ $items0 = field_get_items('node', $node0, 'upload');
+ $this->assertEqual(count($items0), 1);
+ $items1 = field_get_items('node', $node1, 'upload');
+ $this->assertEqual(count($items1), 2);
+ $this->assertEqual($items0[0]['fid'], $items1[0]['fid']);
+ $this->assertEqual($items0[0]['fid'], $items1[1]['fid']);
+ // The revision with more than one reference to the same file should retain
+ // the original settings for each reference.
+ $this->assertEqual($items1[0]['description'], 'first description');
+ $this->assertEqual($items1[0]['display'], 0);
+ $this->assertEqual($items1[1]['description'], 'second description');
+ $this->assertEqual($items1[1]['display'], 1);
+ // Ensure that the latest version of the files are used.
+ $this->assertEqual($items1[0]['filesize'], 316);
+ $this->assertEqual($items1[1]['filesize'], 316);
+ // No duplicate files should remain on the Drupal 7 site.
+ $this->assertEqual(0, db_query("SELECT COUNT(*) FROM {file_managed} GROUP BY uri HAVING COUNT(fid) > 1")->fetchField());
+
// Make sure the file settings were properly migrated.
$d6_file_directory_temp = '/drupal-6/file/directory/temp';
$d6_file_directory_path = '/drupal-6/file/directory/path';
diff --git a/modules/simpletest/tests/url_alter_test.info b/modules/simpletest/tests/url_alter_test.info
index 4d4f84b..4a6d215 100644
--- a/modules/simpletest/tests/url_alter_test.info
+++ b/modules/simpletest/tests/url_alter_test.info
@@ -5,8 +5,8 @@ package = Testing
version = VERSION
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/simpletest/tests/xmlrpc.test b/modules/simpletest/tests/xmlrpc.test
index 1a9ef23..bb74f05 100644
--- a/modules/simpletest/tests/xmlrpc.test
+++ b/modules/simpletest/tests/xmlrpc.test
@@ -246,4 +246,38 @@ class XMLRPCMessagesTestCase extends DrupalWebTestCase {
$this->assertEqual($removed, 'system.methodSignature', 'Hiding builting system.methodSignature with hook_xmlrpc_alter works');
}
+ /**
+ * Test limits on system.multicall that can prevent brute-force attacks.
+ */
+ function testMulticallLimit() {
+ $url = url(NULL, array('absolute' => TRUE)) . 'xmlrpc.php';
+ $multicall_args = array();
+ $num_method_calls = 10;
+ for ($i = 0; $i < $num_method_calls; $i++) {
+ $struct = array('i' => $i);
+ $multicall_args[] = array('methodName' => 'validator1.echoStructTest', 'params' => array($struct));
+ }
+ // Test limits of 1, 5, 9, 13.
+ for ($limit = 1; $limit < $num_method_calls + 4; $limit += 4) {
+ variable_set('xmlrpc_multicall_duplicate_method_limit', $limit);
+ $results = xmlrpc($url, array('system.multicall' => array($multicall_args)));
+ $this->assertEqual($num_method_calls, count($results));
+ for ($i = 0; $i < min($limit, $num_method_calls); $i++) {
+ $x = array_shift($results);
+ $this->assertTrue(empty($x->is_error), "Result $i is not an error");
+ $this->assertEqual($multicall_args[$i]['params'][0], $x);
+ }
+ for (; $i < $num_method_calls; $i++) {
+ $x = array_shift($results);
+ $this->assertFalse(empty($x->is_error), "Result $i is an error");
+ $this->assertEqual(-156579, $x->code);
+ }
+ }
+ variable_set('xmlrpc_multicall_duplicate_method_limit', -1);
+ $results = xmlrpc($url, array('system.multicall' => array($multicall_args)));
+ $this->assertEqual($num_method_calls, count($results));
+ foreach ($results as $i => $x) {
+ $this->assertTrue(empty($x->is_error), "Result $i is not an error");
+ }
+ }
}
diff --git a/modules/simpletest/tests/xmlrpc_test.info b/modules/simpletest/tests/xmlrpc_test.info
index 1a2df82..28f96bb 100644
--- a/modules/simpletest/tests/xmlrpc_test.info
+++ b/modules/simpletest/tests/xmlrpc_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/statistics/statistics.info b/modules/statistics/statistics.info
index 38a1dd2..bcabcf0 100644
--- a/modules/statistics/statistics.info
+++ b/modules/statistics/statistics.info
@@ -6,8 +6,8 @@ core = 7.x
files[] = statistics.test
configure = admin/config/system/statistics
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/statistics/statistics.module b/modules/statistics/statistics.module
index f665a14..b2561d2 100644
--- a/modules/statistics/statistics.module
+++ b/modules/statistics/statistics.module
@@ -118,10 +118,9 @@ function statistics_node_view($node, $view_mode) {
// Attach Ajax node count statistics if configured.
if (variable_get('statistics_count_content_views', 0) && variable_get('statistics_count_content_views_ajax', 0)) {
if (!empty($node->nid) && $view_mode == 'full' && node_is_page($node) && empty($node->in_preview)) {
- $node->content['#attached']['js'] = array(
- drupal_get_path('module', 'statistics') . '/statistics.js' => array(
- 'scope' => 'footer'
- ),
+ $statistics = drupal_get_path('module', 'statistics') . '/statistics.js';
+ $node->content['#attached']['js'][$statistics] = array(
+ 'scope' => 'footer',
);
$settings = array('data' => array('nid' => $node->nid), 'url' => url(drupal_get_path('module', 'statistics') . '/statistics.php'));
$node->content['#attached']['js'][] = array(
diff --git a/modules/statistics/statistics.php b/modules/statistics/statistics.php
index f00e039..48340c8 100644
--- a/modules/statistics/statistics.php
+++ b/modules/statistics/statistics.php
@@ -15,17 +15,19 @@ chdir(DRUPAL_ROOT);
include_once DRUPAL_ROOT . '/includes/bootstrap.inc';
drupal_bootstrap(DRUPAL_BOOTSTRAP_VARIABLES);
if (variable_get('statistics_count_content_views', 0) && variable_get('statistics_count_content_views_ajax', 0)) {
- $nid = $_POST['nid'];
- if (is_numeric($nid)) {
- db_merge('node_counter')
- ->key(array('nid' => $nid))
- ->fields(array(
- 'daycount' => 1,
- 'totalcount' => 1,
- 'timestamp' => REQUEST_TIME,
- ))
- ->expression('daycount', 'daycount + 1')
- ->expression('totalcount', 'totalcount + 1')
- ->execute();
+ if (isset($_POST['nid'])) {
+ $nid = $_POST['nid'];
+ if (is_numeric($nid)) {
+ db_merge('node_counter')
+ ->key(array('nid' => $nid))
+ ->fields(array(
+ 'daycount' => 1,
+ 'totalcount' => 1,
+ 'timestamp' => REQUEST_TIME,
+ ))
+ ->expression('daycount', 'daycount + 1')
+ ->expression('totalcount', 'totalcount + 1')
+ ->execute();
+ }
}
}
diff --git a/modules/statistics/statistics.test b/modules/statistics/statistics.test
index 7e038d6..50accd7 100644
--- a/modules/statistics/statistics.test
+++ b/modules/statistics/statistics.test
@@ -35,7 +35,7 @@ class StatisticsTestCase extends DrupalWebTestCase {
'title' => 'test',
'path' => 'node/1',
'url' => 'http://example.com',
- 'hostname' => '192.168.1.1',
+ 'hostname' => '1.2.3.3',
'uid' => 0,
'sid' => 10,
'timer' => 10,
@@ -268,7 +268,7 @@ class StatisticsBlockVisitorsTestCase extends StatisticsTestCase {
*/
function testIPAddressBlocking() {
// IP address for testing.
- $test_ip_address = '192.168.1.1';
+ $test_ip_address = '1.2.3.3';
// Verify the IP address from accesslog appears on the top visitors page
// and that a 'block IP address' link is displayed.
diff --git a/modules/syslog/syslog.info b/modules/syslog/syslog.info
index 4565bda..91bd74f 100644
--- a/modules/syslog/syslog.info
+++ b/modules/syslog/syslog.info
@@ -6,8 +6,8 @@ core = 7.x
files[] = syslog.test
configure = admin/config/development/logging
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/system/image.gd.inc b/modules/system/image.gd.inc
index 913b0de..3d0797e 100644
--- a/modules/system/image.gd.inc
+++ b/modules/system/image.gd.inc
@@ -116,38 +116,62 @@ function image_gd_rotate(stdClass $image, $degrees, $background = NULL) {
return FALSE;
}
- $width = $image->info['width'];
- $height = $image->info['height'];
+ // PHP 5.5 GD bug: https://bugs.php.net/bug.php?id=65148: To prevent buggy
+ // behavior on negative multiples of 90 degrees we convert any negative
+ // angle to a positive one between 0 and 360 degrees.
+ $degrees -= floor($degrees / 360) * 360;
- // Convert the hexadecimal background value to a color index value.
+ // Convert the hexadecimal background value to a RGBA array.
if (isset($background)) {
- $rgb = array();
- for ($i = 16; $i >= 0; $i -= 8) {
- $rgb[] = (($background >> $i) & 0xFF);
- }
- $background = imagecolorallocatealpha($image->resource, $rgb[0], $rgb[1], $rgb[2], 0);
+ $background = array(
+ 'red' => $background >> 16 & 0xFF,
+ 'green' => $background >> 8 & 0xFF,
+ 'blue' => $background & 0xFF,
+ 'alpha' => 0,
+ );
}
- // Set the background color as transparent if $background is NULL.
else {
- // Get the current transparent color.
- $background = imagecolortransparent($image->resource);
-
- // If no transparent colors, use white.
- if ($background == 0) {
- $background = imagecolorallocatealpha($image->resource, 255, 255, 255, 0);
- }
+ // Background color is not specified: use transparent white as background.
+ $background = array(
+ 'red' => 255,
+ 'green' => 255,
+ 'blue' => 255,
+ 'alpha' => 127
+ );
}
+ // Store the color index for the background as that is what GD uses.
+ $background_idx = imagecolorallocatealpha($image->resource, $background['red'], $background['green'], $background['blue'], $background['alpha']);
+
// Images are assigned a new color palette when rotating, removing any
// transparency flags. For GIF images, keep a record of the transparent color.
if ($image->info['extension'] == 'gif') {
- $transparent_index = imagecolortransparent($image->resource);
- if ($transparent_index != 0) {
- $transparent_gif_color = imagecolorsforindex($image->resource, $transparent_index);
+ // GIF does not work with a transparency channel, but can define 1 color
+ // in its palette to act as transparent.
+
+ // Get the current transparent color, if any.
+ $gif_transparent_id = imagecolortransparent($image->resource);
+ if ($gif_transparent_id !== -1) {
+ // The gif already has a transparent color set: remember it to set it on
+ // the rotated image as well.
+ $transparent_gif_color = imagecolorsforindex($image->resource, $gif_transparent_id);
+
+ if ($background['alpha'] >= 127) {
+ // We want a transparent background: use the color already set to act
+ // as transparent, as background.
+ $background_idx = $gif_transparent_id;
+ }
+ }
+ else {
+ // The gif does not currently have a transparent color set.
+ if ($background['alpha'] >= 127) {
+ // But as the background is transparent, it should get one.
+ $transparent_gif_color = $background;
+ }
}
}
- $image->resource = imagerotate($image->resource, 360 - $degrees, $background);
+ $image->resource = imagerotate($image->resource, 360 - $degrees, $background_idx);
// GIFs need to reassign the transparent color after performing the rotate.
if (isset($transparent_gif_color)) {
diff --git a/modules/system/system.admin.inc b/modules/system/system.admin.inc
index 0f525c6..cdcc78f 100644
--- a/modules/system/system.admin.inc
+++ b/modules/system/system.admin.inc
@@ -1856,7 +1856,7 @@ function system_image_toolkit_settings() {
if (count($toolkits_available) == 0) {
variable_del('image_toolkit');
$form['image_toolkit_help'] = array(
- '#markup' => t("No image toolkits were detected. Drupal includes support for PHP's built-in image processing functions but they were not detected on this system. You should consult your system administrator to have them enabled, or try using a third party toolkit.", array('gd-link' => url('http://php.net/gd'))),
+ '#markup' => t("No image toolkits were detected. Drupal includes support for PHP's built-in image processing functions but they were not detected on this system. You should consult your system administrator to have them enabled, or try using a third party toolkit.", array('!gd-link' => url('http://php.net/gd'))),
);
return $form;
}
@@ -2202,6 +2202,11 @@ function system_add_date_format_type_form_submit($form, &$form_state) {
* Return the date for a given format string via Ajax.
*/
function system_date_time_lookup() {
+ // This callback is protected with a CSRF token because user input from the
+ // query string is reflected in the output.
+ if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], 'admin/config/regional/date-time/formats/lookup')) {
+ return MENU_ACCESS_DENIED;
+ }
$result = format_date(REQUEST_TIME, 'custom', $_GET['format']);
drupal_json_output($result);
}
@@ -2592,6 +2597,8 @@ function theme_status_report($variables) {
if (empty($requirement['#type'])) {
$severity = $severities[isset($requirement['severity']) ? (int) $requirement['severity'] : REQUIREMENT_OK];
$severity['icon'] = '' . $severity['title'] . '';
+ // The requirement's 'value' key is optional, provide a default value.
+ $requirement['value'] = isset($requirement['value']) ? $requirement['value'] : '';
// Output table row(s)
if (!empty($requirement['description'])) {
@@ -2875,13 +2882,14 @@ function system_date_time_formats() {
* Allow users to add additional date formats.
*/
function system_configure_date_formats_form($form, &$form_state, $dfid = 0) {
+ $ajax_path = 'admin/config/regional/date-time/formats/lookup';
$js_settings = array(
'type' => 'setting',
'data' => array(
'dateTime' => array(
'date-format' => array(
'text' => t('Displayed as'),
- 'lookup' => url('admin/config/regional/date-time/formats/lookup'),
+ 'lookup' => url($ajax_path, array('query' => array('token' => drupal_get_token($ajax_path)))),
),
),
),
diff --git a/modules/system/system.api.php b/modules/system/system.api.php
index 0af6156..3152139 100644
--- a/modules/system/system.api.php
+++ b/modules/system/system.api.php
@@ -113,21 +113,21 @@ function hook_hook_info_alter(&$hooks) {
* translation handlers. Array keys are the module names, array values
* can be any data structure the module uses to provide field translation.
* Any empty value disallows the module to appear as a translation handler.
- * - entity keys: An array describing how the Field API can extract the
- * information it needs from the objects of the type. Elements:
+ * - entity keys: (optional) An array describing how the Field API can extract
+ * the information it needs from the objects of the type. Elements:
* - id: The name of the property that contains the primary id of the
* entity. Every entity object passed to the Field API must have this
* property and its value must be numeric.
* - revision: The name of the property that contains the revision id of
* the entity. The Field API assumes that all revision ids are unique
* across all entities of a type. This entry can be omitted if the
- * entities of this type are not versionable.
+ * entities of this type are not versionable. Defaults to an empty string.
* - bundle: The name of the property that contains the bundle name for the
* entity. The bundle name defines which set of fields are attached to
* the entity (e.g. what nodes call "content type"). This entry can be
* omitted if this entity type exposes a single bundle (all entities have
* the same collection of fields). The name of this single bundle will be
- * the same as the entity type.
+ * the same as the entity type. Defaults to an empty string.
* - label: The name of the property that contains the entity label. For
* example, if the entity's label is located in $entity->subject, then
* 'subject' should be specified here. If complex logic is required to
@@ -606,7 +606,7 @@ function hook_cron() {
* @return
* An associative array where the key is the queue name and the value is
* again an associative array. Possible keys are:
- * - 'worker callback': A PHP callable to call that is an implementation of
+ * - 'worker callback': The name of an implementation of
* callback_queue_worker().
* - 'time': (optional) How much time Drupal should spend on calling this
* worker in seconds. Defaults to 15.
@@ -643,28 +643,6 @@ function hook_cron_queue_info_alter(&$queues) {
$queues['aggregator_feeds']['time'] = 90;
}
-/**
- * Work on a single queue item.
- *
- * Callback for hook_queue_info().
- *
- * @param $queue_item_data
- * The data that was passed to DrupalQueue::createItem() when the item was
- * queued.
- *
- * @throws \Exception
- * The worker callback may throw an exception to indicate there was a problem.
- * The cron process will log the exception, and leave the item in the queue to
- * be processed again later.
- *
- * @see drupal_cron_run()
- */
-function callback_queue_worker($queue_item_data) {
- $node = node_load($queue_item_data);
- $node->title = 'Updated title';
- $node->save();
-}
-
/**
* Allows modules to declare their own Form API element types and specify their
* default values.
@@ -1819,6 +1797,8 @@ function hook_form_BASE_FORM_ID_alter(&$form, &$form_state, $form_id) {
* the $form_id input matched your module's format for dynamically-generated
* form IDs, and if so, act appropriately.
*
+ * Third, forms defined in classes can be defined this way.
+ *
* @param $form_id
* The unique string identifying the desired form.
* @param $args
@@ -1829,19 +1809,22 @@ function hook_form_BASE_FORM_ID_alter(&$form, &$form_state, $form_id) {
* @return
* An associative array whose keys define form_ids and whose values are an
* associative array defining the following keys:
- * - callback: The name of the form builder function to invoke. This will be
- * used for the base form ID, for example, to target a base form using
- * hook_form_BASE_FORM_ID_alter().
+ * - callback: The callable returning the form array. If it is the name of
+ * the form builder function then this will be used for the base
+ * form ID, for example, to target a base form using
+ * hook_form_BASE_FORM_ID_alter(). Otherwise use the base_form_id key to
+ * define the base form ID.
* - callback arguments: (optional) Additional arguments to pass to the
* function defined in 'callback', which are prepended to $args.
- * - wrapper_callback: (optional) The name of a form builder function to
- * invoke before the form builder defined in 'callback' is invoked. This
- * wrapper callback may prepopulate the $form array with form elements,
- * which will then be already contained in the $form that is passed on to
- * the form builder defined in 'callback'. For example, a wrapper callback
- * could setup wizard-alike form buttons that are the same for a variety of
- * forms that belong to the wizard, which all share the same wrapper
- * callback.
+ * - base_form_id: The base form ID can be specified explicitly. This is
+ * required when callback is not the name of a function.
+ * - wrapper_callback: (optional) Any callable to invoke before the form
+ * builder defined in 'callback' is invoked. This wrapper callback may
+ * prepopulate the $form array with form elements, which will then be
+ * already contained in the $form that is passed on to the form builder
+ * defined in 'callback'. For example, a wrapper callback could setup
+ * wizard-like form buttons that are the same for a variety of forms that
+ * belong to the wizard, which all share the same wrapper callback.
*/
function hook_forms($form_id, $args) {
// Simply reroute the (non-existing) $form_id 'mymodule_first_form' to
@@ -1865,6 +1848,15 @@ function hook_forms($form_id, $args) {
'wrapper_callback' => 'mymodule_main_form_wrapper',
);
+ // Build a form with a static class callback.
+ $forms['mymodule_class_generated_form'] = array(
+ // This will call: MyClass::generateMainForm().
+ 'callback' => array('MyClass', 'generateMainForm'),
+ // The base_form_id is required when the callback is a static function in
+ // a class. This can also be used to keep newer code backwards compatible.
+ 'base_form_id' => 'mymodule_main_form',
+ );
+
return $forms;
}
@@ -2654,6 +2646,8 @@ function hook_flush_caches() {
* module_enable() for a detailed description of the order in which install and
* enable hooks are invoked.
*
+ * This hook should be implemented in a .module file, not in an .install file.
+ *
* @param $modules
* An array of the modules that were installed.
*
@@ -3195,7 +3189,9 @@ function hook_requirements($phase) {
* creation and alteration of the supported database engines.
*
* See the Schema API Handbook at http://drupal.org/node/146843 for details on
- * schema definition structures.
+ * schema definition structures. Note that foreign key definitions are for
+ * documentation purposes only; foreign keys are not created in the database,
+ * nor are they enforced by Drupal.
*
* @return array
* A schema definition structure array. For each element of the
@@ -3247,6 +3243,8 @@ function hook_schema() {
'nid_vid' => array('nid', 'vid'),
'vid' => array('vid'),
),
+ // For documentation purposes only; foreign keys are not created in the
+ // database.
'foreign keys' => array(
'node_revision' => array(
'table' => 'node_revision',
@@ -3715,8 +3713,9 @@ function hook_registry_files_alter(&$files, $modules) {
*
* Any tasks you define here will be run, in order, after the installer has
* finished the site configuration step but before it has moved on to the
- * final import of languages and the end of the installation. You can have any
- * number of custom tasks to perform during this phase.
+ * final import of languages and the end of the installation. This is invoked
+ * by install_tasks(). You can have any number of custom tasks to perform
+ * during this phase.
*
* Each task you define here corresponds to a callback function which you must
* separately define and which is called when your task is run. This function
@@ -3809,6 +3808,8 @@ function hook_registry_files_alter(&$files, $modules) {
*
* @see install_state_defaults()
* @see batch_set()
+ * @see hook_install_tasks_alter()
+ * @see install_tasks()
*/
function hook_install_tasks(&$install_state) {
// Here, we define a variable to allow tasks to indicate that a particular,
@@ -3911,6 +3912,8 @@ function hook_html_head_alter(&$head_elements) {
/**
* Alter the full list of installation tasks.
*
+ * This hook is invoked on the install profile in install_tasks().
+ *
* @param $tasks
* An array of all available installation tasks, including those provided by
* Drupal core. You can modify this array to change or replace any part of
@@ -3918,6 +3921,9 @@ function hook_html_head_alter(&$head_elements) {
* is selected.
* @param $install_state
* An array of information about the current installation state.
+ *
+ * @see hook_install_tasks()
+ * @see install_tasks()
*/
function hook_install_tasks_alter(&$tasks, $install_state) {
// Replace the "Choose language" installation task provided by Drupal core
@@ -4804,6 +4810,28 @@ function hook_filetransfer_info_alter(&$filetransfer_info) {
* @{
*/
+/**
+ * Work on a single queue item.
+ *
+ * Callback for hook_cron_queue_info().
+ *
+ * @param $queue_item_data
+ * The data that was passed to DrupalQueueInterface::createItem() when the
+ * item was queued.
+ *
+ * @throws Exception
+ * The worker callback may throw an exception to indicate there was a problem.
+ * The cron process will log the exception, and leave the item in the queue to
+ * be processed again later.
+ *
+ * @see drupal_cron_run()
+ */
+function callback_queue_worker($queue_item_data) {
+ $node = node_load($queue_item_data);
+ $node->title = 'Updated title';
+ node_save($node);
+}
+
/**
* Return the URI for an entity.
*
diff --git a/modules/system/system.info b/modules/system/system.info
index fc3f30c..d637f1c 100644
--- a/modules/system/system.info
+++ b/modules/system/system.info
@@ -12,8 +12,8 @@ files[] = system.test
required = TRUE
configure = admin/config/system
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/system/system.install b/modules/system/system.install
index 64c989a..ae55b89 100644
--- a/modules/system/system.install
+++ b/modules/system/system.install
@@ -196,6 +196,12 @@ function system_requirements($phase) {
);
}
+ // Test database-specific multi-byte UTF-8 related requirements.
+ $charset_requirements = _system_check_db_utf8mb4_requirements($phase);
+ if (!empty($charset_requirements)) {
+ $requirements['database_charset'] = $charset_requirements;
+ }
+
// Test PHP memory_limit
$memory_limit = ini_get('memory_limit');
$requirements['php_memory_limit'] = array(
@@ -517,6 +523,75 @@ function system_requirements($phase) {
return $requirements;
}
+/**
+ * Checks whether the requirements for multi-byte UTF-8 support are met.
+ *
+ * @param string $phase
+ * The hook_requirements() stage.
+ *
+ * @return array
+ * A requirements array with the result of the charset check.
+ */
+function _system_check_db_utf8mb4_requirements($phase) {
+ global $install_state;
+ // In the requirements check of the installer, skip the utf8mb4 check unless
+ // the database connection info has been preconfigured by hand with valid
+ // information before running the installer, as otherwise we cannot get a
+ // valid database connection object.
+ if (isset($install_state['settings_verified']) && !$install_state['settings_verified']) {
+ return array();
+ }
+
+ $connection = Database::getConnection();
+ $t = get_t();
+ $requirements['title'] = $t('Database 4 byte UTF-8 support');
+
+ $utf8mb4_configurable = $connection->utf8mb4IsConfigurable();
+ $utf8mb4_active = $connection->utf8mb4IsActive();
+ $utf8mb4_supported = $connection->utf8mb4IsSupported();
+ $driver = $connection->driver();
+ $documentation_url = 'https://www.drupal.org/node/2754539';
+
+ if ($utf8mb4_active) {
+ if ($utf8mb4_supported) {
+ if ($phase != 'install' && $utf8mb4_configurable && !variable_get('drupal_all_databases_are_utf8mb4', FALSE)) {
+ // Supported, active, and configurable, but not all database tables
+ // have been converted yet.
+ $requirements['value'] = $t('Enabled, but database tables need conversion');
+ $requirements['description'] = $t('Please convert all database tables to utf8mb4 prior to enabling it in settings.php. See the documentation on adding 4 byte UTF-8 support for more information.', array('@url' => $documentation_url));
+ $requirements['severity'] = REQUIREMENT_ERROR;
+ }
+ else {
+ // Supported, active.
+ $requirements['value'] = $t('Enabled');
+ $requirements['description'] = $t('4 byte UTF-8 for @driver is enabled.', array('@driver' => $driver));
+ $requirements['severity'] = REQUIREMENT_OK;
+ }
+ }
+ else {
+ // Not supported, active.
+ $requirements['value'] = $t('Not supported');
+ $requirements['description'] = $t('4 byte UTF-8 for @driver is activated, but not supported on your system. Please turn this off in settings.php, or ensure that all database-related requirements are met. See the documentation on adding 4 byte UTF-8 support for more information.', array('@driver' => $driver, '@url' => $documentation_url));
+ $requirements['severity'] = REQUIREMENT_ERROR;
+ }
+ }
+ else {
+ if ($utf8mb4_supported) {
+ // Supported, not active.
+ $requirements['value'] = $t('Not enabled');
+ $requirements['description'] = $t('4 byte UTF-8 for @driver is not activated, but it is supported on your system. It is recommended that you enable this to allow 4-byte UTF-8 input such as emojis, Asian symbols and mathematical symbols to be stored correctly. See the documentation on adding 4 byte UTF-8 support for more information.', array('@driver' => $driver, '@url' => $documentation_url));
+ $requirements['severity'] = REQUIREMENT_INFO;
+ }
+ else {
+ // Not supported, not active.
+ $requirements['value'] = $t('Disabled');
+ $requirements['description'] = $t('4 byte UTF-8 for @driver is disabled. See the documentation on adding 4 byte UTF-8 support for more information.', array('@driver' => $driver, '@url' => $documentation_url));
+ $requirements['severity'] = REQUIREMENT_INFO;
+ }
+ }
+ return $requirements;
+}
+
/**
* Implements hook_install().
*/
@@ -532,6 +607,9 @@ function system_install() {
module_list(TRUE);
module_implements('', FALSE, TRUE);
+ // Ensure the schema versions are not based on a previous module list.
+ drupal_static_reset('drupal_get_schema_versions');
+
// Load system theme data appropriately.
system_rebuild_theme_data();
@@ -800,6 +878,7 @@ function system_schema() {
'type' => 'varchar',
'length' => 100,
'not null' => TRUE,
+ 'binary' => TRUE,
),
'type' => array(
'description' => 'The date format type, e.g. medium.',
@@ -2803,6 +2882,16 @@ function system_update_7061(&$sandbox) {
->from($query)
->execute();
+ // Retrieve a list of duplicate files with the same filepath. Only the
+ // most-recently uploaded of these will be moved to the new {file_managed}
+ // table (and all references will be updated to point to it), since
+ // duplicate file URIs are not allowed in Drupal 7.
+ // Since the Drupal 6 to 7 upgrade path leaves the {files} table behind
+ // after it's done, custom or contributed modules which need to migrate
+ // file references of their own can use a similar query to determine the
+ // file IDs that duplicate filepaths were mapped to.
+ $sandbox['duplicate_filepath_fids_to_use'] = db_query("SELECT filepath, MAX(fid) FROM {files} GROUP BY filepath HAVING COUNT(*) > 1")->fetchAllKeyed();
+
// Initialize batch update information.
$sandbox['progress'] = 0;
$sandbox['last_vid_processed'] = -1;
@@ -2832,6 +2921,16 @@ function system_update_7061(&$sandbox) {
continue;
}
+ // If this file has a duplicate filepath, replace it with the
+ // most-recently uploaded file that has the same filepath.
+ if (isset($sandbox['duplicate_filepath_fids_to_use'][$file['filepath']]) && $record->fid != $sandbox['duplicate_filepath_fids_to_use'][$file['filepath']]) {
+ $file = db_select('files', 'f')
+ ->fields('f', array('fid', 'uid', 'filename', 'filepath', 'filemime', 'filesize', 'status', 'timestamp'))
+ ->condition('f.fid', $sandbox['duplicate_filepath_fids_to_use'][$file['filepath']])
+ ->execute()
+ ->fetchAssoc();
+ }
+
// Add in the file information from the upload table.
$file['description'] = $record->description;
$file['display'] = $record->list;
@@ -3157,6 +3256,35 @@ function system_update_7079() {
db_change_field('file_managed', 'filesize', 'filesize', $spec);
}
+/**
+ * Convert the 'format' column in {date_format_locale} to case sensitive varchar.
+ */
+function system_update_7080() {
+ $spec = array(
+ 'description' => 'The date format string.',
+ 'type' => 'varchar',
+ 'length' => 100,
+ 'not null' => TRUE,
+ 'binary' => TRUE,
+ );
+ db_change_field('date_format_locale', 'format', 'format', $spec);
+}
+
+/**
+ * Remove the Drupal 6 default install profile if it is still in the database.
+ */
+function system_update_7081() {
+ // Sites which used the default install profile in Drupal 6 and then updated
+ // to Drupal 7.44 or earlier will still have a record of this install profile
+ // in the database that needs to be deleted.
+ db_delete('system')
+ ->condition('filename', 'profiles/default/default.profile')
+ ->condition('type', 'module')
+ ->condition('status', 0)
+ ->condition('schema_version', 0)
+ ->execute();
+}
+
/**
* @} End of "defgroup updates-7.x-extra".
* The next series of updates should start at 8000.
diff --git a/modules/system/system.js b/modules/system/system.js
index 910fb5d..c0e76d3 100644
--- a/modules/system/system.js
+++ b/modules/system/system.js
@@ -105,7 +105,7 @@ Drupal.behaviors.dateTime = {
// Attach keyup handler to custom format inputs.
$('input' + source, context).once('date-time').keyup(function () {
var input = $(this);
- var url = fieldSettings.lookup + (/\?q=/.test(fieldSettings.lookup) ? '&format=' : '?format=') + encodeURIComponent(input.val());
+ var url = fieldSettings.lookup + (/\?/.test(fieldSettings.lookup) ? '&format=' : '?format=') + encodeURIComponent(input.val());
$.getJSON(url, function (data) {
$(suffix).empty().append(' ' + fieldSettings.text + ': ' + data + '');
});
diff --git a/modules/system/system.module b/modules/system/system.module
index 6a6200e..59087c8 100644
--- a/modules/system/system.module
+++ b/modules/system/system.module
@@ -359,7 +359,7 @@ function system_element_info() {
'#size' => 60,
'#maxlength' => 128,
'#autocomplete_path' => FALSE,
- '#process' => array('ajax_process_form'),
+ '#process' => array('form_process_autocomplete', 'ajax_process_form'),
'#theme' => 'textfield',
'#theme_wrappers' => array('form_element'),
);
@@ -2030,7 +2030,6 @@ function system_user_timezone(&$form, &$form_state) {
'#description' => t('Select the desired local time and time zone. Dates and times throughout this site will be displayed using this time zone.'),
);
if (!isset($account->timezone) && $account->uid == $user->uid && empty($form_state['input']['timezone'])) {
- $form['timezone']['#description'] = t('Your time zone setting will be automatically detected if possible. Confirm the selection and click save.');
$form['timezone']['timezone']['#attributes'] = array('class' => array('timezone-detect'));
drupal_add_js('misc/timezone.js');
}
@@ -2412,6 +2411,10 @@ function _system_rebuild_module_data() {
// Merge in defaults and save.
$modules[$key]->info = $module->info + $defaults;
+ // The "name" key is required, but to avoid a fatal error in the menu system
+ // we set a reasonable default if it is not provided.
+ $modules[$key]->info += array('name' => $key);
+
// Prefix stylesheets and scripts with module path.
$path = dirname($module->uri);
if (isset($module->info['stylesheets'])) {
@@ -2547,6 +2550,10 @@ function _system_rebuild_theme_data() {
$themes[$key]->filename = $theme->uri;
$themes[$key]->info = drupal_parse_info_file($theme->uri) + $defaults;
+ // The "name" key is required, but to avoid a fatal error in the menu system
+ // we set a reasonable default if it is not provided.
+ $themes[$key]->info += array('name' => $key);
+
// Add the info file modification time, so it becomes available for
// contributed modules to use for ordering theme lists.
$themes[$key]->info['mtime'] = filemtime($theme->uri);
@@ -2698,10 +2705,17 @@ function system_find_base_themes($themes, $key, $used_keys = array()) {
* @param $show
* Possible values: REGIONS_ALL or REGIONS_VISIBLE. Visible excludes hidden
* regions.
- * @return
- * An array of regions in the form $region['name'] = 'description'.
+ * @param bool $labels
+ * (optional) Boolean to specify whether the human readable machine names
+ * should be returned or not. Defaults to TRUE, but calling code can set
+ * this to FALSE for better performance, if it only needs machine names.
+ *
+ * @return array
+ * An associative array of regions in the form $region['name'] = 'description'
+ * if $labels is set to TRUE, or $region['name'] = 'name', if $labels is set
+ * to FALSE.
*/
-function system_region_list($theme_key, $show = REGIONS_ALL) {
+function system_region_list($theme_key, $show = REGIONS_ALL, $labels = TRUE) {
$themes = list_themes();
if (!isset($themes[$theme_key])) {
return array();
@@ -2712,10 +2726,14 @@ function system_region_list($theme_key, $show = REGIONS_ALL) {
// If requested, suppress hidden regions. See block_admin_display_form().
foreach ($info['regions'] as $name => $label) {
if ($show == REGIONS_ALL || !isset($info['regions_hidden']) || !in_array($name, $info['regions_hidden'])) {
- $list[$name] = t($label);
+ if ($labels) {
+ $list[$name] = t($label);
+ }
+ else {
+ $list[$name] = $name;
+ }
}
}
-
return $list;
}
@@ -2736,12 +2754,13 @@ function system_system_info_alter(&$info, $file, $type) {
*
* @param $theme
* The name of a theme.
+ *
* @return
* A string that is the region name.
*/
function system_default_region($theme) {
- $regions = array_keys(system_region_list($theme, REGIONS_VISIBLE));
- return isset($regions[0]) ? $regions[0] : '';
+ $regions = system_region_list($theme, REGIONS_VISIBLE, FALSE);
+ return $regions ? reset($regions) : '';
}
/**
@@ -2808,7 +2827,7 @@ function system_settings_form_submit($form, &$form_state) {
function _system_sort_requirements($a, $b) {
if (!isset($a['weight'])) {
if (!isset($b['weight'])) {
- return strcmp($a['title'], $b['title']);
+ return strcasecmp($a['title'], $b['title']);
}
return -$b['weight'];
}
@@ -3049,8 +3068,20 @@ function system_cron() {
}
}
- $core = array('cache', 'cache_path', 'cache_filter', 'cache_page', 'cache_form', 'cache_menu');
- $cache_tables = array_merge(module_invoke_all('flush_caches'), $core);
+ // Delete expired cache entries.
+ // Avoid invoking hook_flush_cashes() on every cron run because some modules
+ // use this hook to perform expensive rebuilding operations (which are only
+ // designed to happen on full cache clears), rather than just returning a
+ // list of cache tables to be cleared.
+ $cache_object = cache_get('system_cache_tables');
+ if (empty($cache_object)) {
+ $core = array('cache', 'cache_path', 'cache_filter', 'cache_page', 'cache_form', 'cache_menu');
+ $cache_tables = array_merge(module_invoke_all('flush_caches'), $core);
+ cache_set('system_cache_tables', $cache_tables);
+ }
+ else {
+ $cache_tables = $cache_object->data;
+ }
foreach ($cache_tables as $table) {
cache_clear_all(NULL, $table);
}
@@ -3298,7 +3329,7 @@ function system_goto_action_form($context) {
$form['url'] = array(
'#type' => 'textfield',
'#title' => t('URL'),
- '#description' => t('The URL to which the user should be redirected. This can be an internal URL like node/1234 or an external URL like http://drupal.org.'),
+ '#description' => t('The URL to which the user should be redirected. This can be an internal path like node/1234 or an external URL like http://example.com.'),
'#default_value' => isset($context['url']) ? $context['url'] : '',
'#required' => TRUE,
);
@@ -3335,7 +3366,8 @@ function system_goto_action($entity, $context) {
*/
function system_block_ip_action() {
$ip = ip_address();
- db_insert('blocked_ips')
+ db_merge('blocked_ips')
+ ->key(array('ip' => $ip))
->fields(array('ip' => $ip))
->execute();
watchdog('action', 'Banned IP address %ip', array('%ip' => $ip));
@@ -3497,8 +3529,7 @@ function system_retrieve_file($url, $destination = NULL, $managed = FALSE, $repl
function system_page_alter(&$page) {
// Find all non-empty page regions, and add a theme wrapper function that
// allows them to be consistently themed.
- $regions = system_region_list($GLOBALS['theme']);
- foreach (array_keys($regions) as $region) {
+ foreach (system_region_list($GLOBALS['theme'], REGIONS_ALL, FALSE) as $region) {
if (!empty($page[$region])) {
$page[$region]['#theme_wrappers'][] = 'region';
$page[$region]['#region'] = $region;
diff --git a/modules/system/system.queue.inc b/modules/system/system.queue.inc
index 901c4d6..c17084d 100644
--- a/modules/system/system.queue.inc
+++ b/modules/system/system.queue.inc
@@ -231,7 +231,7 @@ class SystemQueue implements DrupalReliableQueueInterface {
// until an item is successfully claimed or we are reasonably sure there
// are no unclaimed items left.
while (TRUE) {
- $item = db_query_range('SELECT data, item_id FROM {queue} q WHERE expire = 0 AND name = :name ORDER BY created ASC', 0, 1, array(':name' => $this->name))->fetchObject();
+ $item = db_query_range('SELECT data, item_id FROM {queue} q WHERE expire = 0 AND name = :name ORDER BY created, item_id ASC', 0, 1, array(':name' => $this->name))->fetchObject();
if ($item) {
// Try to update the item. Only one thread can succeed in UPDATEing the
// same row. We cannot rely on REQUEST_TIME because items might be
@@ -326,6 +326,7 @@ class MemoryQueue implements DrupalQueueInterface {
$item->created = time();
$item->expire = 0;
$this->queue[$item->item_id] = $item;
+ return TRUE;
}
public function numberOfItems() {
diff --git a/modules/system/system.tar.inc b/modules/system/system.tar.inc
index 32bf7f0..86e4e3d 100644
--- a/modules/system/system.tar.inc
+++ b/modules/system/system.tar.inc
@@ -30,81 +30,148 @@
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
- *
- * @category File_Formats
- * @package Archive_Tar
- * @author Vincent Blavet
- * @copyright 1997-2008 The Authors
- * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
- * @version CVS: Id: Tar.php,v 1.43 2008/10/30 17:58:42 dufuz Exp
- * @link http://pear.php.net/package/Archive_Tar
+ * @category File_Formats
+ * @package Archive_Tar
+ * @author Vincent Blavet
+ * @copyright 1997-2010 The Authors
+ * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/Archive_Tar
*/
-//require_once 'PEAR.php';
-//
-//
-define ('ARCHIVE_TAR_ATT_SEPARATOR', 90001);
-define ('ARCHIVE_TAR_END_BLOCK', pack("a512", ''));
+ /**
+ * Note on Drupal 8 porting.
+ * This file origin is Tar.php, release 1.4.0 (stable) with some code
+ * from PEAR.php, release 1.9.5 (stable) both at http://pear.php.net.
+ * To simplify future porting from pear of this file, you should not
+ * do cosmetic or other non significant changes to this file.
+ * The following changes have been done:
+ * Added namespace Drupal\Core\Archiver.
+ * Removed require_once 'PEAR.php'.
+ * Added defintion of OS_WINDOWS taken from PEAR.php.
+ * Renamed class to ArchiveTar.
+ * Removed extends PEAR from class.
+ * Removed call parent:: __construct().
+ * Changed PEAR::loadExtension($extname) to this->loadExtension($extname).
+ * Added function loadExtension() taken from PEAR.php.
+ * Changed all calls of unlink() to drupal_unlink().
+ * Changed $this->error_object = &$this->raiseError($p_message)
+ * to throw new \Exception($p_message).
+ */
+
+ /**
+ * Note on Drupal 7 backporting from Drupal 8.
+ * File origin is core/lib/Drupal/Core/Archiver/ArchiveTar.php from Drupal 8.
+ * The following changes have been done:
+ * Removed namespace Drupal\Core\Archiver.
+ * Renamed class to Archive_Tar.
+ * Changed \Exception to Exception.
+ */
+
+
+// Drupal removal require_once 'PEAR.php'.
+
+// Drupal addition OS_WINDOWS as defined in PEAR.php.
+if (substr(PHP_OS, 0, 3) == 'WIN') {
+ define('OS_WINDOWS', true);
+} else {
+ define('OS_WINDOWS', false);
+}
+
+define('ARCHIVE_TAR_ATT_SEPARATOR', 90001);
+define('ARCHIVE_TAR_END_BLOCK', pack("a512", ''));
+
+if (!function_exists('gzopen') && function_exists('gzopen64')) {
+ function gzopen($filename, $mode, $use_include_path = 0)
+ {
+ return gzopen64($filename, $mode, $use_include_path);
+ }
+}
+
+if (!function_exists('gztell') && function_exists('gztell64')) {
+ function gztell($zp)
+ {
+ return gztell64($zp);
+ }
+}
+
+if (!function_exists('gzseek') && function_exists('gzseek64')) {
+ function gzseek($zp, $offset, $whence = SEEK_SET)
+ {
+ return gzseek64($zp, $offset, $whence);
+ }
+}
/**
-* Creates a (compressed) Tar archive
-*
-* @author Vincent Blavet
-* @version Revision: 1.43
-* @license http://www.opensource.org/licenses/bsd-license.php New BSD License
-* @package Archive_Tar
-*/
-class Archive_Tar // extends PEAR
+ * Creates a (compressed) Tar archive
+ *
+ * @package Archive_Tar
+ * @author Vincent Blavet
+ * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
+ * @version $Revision$
+ */
+// Drupal change class Archive_Tar extends PEAR.
+class Archive_Tar
{
/**
- * @var string Name of the Tar
- */
- var $_tarname='';
+ * @var string Name of the Tar
+ */
+ public $_tarname = '';
/**
- * @var boolean if true, the Tar file will be gzipped
- */
- var $_compress=false;
+ * @var boolean if true, the Tar file will be gzipped
+ */
+ public $_compress = false;
/**
- * @var string Type of compression : 'none', 'gz' or 'bz2'
- */
- var $_compress_type='none';
+ * @var string Type of compression : 'none', 'gz', 'bz2' or 'lzma2'
+ */
+ public $_compress_type = 'none';
/**
- * @var string Explode separator
- */
- var $_separator=' ';
+ * @var string Explode separator
+ */
+ public $_separator = ' ';
/**
- * @var file descriptor
- */
- var $_file=0;
+ * @var file descriptor
+ */
+ public $_file = 0;
/**
- * @var string Local Tar name of a remote Tar (http:// or ftp://)
- */
- var $_temp_tarname='';
+ * @var string Local Tar name of a remote Tar (http:// or ftp://)
+ */
+ public $_temp_tarname = '';
- // {{{ constructor
/**
- * Archive_Tar Class constructor. This flavour of the constructor only
- * declare a new Archive_Tar object, identifying it by the name of the
- * tar file.
- * If the compress argument is set the tar will be read or created as a
- * gzip or bz2 compressed TAR file.
- *
- * @param string $p_tarname The name of the tar archive to create
- * @param string $p_compress can be null, 'gz' or 'bz2'. This
- * parameter indicates if gzip or bz2 compression
- * is required. For compatibility reason the
- * boolean value 'true' means 'gz'.
- * @access public
- */
-// function Archive_Tar($p_tarname, $p_compress = null)
- function __construct($p_tarname, $p_compress = null)
+ * @var string regular expression for ignoring files or directories
+ */
+ public $_ignore_regexp = '';
+
+ /**
+ * @var object PEAR_Error object
+ */
+ public $error_object = null;
+
+ /**
+ * Archive_Tar Class constructor. This flavour of the constructor only
+ * declare a new Archive_Tar object, identifying it by the name of the
+ * tar file.
+ * If the compress argument is set the tar will be read or created as a
+ * gzip or bz2 compressed TAR file.
+ *
+ * @param string $p_tarname The name of the tar archive to create
+ * @param string $p_compress can be null, 'gz', 'bz2' or 'lzma2'. This
+ * parameter indicates if gzip, bz2 or lzma2 compression
+ * is required. For compatibility reason the
+ * boolean value 'true' means 'gz'.
+ *
+ * @return bool
+ */
+ public function __construct($p_tarname, $p_compress = null)
{
-// $this->PEAR();
+ // Drupal removal parent::__construct().
+
$this->_compress = false;
$this->_compress_type = 'none';
if (($p_compress === null) || ($p_compress == '')) {
@@ -116,10 +183,13 @@ class Archive_Tar // extends PEAR
if ($data == "\37\213") {
$this->_compress = true;
$this->_compress_type = 'gz';
- // No sure it's enought for a magic code ....
+ // No sure it's enought for a magic code ....
} elseif ($data == "BZ") {
$this->_compress = true;
$this->_compress_type = 'bz2';
+ } elseif (file_get_contents($p_tarname, false, null, 1, 4) == '7zXZ') {
+ $this->_compress = true;
+ $this->_compress_type = 'lzma2';
}
}
} else {
@@ -129,151 +199,177 @@ class Archive_Tar // extends PEAR
$this->_compress = true;
$this->_compress_type = 'gz';
} elseif ((substr($p_tarname, -3) == 'bz2') ||
- (substr($p_tarname, -2) == 'bz')) {
+ (substr($p_tarname, -2) == 'bz')
+ ) {
$this->_compress = true;
$this->_compress_type = 'bz2';
+ } else {
+ if (substr($p_tarname, -2) == 'xz') {
+ $this->_compress = true;
+ $this->_compress_type = 'lzma2';
+ }
}
}
} else {
if (($p_compress === true) || ($p_compress == 'gz')) {
$this->_compress = true;
$this->_compress_type = 'gz';
- } else if ($p_compress == 'bz2') {
- $this->_compress = true;
- $this->_compress_type = 'bz2';
} else {
- die("Unsupported compression type '$p_compress'\n".
- "Supported types are 'gz' and 'bz2'.\n");
- return false;
+ if ($p_compress == 'bz2') {
+ $this->_compress = true;
+ $this->_compress_type = 'bz2';
+ } else {
+ if ($p_compress == 'lzma2') {
+ $this->_compress = true;
+ $this->_compress_type = 'lzma2';
+ } else {
+ $this->_error(
+ "Unsupported compression type '$p_compress'\n" .
+ "Supported types are 'gz', 'bz2' and 'lzma2'.\n"
+ );
+ return false;
+ }
+ }
}
}
$this->_tarname = $p_tarname;
- if ($this->_compress) { // assert zlib or bz2 extension support
- if ($this->_compress_type == 'gz')
+ if ($this->_compress) { // assert zlib or bz2 or xz extension support
+ if ($this->_compress_type == 'gz') {
$extname = 'zlib';
- else if ($this->_compress_type == 'bz2')
- $extname = 'bz2';
+ } else {
+ if ($this->_compress_type == 'bz2') {
+ $extname = 'bz2';
+ } else {
+ if ($this->_compress_type == 'lzma2') {
+ $extname = 'xz';
+ }
+ }
+ }
if (!extension_loaded($extname)) {
-// PEAR::loadExtension($extname);
+ // Drupal change PEAR::loadExtension($extname).
$this->loadExtension($extname);
}
if (!extension_loaded($extname)) {
- die("The extension '$extname' couldn't be found.\n".
- "Please make sure your version of PHP was built ".
- "with '$extname' support.\n");
+ $this->_error(
+ "The extension '$extname' couldn't be found.\n" .
+ "Please make sure your version of PHP was built " .
+ "with '$extname' support.\n"
+ );
return false;
}
}
}
- // }}}
+ public function __destruct()
+ {
+ $this->_close();
+ // ----- Look for a local copy to delete
+ if ($this->_temp_tarname != '') {
+ @drupal_unlink($this->_temp_tarname);
+ }
+ }
+
+ // Drupal addition from PEAR.php.
/**
* OS independent PHP extension load. Remember to take care
* on the correct extension name for case sensitive OSes.
- * The function is the copy of PEAR::loadExtension().
*
* @param string $ext The extension name
* @return bool Success or not on the dl() call
*/
function loadExtension($ext)
{
- if (!extension_loaded($ext)) {
- // if either returns true dl() will produce a FATAL error, stop that
- if ((ini_get('enable_dl') != 1) || (ini_get('safe_mode') == 1)) {
- return false;
- }
-
- if (OS_WINDOWS) {
- $suffix = '.dll';
- } elseif (PHP_OS == 'HP-UX') {
- $suffix = '.sl';
- } elseif (PHP_OS == 'AIX') {
- $suffix = '.a';
- } elseif (PHP_OS == 'OSX') {
- $suffix = '.bundle';
- } else {
- $suffix = '.so';
- }
-
- return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix);
+ if (extension_loaded($ext)) {
+ return true;
}
- return true;
+ // if either returns true dl() will produce a FATAL error, stop that
+ if (
+ function_exists('dl') === false ||
+ ini_get('enable_dl') != 1 ||
+ ini_get('safe_mode') == 1
+ ) {
+ return false;
+ }
+
+ if (OS_WINDOWS) {
+ $suffix = '.dll';
+ } elseif (PHP_OS == 'HP-UX') {
+ $suffix = '.sl';
+ } elseif (PHP_OS == 'AIX') {
+ $suffix = '.a';
+ } elseif (PHP_OS == 'OSX') {
+ $suffix = '.bundle';
+ } else {
+ $suffix = '.so';
+ }
+
+ return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix);
}
- // {{{ destructor
-// function _Archive_Tar()
- function __destruct()
- {
- $this->_close();
- // ----- Look for a local copy to delete
- if ($this->_temp_tarname != '')
- @drupal_unlink($this->_temp_tarname);
-// $this->_PEAR();
- }
- // }}}
-
- // {{{ create()
/**
- * This method creates the archive file and add the files / directories
- * that are listed in $p_filelist.
- * If a file with the same name exist and is writable, it is replaced
- * by the new tar.
- * The method return false and a PEAR error text.
- * The $p_filelist parameter can be an array of string, each string
- * representing a filename or a directory name with their path if
- * needed. It can also be a single string with names separated by a
- * single blank.
- * For each directory added in the archive, the files and
- * sub-directories are also added.
- * See also createModify() method for more details.
- *
- * @param array $p_filelist An array of filenames and directory names, or a
- * single string with names separated by a single
- * blank space.
- * @return true on success, false on error.
- * @see createModify()
- * @access public
- */
- function create($p_filelist)
+ * This method creates the archive file and add the files / directories
+ * that are listed in $p_filelist.
+ * If a file with the same name exist and is writable, it is replaced
+ * by the new tar.
+ * The method return false and a PEAR error text.
+ * The $p_filelist parameter can be an array of string, each string
+ * representing a filename or a directory name with their path if
+ * needed. It can also be a single string with names separated by a
+ * single blank.
+ * For each directory added in the archive, the files and
+ * sub-directories are also added.
+ * See also createModify() method for more details.
+ *
+ * @param array $p_filelist An array of filenames and directory names, or a
+ * single string with names separated by a single
+ * blank space.
+ *
+ * @return true on success, false on error.
+ * @see createModify()
+ */
+ public function create($p_filelist)
{
return $this->createModify($p_filelist, '', '');
}
- // }}}
- // {{{ add()
/**
- * This method add the files / directories that are listed in $p_filelist in
- * the archive. If the archive does not exist it is created.
- * The method return false and a PEAR error text.
- * The files and directories listed are only added at the end of the archive,
- * even if a file with the same name is already archived.
- * See also createModify() method for more details.
- *
- * @param array $p_filelist An array of filenames and directory names, or a
- * single string with names separated by a single
- * blank space.
- * @return true on success, false on error.
- * @see createModify()
- * @access public
- */
- function add($p_filelist)
+ * This method add the files / directories that are listed in $p_filelist in
+ * the archive. If the archive does not exist it is created.
+ * The method return false and a PEAR error text.
+ * The files and directories listed are only added at the end of the archive,
+ * even if a file with the same name is already archived.
+ * See also createModify() method for more details.
+ *
+ * @param array $p_filelist An array of filenames and directory names, or a
+ * single string with names separated by a single
+ * blank space.
+ *
+ * @return true on success, false on error.
+ * @see createModify()
+ * @access public
+ */
+ public function add($p_filelist)
{
return $this->addModify($p_filelist, '', '');
}
- // }}}
- // {{{ extract()
- function extract($p_path='')
+ /**
+ * @param string $p_path
+ * @param bool $p_preserve
+ * @return bool
+ */
+ public function extract($p_path = '', $p_preserve = false)
{
- return $this->extractModify($p_path, '');
+ return $this->extractModify($p_path, '', $p_preserve);
}
- // }}}
- // {{{ listContent()
- function listContent()
+ /**
+ * @return array|int
+ */
+ public function listContent()
{
$v_list_detail = array();
@@ -287,57 +383,56 @@ class Archive_Tar // extends PEAR
return $v_list_detail;
}
- // }}}
- // {{{ createModify()
/**
- * This method creates the archive file and add the files / directories
- * that are listed in $p_filelist.
- * If the file already exists and is writable, it is replaced by the
- * new tar. It is a create and not an add. If the file exists and is
- * read-only or is a directory it is not replaced. The method return
- * false and a PEAR error text.
- * The $p_filelist parameter can be an array of string, each string
- * representing a filename or a directory name with their path if
- * needed. It can also be a single string with names separated by a
- * single blank.
- * The path indicated in $p_remove_dir will be removed from the
- * memorized path of each file / directory listed when this path
- * exists. By default nothing is removed (empty path '')
- * The path indicated in $p_add_dir will be added at the beginning of
- * the memorized path of each file / directory listed. However it can
- * be set to empty ''. The adding of a path is done after the removing
- * of path.
- * The path add/remove ability enables the user to prepare an archive
- * for extraction in a different path than the origin files are.
- * See also addModify() method for file adding properties.
- *
- * @param array $p_filelist An array of filenames and directory names,
- * or a single string with names separated by
- * a single blank space.
- * @param string $p_add_dir A string which contains a path to be added
- * to the memorized path of each element in
- * the list.
- * @param string $p_remove_dir A string which contains a path to be
- * removed from the memorized path of each
- * element in the list, when relevant.
- * @return boolean true on success, false on error.
- * @access public
- * @see addModify()
- */
- function createModify($p_filelist, $p_add_dir, $p_remove_dir='')
+ * This method creates the archive file and add the files / directories
+ * that are listed in $p_filelist.
+ * If the file already exists and is writable, it is replaced by the
+ * new tar. It is a create and not an add. If the file exists and is
+ * read-only or is a directory it is not replaced. The method return
+ * false and a PEAR error text.
+ * The $p_filelist parameter can be an array of string, each string
+ * representing a filename or a directory name with their path if
+ * needed. It can also be a single string with names separated by a
+ * single blank.
+ * The path indicated in $p_remove_dir will be removed from the
+ * memorized path of each file / directory listed when this path
+ * exists. By default nothing is removed (empty path '')
+ * The path indicated in $p_add_dir will be added at the beginning of
+ * the memorized path of each file / directory listed. However it can
+ * be set to empty ''. The adding of a path is done after the removing
+ * of path.
+ * The path add/remove ability enables the user to prepare an archive
+ * for extraction in a different path than the origin files are.
+ * See also addModify() method for file adding properties.
+ *
+ * @param array $p_filelist An array of filenames and directory names,
+ * or a single string with names separated by
+ * a single blank space.
+ * @param string $p_add_dir A string which contains a path to be added
+ * to the memorized path of each element in
+ * the list.
+ * @param string $p_remove_dir A string which contains a path to be
+ * removed from the memorized path of each
+ * element in the list, when relevant.
+ *
+ * @return boolean true on success, false on error.
+ * @see addModify()
+ */
+ public function createModify($p_filelist, $p_add_dir, $p_remove_dir = '')
{
$v_result = true;
- if (!$this->_openWrite())
+ if (!$this->_openWrite()) {
return false;
+ }
if ($p_filelist != '') {
- if (is_array($p_filelist))
+ if (is_array($p_filelist)) {
$v_list = $p_filelist;
- elseif (is_string($p_filelist))
+ } elseif (is_string($p_filelist)) {
$v_list = explode($this->_separator, $p_filelist);
- else {
+ } else {
$this->_cleanFile();
$this->_error('Invalid file list');
return false;
@@ -349,67 +444,69 @@ class Archive_Tar // extends PEAR
if ($v_result) {
$this->_writeFooter();
$this->_close();
- } else
+ } else {
$this->_cleanFile();
+ }
return $v_result;
}
- // }}}
- // {{{ addModify()
/**
- * This method add the files / directories listed in $p_filelist at the
- * end of the existing archive. If the archive does not yet exists it
- * is created.
- * The $p_filelist parameter can be an array of string, each string
- * representing a filename or a directory name with their path if
- * needed. It can also be a single string with names separated by a
- * single blank.
- * The path indicated in $p_remove_dir will be removed from the
- * memorized path of each file / directory listed when this path
- * exists. By default nothing is removed (empty path '')
- * The path indicated in $p_add_dir will be added at the beginning of
- * the memorized path of each file / directory listed. However it can
- * be set to empty ''. The adding of a path is done after the removing
- * of path.
- * The path add/remove ability enables the user to prepare an archive
- * for extraction in a different path than the origin files are.
- * If a file/dir is already in the archive it will only be added at the
- * end of the archive. There is no update of the existing archived
- * file/dir. However while extracting the archive, the last file will
- * replace the first one. This results in a none optimization of the
- * archive size.
- * If a file/dir does not exist the file/dir is ignored. However an
- * error text is send to PEAR error.
- * If a file/dir is not readable the file/dir is ignored. However an
- * error text is send to PEAR error.
- *
- * @param array $p_filelist An array of filenames and directory
- * names, or a single string with names
- * separated by a single blank space.
- * @param string $p_add_dir A string which contains a path to be
- * added to the memorized path of each
- * element in the list.
- * @param string $p_remove_dir A string which contains a path to be
- * removed from the memorized path of
- * each element in the list, when
- * relevant.
- * @return true on success, false on error.
- * @access public
- */
- function addModify($p_filelist, $p_add_dir, $p_remove_dir='')
+ * This method add the files / directories listed in $p_filelist at the
+ * end of the existing archive. If the archive does not yet exists it
+ * is created.
+ * The $p_filelist parameter can be an array of string, each string
+ * representing a filename or a directory name with their path if
+ * needed. It can also be a single string with names separated by a
+ * single blank.
+ * The path indicated in $p_remove_dir will be removed from the
+ * memorized path of each file / directory listed when this path
+ * exists. By default nothing is removed (empty path '')
+ * The path indicated in $p_add_dir will be added at the beginning of
+ * the memorized path of each file / directory listed. However it can
+ * be set to empty ''. The adding of a path is done after the removing
+ * of path.
+ * The path add/remove ability enables the user to prepare an archive
+ * for extraction in a different path than the origin files are.
+ * If a file/dir is already in the archive it will only be added at the
+ * end of the archive. There is no update of the existing archived
+ * file/dir. However while extracting the archive, the last file will
+ * replace the first one. This results in a none optimization of the
+ * archive size.
+ * If a file/dir does not exist the file/dir is ignored. However an
+ * error text is send to PEAR error.
+ * If a file/dir is not readable the file/dir is ignored. However an
+ * error text is send to PEAR error.
+ *
+ * @param array $p_filelist An array of filenames and directory
+ * names, or a single string with names
+ * separated by a single blank space.
+ * @param string $p_add_dir A string which contains a path to be
+ * added to the memorized path of each
+ * element in the list.
+ * @param string $p_remove_dir A string which contains a path to be
+ * removed from the memorized path of
+ * each element in the list, when
+ * relevant.
+ *
+ * @return true on success, false on error.
+ */
+ public function addModify($p_filelist, $p_add_dir, $p_remove_dir = '')
{
$v_result = true;
- if (!$this->_isArchive())
- $v_result = $this->createModify($p_filelist, $p_add_dir,
- $p_remove_dir);
- else {
- if (is_array($p_filelist))
+ if (!$this->_isArchive()) {
+ $v_result = $this->createModify(
+ $p_filelist,
+ $p_add_dir,
+ $p_remove_dir
+ );
+ } else {
+ if (is_array($p_filelist)) {
$v_list = $p_filelist;
- elseif (is_string($p_filelist))
+ } elseif (is_string($p_filelist)) {
$v_list = explode($this->_separator, $p_filelist);
- else {
+ } else {
$this->_error('Invalid file list');
return false;
}
@@ -419,24 +516,41 @@ class Archive_Tar // extends PEAR
return $v_result;
}
- // }}}
- // {{{ addString()
/**
- * This method add a single string as a file at the
- * end of the existing archive. If the archive does not yet exists it
- * is created.
- *
- * @param string $p_filename A string which contains the full
- * filename path that will be associated
- * with the string.
- * @param string $p_string The content of the file added in
- * the archive.
- * @return true on success, false on error.
- * @access public
- */
- function addString($p_filename, $p_string)
+ * This method add a single string as a file at the
+ * end of the existing archive. If the archive does not yet exists it
+ * is created.
+ *
+ * @param string $p_filename A string which contains the full
+ * filename path that will be associated
+ * with the string.
+ * @param string $p_string The content of the file added in
+ * the archive.
+ * @param bool|int $p_datetime A custom date/time (unix timestamp)
+ * for the file (optional).
+ * @param array $p_params An array of optional params:
+ * stamp => the datetime (replaces
+ * datetime above if it exists)
+ * mode => the permissions on the
+ * file (600 by default)
+ * type => is this a link? See the
+ * tar specification for details.
+ * (default = regular file)
+ * uid => the user ID of the file
+ * (default = 0 = root)
+ * gid => the group ID of the file
+ * (default = 0 = root)
+ *
+ * @return true on success, false on error.
+ */
+ public function addString($p_filename, $p_string, $p_datetime = false, $p_params = array())
{
+ $p_stamp = @$p_params["stamp"] ? $p_params["stamp"] : ($p_datetime ? $p_datetime : time());
+ $p_mode = @$p_params["mode"] ? $p_params["mode"] : 0600;
+ $p_type = @$p_params["type"] ? $p_params["type"] : "";
+ $p_uid = @$p_params["uid"] ? $p_params["uid"] : "";
+ $p_gid = @$p_params["gid"] ? $p_params["gid"] : "";
$v_result = true;
if (!$this->_isArchive()) {
@@ -446,11 +560,12 @@ class Archive_Tar // extends PEAR
$this->_close();
}
- if (!$this->_openAppend())
+ if (!$this->_openAppend()) {
return false;
+ }
// Need to check the get back to the temporary file ? ....
- $v_result = $this->_addString($p_filename, $p_string);
+ $v_result = $this->_addString($p_filename, $p_string, $p_datetime, $p_params);
$this->_writeFooter();
@@ -458,131 +573,138 @@ class Archive_Tar // extends PEAR
return $v_result;
}
- // }}}
- // {{{ extractModify()
/**
- * This method extract all the content of the archive in the directory
- * indicated by $p_path. When relevant the memorized path of the
- * files/dir can be modified by removing the $p_remove_path path at the
- * beginning of the file/dir path.
- * While extracting a file, if the directory path does not exists it is
- * created.
- * While extracting a file, if the file already exists it is replaced
- * without looking for last modification date.
- * While extracting a file, if the file already exists and is write
- * protected, the extraction is aborted.
- * While extracting a file, if a directory with the same name already
- * exists, the extraction is aborted.
- * While extracting a directory, if a file with the same name already
- * exists, the extraction is aborted.
- * While extracting a file/directory if the destination directory exist
- * and is write protected, or does not exist but can not be created,
- * the extraction is aborted.
- * If after extraction an extracted file does not show the correct
- * stored file size, the extraction is aborted.
- * When the extraction is aborted, a PEAR error text is set and false
- * is returned. However the result can be a partial extraction that may
- * need to be manually cleaned.
- *
- * @param string $p_path The path of the directory where the
- * files/dir need to by extracted.
- * @param string $p_remove_path Part of the memorized path that can be
- * removed if present at the beginning of
- * the file/dir path.
- * @return boolean true on success, false on error.
- * @access public
- * @see extractList()
- */
- function extractModify($p_path, $p_remove_path)
+ * This method extract all the content of the archive in the directory
+ * indicated by $p_path. When relevant the memorized path of the
+ * files/dir can be modified by removing the $p_remove_path path at the
+ * beginning of the file/dir path.
+ * While extracting a file, if the directory path does not exists it is
+ * created.
+ * While extracting a file, if the file already exists it is replaced
+ * without looking for last modification date.
+ * While extracting a file, if the file already exists and is write
+ * protected, the extraction is aborted.
+ * While extracting a file, if a directory with the same name already
+ * exists, the extraction is aborted.
+ * While extracting a directory, if a file with the same name already
+ * exists, the extraction is aborted.
+ * While extracting a file/directory if the destination directory exist
+ * and is write protected, or does not exist but can not be created,
+ * the extraction is aborted.
+ * If after extraction an extracted file does not show the correct
+ * stored file size, the extraction is aborted.
+ * When the extraction is aborted, a PEAR error text is set and false
+ * is returned. However the result can be a partial extraction that may
+ * need to be manually cleaned.
+ *
+ * @param string $p_path The path of the directory where the
+ * files/dir need to by extracted.
+ * @param string $p_remove_path Part of the memorized path that can be
+ * removed if present at the beginning of
+ * the file/dir path.
+ * @param boolean $p_preserve Preserve user/group ownership of files
+ *
+ * @return boolean true on success, false on error.
+ * @see extractList()
+ */
+ public function extractModify($p_path, $p_remove_path, $p_preserve = false)
{
$v_result = true;
$v_list_detail = array();
if ($v_result = $this->_openRead()) {
- $v_result = $this->_extractList($p_path, $v_list_detail,
- "complete", 0, $p_remove_path);
+ $v_result = $this->_extractList(
+ $p_path,
+ $v_list_detail,
+ "complete",
+ 0,
+ $p_remove_path,
+ $p_preserve
+ );
$this->_close();
}
return $v_result;
}
- // }}}
- // {{{ extractInString()
/**
- * This method extract from the archive one file identified by $p_filename.
- * The return value is a string with the file content, or NULL on error.
- * @param string $p_filename The path of the file to extract in a string.
- * @return a string with the file content or NULL.
- * @access public
- */
- function extractInString($p_filename)
+ * This method extract from the archive one file identified by $p_filename.
+ * The return value is a string with the file content, or NULL on error.
+ *
+ * @param string $p_filename The path of the file to extract in a string.
+ *
+ * @return a string with the file content or NULL.
+ */
+ public function extractInString($p_filename)
{
if ($this->_openRead()) {
$v_result = $this->_extractInString($p_filename);
$this->_close();
} else {
- $v_result = NULL;
+ $v_result = null;
}
return $v_result;
}
- // }}}
- // {{{ extractList()
/**
- * This method extract from the archive only the files indicated in the
- * $p_filelist. These files are extracted in the current directory or
- * in the directory indicated by the optional $p_path parameter.
- * If indicated the $p_remove_path can be used in the same way as it is
- * used in extractModify() method.
- * @param array $p_filelist An array of filenames and directory names,
- * or a single string with names separated
- * by a single blank space.
- * @param string $p_path The path of the directory where the
- * files/dir need to by extracted.
- * @param string $p_remove_path Part of the memorized path that can be
- * removed if present at the beginning of
- * the file/dir path.
- * @return true on success, false on error.
- * @access public
- * @see extractModify()
- */
- function extractList($p_filelist, $p_path='', $p_remove_path='')
+ * This method extract from the archive only the files indicated in the
+ * $p_filelist. These files are extracted in the current directory or
+ * in the directory indicated by the optional $p_path parameter.
+ * If indicated the $p_remove_path can be used in the same way as it is
+ * used in extractModify() method.
+ *
+ * @param array $p_filelist An array of filenames and directory names,
+ * or a single string with names separated
+ * by a single blank space.
+ * @param string $p_path The path of the directory where the
+ * files/dir need to by extracted.
+ * @param string $p_remove_path Part of the memorized path that can be
+ * removed if present at the beginning of
+ * the file/dir path.
+ * @param boolean $p_preserve Preserve user/group ownership of files
+ *
+ * @return true on success, false on error.
+ * @see extractModify()
+ */
+ public function extractList($p_filelist, $p_path = '', $p_remove_path = '', $p_preserve = false)
{
$v_result = true;
$v_list_detail = array();
- if (is_array($p_filelist))
+ if (is_array($p_filelist)) {
$v_list = $p_filelist;
- elseif (is_string($p_filelist))
+ } elseif (is_string($p_filelist)) {
$v_list = explode($this->_separator, $p_filelist);
- else {
+ } else {
$this->_error('Invalid string list');
return false;
}
if ($v_result = $this->_openRead()) {
- $v_result = $this->_extractList($p_path, $v_list_detail, "partial",
- $v_list, $p_remove_path);
+ $v_result = $this->_extractList(
+ $p_path,
+ $v_list_detail,
+ "partial",
+ $v_list,
+ $p_remove_path,
+ $p_preserve
+ );
$this->_close();
}
return $v_result;
}
- // }}}
- // {{{ setAttribute()
/**
- * This method set specific attributes of the archive. It uses a variable
- * list of parameters, in the format attribute code + attribute values :
- * $arch->setAttribute(ARCHIVE_TAR_ATT_SEPARATOR, ',');
- * @param mixed $argv variable list of attributes and values
- * @return true on success, false on error.
- * @access public
- */
- function setAttribute()
+ * This method set specific attributes of the archive. It uses a variable
+ * list of parameters, in the format attribute code + attribute values :
+ * $arch->setAttribute(ARCHIVE_TAR_ATT_SEPARATOR, ',');
+ *
+ * @return true on success, false on error.
+ */
+ public function setAttribute()
{
$v_result = true;
@@ -592,30 +714,32 @@ class Archive_Tar // extends PEAR
}
// ----- Get the arguments
- $v_att_list = &func_get_args();
+ $v_att_list = & func_get_args();
// ----- Read the attributes
- $i=0;
- while ($i<$v_size) {
+ $i = 0;
+ while ($i < $v_size) {
// ----- Look for next option
switch ($v_att_list[$i]) {
// ----- Look for options that request a string value
case ARCHIVE_TAR_ATT_SEPARATOR :
// ----- Check the number of parameters
- if (($i+1) >= $v_size) {
- $this->_error('Invalid number of parameters for '
- .'attribute ARCHIVE_TAR_ATT_SEPARATOR');
+ if (($i + 1) >= $v_size) {
+ $this->_error(
+ 'Invalid number of parameters for '
+ . 'attribute ARCHIVE_TAR_ATT_SEPARATOR'
+ );
return false;
}
// ----- Get the value
- $this->_separator = $v_att_list[$i+1];
+ $this->_separator = $v_att_list[$i + 1];
$i++;
- break;
+ break;
default :
- $this->_error('Unknow attribute code '.$v_att_list[$i].'');
+ $this->_error('Unknown attribute code ' . $v_att_list[$i] . '');
return false;
}
@@ -625,151 +749,248 @@ class Archive_Tar // extends PEAR
return $v_result;
}
- // }}}
- // {{{ _error()
- function _error($p_message)
+ /**
+ * This method sets the regular expression for ignoring files and directories
+ * at import, for example:
+ * $arch->setIgnoreRegexp("#CVS|\.svn#");
+ *
+ * @param string $regexp regular expression defining which files or directories to ignore
+ */
+ public function setIgnoreRegexp($regexp)
{
- // ----- To be completed
-// $this->raiseError($p_message);
+ $this->_ignore_regexp = $regexp;
+ }
+
+ /**
+ * This method sets the regular expression for ignoring all files and directories
+ * matching the filenames in the array list at import, for example:
+ * $arch->setIgnoreList(array('CVS', '.svn', 'bin/tool'));
+ *
+ * @param array $list a list of file or directory names to ignore
+ *
+ * @access public
+ */
+ public function setIgnoreList($list)
+ {
+ $regexp = str_replace(array('#', '.', '^', '$'), array('\#', '\.', '\^', '\$'), $list);
+ $regexp = '#/' . join('$|/', $list) . '#';
+ $this->setIgnoreRegexp($regexp);
+ }
+
+ /**
+ * @param string $p_message
+ */
+ public function _error($p_message)
+ {
+ // Drupal change $this->error_object = $this->raiseError($p_message).
throw new Exception($p_message);
}
- // }}}
- // {{{ _warning()
- function _warning($p_message)
+ /**
+ * @param string $p_message
+ */
+ public function _warning($p_message)
{
- // ----- To be completed
-// $this->raiseError($p_message);
+ // Drupal change $this->error_object = $this->raiseError($p_message).
throw new Exception($p_message);
}
- // }}}
- // {{{ _isArchive()
- function _isArchive($p_filename=NULL)
+ /**
+ * @param string $p_filename
+ * @return bool
+ */
+ public function _isArchive($p_filename = null)
{
- if ($p_filename == NULL) {
+ if ($p_filename == null) {
$p_filename = $this->_tarname;
}
clearstatcache();
return @is_file($p_filename) && !@is_link($p_filename);
}
- // }}}
- // {{{ _openWrite()
- function _openWrite()
+ /**
+ * @return bool
+ */
+ public function _openWrite()
{
- if ($this->_compress_type == 'gz')
+ if ($this->_compress_type == 'gz' && function_exists('gzopen')) {
$this->_file = @gzopen($this->_tarname, "wb9");
- else if ($this->_compress_type == 'bz2')
- $this->_file = @bzopen($this->_tarname, "w");
- else if ($this->_compress_type == 'none')
- $this->_file = @fopen($this->_tarname, "wb");
- else
- $this->_error('Unknown or missing compression type ('
- .$this->_compress_type.')');
+ } else {
+ if ($this->_compress_type == 'bz2' && function_exists('bzopen')) {
+ $this->_file = @bzopen($this->_tarname, "w");
+ } else {
+ if ($this->_compress_type == 'lzma2' && function_exists('xzopen')) {
+ $this->_file = @xzopen($this->_tarname, 'w');
+ } else {
+ if ($this->_compress_type == 'none') {
+ $this->_file = @fopen($this->_tarname, "wb");
+ } else {
+ $this->_error(
+ 'Unknown or missing compression type ('
+ . $this->_compress_type . ')'
+ );
+ return false;
+ }
+ }
+ }
+ }
if ($this->_file == 0) {
- $this->_error('Unable to open in write mode \''
- .$this->_tarname.'\'');
+ $this->_error(
+ 'Unable to open in write mode \''
+ . $this->_tarname . '\''
+ );
return false;
}
return true;
}
- // }}}
- // {{{ _openRead()
- function _openRead()
+ /**
+ * @return bool
+ */
+ public function _openRead()
{
if (strtolower(substr($this->_tarname, 0, 7)) == 'http://') {
- // ----- Look if a local copy need to be done
- if ($this->_temp_tarname == '') {
- $this->_temp_tarname = uniqid('tar').'.tmp';
- if (!$v_file_from = @fopen($this->_tarname, 'rb')) {
- $this->_error('Unable to open in read mode \''
- .$this->_tarname.'\'');
- $this->_temp_tarname = '';
- return false;
- }
- if (!$v_file_to = @fopen($this->_temp_tarname, 'wb')) {
- $this->_error('Unable to open in write mode \''
- .$this->_temp_tarname.'\'');
- $this->_temp_tarname = '';
- return false;
- }
- while ($v_data = @fread($v_file_from, 1024))
- @fwrite($v_file_to, $v_data);
- @fclose($v_file_from);
- @fclose($v_file_to);
- }
+ // ----- Look if a local copy need to be done
+ if ($this->_temp_tarname == '') {
+ $this->_temp_tarname = uniqid('tar') . '.tmp';
+ if (!$v_file_from = @fopen($this->_tarname, 'rb')) {
+ $this->_error(
+ 'Unable to open in read mode \''
+ . $this->_tarname . '\''
+ );
+ $this->_temp_tarname = '';
+ return false;
+ }
+ if (!$v_file_to = @fopen($this->_temp_tarname, 'wb')) {
+ $this->_error(
+ 'Unable to open in write mode \''
+ . $this->_temp_tarname . '\''
+ );
+ $this->_temp_tarname = '';
+ return false;
+ }
+ while ($v_data = @fread($v_file_from, 1024)) {
+ @fwrite($v_file_to, $v_data);
+ }
+ @fclose($v_file_from);
+ @fclose($v_file_to);
+ }
- // ----- File to open if the local copy
- $v_filename = $this->_temp_tarname;
+ // ----- File to open if the local copy
+ $v_filename = $this->_temp_tarname;
+ } else {
+ // ----- File to open if the normal Tar file
- } else
- // ----- File to open if the normal Tar file
- $v_filename = $this->_tarname;
+ $v_filename = $this->_tarname;
+ }
- if ($this->_compress_type == 'gz')
+ if ($this->_compress_type == 'gz' && function_exists('gzopen')) {
$this->_file = @gzopen($v_filename, "rb");
- else if ($this->_compress_type == 'bz2')
- $this->_file = @bzopen($v_filename, "r");
- else if ($this->_compress_type == 'none')
- $this->_file = @fopen($v_filename, "rb");
- else
- $this->_error('Unknown or missing compression type ('
- .$this->_compress_type.')');
+ } else {
+ if ($this->_compress_type == 'bz2' && function_exists('bzopen')) {
+ $this->_file = @bzopen($v_filename, "r");
+ } else {
+ if ($this->_compress_type == 'lzma2' && function_exists('xzopen')) {
+ $this->_file = @xzopen($v_filename, "r");
+ } else {
+ if ($this->_compress_type == 'none') {
+ $this->_file = @fopen($v_filename, "rb");
+ } else {
+ $this->_error(
+ 'Unknown or missing compression type ('
+ . $this->_compress_type . ')'
+ );
+ return false;
+ }
+ }
+ }
+ }
if ($this->_file == 0) {
- $this->_error('Unable to open in read mode \''.$v_filename.'\'');
+ $this->_error('Unable to open in read mode \'' . $v_filename . '\'');
return false;
}
return true;
}
- // }}}
- // {{{ _openReadWrite()
- function _openReadWrite()
+ /**
+ * @return bool
+ */
+ public function _openReadWrite()
{
- if ($this->_compress_type == 'gz')
+ if ($this->_compress_type == 'gz') {
$this->_file = @gzopen($this->_tarname, "r+b");
- else if ($this->_compress_type == 'bz2') {
- $this->_error('Unable to open bz2 in read/write mode \''
- .$this->_tarname.'\' (limitation of bz2 extension)');
- return false;
- } else if ($this->_compress_type == 'none')
- $this->_file = @fopen($this->_tarname, "r+b");
- else
- $this->_error('Unknown or missing compression type ('
- .$this->_compress_type.')');
+ } else {
+ if ($this->_compress_type == 'bz2') {
+ $this->_error(
+ 'Unable to open bz2 in read/write mode \''
+ . $this->_tarname . '\' (limitation of bz2 extension)'
+ );
+ return false;
+ } else {
+ if ($this->_compress_type == 'lzma2') {
+ $this->_error(
+ 'Unable to open lzma2 in read/write mode \''
+ . $this->_tarname . '\' (limitation of lzma2 extension)'
+ );
+ return false;
+ } else {
+ if ($this->_compress_type == 'none') {
+ $this->_file = @fopen($this->_tarname, "r+b");
+ } else {
+ $this->_error(
+ 'Unknown or missing compression type ('
+ . $this->_compress_type . ')'
+ );
+ return false;
+ }
+ }
+ }
+ }
if ($this->_file == 0) {
- $this->_error('Unable to open in read/write mode \''
- .$this->_tarname.'\'');
+ $this->_error(
+ 'Unable to open in read/write mode \''
+ . $this->_tarname . '\''
+ );
return false;
}
return true;
}
- // }}}
- // {{{ _close()
- function _close()
+ /**
+ * @return bool
+ */
+ public function _close()
{
//if (isset($this->_file)) {
if (is_resource($this->_file)) {
- if ($this->_compress_type == 'gz')
+ if ($this->_compress_type == 'gz') {
@gzclose($this->_file);
- else if ($this->_compress_type == 'bz2')
- @bzclose($this->_file);
- else if ($this->_compress_type == 'none')
- @fclose($this->_file);
- else
- $this->_error('Unknown or missing compression type ('
- .$this->_compress_type.')');
+ } else {
+ if ($this->_compress_type == 'bz2') {
+ @bzclose($this->_file);
+ } else {
+ if ($this->_compress_type == 'lzma2') {
+ @xzclose($this->_file);
+ } else {
+ if ($this->_compress_type == 'none') {
+ @fclose($this->_file);
+ } else {
+ $this->_error(
+ 'Unknown or missing compression type ('
+ . $this->_compress_type . ')'
+ );
+ }
+ }
+ }
+ }
$this->_file = 0;
}
@@ -783,10 +1004,11 @@ class Archive_Tar // extends PEAR
return true;
}
- // }}}
- // {{{ _cleanFile()
- function _cleanFile()
+ /**
+ * @return bool
+ */
+ public function _cleanFile()
{
$this->_close();
@@ -803,296 +1025,419 @@ class Archive_Tar // extends PEAR
return true;
}
- // }}}
- // {{{ _writeBlock()
- function _writeBlock($p_binary_data, $p_len=null)
+ /**
+ * @param mixed $p_binary_data
+ * @param integer $p_len
+ * @return bool
+ */
+ public function _writeBlock($p_binary_data, $p_len = null)
{
- if (is_resource($this->_file)) {
- if ($p_len === null) {
- if ($this->_compress_type == 'gz')
- @gzputs($this->_file, $p_binary_data);
- else if ($this->_compress_type == 'bz2')
- @bzwrite($this->_file, $p_binary_data);
- else if ($this->_compress_type == 'none')
- @fputs($this->_file, $p_binary_data);
- else
- $this->_error('Unknown or missing compression type ('
- .$this->_compress_type.')');
- } else {
- if ($this->_compress_type == 'gz')
- @gzputs($this->_file, $p_binary_data, $p_len);
- else if ($this->_compress_type == 'bz2')
- @bzwrite($this->_file, $p_binary_data, $p_len);
- else if ($this->_compress_type == 'none')
- @fputs($this->_file, $p_binary_data, $p_len);
- else
- $this->_error('Unknown or missing compression type ('
- .$this->_compress_type.')');
-
- }
- }
- return true;
+ if (is_resource($this->_file)) {
+ if ($p_len === null) {
+ if ($this->_compress_type == 'gz') {
+ @gzputs($this->_file, $p_binary_data);
+ } else {
+ if ($this->_compress_type == 'bz2') {
+ @bzwrite($this->_file, $p_binary_data);
+ } else {
+ if ($this->_compress_type == 'lzma2') {
+ @xzwrite($this->_file, $p_binary_data);
+ } else {
+ if ($this->_compress_type == 'none') {
+ @fputs($this->_file, $p_binary_data);
+ } else {
+ $this->_error(
+ 'Unknown or missing compression type ('
+ . $this->_compress_type . ')'
+ );
+ }
+ }
+ }
+ }
+ } else {
+ if ($this->_compress_type == 'gz') {
+ @gzputs($this->_file, $p_binary_data, $p_len);
+ } else {
+ if ($this->_compress_type == 'bz2') {
+ @bzwrite($this->_file, $p_binary_data, $p_len);
+ } else {
+ if ($this->_compress_type == 'lzma2') {
+ @xzwrite($this->_file, $p_binary_data, $p_len);
+ } else {
+ if ($this->_compress_type == 'none') {
+ @fputs($this->_file, $p_binary_data, $p_len);
+ } else {
+ $this->_error(
+ 'Unknown or missing compression type ('
+ . $this->_compress_type . ')'
+ );
+ }
+ }
+ }
+ }
+ }
+ }
+ return true;
}
- // }}}
- // {{{ _readBlock()
- function _readBlock()
+ /**
+ * @return null|string
+ */
+ public function _readBlock()
{
- $v_block = null;
- if (is_resource($this->_file)) {
- if ($this->_compress_type == 'gz')
- $v_block = @gzread($this->_file, 512);
- else if ($this->_compress_type == 'bz2')
- $v_block = @bzread($this->_file, 512);
- else if ($this->_compress_type == 'none')
- $v_block = @fread($this->_file, 512);
- else
- $this->_error('Unknown or missing compression type ('
- .$this->_compress_type.')');
- }
- return $v_block;
+ $v_block = null;
+ if (is_resource($this->_file)) {
+ if ($this->_compress_type == 'gz') {
+ $v_block = @gzread($this->_file, 512);
+ } else {
+ if ($this->_compress_type == 'bz2') {
+ $v_block = @bzread($this->_file, 512);
+ } else {
+ if ($this->_compress_type == 'lzma2') {
+ $v_block = @xzread($this->_file, 512);
+ } else {
+ if ($this->_compress_type == 'none') {
+ $v_block = @fread($this->_file, 512);
+ } else {
+ $this->_error(
+ 'Unknown or missing compression type ('
+ . $this->_compress_type . ')'
+ );
+ }
+ }
+ }
+ }
+ }
+ return $v_block;
}
- // }}}
- // {{{ _jumpBlock()
- function _jumpBlock($p_len=null)
+ /**
+ * @param null $p_len
+ * @return bool
+ */
+ public function _jumpBlock($p_len = null)
{
- if (is_resource($this->_file)) {
- if ($p_len === null)
- $p_len = 1;
+ if (is_resource($this->_file)) {
+ if ($p_len === null) {
+ $p_len = 1;
+ }
- if ($this->_compress_type == 'gz') {
- @gzseek($this->_file, gztell($this->_file)+($p_len*512));
- }
- else if ($this->_compress_type == 'bz2') {
- // ----- Replace missing bztell() and bzseek()
- for ($i=0; $i<$p_len; $i++)
- $this->_readBlock();
- } else if ($this->_compress_type == 'none')
- @fseek($this->_file, ftell($this->_file)+($p_len*512));
- else
- $this->_error('Unknown or missing compression type ('
- .$this->_compress_type.')');
-
- }
- return true;
+ if ($this->_compress_type == 'gz') {
+ @gzseek($this->_file, gztell($this->_file) + ($p_len * 512));
+ } else {
+ if ($this->_compress_type == 'bz2') {
+ // ----- Replace missing bztell() and bzseek()
+ for ($i = 0; $i < $p_len; $i++) {
+ $this->_readBlock();
+ }
+ } else {
+ if ($this->_compress_type == 'lzma2') {
+ // ----- Replace missing xztell() and xzseek()
+ for ($i = 0; $i < $p_len; $i++) {
+ $this->_readBlock();
+ }
+ } else {
+ if ($this->_compress_type == 'none') {
+ @fseek($this->_file, $p_len * 512, SEEK_CUR);
+ } else {
+ $this->_error(
+ 'Unknown or missing compression type ('
+ . $this->_compress_type . ')'
+ );
+ }
+ }
+ }
+ }
+ }
+ return true;
}
- // }}}
- // {{{ _writeFooter()
- function _writeFooter()
+ /**
+ * @return bool
+ */
+ public function _writeFooter()
{
- if (is_resource($this->_file)) {
- // ----- Write the last 0 filled block for end of archive
- $v_binary_data = pack('a1024', '');
- $this->_writeBlock($v_binary_data);
- }
- return true;
+ if (is_resource($this->_file)) {
+ // ----- Write the last 0 filled block for end of archive
+ $v_binary_data = pack('a1024', '');
+ $this->_writeBlock($v_binary_data);
+ }
+ return true;
}
- // }}}
- // {{{ _addList()
- function _addList($p_list, $p_add_dir, $p_remove_dir)
+ /**
+ * @param array $p_list
+ * @param string $p_add_dir
+ * @param string $p_remove_dir
+ * @return bool
+ */
+ public function _addList($p_list, $p_add_dir, $p_remove_dir)
{
- $v_result=true;
- $v_header = array();
+ $v_result = true;
+ $v_header = array();
- // ----- Remove potential windows directory separator
- $p_add_dir = $this->_translateWinPath($p_add_dir);
- $p_remove_dir = $this->_translateWinPath($p_remove_dir, false);
+ // ----- Remove potential windows directory separator
+ $p_add_dir = $this->_translateWinPath($p_add_dir);
+ $p_remove_dir = $this->_translateWinPath($p_remove_dir, false);
- if (!$this->_file) {
- $this->_error('Invalid file descriptor');
- return false;
- }
-
- if (sizeof($p_list) == 0)
- return true;
-
- foreach ($p_list as $v_filename) {
- if (!$v_result) {
- break;
- }
-
- // ----- Skip the current tar name
- if ($v_filename == $this->_tarname)
- continue;
-
- if ($v_filename == '')
- continue;
-
- if (!file_exists($v_filename)) {
- $this->_warning("File '$v_filename' does not exist");
- continue;
+ if (!$this->_file) {
+ $this->_error('Invalid file descriptor');
+ return false;
}
- // ----- Add the file or directory header
- if (!$this->_addFile($v_filename, $v_header, $p_add_dir, $p_remove_dir))
- return false;
+ if (sizeof($p_list) == 0) {
+ return true;
+ }
- if (@is_dir($v_filename) && !@is_link($v_filename)) {
- if (!($p_hdir = opendir($v_filename))) {
- $this->_warning("Directory '$v_filename' can not be read");
+ foreach ($p_list as $v_filename) {
+ if (!$v_result) {
+ break;
+ }
+
+ // ----- Skip the current tar name
+ if ($v_filename == $this->_tarname) {
continue;
}
- while (false !== ($p_hitem = readdir($p_hdir))) {
- if (($p_hitem != '.') && ($p_hitem != '..')) {
- if ($v_filename != ".")
- $p_temp_list[0] = $v_filename.'/'.$p_hitem;
- else
- $p_temp_list[0] = $p_hitem;
- $v_result = $this->_addList($p_temp_list,
- $p_add_dir,
- $p_remove_dir);
+ if ($v_filename == '') {
+ continue;
+ }
+
+ // ----- ignore files and directories matching the ignore regular expression
+ if ($this->_ignore_regexp && preg_match($this->_ignore_regexp, '/' . $v_filename)) {
+ $this->_warning("File '$v_filename' ignored");
+ continue;
+ }
+
+ if (!file_exists($v_filename) && !is_link($v_filename)) {
+ $this->_warning("File '$v_filename' does not exist");
+ continue;
+ }
+
+ // ----- Add the file or directory header
+ if (!$this->_addFile($v_filename, $v_header, $p_add_dir, $p_remove_dir)) {
+ return false;
+ }
+
+ if (@is_dir($v_filename) && !@is_link($v_filename)) {
+ if (!($p_hdir = opendir($v_filename))) {
+ $this->_warning("Directory '$v_filename' can not be read");
+ continue;
+ }
+ while (false !== ($p_hitem = readdir($p_hdir))) {
+ if (($p_hitem != '.') && ($p_hitem != '..')) {
+ if ($v_filename != ".") {
+ $p_temp_list[0] = $v_filename . '/' . $p_hitem;
+ } else {
+ $p_temp_list[0] = $p_hitem;
+ }
+
+ $v_result = $this->_addList(
+ $p_temp_list,
+ $p_add_dir,
+ $p_remove_dir
+ );
+ }
+ }
+
+ unset($p_temp_list);
+ unset($p_hdir);
+ unset($p_hitem);
+ }
+ }
+
+ return $v_result;
+ }
+
+ /**
+ * @param string $p_filename
+ * @param mixed $p_header
+ * @param string $p_add_dir
+ * @param string $p_remove_dir
+ * @param null $v_stored_filename
+ * @return bool
+ */
+ public function _addFile($p_filename, &$p_header, $p_add_dir, $p_remove_dir, $v_stored_filename = null)
+ {
+ if (!$this->_file) {
+ $this->_error('Invalid file descriptor');
+ return false;
+ }
+
+ if ($p_filename == '') {
+ $this->_error('Invalid file name');
+ return false;
+ }
+
+ if (is_null($v_stored_filename)) {
+ // ----- Calculate the stored filename
+ $p_filename = $this->_translateWinPath($p_filename, false);
+ $v_stored_filename = $p_filename;
+
+ if (strcmp($p_filename, $p_remove_dir) == 0) {
+ return true;
+ }
+
+ if ($p_remove_dir != '') {
+ if (substr($p_remove_dir, -1) != '/') {
+ $p_remove_dir .= '/';
+ }
+
+ if (substr($p_filename, 0, strlen($p_remove_dir)) == $p_remove_dir) {
+ $v_stored_filename = substr($p_filename, strlen($p_remove_dir));
}
}
- unset($p_temp_list);
- unset($p_hdir);
- unset($p_hitem);
+ $v_stored_filename = $this->_translateWinPath($v_stored_filename);
+ if ($p_add_dir != '') {
+ if (substr($p_add_dir, -1) == '/') {
+ $v_stored_filename = $p_add_dir . $v_stored_filename;
+ } else {
+ $v_stored_filename = $p_add_dir . '/' . $v_stored_filename;
+ }
+ }
+
+ $v_stored_filename = $this->_pathReduction($v_stored_filename);
}
- }
- return $v_result;
+ if ($this->_isArchive($p_filename)) {
+ if (($v_file = @fopen($p_filename, "rb")) == 0) {
+ $this->_warning(
+ "Unable to open file '" . $p_filename
+ . "' in binary read mode"
+ );
+ return true;
+ }
+
+ if (!$this->_writeHeader($p_filename, $v_stored_filename)) {
+ return false;
+ }
+
+ while (($v_buffer = fread($v_file, 512)) != '') {
+ $v_binary_data = pack("a512", "$v_buffer");
+ $this->_writeBlock($v_binary_data);
+ }
+
+ fclose($v_file);
+ } else {
+ // ----- Only header for dir
+ if (!$this->_writeHeader($p_filename, $v_stored_filename)) {
+ return false;
+ }
+ }
+
+ return true;
}
- // }}}
- // {{{ _addFile()
- function _addFile($p_filename, &$p_header, $p_add_dir, $p_remove_dir)
+ /**
+ * @param string $p_filename
+ * @param string $p_string
+ * @param bool $p_datetime
+ * @param array $p_params
+ * @return bool
+ */
+ public function _addString($p_filename, $p_string, $p_datetime = false, $p_params = array())
{
- if (!$this->_file) {
- $this->_error('Invalid file descriptor');
- return false;
- }
-
- if ($p_filename == '') {
- $this->_error('Invalid file name');
- return false;
- }
-
- // ----- Calculate the stored filename
- $p_filename = $this->_translateWinPath($p_filename, false);;
- $v_stored_filename = $p_filename;
- if (strcmp($p_filename, $p_remove_dir) == 0) {
- return true;
- }
- if ($p_remove_dir != '') {
- if (substr($p_remove_dir, -1) != '/')
- $p_remove_dir .= '/';
-
- if (substr($p_filename, 0, strlen($p_remove_dir)) == $p_remove_dir)
- $v_stored_filename = substr($p_filename, strlen($p_remove_dir));
- }
- $v_stored_filename = $this->_translateWinPath($v_stored_filename);
- if ($p_add_dir != '') {
- if (substr($p_add_dir, -1) == '/')
- $v_stored_filename = $p_add_dir.$v_stored_filename;
- else
- $v_stored_filename = $p_add_dir.'/'.$v_stored_filename;
- }
-
- $v_stored_filename = $this->_pathReduction($v_stored_filename);
-
- if ($this->_isArchive($p_filename)) {
- if (($v_file = @fopen($p_filename, "rb")) == 0) {
- $this->_warning("Unable to open file '".$p_filename
- ."' in binary read mode");
- return true;
- }
-
- if (!$this->_writeHeader($p_filename, $v_stored_filename))
- return false;
-
- while (($v_buffer = fread($v_file, 512)) != '') {
- $v_binary_data = pack("a512", "$v_buffer");
- $this->_writeBlock($v_binary_data);
- }
-
- fclose($v_file);
-
- } else {
- // ----- Only header for dir
- if (!$this->_writeHeader($p_filename, $v_stored_filename))
- return false;
- }
-
- return true;
- }
- // }}}
-
- // {{{ _addString()
- function _addString($p_filename, $p_string)
- {
- if (!$this->_file) {
- $this->_error('Invalid file descriptor');
- return false;
- }
-
- if ($p_filename == '') {
- $this->_error('Invalid file name');
- return false;
- }
-
- // ----- Calculate the stored filename
- $p_filename = $this->_translateWinPath($p_filename, false);;
-
- if (!$this->_writeHeaderBlock($p_filename, strlen($p_string),
- time(), 384, "", 0, 0))
- return false;
-
- $i=0;
- while (($v_buffer = substr($p_string, (($i++)*512), 512)) != '') {
- $v_binary_data = pack("a512", $v_buffer);
- $this->_writeBlock($v_binary_data);
- }
-
- return true;
- }
- // }}}
-
- // {{{ _writeHeader()
- function _writeHeader($p_filename, $p_stored_filename)
- {
- if ($p_stored_filename == '')
- $p_stored_filename = $p_filename;
- $v_reduce_filename = $this->_pathReduction($p_stored_filename);
-
- if (strlen($v_reduce_filename) > 99) {
- if (!$this->_writeLongHeader($v_reduce_filename))
+ $p_stamp = @$p_params["stamp"] ? $p_params["stamp"] : ($p_datetime ? $p_datetime : time());
+ $p_mode = @$p_params["mode"] ? $p_params["mode"] : 0600;
+ $p_type = @$p_params["type"] ? $p_params["type"] : "";
+ $p_uid = @$p_params["uid"] ? $p_params["uid"] : 0;
+ $p_gid = @$p_params["gid"] ? $p_params["gid"] : 0;
+ if (!$this->_file) {
+ $this->_error('Invalid file descriptor');
return false;
}
- $v_info = lstat($p_filename);
- $v_uid = sprintf("%6s ", DecOct($v_info[4]));
- $v_gid = sprintf("%6s ", DecOct($v_info[5]));
- $v_perms = sprintf("%6s ", DecOct($v_info['mode']));
+ if ($p_filename == '') {
+ $this->_error('Invalid file name');
+ return false;
+ }
- $v_mtime = sprintf("%11s", DecOct($v_info['mode']));
+ // ----- Calculate the stored filename
+ $p_filename = $this->_translateWinPath($p_filename, false);
+
+ // ----- If datetime is not specified, set current time
+ if ($p_datetime === false) {
+ $p_datetime = time();
+ }
+
+ if (!$this->_writeHeaderBlock(
+ $p_filename,
+ strlen($p_string),
+ $p_stamp,
+ $p_mode,
+ $p_type,
+ $p_uid,
+ $p_gid
+ )
+ ) {
+ return false;
+ }
+
+ $i = 0;
+ while (($v_buffer = substr($p_string, (($i++) * 512), 512)) != '') {
+ $v_binary_data = pack("a512", $v_buffer);
+ $this->_writeBlock($v_binary_data);
+ }
+
+ return true;
+ }
+
+ /**
+ * @param string $p_filename
+ * @param string $p_stored_filename
+ * @return bool
+ */
+ public function _writeHeader($p_filename, $p_stored_filename)
+ {
+ if ($p_stored_filename == '') {
+ $p_stored_filename = $p_filename;
+ }
+ $v_reduce_filename = $this->_pathReduction($p_stored_filename);
+
+ if (strlen($v_reduce_filename) > 99) {
+ if (!$this->_writeLongHeader($v_reduce_filename)) {
+ return false;
+ }
+ }
+
+ $v_info = lstat($p_filename);
+ $v_uid = sprintf("%07s", DecOct($v_info[4]));
+ $v_gid = sprintf("%07s", DecOct($v_info[5]));
+ $v_perms = sprintf("%07s", DecOct($v_info['mode'] & 000777));
+
+ $v_mtime = sprintf("%011s", DecOct($v_info['mtime']));
$v_linkname = '';
if (@is_link($p_filename)) {
- $v_typeflag = '2';
- $v_linkname = readlink($p_filename);
- $v_size = sprintf("%11s ", DecOct(0));
+ $v_typeflag = '2';
+ $v_linkname = readlink($p_filename);
+ $v_size = sprintf("%011s", DecOct(0));
} elseif (@is_dir($p_filename)) {
- $v_typeflag = "5";
- $v_size = sprintf("%11s ", DecOct(0));
+ $v_typeflag = "5";
+ $v_size = sprintf("%011s", DecOct(0));
} else {
- $v_typeflag = '';
- clearstatcache();
- $v_size = sprintf("%11s ", DecOct($v_info['size']));
+ $v_typeflag = '0';
+ clearstatcache();
+ $v_size = sprintf("%011s", DecOct($v_info['size']));
}
- $v_magic = '';
+ $v_magic = 'ustar ';
- $v_version = '';
+ $v_version = ' ';
- $v_uname = '';
+ if (function_exists('posix_getpwuid')) {
+ $userinfo = posix_getpwuid($v_info[4]);
+ $groupinfo = posix_getgrgid($v_info[5]);
- $v_gname = '';
+ $v_uname = $userinfo['name'];
+ $v_gname = $groupinfo['name'];
+ } else {
+ $v_uname = '';
+ $v_gname = '';
+ }
$v_devmajor = '';
@@ -1100,31 +1445,49 @@ class Archive_Tar // extends PEAR
$v_prefix = '';
- $v_binary_data_first = pack("a100a8a8a8a12A12",
- $v_reduce_filename, $v_perms, $v_uid,
- $v_gid, $v_size, $v_mtime);
- $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12",
- $v_typeflag, $v_linkname, $v_magic,
- $v_version, $v_uname, $v_gname,
- $v_devmajor, $v_devminor, $v_prefix, '');
+ $v_binary_data_first = pack(
+ "a100a8a8a8a12a12",
+ $v_reduce_filename,
+ $v_perms,
+ $v_uid,
+ $v_gid,
+ $v_size,
+ $v_mtime
+ );
+ $v_binary_data_last = pack(
+ "a1a100a6a2a32a32a8a8a155a12",
+ $v_typeflag,
+ $v_linkname,
+ $v_magic,
+ $v_version,
+ $v_uname,
+ $v_gname,
+ $v_devmajor,
+ $v_devminor,
+ $v_prefix,
+ ''
+ );
// ----- Calculate the checksum
$v_checksum = 0;
// ..... First part of the header
- for ($i=0; $i<148; $i++)
- $v_checksum += ord(substr($v_binary_data_first,$i,1));
+ for ($i = 0; $i < 148; $i++) {
+ $v_checksum += ord(substr($v_binary_data_first, $i, 1));
+ }
// ..... Ignore the checksum value and replace it by ' ' (space)
- for ($i=148; $i<156; $i++)
+ for ($i = 148; $i < 156; $i++) {
$v_checksum += ord(' ');
+ }
// ..... Last part of the header
- for ($i=156, $j=0; $i<512; $i++, $j++)
- $v_checksum += ord(substr($v_binary_data_last,$j,1));
+ for ($i = 156, $j = 0; $i < 512; $i++, $j++) {
+ $v_checksum += ord(substr($v_binary_data_last, $j, 1));
+ }
// ----- Write the first 148 bytes of the header in the archive
$this->_writeBlock($v_binary_data_first, 148);
// ----- Write the calculated checksum
- $v_checksum = sprintf("%6s ", DecOct($v_checksum));
+ $v_checksum = sprintf("%06s ", DecOct($v_checksum));
$v_binary_data = pack("a8", $v_checksum);
$this->_writeBlock($v_binary_data, 8);
@@ -1133,40 +1496,62 @@ class Archive_Tar // extends PEAR
return true;
}
- // }}}
- // {{{ _writeHeaderBlock()
- function _writeHeaderBlock($p_filename, $p_size, $p_mtime=0, $p_perms=0,
- $p_type='', $p_uid=0, $p_gid=0)
- {
+ /**
+ * @param string $p_filename
+ * @param int $p_size
+ * @param int $p_mtime
+ * @param int $p_perms
+ * @param string $p_type
+ * @param int $p_uid
+ * @param int $p_gid
+ * @return bool
+ */
+ public function _writeHeaderBlock(
+ $p_filename,
+ $p_size,
+ $p_mtime = 0,
+ $p_perms = 0,
+ $p_type = '',
+ $p_uid = 0,
+ $p_gid = 0
+ ) {
$p_filename = $this->_pathReduction($p_filename);
if (strlen($p_filename) > 99) {
- if (!$this->_writeLongHeader($p_filename))
- return false;
+ if (!$this->_writeLongHeader($p_filename)) {
+ return false;
+ }
}
if ($p_type == "5") {
- $v_size = sprintf("%11s ", DecOct(0));
+ $v_size = sprintf("%011s", DecOct(0));
} else {
- $v_size = sprintf("%11s ", DecOct($p_size));
+ $v_size = sprintf("%011s", DecOct($p_size));
}
- $v_uid = sprintf("%6s ", DecOct($p_uid));
- $v_gid = sprintf("%6s ", DecOct($p_gid));
- $v_perms = sprintf("%6s ", DecOct($p_perms));
+ $v_uid = sprintf("%07s", DecOct($p_uid));
+ $v_gid = sprintf("%07s", DecOct($p_gid));
+ $v_perms = sprintf("%07s", DecOct($p_perms & 000777));
$v_mtime = sprintf("%11s", DecOct($p_mtime));
$v_linkname = '';
- $v_magic = '';
+ $v_magic = 'ustar ';
- $v_version = '';
+ $v_version = ' ';
- $v_uname = '';
+ if (function_exists('posix_getpwuid')) {
+ $userinfo = posix_getpwuid($p_uid);
+ $groupinfo = posix_getgrgid($p_gid);
- $v_gname = '';
+ $v_uname = $userinfo['name'];
+ $v_gname = $groupinfo['name'];
+ } else {
+ $v_uname = '';
+ $v_gname = '';
+ }
$v_devmajor = '';
@@ -1174,31 +1559,49 @@ class Archive_Tar // extends PEAR
$v_prefix = '';
- $v_binary_data_first = pack("a100a8a8a8a12A12",
- $p_filename, $v_perms, $v_uid, $v_gid,
- $v_size, $v_mtime);
- $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12",
- $p_type, $v_linkname, $v_magic,
- $v_version, $v_uname, $v_gname,
- $v_devmajor, $v_devminor, $v_prefix, '');
+ $v_binary_data_first = pack(
+ "a100a8a8a8a12A12",
+ $p_filename,
+ $v_perms,
+ $v_uid,
+ $v_gid,
+ $v_size,
+ $v_mtime
+ );
+ $v_binary_data_last = pack(
+ "a1a100a6a2a32a32a8a8a155a12",
+ $p_type,
+ $v_linkname,
+ $v_magic,
+ $v_version,
+ $v_uname,
+ $v_gname,
+ $v_devmajor,
+ $v_devminor,
+ $v_prefix,
+ ''
+ );
// ----- Calculate the checksum
$v_checksum = 0;
// ..... First part of the header
- for ($i=0; $i<148; $i++)
- $v_checksum += ord(substr($v_binary_data_first,$i,1));
+ for ($i = 0; $i < 148; $i++) {
+ $v_checksum += ord(substr($v_binary_data_first, $i, 1));
+ }
// ..... Ignore the checksum value and replace it by ' ' (space)
- for ($i=148; $i<156; $i++)
+ for ($i = 148; $i < 156; $i++) {
$v_checksum += ord(' ');
+ }
// ..... Last part of the header
- for ($i=156, $j=0; $i<512; $i++, $j++)
- $v_checksum += ord(substr($v_binary_data_last,$j,1));
+ for ($i = 156, $j = 0; $i < 512; $i++, $j++) {
+ $v_checksum += ord(substr($v_binary_data_last, $j, 1));
+ }
// ----- Write the first 148 bytes of the header in the archive
$this->_writeBlock($v_binary_data_first, 148);
// ----- Write the calculated checksum
- $v_checksum = sprintf("%6s ", DecOct($v_checksum));
+ $v_checksum = sprintf("%06s ", DecOct($v_checksum));
$v_binary_data = pack("a8", $v_checksum);
$this->_writeBlock($v_binary_data, 8);
@@ -1207,10 +1610,12 @@ class Archive_Tar // extends PEAR
return true;
}
- // }}}
- // {{{ _writeLongHeader()
- function _writeLongHeader($p_filename)
+ /**
+ * @param string $p_filename
+ * @return bool
+ */
+ public function _writeLongHeader($p_filename)
{
$v_size = sprintf("%11s ", DecOct(strlen($p_filename)));
@@ -1232,30 +1637,49 @@ class Archive_Tar // extends PEAR
$v_prefix = '';
- $v_binary_data_first = pack("a100a8a8a8a12A12",
- '././@LongLink', 0, 0, 0, $v_size, 0);
- $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12",
- $v_typeflag, $v_linkname, $v_magic,
- $v_version, $v_uname, $v_gname,
- $v_devmajor, $v_devminor, $v_prefix, '');
+ $v_binary_data_first = pack(
+ "a100a8a8a8a12a12",
+ '././@LongLink',
+ 0,
+ 0,
+ 0,
+ $v_size,
+ 0
+ );
+ $v_binary_data_last = pack(
+ "a1a100a6a2a32a32a8a8a155a12",
+ $v_typeflag,
+ $v_linkname,
+ $v_magic,
+ $v_version,
+ $v_uname,
+ $v_gname,
+ $v_devmajor,
+ $v_devminor,
+ $v_prefix,
+ ''
+ );
// ----- Calculate the checksum
$v_checksum = 0;
// ..... First part of the header
- for ($i=0; $i<148; $i++)
- $v_checksum += ord(substr($v_binary_data_first,$i,1));
+ for ($i = 0; $i < 148; $i++) {
+ $v_checksum += ord(substr($v_binary_data_first, $i, 1));
+ }
// ..... Ignore the checksum value and replace it by ' ' (space)
- for ($i=148; $i<156; $i++)
+ for ($i = 148; $i < 156; $i++) {
$v_checksum += ord(' ');
+ }
// ..... Last part of the header
- for ($i=156, $j=0; $i<512; $i++, $j++)
- $v_checksum += ord(substr($v_binary_data_last,$j,1));
+ for ($i = 156, $j = 0; $i < 512; $i++, $j++) {
+ $v_checksum += ord(substr($v_binary_data_last, $j, 1));
+ }
// ----- Write the first 148 bytes of the header in the archive
$this->_writeBlock($v_binary_data_first, 148);
// ----- Write the calculated checksum
- $v_checksum = sprintf("%6s ", DecOct($v_checksum));
+ $v_checksum = sprintf("%06s ", DecOct($v_checksum));
$v_binary_data = pack("a8", $v_checksum);
$this->_writeBlock($v_binary_data, 8);
@@ -1263,27 +1687,30 @@ class Archive_Tar // extends PEAR
$this->_writeBlock($v_binary_data_last, 356);
// ----- Write the filename as content of the block
- $i=0;
- while (($v_buffer = substr($p_filename, (($i++)*512), 512)) != '') {
+ $i = 0;
+ while (($v_buffer = substr($p_filename, (($i++) * 512), 512)) != '') {
$v_binary_data = pack("a512", "$v_buffer");
$this->_writeBlock($v_binary_data);
}
return true;
}
- // }}}
- // {{{ _readHeader()
- function _readHeader($v_binary_data, &$v_header)
+ /**
+ * @param mixed $v_binary_data
+ * @param mixed $v_header
+ * @return bool
+ */
+ public function _readHeader($v_binary_data, &$v_header)
{
- if (strlen($v_binary_data)==0) {
+ if (strlen($v_binary_data) == 0) {
$v_header['filename'] = '';
return true;
}
if (strlen($v_binary_data) != 512) {
$v_header['filename'] = '';
- $this->_error('Invalid block size : '.strlen($v_binary_data));
+ $this->_error('Invalid block size : ' . strlen($v_binary_data));
return false;
}
@@ -1293,19 +1720,32 @@ class Archive_Tar // extends PEAR
// ----- Calculate the checksum
$v_checksum = 0;
// ..... First part of the header
- for ($i=0; $i<148; $i++)
- $v_checksum+=ord(substr($v_binary_data,$i,1));
+ for ($i = 0; $i < 148; $i++) {
+ $v_checksum += ord(substr($v_binary_data, $i, 1));
+ }
// ..... Ignore the checksum value and replace it by ' ' (space)
- for ($i=148; $i<156; $i++)
+ for ($i = 148; $i < 156; $i++) {
$v_checksum += ord(' ');
+ }
// ..... Last part of the header
- for ($i=156; $i<512; $i++)
- $v_checksum+=ord(substr($v_binary_data,$i,1));
+ for ($i = 156; $i < 512; $i++) {
+ $v_checksum += ord(substr($v_binary_data, $i, 1));
+ }
- $v_data = unpack("a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/"
- ."a8checksum/a1typeflag/a100link/a6magic/a2version/"
- ."a32uname/a32gname/a8devmajor/a8devminor",
- $v_binary_data);
+ if (version_compare(PHP_VERSION, "5.5.0-dev") < 0) {
+ $fmt = "a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/" .
+ "a8checksum/a1typeflag/a100link/a6magic/a2version/" .
+ "a32uname/a32gname/a8devmajor/a8devminor/a131prefix";
+ } else {
+ $fmt = "Z100filename/Z8mode/Z8uid/Z8gid/Z12size/Z12mtime/" .
+ "Z8checksum/Z1typeflag/Z100link/Z6magic/Z2version/" .
+ "Z32uname/Z32gname/Z8devmajor/Z8devminor/Z131prefix";
+ }
+ $v_data = unpack($fmt, $v_binary_data);
+
+ if (strlen($v_data["prefix"]) > 0) {
+ $v_data["filename"] = "$v_data[prefix]/$v_data[filename]";
+ }
// ----- Extract the checksum
$v_header['checksum'] = OctDec(trim($v_data['checksum']));
@@ -1313,20 +1753,25 @@ class Archive_Tar // extends PEAR
$v_header['filename'] = '';
// ----- Look for last block (empty block)
- if (($v_checksum == 256) && ($v_header['checksum'] == 0))
+ if (($v_checksum == 256) && ($v_header['checksum'] == 0)) {
return true;
+ }
- $this->_error('Invalid checksum for file "'.$v_data['filename']
- .'" : '.$v_checksum.' calculated, '
- .$v_header['checksum'].' expected');
+ $this->_error(
+ 'Invalid checksum for file "' . $v_data['filename']
+ . '" : ' . $v_checksum . ' calculated, '
+ . $v_header['checksum'] . ' expected'
+ );
return false;
}
// ----- Extract the properties
- $v_header['filename'] = trim($v_data['filename']);
+ $v_header['filename'] = rtrim($v_data['filename'], "\0");
if ($this->_maliciousFilename($v_header['filename'])) {
- $this->_error('Malicious .tar detected, file "' . $v_header['filename'] .
- '" will not install in desired directory tree');
+ $this->_error(
+ 'Malicious .tar detected, file "' . $v_header['filename'] .
+ '" will not install in desired directory tree'
+ );
return false;
}
$v_header['mode'] = OctDec(trim($v_data['mode']));
@@ -1335,11 +1780,11 @@ class Archive_Tar // extends PEAR
$v_header['size'] = OctDec(trim($v_data['size']));
$v_header['mtime'] = OctDec(trim($v_data['mtime']));
if (($v_header['typeflag'] = $v_data['typeflag']) == "5") {
- $v_header['size'] = 0;
+ $v_header['size'] = 0;
}
$v_header['link'] = trim($v_data['link']);
/* ----- All these fields are removed form the header because
- they do not carry interesting info
+ they do not carry interesting info
$v_header[magic] = trim($v_data[magic]);
$v_header[version] = trim($v_data[version]);
$v_header[uname] = trim($v_data[uname]);
@@ -1350,17 +1795,15 @@ class Archive_Tar // extends PEAR
return true;
}
- // }}}
- // {{{ _maliciousFilename()
/**
* Detect and report a malicious file name
*
* @param string $file
+ *
* @return bool
- * @access private
*/
- function _maliciousFilename($file)
+ private function _maliciousFilename($file)
{
if (strpos($file, '/../') !== false) {
return true;
@@ -1370,386 +1813,507 @@ class Archive_Tar // extends PEAR
}
return false;
}
- // }}}
- // {{{ _readLongHeader()
- function _readLongHeader(&$v_header)
- {
- $v_filename = '';
- $n = floor($v_header['size']/512);
- for ($i=0; $i<$n; $i++) {
- $v_content = $this->_readBlock();
- $v_filename .= $v_content;
- }
- if (($v_header['size'] % 512) != 0) {
- $v_content = $this->_readBlock();
- $v_filename .= $v_content;
- }
-
- // ----- Read the next header
- $v_binary_data = $this->_readBlock();
-
- if (!$this->_readHeader($v_binary_data, $v_header))
- return false;
-
- $v_filename = trim($v_filename);
- $v_header['filename'] = $v_filename;
- if ($this->_maliciousFilename($v_filename)) {
- $this->_error('Malicious .tar detected, file "' . $v_filename .
- '" will not install in desired directory tree');
- return false;
- }
-
- return true;
- }
- // }}}
-
- // {{{ _extractInString()
/**
- * This method extract from the archive one file identified by $p_filename.
- * The return value is a string with the file content, or NULL on error.
- * @param string $p_filename The path of the file to extract in a string.
- * @return a string with the file content or NULL.
- * @access private
- */
- function _extractInString($p_filename)
+ * @param $v_header
+ * @return bool
+ */
+ public function _readLongHeader(&$v_header)
{
- $v_result_str = "";
-
- While (strlen($v_binary_data = $this->_readBlock()) != 0)
- {
- if (!$this->_readHeader($v_binary_data, $v_header))
- return NULL;
-
- if ($v_header['filename'] == '')
- continue;
-
- // ----- Look for long filename
- if ($v_header['typeflag'] == 'L') {
- if (!$this->_readLongHeader($v_header))
- return NULL;
- }
-
- if ($v_header['filename'] == $p_filename) {
- if ($v_header['typeflag'] == "5") {
- $this->_error('Unable to extract in string a directory '
- .'entry {'.$v_header['filename'].'}');
- return NULL;
- } else {
- $n = floor($v_header['size']/512);
- for ($i=0; $i<$n; $i++) {
- $v_result_str .= $this->_readBlock();
- }
- if (($v_header['size'] % 512) != 0) {
- $v_content = $this->_readBlock();
- $v_result_str .= substr($v_content, 0,
- ($v_header['size'] % 512));
- }
- return $v_result_str;
- }
- } else {
- $this->_jumpBlock(ceil(($v_header['size']/512)));
- }
+ $v_filename = '';
+ $v_filesize = $v_header['size'];
+ $n = floor($v_header['size'] / 512);
+ for ($i = 0; $i < $n; $i++) {
+ $v_content = $this->_readBlock();
+ $v_filename .= $v_content;
+ }
+ if (($v_header['size'] % 512) != 0) {
+ $v_content = $this->_readBlock();
+ $v_filename .= $v_content;
}
- return NULL;
- }
- // }}}
+ // ----- Read the next header
+ $v_binary_data = $this->_readBlock();
- // {{{ _extractList()
- function _extractList($p_path, &$p_list_detail, $p_mode,
- $p_file_list, $p_remove_path)
- {
- $v_result=true;
- $v_nb = 0;
- $v_extract_all = true;
- $v_listing = false;
-
- $p_path = $this->_translateWinPath($p_path, false);
- if ($p_path == '' || (substr($p_path, 0, 1) != '/'
- && substr($p_path, 0, 3) != "../" && !strpos($p_path, ':'))) {
- $p_path = "./".$p_path;
- }
- $p_remove_path = $this->_translateWinPath($p_remove_path);
-
- // ----- Look for path to remove format (should end by /)
- if (($p_remove_path != '') && (substr($p_remove_path, -1) != '/'))
- $p_remove_path .= '/';
- $p_remove_path_size = strlen($p_remove_path);
-
- switch ($p_mode) {
- case "complete" :
- $v_extract_all = TRUE;
- $v_listing = FALSE;
- break;
- case "partial" :
- $v_extract_all = FALSE;
- $v_listing = FALSE;
- break;
- case "list" :
- $v_extract_all = FALSE;
- $v_listing = TRUE;
- break;
- default :
- $this->_error('Invalid extract mode ('.$p_mode.')');
- return false;
- }
-
- clearstatcache();
-
- while (strlen($v_binary_data = $this->_readBlock()) != 0)
- {
- $v_extract_file = FALSE;
- $v_extraction_stopped = 0;
-
- if (!$this->_readHeader($v_binary_data, $v_header))
- return false;
-
- if ($v_header['filename'] == '') {
- continue;
- }
-
- // ----- Look for long filename
- if ($v_header['typeflag'] == 'L') {
- if (!$this->_readLongHeader($v_header))
- return false;
- }
-
- if ((!$v_extract_all) && (is_array($p_file_list))) {
- // ----- By default no unzip if the file is not found
- $v_extract_file = false;
-
- for ($i=0; $i strlen($p_file_list[$i]))
- && (substr($v_header['filename'], 0, strlen($p_file_list[$i]))
- == $p_file_list[$i])) {
- $v_extract_file = TRUE;
- break;
- }
- }
-
- // ----- It is a file, so compare the file names
- elseif ($p_file_list[$i] == $v_header['filename']) {
- $v_extract_file = TRUE;
- break;
- }
- }
- } else {
- $v_extract_file = TRUE;
- }
-
- // ----- Look if this file need to be extracted
- if (($v_extract_file) && (!$v_listing))
- {
- if (($p_remove_path != '')
- && (substr($v_header['filename'], 0, $p_remove_path_size)
- == $p_remove_path))
- $v_header['filename'] = substr($v_header['filename'],
- $p_remove_path_size);
- if (($p_path != './') && ($p_path != '/')) {
- while (substr($p_path, -1) == '/')
- $p_path = substr($p_path, 0, strlen($p_path)-1);
-
- if (substr($v_header['filename'], 0, 1) == '/')
- $v_header['filename'] = $p_path.$v_header['filename'];
- else
- $v_header['filename'] = $p_path.'/'.$v_header['filename'];
- }
- if (file_exists($v_header['filename'])) {
- if ( (@is_dir($v_header['filename']))
- && ($v_header['typeflag'] == '')) {
- $this->_error('File '.$v_header['filename']
- .' already exists as a directory');
- return false;
- }
- if ( ($this->_isArchive($v_header['filename']))
- && ($v_header['typeflag'] == "5")) {
- $this->_error('Directory '.$v_header['filename']
- .' already exists as a file');
- return false;
- }
- if (!is_writeable($v_header['filename'])) {
- $this->_error('File '.$v_header['filename']
- .' already exists and is write protected');
- return false;
- }
- if (filemtime($v_header['filename']) > $v_header['mtime']) {
- // To be completed : An error or silent no replace ?
- }
- }
-
- // ----- Check the directory availability and create it if necessary
- elseif (($v_result
- = $this->_dirCheck(($v_header['typeflag'] == "5"
- ?$v_header['filename']
- :dirname($v_header['filename'])))) != 1) {
- $this->_error('Unable to create path for '.$v_header['filename']);
+ if (!$this->_readHeader($v_binary_data, $v_header)) {
return false;
}
- if ($v_extract_file) {
- if ($v_header['typeflag'] == "5") {
- if (!@file_exists($v_header['filename'])) {
- // Drupal integration.
- // Changed the code to use drupal_mkdir() instead of mkdir().
- if (!@drupal_mkdir($v_header['filename'], 0777)) {
- $this->_error('Unable to create directory {'
- .$v_header['filename'].'}');
- return false;
- }
- }
- } elseif ($v_header['typeflag'] == "2") {
- if (@file_exists($v_header['filename'])) {
- @drupal_unlink($v_header['filename']);
- }
- if (!@symlink($v_header['link'], $v_header['filename'])) {
- $this->_error('Unable to extract symbolic link {'
- .$v_header['filename'].'}');
- return false;
- }
- } else {
- if (($v_dest_file = @fopen($v_header['filename'], "wb")) == 0) {
- $this->_error('Error while opening {'.$v_header['filename']
- .'} in write binary mode');
- return false;
- } else {
- $n = floor($v_header['size']/512);
- for ($i=0; $i<$n; $i++) {
- $v_content = $this->_readBlock();
- fwrite($v_dest_file, $v_content, 512);
- }
- if (($v_header['size'] % 512) != 0) {
- $v_content = $this->_readBlock();
- fwrite($v_dest_file, $v_content, ($v_header['size'] % 512));
- }
-
- @fclose($v_dest_file);
-
- // ----- Change the file mode, mtime
- @touch($v_header['filename'], $v_header['mtime']);
- if ($v_header['mode'] & 0111) {
- // make file executable, obey umask
- $mode = fileperms($v_header['filename']) | (~umask() & 0111);
- @chmod($v_header['filename'], $mode);
- }
- }
-
- // ----- Check the file size
- clearstatcache();
- if (filesize($v_header['filename']) != $v_header['size']) {
- $this->_error('Extracted file '.$v_header['filename']
- .' does not have the correct file size \''
- .filesize($v_header['filename'])
- .'\' ('.$v_header['size']
- .' expected). Archive may be corrupted.');
- return false;
- }
- }
- } else {
- $this->_jumpBlock(ceil(($v_header['size']/512)));
+ $v_filename = rtrim(substr($v_filename, 0, $v_filesize), "\0");
+ $v_header['filename'] = $v_filename;
+ if ($this->_maliciousFilename($v_filename)) {
+ $this->_error(
+ 'Malicious .tar detected, file "' . $v_filename .
+ '" will not install in desired directory tree'
+ );
+ return false;
}
- } else {
- $this->_jumpBlock(ceil(($v_header['size']/512)));
- }
-
- /* TBC : Seems to be unused ...
- if ($this->_compress)
- $v_end_of_file = @gzeof($this->_file);
- else
- $v_end_of_file = @feof($this->_file);
- */
-
- if ($v_listing || $v_extract_file || $v_extraction_stopped) {
- // ----- Log extracted files
- if (($v_file_dir = dirname($v_header['filename']))
- == $v_header['filename'])
- $v_file_dir = '';
- if ((substr($v_header['filename'], 0, 1) == '/') && ($v_file_dir == ''))
- $v_file_dir = '/';
-
- $p_list_detail[$v_nb++] = $v_header;
- if (is_array($p_file_list) && (count($p_list_detail) == count($p_file_list))) {
- return true;
- }
- }
- }
return true;
}
- // }}}
- // {{{ _openAppend()
- function _openAppend()
+ /**
+ * This method extract from the archive one file identified by $p_filename.
+ * The return value is a string with the file content, or null on error.
+ *
+ * @param string $p_filename The path of the file to extract in a string.
+ *
+ * @return a string with the file content or null.
+ */
+ private function _extractInString($p_filename)
{
- if (filesize($this->_tarname) == 0)
- return $this->_openWrite();
+ $v_result_str = "";
+
+ while (strlen($v_binary_data = $this->_readBlock()) != 0) {
+ if (!$this->_readHeader($v_binary_data, $v_header)) {
+ return null;
+ }
+
+ if ($v_header['filename'] == '') {
+ continue;
+ }
+
+ // ----- Look for long filename
+ if ($v_header['typeflag'] == 'L') {
+ if (!$this->_readLongHeader($v_header)) {
+ return null;
+ }
+ }
+
+ if ($v_header['filename'] == $p_filename) {
+ if ($v_header['typeflag'] == "5") {
+ $this->_error(
+ 'Unable to extract in string a directory '
+ . 'entry {' . $v_header['filename'] . '}'
+ );
+ return null;
+ } else {
+ $n = floor($v_header['size'] / 512);
+ for ($i = 0; $i < $n; $i++) {
+ $v_result_str .= $this->_readBlock();
+ }
+ if (($v_header['size'] % 512) != 0) {
+ $v_content = $this->_readBlock();
+ $v_result_str .= substr(
+ $v_content,
+ 0,
+ ($v_header['size'] % 512)
+ );
+ }
+ return $v_result_str;
+ }
+ } else {
+ $this->_jumpBlock(ceil(($v_header['size'] / 512)));
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @param string $p_path
+ * @param string $p_list_detail
+ * @param string $p_mode
+ * @param string $p_file_list
+ * @param string $p_remove_path
+ * @param bool $p_preserve
+ * @return bool
+ */
+ public function _extractList(
+ $p_path,
+ &$p_list_detail,
+ $p_mode,
+ $p_file_list,
+ $p_remove_path,
+ $p_preserve = false
+ ) {
+ $v_result = true;
+ $v_nb = 0;
+ $v_extract_all = true;
+ $v_listing = false;
+
+ $p_path = $this->_translateWinPath($p_path, false);
+ if ($p_path == '' || (substr($p_path, 0, 1) != '/'
+ && substr($p_path, 0, 3) != "../" && !strpos($p_path, ':'))
+ ) {
+ $p_path = "./" . $p_path;
+ }
+ $p_remove_path = $this->_translateWinPath($p_remove_path);
+
+ // ----- Look for path to remove format (should end by /)
+ if (($p_remove_path != '') && (substr($p_remove_path, -1) != '/')) {
+ $p_remove_path .= '/';
+ }
+ $p_remove_path_size = strlen($p_remove_path);
+
+ switch ($p_mode) {
+ case "complete" :
+ $v_extract_all = true;
+ $v_listing = false;
+ break;
+ case "partial" :
+ $v_extract_all = false;
+ $v_listing = false;
+ break;
+ case "list" :
+ $v_extract_all = false;
+ $v_listing = true;
+ break;
+ default :
+ $this->_error('Invalid extract mode (' . $p_mode . ')');
+ return false;
+ }
+
+ clearstatcache();
+
+ while (strlen($v_binary_data = $this->_readBlock()) != 0) {
+ $v_extract_file = false;
+ $v_extraction_stopped = 0;
+
+ if (!$this->_readHeader($v_binary_data, $v_header)) {
+ return false;
+ }
+
+ if ($v_header['filename'] == '') {
+ continue;
+ }
+
+ // ----- Look for long filename
+ if ($v_header['typeflag'] == 'L') {
+ if (!$this->_readLongHeader($v_header)) {
+ return false;
+ }
+ }
+
+ // ignore extended / pax headers
+ if ($v_header['typeflag'] == 'x' || $v_header['typeflag'] == 'g') {
+ $this->_jumpBlock(ceil(($v_header['size'] / 512)));
+ continue;
+ }
+
+ if ((!$v_extract_all) && (is_array($p_file_list))) {
+ // ----- By default no unzip if the file is not found
+ $v_extract_file = false;
+
+ for ($i = 0; $i < sizeof($p_file_list); $i++) {
+ // ----- Look if it is a directory
+ if (substr($p_file_list[$i], -1) == '/') {
+ // ----- Look if the directory is in the filename path
+ if ((strlen($v_header['filename']) > strlen($p_file_list[$i]))
+ && (substr($v_header['filename'], 0, strlen($p_file_list[$i]))
+ == $p_file_list[$i])
+ ) {
+ $v_extract_file = true;
+ break;
+ }
+ } // ----- It is a file, so compare the file names
+ elseif ($p_file_list[$i] == $v_header['filename']) {
+ $v_extract_file = true;
+ break;
+ }
+ }
+ } else {
+ $v_extract_file = true;
+ }
+
+ // ----- Look if this file need to be extracted
+ if (($v_extract_file) && (!$v_listing)) {
+ if (($p_remove_path != '')
+ && (substr($v_header['filename'] . '/', 0, $p_remove_path_size)
+ == $p_remove_path)
+ ) {
+ $v_header['filename'] = substr(
+ $v_header['filename'],
+ $p_remove_path_size
+ );
+ if ($v_header['filename'] == '') {
+ continue;
+ }
+ }
+ if (($p_path != './') && ($p_path != '/')) {
+ while (substr($p_path, -1) == '/') {
+ $p_path = substr($p_path, 0, strlen($p_path) - 1);
+ }
+
+ if (substr($v_header['filename'], 0, 1) == '/') {
+ $v_header['filename'] = $p_path . $v_header['filename'];
+ } else {
+ $v_header['filename'] = $p_path . '/' . $v_header['filename'];
+ }
+ }
+ if (file_exists($v_header['filename'])) {
+ if ((@is_dir($v_header['filename']))
+ && ($v_header['typeflag'] == '')
+ ) {
+ $this->_error(
+ 'File ' . $v_header['filename']
+ . ' already exists as a directory'
+ );
+ return false;
+ }
+ if (($this->_isArchive($v_header['filename']))
+ && ($v_header['typeflag'] == "5")
+ ) {
+ $this->_error(
+ 'Directory ' . $v_header['filename']
+ . ' already exists as a file'
+ );
+ return false;
+ }
+ if (!is_writeable($v_header['filename'])) {
+ $this->_error(
+ 'File ' . $v_header['filename']
+ . ' already exists and is write protected'
+ );
+ return false;
+ }
+ if (filemtime($v_header['filename']) > $v_header['mtime']) {
+ // To be completed : An error or silent no replace ?
+ }
+ } // ----- Check the directory availability and create it if necessary
+ elseif (($v_result
+ = $this->_dirCheck(
+ ($v_header['typeflag'] == "5"
+ ? $v_header['filename']
+ : dirname($v_header['filename']))
+ )) != 1
+ ) {
+ $this->_error('Unable to create path for ' . $v_header['filename']);
+ return false;
+ }
+
+ if ($v_extract_file) {
+ if ($v_header['typeflag'] == "5") {
+ if (!@file_exists($v_header['filename'])) {
+ if (!@mkdir($v_header['filename'], 0777)) {
+ $this->_error(
+ 'Unable to create directory {'
+ . $v_header['filename'] . '}'
+ );
+ return false;
+ }
+ }
+ } elseif ($v_header['typeflag'] == "2") {
+ if (@file_exists($v_header['filename'])) {
+ @drupal_unlink($v_header['filename']);
+ }
+ if (!@symlink($v_header['link'], $v_header['filename'])) {
+ $this->_error(
+ 'Unable to extract symbolic link {'
+ . $v_header['filename'] . '}'
+ );
+ return false;
+ }
+ } else {
+ if (($v_dest_file = @fopen($v_header['filename'], "wb")) == 0) {
+ $this->_error(
+ 'Error while opening {' . $v_header['filename']
+ . '} in write binary mode'
+ );
+ return false;
+ } else {
+ $n = floor($v_header['size'] / 512);
+ for ($i = 0; $i < $n; $i++) {
+ $v_content = $this->_readBlock();
+ fwrite($v_dest_file, $v_content, 512);
+ }
+ if (($v_header['size'] % 512) != 0) {
+ $v_content = $this->_readBlock();
+ fwrite($v_dest_file, $v_content, ($v_header['size'] % 512));
+ }
+
+ @fclose($v_dest_file);
+
+ if ($p_preserve) {
+ @chown($v_header['filename'], $v_header['uid']);
+ @chgrp($v_header['filename'], $v_header['gid']);
+ }
+
+ // ----- Change the file mode, mtime
+ @touch($v_header['filename'], $v_header['mtime']);
+ if ($v_header['mode'] & 0111) {
+ // make file executable, obey umask
+ $mode = fileperms($v_header['filename']) | (~umask() & 0111);
+ @chmod($v_header['filename'], $mode);
+ }
+ }
+
+ // ----- Check the file size
+ clearstatcache();
+ if (!is_file($v_header['filename'])) {
+ $this->_error(
+ 'Extracted file ' . $v_header['filename']
+ . 'does not exist. Archive may be corrupted.'
+ );
+ return false;
+ }
+
+ $filesize = filesize($v_header['filename']);
+ if ($filesize != $v_header['size']) {
+ $this->_error(
+ 'Extracted file ' . $v_header['filename']
+ . ' does not have the correct file size \''
+ . $filesize
+ . '\' (' . $v_header['size']
+ . ' expected). Archive may be corrupted.'
+ );
+ return false;
+ }
+ }
+ } else {
+ $this->_jumpBlock(ceil(($v_header['size'] / 512)));
+ }
+ } else {
+ $this->_jumpBlock(ceil(($v_header['size'] / 512)));
+ }
+
+ /* TBC : Seems to be unused ...
+ if ($this->_compress)
+ $v_end_of_file = @gzeof($this->_file);
+ else
+ $v_end_of_file = @feof($this->_file);
+ */
+
+ if ($v_listing || $v_extract_file || $v_extraction_stopped) {
+ // ----- Log extracted files
+ if (($v_file_dir = dirname($v_header['filename']))
+ == $v_header['filename']
+ ) {
+ $v_file_dir = '';
+ }
+ if ((substr($v_header['filename'], 0, 1) == '/') && ($v_file_dir == '')) {
+ $v_file_dir = '/';
+ }
+
+ $p_list_detail[$v_nb++] = $v_header;
+ if (is_array($p_file_list) && (count($p_list_detail) == count($p_file_list))) {
+ return true;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * @return bool
+ */
+ public function _openAppend()
+ {
+ if (filesize($this->_tarname) == 0) {
+ return $this->_openWrite();
+ }
if ($this->_compress) {
$this->_close();
- if (!@rename($this->_tarname, $this->_tarname.".tmp")) {
- $this->_error('Error while renaming \''.$this->_tarname
- .'\' to temporary file \''.$this->_tarname
- .'.tmp\'');
- return false;
- }
-
- if ($this->_compress_type == 'gz')
- $v_temp_tar = @gzopen($this->_tarname.".tmp", "rb");
- elseif ($this->_compress_type == 'bz2')
- $v_temp_tar = @bzopen($this->_tarname.".tmp", "r");
-
- if ($v_temp_tar == 0) {
- $this->_error('Unable to open file \''.$this->_tarname
- .'.tmp\' in binary read mode');
- @rename($this->_tarname.".tmp", $this->_tarname);
- return false;
- }
-
- if (!$this->_openWrite()) {
- @rename($this->_tarname.".tmp", $this->_tarname);
+ if (!@rename($this->_tarname, $this->_tarname . ".tmp")) {
+ $this->_error(
+ 'Error while renaming \'' . $this->_tarname
+ . '\' to temporary file \'' . $this->_tarname
+ . '.tmp\''
+ );
return false;
}
if ($this->_compress_type == 'gz') {
+ $v_temp_tar = @gzopen($this->_tarname . ".tmp", "rb");
+ } elseif ($this->_compress_type == 'bz2') {
+ $v_temp_tar = @bzopen($this->_tarname . ".tmp", "r");
+ } elseif ($this->_compress_type == 'lzma2') {
+ $v_temp_tar = @xzopen($this->_tarname . ".tmp", "r");
+ }
+
+
+ if ($v_temp_tar == 0) {
+ $this->_error(
+ 'Unable to open file \'' . $this->_tarname
+ . '.tmp\' in binary read mode'
+ );
+ @rename($this->_tarname . ".tmp", $this->_tarname);
+ return false;
+ }
+
+ if (!$this->_openWrite()) {
+ @rename($this->_tarname . ".tmp", $this->_tarname);
+ return false;
+ }
+
+ if ($this->_compress_type == 'gz') {
+ $end_blocks = 0;
+
while (!@gzeof($v_temp_tar)) {
$v_buffer = @gzread($v_temp_tar, 512);
- if ($v_buffer == ARCHIVE_TAR_END_BLOCK) {
+ if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) {
+ $end_blocks++;
// do not copy end blocks, we will re-make them
// after appending
continue;
+ } elseif ($end_blocks > 0) {
+ for ($i = 0; $i < $end_blocks; $i++) {
+ $this->_writeBlock(ARCHIVE_TAR_END_BLOCK);
+ }
+ $end_blocks = 0;
}
$v_binary_data = pack("a512", $v_buffer);
$this->_writeBlock($v_binary_data);
}
@gzclose($v_temp_tar);
- }
- elseif ($this->_compress_type == 'bz2') {
+ } elseif ($this->_compress_type == 'bz2') {
+ $end_blocks = 0;
+
while (strlen($v_buffer = @bzread($v_temp_tar, 512)) > 0) {
- if ($v_buffer == ARCHIVE_TAR_END_BLOCK) {
+ if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) {
+ $end_blocks++;
+ // do not copy end blocks, we will re-make them
+ // after appending
continue;
+ } elseif ($end_blocks > 0) {
+ for ($i = 0; $i < $end_blocks; $i++) {
+ $this->_writeBlock(ARCHIVE_TAR_END_BLOCK);
+ }
+ $end_blocks = 0;
}
$v_binary_data = pack("a512", $v_buffer);
$this->_writeBlock($v_binary_data);
}
@bzclose($v_temp_tar);
+ } elseif ($this->_compress_type == 'lzma2') {
+ $end_blocks = 0;
+
+ while (strlen($v_buffer = @xzread($v_temp_tar, 512)) > 0) {
+ if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) {
+ $end_blocks++;
+ // do not copy end blocks, we will re-make them
+ // after appending
+ continue;
+ } elseif ($end_blocks > 0) {
+ for ($i = 0; $i < $end_blocks; $i++) {
+ $this->_writeBlock(ARCHIVE_TAR_END_BLOCK);
+ }
+ $end_blocks = 0;
+ }
+ $v_binary_data = pack("a512", $v_buffer);
+ $this->_writeBlock($v_binary_data);
+ }
+
+ @xzclose($v_temp_tar);
}
- if (!@drupal_unlink($this->_tarname.".tmp")) {
- $this->_error('Error while deleting temporary file \''
- .$this->_tarname.'.tmp\'');
+ if (!@drupal_unlink($this->_tarname . ".tmp")) {
+ $this->_error(
+ 'Error while deleting temporary file \''
+ . $this->_tarname . '.tmp\''
+ );
}
-
} else {
// ----- For not compressed tar, just add files before the last
- // one or two 512 bytes block
- if (!$this->_openReadWrite())
- return false;
+ // one or two 512 bytes block
+ if (!$this->_openReadWrite()) {
+ return false;
+ }
clearstatcache();
$v_size = filesize($this->_tarname);
@@ -1760,32 +2324,34 @@ class Archive_Tar // extends PEAR
fseek($this->_file, $v_size - 1024);
if (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) {
fseek($this->_file, $v_size - 1024);
- }
- elseif (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) {
+ } elseif (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) {
fseek($this->_file, $v_size - 512);
}
}
return true;
}
- // }}}
- // {{{ _append()
- function _append($p_filelist, $p_add_dir='', $p_remove_dir='')
+ /**
+ * @param $p_filelist
+ * @param string $p_add_dir
+ * @param string $p_remove_dir
+ * @return bool
+ */
+ public function _append($p_filelist, $p_add_dir = '', $p_remove_dir = '')
{
- if (!$this->_openAppend())
+ if (!$this->_openAppend()) {
return false;
+ }
- if ($this->_addList($p_filelist, $p_add_dir, $p_remove_dir))
- $this->_writeFooter();
+ if ($this->_addList($p_filelist, $p_add_dir, $p_remove_dir)) {
+ $this->_writeFooter();
+ }
$this->_close();
return true;
}
- // }}}
-
- // {{{ _dirCheck()
/**
* Check if a directory exists and create it (including parent
@@ -1793,24 +2359,25 @@ class Archive_Tar // extends PEAR
*
* @param string $p_dir directory to check
*
- * @return bool TRUE if the directory exists or was created
+ * @return bool true if the directory exists or was created
*/
- function _dirCheck($p_dir)
+ public function _dirCheck($p_dir)
{
clearstatcache();
- if ((@is_dir($p_dir)) || ($p_dir == ''))
+ if ((@is_dir($p_dir)) || ($p_dir == '')) {
return true;
+ }
$p_parent_dir = dirname($p_dir);
if (($p_parent_dir != $p_dir) &&
($p_parent_dir != '') &&
- (!$this->_dirCheck($p_parent_dir)))
- return false;
+ (!$this->_dirCheck($p_parent_dir))
+ ) {
+ return false;
+ }
- // Drupal integration.
- // Changed the code to use drupal_mkdir() instead of mkdir().
- if (!@drupal_mkdir($p_dir, 0777)) {
+ if (!@mkdir($p_dir, 0777)) {
$this->_error("Unable to create directory '$p_dir'");
return false;
}
@@ -1818,10 +2385,6 @@ class Archive_Tar // extends PEAR
return true;
}
- // }}}
-
- // {{{ _pathReduction()
-
/**
* Compress path by changing for example "/dir/foo/../bar" to "/dir/bar",
* rand emove double slashes.
@@ -1829,11 +2392,8 @@ class Archive_Tar // extends PEAR
* @param string $p_dir path to reduce
*
* @return string reduced path
- *
- * @access private
- *
*/
- function _pathReduction($p_dir)
+ private function _pathReduction($p_dir)
{
$v_result = '';
@@ -1843,50 +2403,57 @@ class Archive_Tar // extends PEAR
$v_list = explode('/', $p_dir);
// ----- Study directories from last to first
- for ($i=sizeof($v_list)-1; $i>=0; $i--) {
+ for ($i = sizeof($v_list) - 1; $i >= 0; $i--) {
// ----- Look for current path
if ($v_list[$i] == ".") {
// ----- Ignore this directory
// Should be the first $i=0, but no check is done
- }
- else if ($v_list[$i] == "..") {
- // ----- Ignore it and ignore the $i-1
- $i--;
- }
- else if ( ($v_list[$i] == '')
- && ($i!=(sizeof($v_list)-1))
- && ($i!=0)) {
- // ----- Ignore only the double '//' in path,
- // but not the first and last /
} else {
- $v_result = $v_list[$i].($i!=(sizeof($v_list)-1)?'/'
- .$v_result:'');
+ if ($v_list[$i] == "..") {
+ // ----- Ignore it and ignore the $i-1
+ $i--;
+ } else {
+ if (($v_list[$i] == '')
+ && ($i != (sizeof($v_list) - 1))
+ && ($i != 0)
+ ) {
+ // ----- Ignore only the double '//' in path,
+ // but not the first and last /
+ } else {
+ $v_result = $v_list[$i] . ($i != (sizeof($v_list) - 1) ? '/'
+ . $v_result : '');
+ }
+ }
}
}
}
- $v_result = strtr($v_result, '\\', '/');
+
+ if (defined('OS_WINDOWS') && OS_WINDOWS) {
+ $v_result = strtr($v_result, '\\', '/');
+ }
+
return $v_result;
}
- // }}}
-
- // {{{ _translateWinPath()
- function _translateWinPath($p_path, $p_remove_disk_letter=true)
+ /**
+ * @param $p_path
+ * @param bool $p_remove_disk_letter
+ * @return string
+ */
+ public function _translateWinPath($p_path, $p_remove_disk_letter = true)
{
- if (defined('OS_WINDOWS') && OS_WINDOWS) {
- // ----- Look for potential disk letter
- if ( ($p_remove_disk_letter)
- && (($v_position = strpos($p_path, ':')) != false)) {
- $p_path = substr($p_path, $v_position+1);
- }
- // ----- Change potential windows directory separator
- if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0,1) == '\\')) {
- $p_path = strtr($p_path, '\\', '/');
- }
- }
- return $p_path;
+ if (defined('OS_WINDOWS') && OS_WINDOWS) {
+ // ----- Look for potential disk letter
+ if (($p_remove_disk_letter)
+ && (($v_position = strpos($p_path, ':')) != false)
+ ) {
+ $p_path = substr($p_path, $v_position + 1);
+ }
+ // ----- Change potential windows directory separator
+ if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0, 1) == '\\')) {
+ $p_path = strtr($p_path, '\\', '/');
+ }
+ }
+ return $p_path;
}
- // }}}
-
}
-?>
diff --git a/modules/system/system.test b/modules/system/system.test
index 3e26bae..ec71093 100644
--- a/modules/system/system.test
+++ b/modules/system/system.test
@@ -389,6 +389,18 @@ class ModuleDependencyTestCase extends ModuleTestCase {
);
}
+ /**
+ * Checks functionality of project namespaces for dependencies.
+ */
+ function testProjectNamespaceForDependencies() {
+ // Enable module with project namespace to ensure nothing breaks.
+ $edit = array(
+ 'modules[Testing][system_project_namespace_test][enable]' => TRUE,
+ );
+ $this->drupalPost('admin/modules', $edit, t('Save configuration'));
+ $this->assertModules(array('system_project_namespace_test'), TRUE);
+ }
+
/**
* Attempt to enable translation module without locale enabled.
*/
@@ -714,7 +726,7 @@ class IPAddressBlockingTestCase extends DrupalWebTestCase {
// Block a valid IP address.
$edit = array();
- $edit['ip'] = '192.168.1.1';
+ $edit['ip'] = '1.2.3.3';
$this->drupalPost('admin/config/people/ip-blocking', $edit, t('Add'));
$ip = db_query("SELECT iid from {blocked_ips} WHERE ip = :ip", array(':ip' => $edit['ip']))->fetchField();
$this->assertTrue($ip, t('IP address found in database.'));
@@ -722,7 +734,7 @@ class IPAddressBlockingTestCase extends DrupalWebTestCase {
// Try to block an IP address that's already blocked.
$edit = array();
- $edit['ip'] = '192.168.1.1';
+ $edit['ip'] = '1.2.3.3';
$this->drupalPost('admin/config/people/ip-blocking', $edit, t('Add'));
$this->assertText(t('This IP address is already blocked.'));
@@ -758,6 +770,25 @@ class IPAddressBlockingTestCase extends DrupalWebTestCase {
// $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Save'));
// $this->assertText(t('You may not block your own IP address.'));
}
+
+ /**
+ * Test duplicate IP addresses are not present in the 'blocked_ips' table.
+ */
+ function testDuplicateIpAddress() {
+ drupal_static_reset('ip_address');
+ $submit_ip = $_SERVER['REMOTE_ADDR'] = '192.168.1.1';
+ system_block_ip_action();
+ system_block_ip_action();
+ $ip_count = db_query("SELECT iid from {blocked_ips} WHERE ip = :ip", array(':ip' => $submit_ip))->rowCount();
+ $this->assertEqual('1', $ip_count);
+ drupal_static_reset('ip_address');
+ $submit_ip = $_SERVER['REMOTE_ADDR'] = ' ';
+ system_block_ip_action();
+ system_block_ip_action();
+ system_block_ip_action();
+ $ip_count = db_query("SELECT iid from {blocked_ips} WHERE ip = :ip", array(':ip' => $submit_ip))->rowCount();
+ $this->assertEqual('1', $ip_count);
+ }
}
class CronRunTestCase extends DrupalWebTestCase {
@@ -893,6 +924,29 @@ class CronRunTestCase extends DrupalWebTestCase {
$result = variable_get('common_test_cron');
$this->assertEqual($result, 'success', 'Cron correctly handles exceptions thrown during hook_cron() invocations.');
}
+
+ /**
+ * Tests that hook_flush_caches() is not invoked on every single cron run.
+ *
+ * @see system_cron()
+ */
+ public function testCronCacheExpiration() {
+ module_enable(array('system_cron_test'));
+ variable_del('system_cron_test_flush_caches');
+
+ // Invoke cron the first time: hook_flush_caches() should be called and then
+ // get cached.
+ drupal_cron_run();
+ $this->assertEqual(variable_get('system_cron_test_flush_caches'), 1, 'hook_flush_caches() was invoked the first time.');
+ $cache = cache_get('system_cache_tables');
+ $this->assertEqual(empty($cache), FALSE, 'Cache is filled with cache table data.');
+
+ // Run cron again and ensure that hook_flush_caches() is not called.
+ variable_del('system_cron_test_flush_caches');
+ drupal_cron_run();
+ $this->assertNull(variable_get('system_cron_test_flush_caches'), 'hook_flush_caches() was not invoked the second time.');
+ }
+
}
/**
@@ -911,7 +965,7 @@ class CronQueueTestCase extends DrupalWebTestCase {
}
function setUp() {
- parent::setUp(array('common_test', 'common_test_cron_helper'));
+ parent::setUp(array('common_test', 'common_test_cron_helper', 'cron_queue_test'));
}
/**
@@ -931,6 +985,23 @@ class CronQueueTestCase extends DrupalWebTestCase {
$this->assertEqual($queue->numberOfItems(), 1, 'Failing item still in the queue after throwing an exception.');
}
+ /**
+ * Tests worker defined as a class method callable.
+ */
+ function testCallable() {
+ $queue = DrupalQueue::get('cron_queue_test_callback');
+
+ // Enqueue an item for processing.
+ $queue->createItem(array($this->randomName() => $this->randomName()));
+
+ // Run cron; the worker should perform the task and delete the item from the
+ // queue.
+ $this->cronRun();
+
+ // The queue should be empty.
+ $this->assertEqual($queue->numberOfItems(), 0);
+ }
+
}
class AdminMetaTagTestCase extends DrupalWebTestCase {
@@ -1068,6 +1139,11 @@ class PageNotFoundTestCase extends DrupalWebTestCase {
);
$node = $this->drupalCreateNode($edit);
+ // As node IDs must be integers, make sure requests for non-integer IDs
+ // return a page not found error.
+ $this->drupalGet('node/invalid');
+ $this->assertResponse(404);
+
// Use a custom 404 page.
$this->drupalPost('admin/config/system/site-information', array('site_404' => 'node/' . $node->nid), t('Save configuration'));
@@ -1293,7 +1369,23 @@ class DateTimeFunctionalTest extends DrupalWebTestCase {
$this->assertEqual($this->getUrl(), url('admin/config/regional/date-time/formats', array('absolute' => TRUE)), 'Correct page redirection.');
$this->assertText(t('Custom date format updated.'), 'Custom date format successfully updated.');
+ // Check that ajax callback is protected by CSRF token.
+ $this->drupalGet('admin/config/regional/date-time/formats/lookup', array('query' => array('format' => 'Y m d')));
+ $this->assertResponse(403, 'Access denied with no token');
+ $this->drupalGet('admin/config/regional/date-time/formats/lookup', array('query' => array('token' => 'invalid', 'format' => 'Y m d')));
+ $this->assertResponse(403, 'Access denied with invalid token');
+ $this->drupalGet('admin/config/regional/date-time/formats');
+ $this->clickLink(t('edit'));
+ $settings = $this->drupalGetSettings();
+ $lookup_url = $settings['dateTime']['date-format']['lookup'];
+ preg_match('/token=([^&]+)/', $lookup_url, $matches);
+ $this->assertFalse(empty($matches[1]), 'Found token value');
+ $this->drupalGet('admin/config/regional/date-time/formats/lookup', array('query' => array('token' => $matches[1], 'format' => 'Y m d')));
+ $this->assertResponse(200, 'Access allowed with valid token');
+ $this->assertText(format_date(time(), 'custom', 'Y m d'));
+
// Delete custom date format.
+ $this->drupalGet('admin/config/regional/date-time/formats');
$this->clickLink(t('delete'));
$this->drupalPost($this->getUrl(), array(), t('Remove'));
$this->assertEqual($this->getUrl(), url('admin/config/regional/date-time/formats', array('absolute' => TRUE)), 'Correct page redirection.');
@@ -2281,6 +2373,20 @@ class UpdateScriptFunctionalTest extends DrupalWebTestCase {
$this->update_user = $this->drupalCreateUser(array('administer software updates'));
}
+ /**
+ * Tests that there are no pending updates for the first test method.
+ */
+ function testNoPendingUpdates() {
+ // Ensure that for the first test method in a class, there are no pending
+ // updates. This tests a drupal_get_schema_versions() bug that previously
+ // led to the wrong schema version being recorded for the initial install
+ // of a child site during automated testing.
+ $this->drupalLogin($this->update_user);
+ $this->drupalGet($this->update_url, array('external' => TRUE));
+ $this->drupalPost(NULL, array(), t('Continue'));
+ $this->assertText(t('No pending updates.'), 'End of update process was reached.');
+ }
+
/**
* Tests access to the update script.
*/
@@ -2362,6 +2468,12 @@ class UpdateScriptFunctionalTest extends DrupalWebTestCase {
$this->assertText('This is a requirements error provided by the update_script_test module.');
$this->clickLink('try again');
$this->assertText('This is a requirements error provided by the update_script_test module.');
+
+ // Check if the optional 'value' key displays without a notice.
+ variable_set('update_script_test_requirement_type', REQUIREMENT_INFO);
+ $this->drupalGet($this->update_url, array('external' => TRUE));
+ $this->assertText('This is a requirements info provided by the update_script_test module.');
+ $this->assertNoText('Notice: Undefined index: value in theme_status_report()');
}
/**
diff --git a/modules/system/system.updater.inc b/modules/system/system.updater.inc
index a14d788..2a32c4b 100644
--- a/modules/system/system.updater.inc
+++ b/modules/system/system.updater.inc
@@ -24,7 +24,7 @@ class ModuleUpdater extends Updater implements DrupalUpdaterInterface {
* found on your system, and if there was a copy in sites/all, we'd see it.
*/
public function getInstallDirectory() {
- if ($relative_path = drupal_get_path('module', $this->name)) {
+ if ($this->isInstalled() && ($relative_path = drupal_get_path('module', $this->name))) {
$relative_path = dirname($relative_path);
}
else {
@@ -34,7 +34,7 @@ class ModuleUpdater extends Updater implements DrupalUpdaterInterface {
}
public function isInstalled() {
- return (bool) drupal_get_path('module', $this->name);
+ return (bool) drupal_get_filename('module', $this->name, NULL, FALSE);
}
public static function canUpdateDirectory($directory) {
@@ -109,7 +109,7 @@ class ThemeUpdater extends Updater implements DrupalUpdaterInterface {
* found on your system, and if there was a copy in sites/all, we'd see it.
*/
public function getInstallDirectory() {
- if ($relative_path = drupal_get_path('theme', $this->name)) {
+ if ($this->isInstalled() && ($relative_path = drupal_get_path('theme', $this->name))) {
$relative_path = dirname($relative_path);
}
else {
@@ -119,7 +119,7 @@ class ThemeUpdater extends Updater implements DrupalUpdaterInterface {
}
public function isInstalled() {
- return (bool) drupal_get_path('theme', $this->name);
+ return (bool) drupal_get_filename('theme', $this->name, NULL, FALSE);
}
static function canUpdateDirectory($directory) {
diff --git a/modules/system/tests/cron_queue_test.info b/modules/system/tests/cron_queue_test.info
index 75eb4aa..03b3224 100644
--- a/modules/system/tests/cron_queue_test.info
+++ b/modules/system/tests/cron_queue_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/system/tests/cron_queue_test.module b/modules/system/tests/cron_queue_test.module
index e95c6b6..0df6396 100644
--- a/modules/system/tests/cron_queue_test.module
+++ b/modules/system/tests/cron_queue_test.module
@@ -7,9 +7,21 @@ function cron_queue_test_cron_queue_info() {
$queues['cron_queue_test_exception'] = array(
'worker callback' => 'cron_queue_test_exception',
);
+ $queues['cron_queue_test_callback'] = array(
+ 'worker callback' => array('CronQueueTestCallbackClass', 'foo'),
+ );
+
return $queues;
}
function cron_queue_test_exception($item) {
throw new Exception('That is not supposed to happen.');
}
+
+class CronQueueTestCallbackClass {
+
+ static public function foo() {
+ // Do nothing.
+ }
+
+}
diff --git a/modules/system/tests/system_cron_test.info b/modules/system/tests/system_cron_test.info
new file mode 100644
index 0000000..4f7a2da
--- /dev/null
+++ b/modules/system/tests/system_cron_test.info
@@ -0,0 +1,12 @@
+name = System Cron Test
+description = 'Support module for testing the system_cron().'
+package = Testing
+version = VERSION
+core = 7.x
+hidden = TRUE
+
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
+project = "drupal"
+datestamp = "1475694174"
+
diff --git a/modules/system/tests/system_cron_test.module b/modules/system/tests/system_cron_test.module
new file mode 100644
index 0000000..9ef80e2
--- /dev/null
+++ b/modules/system/tests/system_cron_test.module
@@ -0,0 +1,15 @@
+ $bundle->type,
'settings' => array(),
'description' => 'Debris left over after upgrade from Drupal 6',
+ 'required' => FALSE,
'widget' => array(
'type' => 'taxonomy_autocomplete',
'module' => 'taxonomy',
@@ -557,7 +558,7 @@ function taxonomy_update_7005(&$sandbox) {
// of term references stored so far for the current revision, which
// provides the delta value for each term reference data insert. The
// deltas are reset for each new revision.
-
+
$conditions = array(
'type' => 'taxonomy_term_reference',
'deleted' => 0,
diff --git a/modules/taxonomy/taxonomy.module b/modules/taxonomy/taxonomy.module
index e147c1c..554d6d2 100644
--- a/modules/taxonomy/taxonomy.module
+++ b/modules/taxonomy/taxonomy.module
@@ -25,7 +25,7 @@ function taxonomy_help($path, $arg) {
$output .= '' . t('Uses') . '
';
$output .= '';
$output .= '- ' . t('Creating vocabularies') . '
';
- $output .= '- ' . t('Users with sufficient permissions can create vocabularies and terms through the Taxonomy page. The page listing the terms provides a drag-and-drop interface for controlling the order of the terms and sub-terms within a vocabulary, in a hierarchical fashion. A controlled vocabulary classifying music by genre with terms and sub-terms could look as follows:', array('@taxo' => url('admin/structure/taxonomy'), '@perm' => url('admin/people/permissions', array('fragment'=>'module-taxonomy'))));
+ $output .= '
- ' . t('Users with sufficient permissions can create vocabularies and terms through the Taxonomy page. The page listing the terms provides a drag-and-drop interface for controlling the order of the terms and sub-terms within a vocabulary, in a hierarchical fashion. A controlled vocabulary classifying music by genre with terms and sub-terms could look as follows:', array('@taxo' => url('admin/structure/taxonomy'), '@perm' => url('admin/people/permissions', array('fragment' => 'module-taxonomy'))));
$output .= '
- ' . t('vocabulary: Music') . '
';
$output .= '- ' . t('term: Jazz') . '
';
$output .= '- ' . t('sub-term: Swing') . '
';
diff --git a/modules/taxonomy/taxonomy.test b/modules/taxonomy/taxonomy.test
index fdf354b..e9dac1e 100644
--- a/modules/taxonomy/taxonomy.test
+++ b/modules/taxonomy/taxonomy.test
@@ -1025,7 +1025,7 @@ class TaxonomyRSSTestCase extends TaxonomyWebTestCase {
function setUp() {
parent::setUp('taxonomy');
- $this->admin_user = $this->drupalCreateUser(array('administer taxonomy', 'bypass node access', 'administer content types'));
+ $this->admin_user = $this->drupalCreateUser(array('administer taxonomy', 'bypass node access', 'administer content types', 'administer fields'));
$this->drupalLogin($this->admin_user);
$this->vocabulary = $this->createVocabulary();
diff --git a/modules/toolbar/toolbar.info b/modules/toolbar/toolbar.info
index 7aac4b7..3ce4e98 100644
--- a/modules/toolbar/toolbar.info
+++ b/modules/toolbar/toolbar.info
@@ -4,8 +4,8 @@ core = 7.x
package = Core
version = VERSION
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/tracker/tracker.info b/modules/tracker/tracker.info
index a5cbcc0..eca5614 100644
--- a/modules/tracker/tracker.info
+++ b/modules/tracker/tracker.info
@@ -6,8 +6,8 @@ version = VERSION
core = 7.x
files[] = tracker.test
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/tracker/tracker.test b/modules/tracker/tracker.test
index 8a48ea8..e472978 100644
--- a/modules/tracker/tracker.test
+++ b/modules/tracker/tracker.test
@@ -151,7 +151,6 @@ class TrackerTest extends DrupalWebTestCase {
$node = $this->drupalCreateNode(array(
'comment' => 2,
- 'title' => array(LANGUAGE_NONE => array(array('value' => $this->randomName(8)))),
));
// Add a comment to the page.
diff --git a/modules/translation/tests/translation_test.info b/modules/translation/tests/translation_test.info
index b12ce1f..0f2de51 100644
--- a/modules/translation/tests/translation_test.info
+++ b/modules/translation/tests/translation_test.info
@@ -5,8 +5,8 @@ package = Testing
version = VERSION
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/translation/translation.info b/modules/translation/translation.info
index 5815c93..3f36bfc 100644
--- a/modules/translation/translation.info
+++ b/modules/translation/translation.info
@@ -6,8 +6,8 @@ version = VERSION
core = 7.x
files[] = translation.test
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/translation/translation.module b/modules/translation/translation.module
index 53c4641..580d000 100644
--- a/modules/translation/translation.module
+++ b/modules/translation/translation.module
@@ -428,7 +428,7 @@ function translation_node_delete($node) {
* A node object.
*/
function translation_remove_from_set($node) {
- if (isset($node->tnid)) {
+ if (isset($node->tnid) && $node->tnid) {
$query = db_update('node')
->fields(array(
'tnid' => 0,
diff --git a/modules/trigger/tests/trigger_test.info b/modules/trigger/tests/trigger_test.info
index f0b254b..68ade94 100644
--- a/modules/trigger/tests/trigger_test.info
+++ b/modules/trigger/tests/trigger_test.info
@@ -4,8 +4,8 @@ package = Testing
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/trigger/trigger.info b/modules/trigger/trigger.info
index 18e1e19..72be9cc 100644
--- a/modules/trigger/trigger.info
+++ b/modules/trigger/trigger.info
@@ -6,8 +6,8 @@ core = 7.x
files[] = trigger.test
configure = admin/structure/trigger
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/trigger/trigger.test b/modules/trigger/trigger.test
index 9e5f114..09169b7 100644
--- a/modules/trigger/trigger.test
+++ b/modules/trigger/trigger.test
@@ -85,7 +85,7 @@ class TriggerContentTestCase extends TriggerWebTestCase {
$this->assertRaw(t('!post %title has been created.', array('!post' => 'Basic page', '%title' => $edit["title"])), 'Make sure the Basic page has actually been created');
// Action should have been fired.
$loaded_node = $this->drupalGetNodeByTitle($edit["title"]);
- $this->assertTrue($loaded_node->$info['property'] == $info['expected'], format_string('Make sure the @action action fired.', array('@action' => $info['name'])));
+ $this->assertTrue($loaded_node->{$info['property']} == $info['expected'], format_string('Make sure the @action action fired.', array('@action' => $info['name'])));
// Leave action assigned for next test
// There should be an error when the action is assigned to the trigger
diff --git a/modules/update/tests/aaa_update_test.info b/modules/update/tests/aaa_update_test.info
index 716ecf9..2f14b29 100644
--- a/modules/update/tests/aaa_update_test.info
+++ b/modules/update/tests/aaa_update_test.info
@@ -4,8 +4,8 @@ package = Testing
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/update/tests/bbb_update_test.info b/modules/update/tests/bbb_update_test.info
index 3b38ddd..900d068 100644
--- a/modules/update/tests/bbb_update_test.info
+++ b/modules/update/tests/bbb_update_test.info
@@ -4,8 +4,8 @@ package = Testing
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/update/tests/ccc_update_test.info b/modules/update/tests/ccc_update_test.info
index f1ce3c2..51dc859 100644
--- a/modules/update/tests/ccc_update_test.info
+++ b/modules/update/tests/ccc_update_test.info
@@ -4,8 +4,8 @@ package = Testing
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/update/tests/themes/update_test_admintheme/update_test_admintheme.info b/modules/update/tests/themes/update_test_admintheme/update_test_admintheme.info
new file mode 100644
index 0000000..cc3e3c5
--- /dev/null
+++ b/modules/update/tests/themes/update_test_admintheme/update_test_admintheme.info
@@ -0,0 +1,10 @@
+name = Update test admin theme
+description = Test theme which is used as admin theme.
+core = 7.x
+hidden = TRUE
+
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
+project = "drupal"
+datestamp = "1475694174"
+
diff --git a/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info b/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info
index 17ce0ad..e8f2bfd 100644
--- a/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info
+++ b/modules/update/tests/themes/update_test_basetheme/update_test_basetheme.info
@@ -3,8 +3,8 @@ description = Test theme which acts as a base theme for other test subthemes.
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info b/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info
index 87de3a7..ae70713 100644
--- a/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info
+++ b/modules/update/tests/themes/update_test_subtheme/update_test_subtheme.info
@@ -4,8 +4,8 @@ core = 7.x
base theme = update_test_basetheme
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/update/tests/update_test.info b/modules/update/tests/update_test.info
index ae3f6c2..8cd643b 100644
--- a/modules/update/tests/update_test.info
+++ b/modules/update/tests/update_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/update/tests/update_test.module b/modules/update/tests/update_test.module
index 6fe4bdd..594f80f 100644
--- a/modules/update/tests/update_test.module
+++ b/modules/update/tests/update_test.module
@@ -11,6 +11,7 @@
function update_test_system_theme_info() {
$themes['update_test_basetheme'] = drupal_get_path('module', 'update_test') . '/themes/update_test_basetheme/update_test_basetheme.info';
$themes['update_test_subtheme'] = drupal_get_path('module', 'update_test') . '/themes/update_test_subtheme/update_test_subtheme.info';
+ $themes['update_test_admintheme'] = drupal_get_path('module', 'update_test') . '/themes/update_test_admintheme/update_test_admintheme.info';
return $themes;
}
diff --git a/modules/update/update.authorize.inc b/modules/update/update.authorize.inc
index 6ddd2c5..03d3704 100644
--- a/modules/update/update.authorize.inc
+++ b/modules/update/update.authorize.inc
@@ -97,7 +97,9 @@ function update_authorize_run_install($filetransfer, $project, $updater_name, $l
}
/**
- * Batch callback: Copies project to its proper place when authorized to do so.
+ * Implements callback_batch_operation().
+ *
+ * Copies project to its proper place when authorized to do so.
*
* @param string $project
* The canonical short name of the project being installed.
@@ -168,7 +170,9 @@ function update_authorize_batch_copy_project($project, $updater_name, $local_url
}
/**
- * Batch callback: Performs actions when the authorized update batch is done.
+ * Implements callback_batch_finished().
+ *
+ * Performs actions when the authorized update batch is done.
*
* This processes the results and stashes them into SESSION such that
* authorize.php will render a report. Also responsible for putting the site
@@ -235,7 +239,9 @@ function update_authorize_update_batch_finished($success, $results) {
}
/**
- * Batch callback: Performs actions when the authorized install batch is done.
+ * Implements callback_batch_finished().
+ *
+ * Performs actions when the authorized install batch is done.
*
* This processes the results and stashes them into SESSION such that
* authorize.php will render a report. Also responsible for putting the site
diff --git a/modules/update/update.compare.inc b/modules/update/update.compare.inc
index 072a0da..e3e0de3 100644
--- a/modules/update/update.compare.inc
+++ b/modules/update/update.compare.inc
@@ -104,7 +104,13 @@ function update_get_projects() {
* @see update_get_projects()
*/
function _update_process_info_list(&$projects, $list, $project_type, $status) {
+ $admin_theme = variable_get('admin_theme', 'seven');
foreach ($list as $file) {
+ // The admin theme is a special case. It should always be considered enabled
+ // for the purposes of update checking.
+ if ($file->name === $admin_theme) {
+ $file->status = TRUE;
+ }
// A disabled base theme of an enabled sub-theme still has all of its code
// run by the sub-theme, so we include it in our "enabled" projects list.
if ($status && !$file->status && !empty($file->sub_themes)) {
diff --git a/modules/update/update.fetch.inc b/modules/update/update.fetch.inc
index 9dd2f0b..428cace 100644
--- a/modules/update/update.fetch.inc
+++ b/modules/update/update.fetch.inc
@@ -29,7 +29,9 @@ function update_manual_status() {
}
/**
- * Batch callback: Processes a step in batch for fetching available update data.
+ * Implements callback_batch_operation().
+ *
+ * Processes a step in batch for fetching available update data.
*
* @param $context
* Reference to an array used for Batch API storage.
@@ -77,7 +79,9 @@ function update_fetch_data_batch(&$context) {
}
/**
- * Batch callback: Performs actions when all fetch tasks have been completed.
+ * Implements callback_batch_finished().
+ *
+ * Performs actions when all fetch tasks have been completed.
*
* @param $success
* TRUE if the batch operation was successful; FALSE if there were errors.
diff --git a/modules/update/update.info b/modules/update/update.info
index 230a414..d4477d8 100644
--- a/modules/update/update.info
+++ b/modules/update/update.info
@@ -6,8 +6,8 @@ core = 7.x
files[] = update.test
configure = admin/reports/updates/settings
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/update/update.manager.inc b/modules/update/update.manager.inc
index 85b587d..c7c4e4a 100644
--- a/modules/update/update.manager.inc
+++ b/modules/update/update.manager.inc
@@ -59,7 +59,7 @@
* @see update_menu()
* @ingroup forms
*/
-function update_manager_update_form($form, $form_state = array(), $context) {
+function update_manager_update_form($form, $form_state, $context) {
if (!_update_manager_check_backends($form, 'update')) {
return $form;
}
@@ -335,6 +335,8 @@ function update_manager_update_form_submit($form, &$form_state) {
}
/**
+ * Implements callback_batch_finished().
+ *
* Batch callback: Performs actions when the download batch is completed.
*
* @param $success
@@ -847,7 +849,9 @@ function update_manager_file_get($url) {
}
/**
- * Batch callback: Downloads, unpacks, and verifies a project.
+ * Implements callback_batch_operation().
+ *
+ * Downloads, unpacks, and verifies a project.
*
* This function assumes that the provided URL points to a file archive of some
* sort. The URL can have any scheme that we have a file stream wrapper to
diff --git a/modules/update/update.module b/modules/update/update.module
index d1f0d85..a59c7d7 100644
--- a/modules/update/update.module
+++ b/modules/update/update.module
@@ -278,12 +278,15 @@ function update_theme() {
),
'update_report' => array(
'variables' => array('data' => NULL),
+ 'file' => 'update.report.inc',
),
'update_version' => array(
'variables' => array('version' => NULL, 'tag' => NULL, 'class' => array()),
+ 'file' => 'update.report.inc',
),
'update_status_label' => array(
'variables' => array('status' => NULL),
+ 'file' => 'update.report.inc',
),
);
}
diff --git a/modules/update/update.settings.inc b/modules/update/update.settings.inc
index 5cd2414..75de6cd 100644
--- a/modules/update/update.settings.inc
+++ b/modules/update/update.settings.inc
@@ -26,7 +26,7 @@ function update_settings($form) {
$form['update_check_disabled'] = array(
'#type' => 'checkbox',
- '#title' => t('Check for updates of disabled modules and themes'),
+ '#title' => t('Check for updates of disabled and uninstalled modules and themes'),
'#default_value' => variable_get('update_check_disabled', FALSE),
);
@@ -98,10 +98,11 @@ function update_settings_validate($form, &$form_state) {
* Form submission handler for update_settings().
*
* Also invalidates the cache of available updates if the "Check for updates of
- * disabled modules and themes" setting is being changed. The available updates
- * report needs to refetch available update data after this setting changes or
- * it would show misleading things (e.g., listing the disabled projects on the
- * site with the "No available releases found" warning).
+ * disabled and uninstalled modules and themes" setting is being changed. The
+ * available updates report needs to refetch available update data after this
+ * setting changes or it would show misleading things (e.g., listing the
+ * disabled projects on the site with the "No available releases found"
+ * warning).
*
* @see update_settings_validate()
*/
diff --git a/modules/update/update.test b/modules/update/update.test
index 9e04cda..5ce5bb8 100644
--- a/modules/update/update.test
+++ b/modules/update/update.test
@@ -462,6 +462,55 @@ class UpdateTestContribCase extends UpdateTestHelper {
$this->assertRaw(l(t('Update test base theme'), 'http://example.com/project/update_test_basetheme'), 'Link to the Update test base theme project appears.');
}
+ /**
+ * Tests that the admin theme is always notified about security updates.
+ */
+ function testUpdateAdminThemeSecurityUpdate() {
+ // Disable the admin theme.
+ db_update('system')
+ ->fields(array('status' => 0))
+ ->condition('type', 'theme')
+ ->condition('name', 'update_test_%', 'LIKE')
+ ->execute();
+
+ variable_set('admin_theme', 'update_test_admintheme');
+
+ // Define the initial state for core and the themes.
+ $system_info = array(
+ '#all' => array(
+ 'version' => '7.0',
+ ),
+ 'update_test_admintheme' => array(
+ 'project' => 'update_test_admintheme',
+ 'version' => '7.x-1.0',
+ 'hidden' => FALSE,
+ ),
+ 'update_test_basetheme' => array(
+ 'project' => 'update_test_basetheme',
+ 'version' => '7.x-1.1',
+ 'hidden' => FALSE,
+ ),
+ 'update_test_subtheme' => array(
+ 'project' => 'update_test_subtheme',
+ 'version' => '7.x-1.0',
+ 'hidden' => FALSE,
+ ),
+ );
+ variable_set('update_test_system_info', $system_info);
+ variable_set('update_check_disabled', FALSE);
+ $xml_mapping = array(
+ // This is enough because we don't check the update status of the admin
+ // theme. We want to check that the admin theme is included in the list.
+ 'drupal' => '0',
+ );
+ $this->refreshUpdateStatus($xml_mapping);
+ // The admin theme is displayed even if it's disabled.
+ $this->assertText('update_test_admintheme', "The admin theme is checked for update even if it's disabled");
+ // The other disabled themes are not displayed.
+ $this->assertNoText('update_test_basetheme', 'Disabled theme is not checked for update in the list.');
+ $this->assertNoText('update_test_subtheme', 'Disabled theme is not checked for update in the list.');
+ }
+
/**
* Tests that disabled themes are only shown when desired.
*/
@@ -800,4 +849,4 @@ class UpdateCoreUnitTestCase extends DrupalUnitTestCase {
$this->assertEqual($url, $expected, "When ? is present, '$url' should be '$expected'.");
}
-}
\ No newline at end of file
+}
diff --git a/modules/user/tests/user_form_test.info b/modules/user/tests/user_form_test.info
index e058811..57d8fd0 100644
--- a/modules/user/tests/user_form_test.info
+++ b/modules/user/tests/user_form_test.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/user/tests/user_form_test.module b/modules/user/tests/user_form_test.module
index 4e907f3..382bc57 100644
--- a/modules/user/tests/user_form_test.module
+++ b/modules/user/tests/user_form_test.module
@@ -62,3 +62,21 @@ function user_form_test_current_password($form, &$form_state, $account) {
function user_form_test_current_password_submit($form, &$form_state) {
drupal_set_message(t('The password has been validated and the form submitted successfully.'));
}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function user_form_test_form_user_profile_form_alter(&$form, &$form_state) {
+ if (variable_get('user_form_test_user_profile_form_rebuild', FALSE)) {
+ $form['#submit'][] = 'user_form_test_user_account_submit';
+ }
+}
+
+/**
+ * Submit function for user_profile_form().
+ */
+function user_form_test_user_account_submit($form, &$form_state) {
+ // Rebuild the form instead of letting the process end. This allows us to
+ // test for bugs that can be triggered in contributed modules.
+ $form_state['rebuild'] = TRUE;
+}
diff --git a/modules/user/user-picture.tpl.php b/modules/user/user-picture.tpl.php
index ee82187..11d92cc 100644
--- a/modules/user/user-picture.tpl.php
+++ b/modules/user/user-picture.tpl.php
@@ -17,7 +17,7 @@
*/
?>
-
+
diff --git a/modules/user/user.api.php b/modules/user/user.api.php
index edc61bd..f205a85 100644
--- a/modules/user/user.api.php
+++ b/modules/user/user.api.php
@@ -123,8 +123,8 @@ function hook_user_cancel($edit, $account, $method) {
* description is NOT used for the radio button, but instead should provide
* additional explanation to the user seeking to cancel their account.
* - access: (optional) A boolean value indicating whether the user can access
- * a method. If #access is defined, the method cannot be configured as default
- * method.
+ * a method. If access is defined, the method cannot be configured as the
+ * default method.
*
* @param $methods
* An array containing user account cancellation methods, keyed by method id.
@@ -183,7 +183,23 @@ function hook_user_operations() {
}
/**
- * Retrieve a list of user setting or profile information categories.
+ * Define a list of user settings or profile information categories.
+ *
+ * There are two steps to using hook_user_categories():
+ * - Create the category with hook_user_categories().
+ * - Display that category on the form ID of "user_profile_form" with
+ * hook_form_FORM_ID_alter().
+ *
+ * Step one builds out the category but it won't be visible on your form until
+ * you explicitly tell it to do so.
+ *
+ * The function in step two should contain the following code in order to
+ * display your new category:
+ * @code
+ * if ($form['#user_category'] == 'mycategory') {
+ * // Return your form here.
+ * }
+ * @endcode
*
* @return
* An array of associative arrays. Each inner array has elements:
diff --git a/modules/user/user.info b/modules/user/user.info
index d2de841..44d2fd2 100644
--- a/modules/user/user.info
+++ b/modules/user/user.info
@@ -9,8 +9,8 @@ required = TRUE
configure = admin/config/people
stylesheets[all][] = user.css
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/modules/user/user.install b/modules/user/user.install
index b573e72..7a74766 100644
--- a/modules/user/user.install
+++ b/modules/user/user.install
@@ -49,6 +49,9 @@ function user_schema() {
'columns' => array('uid' => 'uid'),
),
),
+ 'indexes' => array(
+ 'uid_module' => array('uid', 'module'),
+ ),
);
$schema['role_permission'] = array(
@@ -910,6 +913,15 @@ function user_update_7018() {
}
}
+/**
+ * Ensure there is a combined index on {authmap}.uid and {authmap}.module.
+ */
+function user_update_7019() {
+ // Check first in case it was already added manually.
+ if (!db_index_exists('authmap', 'uid_module')) {
+ db_add_index('authmap', 'uid_module', array('uid', 'module'));
+ }
+}
/**
* @} End of "addtogroup updates-7.x-extra".
*/
diff --git a/modules/user/user.js b/modules/user/user.js
index d182066..4cf9816 100644
--- a/modules/user/user.js
+++ b/modules/user/user.js
@@ -93,6 +93,8 @@ Drupal.behaviors.password = {
* Returns the estimated strength and the relevant output message.
*/
Drupal.evaluatePasswordStrength = function (password, translate) {
+ password = $.trim(password);
+
var weaknesses = 0, strength = 100, msg = [];
var hasLowercase = /[a-z]+/.test(password);
diff --git a/modules/user/user.module b/modules/user/user.module
index 9637a71..b818d79 100644
--- a/modules/user/user.module
+++ b/modules/user/user.module
@@ -418,13 +418,11 @@ function user_load_by_name($name) {
*
* @return
* A fully-loaded $user object upon successful save or FALSE if the save failed.
- *
- * @todo D8: Drop $edit and fix user_save() to be consistent with others.
*/
function user_save($account, $edit = array(), $category = 'account') {
$transaction = db_transaction();
try {
- if (!empty($edit['pass'])) {
+ if (isset($edit['pass']) && strlen(trim($edit['pass'])) > 0) {
// Allow alternate password hashing schemes.
require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
$edit['pass'] = user_hash_password(trim($edit['pass']));
@@ -791,7 +789,7 @@ function user_role_permissions($roles = array()) {
* (optional) The account to check, if not given use currently logged in user.
*
* @return
- * Boolean TRUE if the current user has the requested permission.
+ * Boolean TRUE if the user has the requested permission.
*
* All permission checks in Drupal should go through this function. This
* way, we guarantee consistent behavior, and ensure that the superuser
@@ -958,6 +956,8 @@ function user_search_access() {
*/
function user_search_execute($keys = NULL, $conditions = NULL) {
$find = array();
+ // Escape for LIKE matching.
+ $keys = db_like($keys);
// Replace wildcards with MySQL/PostgreSQL wildcards.
$keys = preg_replace('!\*+!', '%', $keys);
$query = db_select('users')->extend('PagerDefault');
@@ -967,13 +967,13 @@ function user_search_execute($keys = NULL, $conditions = NULL) {
// and they don't need to be restricted to only active users.
$query->fields('users', array('mail'));
$query->condition(db_or()->
- condition('name', '%' . db_like($keys) . '%', 'LIKE')->
- condition('mail', '%' . db_like($keys) . '%', 'LIKE'));
+ condition('name', '%' . $keys . '%', 'LIKE')->
+ condition('mail', '%' . $keys . '%', 'LIKE'));
}
else {
// Regular users can only search via usernames, and we do not show them
// blocked accounts.
- $query->condition('name', '%' . db_like($keys) . '%', 'LIKE')
+ $query->condition('name', '%' . $keys . '%', 'LIKE')
->condition('status', 1);
}
$uids = $query
@@ -1160,7 +1160,7 @@ function user_account_form(&$form, &$form_state) {
$form['account']['roles'] = array(
'#type' => 'checkboxes',
'#title' => t('Roles'),
- '#default_value' => (!$register && isset($account->roles) ? array_keys($account->roles) : array()),
+ '#default_value' => (!$register && !empty($account->roles) ? array_keys(array_filter($account->roles)) : array()),
'#options' => $roles,
'#access' => $roles && user_access('administer permissions'),
DRUPAL_AUTHENTICATED_RID => $checkbox_authenticated,
@@ -1230,7 +1230,7 @@ function user_validate_current_pass(&$form, &$form_state) {
// that prevent them from being empty if they are changed.
if ((strlen(trim($form_state['values'][$key])) > 0) && ($form_state['values'][$key] != $account->$key)) {
require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
- $current_pass_failed = empty($form_state['values']['current_pass']) || !user_check_password($form_state['values']['current_pass'], $account);
+ $current_pass_failed = strlen(trim($form_state['values']['current_pass'])) == 0 || !user_check_password($form_state['values']['current_pass'], $account);
if ($current_pass_failed) {
form_set_error('current_pass', t("Your current password is missing or incorrect; it's required to change the %name.", array('%name' => $name)));
form_set_error($key);
@@ -1306,10 +1306,12 @@ function user_user_presave(&$edit, $account, $category) {
elseif (!empty($edit['picture_delete'])) {
$edit['picture'] = NULL;
}
- // Prepare user roles.
- if (isset($edit['roles'])) {
- $edit['roles'] = array_filter($edit['roles']);
- }
+ }
+
+ // Filter out roles with empty values to avoid granting extra roles when
+ // processing custom form submissions.
+ if (isset($edit['roles'])) {
+ $edit['roles'] = array_filter($edit['roles']);
}
// Move account cancellation information into $user->data.
@@ -1751,9 +1753,11 @@ function user_menu() {
$items['admin/people/create'] = array(
'title' => 'Add user',
+ 'page callback' => 'user_admin',
'page arguments' => array('create'),
'access arguments' => array('administer users'),
'type' => MENU_LOCAL_ACTION,
+ 'file' => 'user.admin.inc',
);
// Administration pages.
@@ -1911,13 +1915,13 @@ function user_menu_link_alter(&$link) {
// for authenticated users. Authenticated users should see "My account", but
// anonymous users should not see it at all. Therefore, invoke
// user_translated_menu_link_alter() to conditionally hide the link.
- if ($link['link_path'] == 'user' && $link['module'] == 'system') {
+ if ($link['link_path'] == 'user' && isset($link['module']) && $link['module'] == 'system') {
$link['options']['alter'] = TRUE;
}
// Force the Logout link to appear on the top-level of 'user-menu' menu by
// default (i.e., unless it has been customized).
- if ($link['link_path'] == 'user/logout' && $link['module'] == 'system' && empty($link['customized'])) {
+ if ($link['link_path'] == 'user/logout' && isset($link['module']) && $link['module'] == 'system' && empty($link['customized'])) {
$link['plid'] = 0;
}
}
@@ -2161,7 +2165,7 @@ function user_login_name_validate($form, &$form_state) {
*/
function user_login_authenticate_validate($form, &$form_state) {
$password = trim($form_state['values']['pass']);
- if (!empty($form_state['values']['name']) && !empty($password)) {
+ if (!empty($form_state['values']['name']) && strlen(trim($password)) > 0) {
// Do not allow any login from the current user's IP if the limit has been
// reached. Default is 50 failed attempts allowed in one hour. This is
// independent of the per-user limit to catch attempts from one IP to log
@@ -2225,7 +2229,11 @@ function user_login_final_validate($form, &$form_state) {
}
}
else {
- form_set_error('name', t('Sorry, unrecognized username or password. Have you forgotten your password?', array('@password' => url('user/password', array('query' => array('name' => $form_state['values']['name']))))));
+ // Use $form_state['input']['name'] here to guarantee that we send
+ // exactly what the user typed in. $form_state['values']['name'] may have
+ // been modified by validation handlers that ran earlier than this one.
+ $query = isset($form_state['input']['name']) ? array('name' => $form_state['input']['name']) : array();
+ form_set_error('name', t('Sorry, unrecognized username or password. Have you forgotten your password?', array('@password' => url('user/password', array('query' => $query)))));
watchdog('user', 'Login attempt failed for %user.', array('%user' => $form_state['values']['name']));
}
}
@@ -2248,7 +2256,7 @@ function user_login_final_validate($form, &$form_state) {
*/
function user_authenticate($name, $password) {
$uid = FALSE;
- if (!empty($name) && !empty($password)) {
+ if (!empty($name) && strlen(trim($password)) > 0) {
$account = user_load_by_name($name);
if ($account) {
// Allow alternate password hashing schemes.
@@ -2488,7 +2496,9 @@ function user_cancel($edit, $uid, $method) {
}
/**
- * Last batch processing step for cancelling a user account.
+ * Implements callback_batch_operation().
+ *
+ * Last step for cancelling a user account.
*
* Since batch and session API require a valid user account, the actual
* cancellation of a user account needs to happen last.
@@ -2536,6 +2546,8 @@ function _user_cancel($edit, $account, $method) {
}
/**
+ * Implements callback_batch_finished().
+ *
* Finished batch processing callback for cancelling a user account.
*
* @see user_cancel()
@@ -3039,6 +3051,11 @@ function user_role_delete($role) {
$role = user_role_load_by_name($role);
}
+ // If this is the administrator role, delete the user_admin_role variable.
+ if ($role->rid == variable_get('user_admin_role')) {
+ variable_del('user_admin_role');
+ }
+
db_delete('role')
->condition('rid', $role->rid)
->execute();
@@ -3654,12 +3671,7 @@ function user_form_process_password_confirm($element) {
);
$element['#attached']['js'][] = drupal_get_path('module', 'user') . '/user.js';
- // Ensure settings are only added once per page.
- static $already_added = FALSE;
- if (!$already_added) {
- $already_added = TRUE;
- $element['#attached']['js'][] = array('data' => $js_settings, 'type' => 'setting');
- }
+ $element['#attached']['js'][] = array('data' => $js_settings, 'type' => 'setting');
return $element;
}
diff --git a/modules/user/user.pages.inc b/modules/user/user.pages.inc
index f21bd13..2a1b291 100644
--- a/modules/user/user.pages.inc
+++ b/modules/user/user.pages.inc
@@ -44,6 +44,12 @@ function user_pass() {
$form['name']['#value'] = $user->mail;
$form['mail'] = array(
'#prefix' => '',
+ // As of https://www.drupal.org/node/889772 the user no longer must log
+ // out (if they are still logged in when using the password reset link,
+ // they will be logged out automatically then), but this text is kept as
+ // is to avoid breaking translations as well as to encourage the user to
+ // log out manually at a time of their own choosing (when it will not
+ // interrupt anything else they may have been in the middle of doing).
'#markup' => t('Password reset instructions will be mailed to %email. You must log out to use the password reset link in the e-mail.', array('%email' => $user->mail)),
'#suffix' => '
',
);
@@ -54,6 +60,11 @@ function user_pass() {
return $form;
}
+/**
+ * Form validation handler for user_pass().
+ *
+ * @see user_pass_submit()
+ */
function user_pass_validate($form, &$form_state) {
$name = trim($form_state['values']['name']);
// Try to load by email.
@@ -72,6 +83,11 @@ function user_pass_validate($form, &$form_state) {
}
}
+/**
+ * Form submission handler for user_pass().
+ *
+ * @see user_pass_validate()
+ */
function user_pass_submit($form, &$form_state) {
global $language;
@@ -96,22 +112,33 @@ function user_pass_reset($form, &$form_state, $uid, $timestamp, $hashed_pass, $a
// When processing the one-time login link, we have to make sure that a user
// isn't already logged in.
if ($user->uid) {
- // The existing user is already logged in.
+ // The existing user is already logged in. Log them out and reload the
+ // current page so the password reset process can continue.
if ($user->uid == $uid) {
- drupal_set_message(t('You are logged in as %user. Change your password.', array('%user' => $user->name, '!user_edit' => url("user/$user->uid/edit"))));
+ // Preserve the current destination (if any) and ensure the redirect goes
+ // back to the current page; any custom destination set in
+ // hook_user_logout() and intended for regular logouts would not be
+ // appropriate here.
+ $destination = array();
+ if (isset($_GET['destination'])) {
+ $destination = drupal_get_destination();
+ }
+ user_logout_current_user();
+ unset($_GET['destination']);
+ drupal_goto(current_path(), array('query' => drupal_get_query_parameters() + $destination));
}
// A different user is already logged in on the computer.
else {
$reset_link_account = user_load($uid);
if (!empty($reset_link_account)) {
drupal_set_message(t('Another user (%other_user) is already logged into the site on this computer, but you tried to use a one-time link for user %resetting_user. Please logout and try using the link again.',
- array('%other_user' => $user->name, '%resetting_user' => $reset_link_account->name, '!logout' => url('user/logout'))));
+ array('%other_user' => $user->name, '%resetting_user' => $reset_link_account->name, '!logout' => url('user/logout'))), 'warning');
} else {
// Invalid one-time link specifies an unknown user.
- drupal_set_message(t('The one-time login link you clicked is invalid.'));
+ drupal_set_message(t('The one-time login link you clicked is invalid.'), 'error');
}
+ drupal_goto();
}
- drupal_goto();
}
else {
// Time out, in seconds, until login URL expires. Defaults to 24 hours =
@@ -123,7 +150,7 @@ function user_pass_reset($form, &$form_state, $uid, $timestamp, $hashed_pass, $a
if ($timestamp <= $current && $account = reset($users)) {
// No time out for first time login.
if ($account->login && $current - $timestamp > $timeout) {
- drupal_set_message(t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.'));
+ drupal_set_message(t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.'), 'error');
drupal_goto('user/password');
}
elseif ($account->uid && $timestamp >= $account->login && $timestamp <= $current && $hashed_pass == user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid)) {
@@ -151,7 +178,7 @@ function user_pass_reset($form, &$form_state, $uid, $timestamp, $hashed_pass, $a
}
}
else {
- drupal_set_message(t('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.'));
+ drupal_set_message(t('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.'), 'error');
drupal_goto('user/password');
}
}
@@ -168,6 +195,14 @@ function user_pass_reset($form, &$form_state, $uid, $timestamp, $hashed_pass, $a
* Menu callback; logs the current user out, and redirects to the home page.
*/
function user_logout() {
+ user_logout_current_user();
+ drupal_goto();
+}
+
+/**
+ * Logs the current user out.
+ */
+function user_logout_current_user() {
global $user;
watchdog('user', 'Session closed for %name.', array('%name' => $user->name));
@@ -176,8 +211,6 @@ function user_logout() {
// Destroy the current session, and reset $user to the anonymous user.
session_destroy();
-
- drupal_goto();
}
/**
@@ -294,14 +327,18 @@ function user_profile_form($form, &$form_state, $account, $category = 'account')
}
/**
- * Validation function for the user account and profile editing form.
+ * Form validation handler for user_profile_form().
+ *
+ * @see user_profile_form_submit()
*/
function user_profile_form_validate($form, &$form_state) {
entity_form_field_validate('user', $form, $form_state);
}
/**
- * Submit function for the user account and profile editing form.
+ * Form submission handler for user_profile_form().
+ *
+ * @see user_profile_form_validate()
*/
function user_profile_form_submit($form, &$form_state) {
$account = $form_state['user'];
@@ -533,7 +570,7 @@ function user_cancel_confirm($account, $timestamp = 0, $hashed_pass = '') {
batch_process('');
}
else {
- drupal_set_message(t('You have tried to use an account cancellation link that has expired. Please request a new one using the form below.'));
+ drupal_set_message(t('You have tried to use an account cancellation link that has expired. Please request a new one using the form below.'), 'error');
drupal_goto("user/$account->uid/cancel");
}
}
diff --git a/modules/user/user.test b/modules/user/user.test
index 07be4c2..63143c3 100644
--- a/modules/user/user.test
+++ b/modules/user/user.test
@@ -480,6 +480,34 @@ class UserPasswordResetTestCase extends DrupalWebTestCase {
$this->assertText(t('Further instructions have been sent to your e-mail address.'), 'Password reset instructions mailed message displayed.');
}
+ /**
+ * Test user password reset while logged in.
+ */
+ function testUserPasswordResetLoggedIn() {
+ $account = $this->drupalCreateUser();
+ $this->drupalLogin($account);
+ // Make sure the test account has a valid password.
+ user_save($account, array('pass' => user_password()));
+
+ // Generate one time login link.
+ $reset_url = user_pass_reset_url($account);
+ $this->drupalGet($reset_url);
+
+ $this->assertText('Reset password');
+ $this->drupalPost(NULL, NULL, t('Log in'));
+
+ $this->assertText('You have just used your one-time login link. It is no longer necessary to use this link to log in. Please change your password.');
+
+ $pass = user_password();
+ $edit = array(
+ 'pass[pass1]' => $pass,
+ 'pass[pass2]' => $pass,
+ );
+ $this->drupalPost(NULL, $edit, t('Save'));
+
+ $this->assertText('The changes have been saved.');
+ }
+
/**
* Attempts login using an expired password reset link.
*/
@@ -1849,6 +1877,19 @@ class UserCreateTestCase extends DrupalWebTestCase {
$this->drupalGet('admin/people');
$this->assertText($edit['name'], 'User found in list of users');
}
+
+ // Test that the password '0' is considered a password.
+ $name = $this->randomName();
+ $edit = array(
+ 'name' => $name,
+ 'mail' => $name . '@example.com',
+ 'pass[pass1]' => 0,
+ 'pass[pass2]' => 0,
+ 'notify' => FALSE,
+ );
+ $this->drupalPost('admin/people/create', $edit, t('Create new account'));
+ $this->assertText(t('Created a new user account for @name. No e-mail has been sent.', array('@name' => $edit['name'])), 'User created with password 0');
+ $this->assertNoText('Password field is required');
}
}
@@ -1926,6 +1967,74 @@ class UserEditTestCase extends DrupalWebTestCase {
$this->drupalLogin($user1);
$this->drupalLogout();
}
+
+ /**
+ * Tests setting the password to "0".
+ */
+ public function testUserWith0Password() {
+ $admin = $this->drupalCreateUser(array('administer users'));
+ $this->drupalLogin($admin);
+ // Create a regular user.
+ $user1 = $this->drupalCreateUser(array());
+
+ $edit = array('pass[pass1]' => '0', 'pass[pass2]' => '0');
+ $this->drupalPost("user/" . $user1->uid . "/edit", $edit, t('Save'));
+ $this->assertRaw(t("The changes have been saved."));
+
+ $this->drupalLogout();
+ $user1->pass_raw = '0';
+ $this->drupalLogin($user1);
+ $this->drupalLogout();
+ }
+}
+
+/**
+ * Tests editing a user account with and without a form rebuild.
+ */
+class UserEditRebuildTestCase extends DrupalWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'User edit with form rebuild',
+ 'description' => 'Test user edit page when a form rebuild is triggered.',
+ 'group' => 'User',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('user_form_test');
+ }
+
+ /**
+ * Test user edit page when the form is set to rebuild.
+ */
+ function testUserEditFormRebuild() {
+ $user1 = $this->drupalCreateUser(array('change own username'));
+ $this->drupalLogin($user1);
+
+ $roles = array_keys($user1->roles);
+ // Save the user form twice.
+ $edit = array();
+ $edit['current_pass'] = $user1->pass_raw;
+ $this->drupalPost("user/$user1->uid/edit", $edit, t('Save'));
+ $this->assertRaw(t("The changes have been saved."));
+ $this->drupalPost(NULL, $edit, t('Save'));
+ $this->assertRaw(t("The changes have been saved."));
+ $saved_user1 = entity_load_unchanged('user', $user1->uid);
+ $this->assertEqual(count($roles), count($saved_user1->roles), 'Count of user roles in database matches original count.');
+ $diff = array_diff(array_keys($saved_user1->roles), $roles);
+ $this->assertTrue(empty($diff), format_string('User roles in database match original: @roles', array('@roles' => implode(', ', $saved_user1->roles))));
+ // Set variable that causes the form to be rebuilt in user_form_test.module.
+ variable_set('user_form_test_user_profile_form_rebuild', TRUE);
+ $this->drupalPost("user/$user1->uid/edit", $edit, t('Save'));
+ $this->assertRaw(t("The changes have been saved."));
+ $this->drupalPost(NULL, $edit, t('Save'));
+ $this->assertRaw(t("The changes have been saved."));
+ $saved_user1 = entity_load_unchanged('user', $user1->uid);
+ $this->assertEqual(count($roles), count($saved_user1->roles), 'Count of user roles in database matches original count.');
+ $diff = array_diff(array_keys($saved_user1->roles), $roles);
+ $this->assertTrue(empty($diff), format_string('User roles in database match original: @roles', array('@roles' => implode(', ', $saved_user1->roles))));
+ }
}
/**
@@ -2095,12 +2204,16 @@ class UserRoleAdminTestCase extends DrupalWebTestCase {
$this->assertFalse(user_role_load_by_name($old_name), 'The role can no longer be retrieved from the database using its old name.');
$this->assertTrue(is_object(user_role_load_by_name($role_name)), 'The role can be retrieved from the database using its new name.');
- // Test deleting a role.
+ // Test deleting the default administrator role.
+ $role_name = 'administrator';
+ $role = user_role_load_by_name($role_name);
$this->drupalPost("admin/people/permissions/roles/edit/{$role->rid}", NULL, t('Delete role'));
$this->drupalPost(NULL, NULL, t('Delete'));
$this->assertText(t('The role has been deleted.'), 'The role has been deleted');
$this->assertNoLinkByHref("admin/people/permissions/roles/edit/{$role->rid}", 'Role edit link removed.');
$this->assertFalse(user_role_load_by_name($role_name), 'A deleted role can no longer be loaded.');
+ // Make sure this role is no longer configured as the administrator role.
+ $this->assertNull(variable_get('user_admin_role'), 'The administrator role is no longer configured as the administrator role.');
// Make sure that the system-defined roles cannot be edited via the user
// interface.
@@ -2226,6 +2339,20 @@ class UserUserSearchTestCase extends DrupalWebTestCase {
$this->drupalPost('search/user/', $edit, t('Search'));
$this->assertText($keys);
+ // Verify that wildcard search works.
+ $keys = $user1->name;
+ $keys = substr($keys, 0, 2) . '*' . substr($keys, 4, 2);
+ $edit = array('keys' => $keys);
+ $this->drupalPost('search/user/', $edit, t('Search'));
+ $this->assertText($user1->name, 'Search for username wildcard resulted in user name on page for administrative user.');
+
+ // Verify that wildcard search works for email.
+ $keys = $user1->mail;
+ $keys = substr($keys, 0, 2) . '*' . substr($keys, 4, 2);
+ $edit = array('keys' => $keys);
+ $this->drupalPost('search/user/', $edit, t('Search'));
+ $this->assertText($user1->name, 'Search for email wildcard resulted in user name on page for administrative user.');
+
// Create a blocked user.
$blocked_user = $this->drupalCreateUser();
$edit = array('status' => 0);
diff --git a/phpinfo.php b/phpinfo.php
deleted file mode 100644
index 44f0f1c..0000000
--- a/phpinfo.php
+++ /dev/null
@@ -1,3 +0,0 @@
-
\ No newline at end of file
diff --git a/profiles/README.txt b/profiles/README.txt
new file mode 100644
index 0000000..91d012b
--- /dev/null
+++ b/profiles/README.txt
@@ -0,0 +1,28 @@
+Installation profiles define additional steps that run after the base
+installation provided by Drupal core when Drupal is first installed.
+
+WHAT TO PLACE IN THIS DIRECTORY?
+--------------------------------
+
+Place downloaded and custom installation profiles in this directory.
+Installation profiles are generally provided as part of a Drupal distribution.
+They only impact the installation of your site. They do not have any effect on
+an already running site.
+
+DOWNLOAD ADDITIONAL DISTRIBUTIONS
+---------------------------------
+
+Contributed distributions from the Drupal community may be downloaded at
+https://www.drupal.org/project/project_distribution.
+
+MULTISITE CONFIGURATION
+-----------------------
+
+In multisite configurations, installation profiles found in this directory are
+available to all sites during their initial site installation.
+
+MORE INFORMATION
+----------------
+
+Refer to the "Installation profiles" section of the README.txt in the Drupal
+root directory for further information on extending Drupal with custom profiles.
diff --git a/profiles/minimal/minimal.info b/profiles/minimal/minimal.info
index 1b9d8fa..bb30cdc 100644
--- a/profiles/minimal/minimal.info
+++ b/profiles/minimal/minimal.info
@@ -5,8 +5,8 @@ core = 7.x
dependencies[] = block
dependencies[] = dblog
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/profiles/standard/standard.info b/profiles/standard/standard.info
index a48955f..effe395 100644
--- a/profiles/standard/standard.info
+++ b/profiles/standard/standard.info
@@ -24,8 +24,8 @@ dependencies[] = field_ui
dependencies[] = file
dependencies[] = rdf
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info b/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info
index cce3872..d4d1248 100644
--- a/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info
+++ b/profiles/testing/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info
@@ -6,8 +6,8 @@ core = 7.x
hidden = TRUE
files[] = drupal_system_listing_compatible_test.test
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info b/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info
index 25c98de..7469d2f 100644
--- a/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info
+++ b/profiles/testing/modules/drupal_system_listing_incompatible_test/drupal_system_listing_incompatible_test.info
@@ -8,8 +8,8 @@ version = VERSION
core = 6.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/profiles/testing/testing.info b/profiles/testing/testing.info
index d0e7674..937c48e 100644
--- a/profiles/testing/testing.info
+++ b/profiles/testing/testing.info
@@ -4,8 +4,8 @@ version = VERSION
core = 7.x
hidden = TRUE
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/robots.txt b/robots.txt
index ff9e286..a2ee32e 100644
--- a/robots.txt
+++ b/robots.txt
@@ -15,6 +15,39 @@
User-agent: *
Crawl-delay: 10
+# CSS, JS, Images
+Allow: /misc/*.css$
+Allow: /misc/*.css?
+Allow: /misc/*.js$
+Allow: /misc/*.js?
+Allow: /misc/*.gif
+Allow: /misc/*.jpg
+Allow: /misc/*.jpeg
+Allow: /misc/*.png
+Allow: /modules/*.css$
+Allow: /modules/*.css?
+Allow: /modules/*.js$
+Allow: /modules/*.js?
+Allow: /modules/*.gif
+Allow: /modules/*.jpg
+Allow: /modules/*.jpeg
+Allow: /modules/*.png
+Allow: /profiles/*.css$
+Allow: /profiles/*.css?
+Allow: /profiles/*.js$
+Allow: /profiles/*.js?
+Allow: /profiles/*.gif
+Allow: /profiles/*.jpg
+Allow: /profiles/*.jpeg
+Allow: /profiles/*.png
+Allow: /themes/*.css$
+Allow: /themes/*.css?
+Allow: /themes/*.js$
+Allow: /themes/*.js?
+Allow: /themes/*.gif
+Allow: /themes/*.jpg
+Allow: /themes/*.jpeg
+Allow: /themes/*.png
# Directories
Disallow: /includes/
Disallow: /misc/
diff --git a/scripts/generate-d6-content.sh b/scripts/generate-d6-content.sh
index fc4c68f..cd33e4d 100644
--- a/scripts/generate-d6-content.sh
+++ b/scripts/generate-d6-content.sh
@@ -67,6 +67,7 @@ for ($i = 0; $i < 24; $i++) {
++$voc_id;
$vocabulary['name'] = "vocabulary $voc_id (i=$i)";
$vocabulary['description'] = "description of ". $vocabulary['name'];
+ $vocabulary['help'] = "help for ". $vocabulary['name'];
$vocabulary['nodes'] = $i > 11 ? array('page' => TRUE) : array();
$vocabulary['multiple'] = $multiple[$i % 12];
$vocabulary['required'] = $required[$i % 12];
diff --git a/scripts/password-hash.sh b/scripts/password-hash.sh
index 004421a..1afe438 100644
--- a/scripts/password-hash.sh
+++ b/scripts/password-hash.sh
@@ -1,4 +1,4 @@
-#!/usr/bin/php
+#!/usr/bin/env php
useDefaults(array('test_id'))->execute();
// Execute tests.
-simpletest_script_execute_batch($test_id, simpletest_script_get_test_list());
+$status = simpletest_script_execute_batch($test_id, simpletest_script_get_test_list());
// Retrieve the last database prefix used for testing and the last test class
// that was run from. Use the information to read the lgo file in case any
@@ -100,7 +104,7 @@ if ($args['xml']) {
simpletest_clean_results_table($test_id);
// Test complete, exit.
-exit;
+exit($status);
/**
* Print help text.
@@ -142,6 +146,8 @@ All arguments are long options.
--file Run tests identified by specific file names, instead of group names.
Specify the path and the extension (i.e. 'modules/user/user.test').
+ --directory Run all tests found within the specified file directory.
+
--xml
If provided, test results will be written as xml files to this path.
@@ -190,6 +196,7 @@ function simpletest_script_parse_args() {
'all' => FALSE,
'class' => FALSE,
'file' => FALSE,
+ 'directory' => '',
'color' => FALSE,
'verbose' => FALSE,
'test_names' => array(),
@@ -222,7 +229,7 @@ function simpletest_script_parse_args() {
else {
// Argument not found in list.
simpletest_script_print_error("Unknown argument '$arg'.");
- exit;
+ exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
}
}
else {
@@ -235,7 +242,7 @@ function simpletest_script_parse_args() {
// Validate the concurrency argument
if (!is_numeric($args['concurrency']) || $args['concurrency'] <= 0) {
simpletest_script_print_error("--concurrency must be a strictly positive integer.");
- exit;
+ exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
}
return array($args, $count);
@@ -265,7 +272,7 @@ function simpletest_script_init($server_software) {
else {
simpletest_script_print_error('Unable to automatically determine the path to the PHP interpreter. Supply the --php command line argument.');
simpletest_script_help();
- exit();
+ exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
}
// Get URL from arguments.
@@ -310,6 +317,8 @@ function simpletest_script_init($server_software) {
function simpletest_script_execute_batch($test_id, $test_classes) {
global $args;
+ $total_status = SIMPLETEST_SCRIPT_EXIT_SUCCESS;
+
// Multi-process execution.
$children = array();
while (!empty($test_classes) || !empty($children)) {
@@ -325,7 +334,7 @@ function simpletest_script_execute_batch($test_id, $test_classes) {
if (!is_resource($process)) {
echo "Unable to fork test process. Aborting.\n";
- exit;
+ exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
}
// Register our new child.
@@ -345,13 +354,22 @@ function simpletest_script_execute_batch($test_id, $test_classes) {
if (empty($status['running'])) {
// The child exited, unregister it.
proc_close($child['process']);
- if ($status['exitcode']) {
+ if ($status['exitcode'] == SIMPLETEST_SCRIPT_EXIT_FAILURE) {
+ if ($status['exitcode'] > $total_status) {
+ $total_status = $status['exitcode'];
+ }
+ }
+ elseif ($status['exitcode']) {
+ $total_status = $status['exitcode'];
echo 'FATAL ' . $test_class . ': test runner returned a non-zero error code (' . $status['exitcode'] . ').' . "\n";
}
+
+ // Remove this child.
unset($children[$cid]);
}
}
}
+ return $total_status;
}
/**
@@ -374,11 +392,14 @@ function simpletest_script_run_one_test($test_id, $test_class) {
simpletest_script_print($info['name'] . ' ' . _simpletest_format_summary_line($test->results) . "\n", simpletest_script_color_code($status));
// Finished, kill this runner.
- exit(0);
+ if ($had_fails || $had_exceptions) {
+ exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
+ }
+ exit(SIMPLETEST_SCRIPT_EXIT_SUCCESS);
}
catch (Exception $e) {
echo (string) $e;
- exit(1);
+ exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
}
}
@@ -432,7 +453,7 @@ function simpletest_script_get_test_list() {
}
simpletest_script_print_error('Test class not found: ' . $test_class);
simpletest_script_print_alternatives($test_class, $all_classes, 6);
- exit(1);
+ exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
}
}
}
@@ -451,6 +472,51 @@ function simpletest_script_get_test_list() {
}
}
}
+ elseif ($args['directory']) {
+ // Extract test case class names from specified directory.
+ // Find all tests in the PSR-X structure; Drupal\$extension\Tests\*.php
+ // Since we do not want to hard-code too many structural file/directory
+ // assumptions about PSR-0/4 files and directories, we check for the
+ // minimal conditions only; i.e., a '*.php' file that has '/Tests/' in
+ // its path.
+ // Ignore anything from third party vendors, and ignore template files used in tests.
+ // And any api.php files.
+ $ignore = array('nomask' => '/vendor|\.tpl\.php|\.api\.php/');
+ $files = array();
+ if ($args['directory'][0] === '/') {
+ $directory = $args['directory'];
+ }
+ else {
+ $directory = DRUPAL_ROOT . "/" . $args['directory'];
+ }
+ $file_list = file_scan_directory($directory, '/\.php|\.test$/', $ignore);
+ foreach ($file_list as $file) {
+ // '/Tests/' can be contained anywhere in the file's path (there can be
+ // sub-directories below /Tests), but must be contained literally.
+ // Case-insensitive to match all Simpletest and PHPUnit tests:
+ // ./lib/Drupal/foo/Tests/Bar/Baz.php
+ // ./foo/src/Tests/Bar/Baz.php
+ // ./foo/tests/Drupal/foo/Tests/FooTest.php
+ // ./foo/tests/src/FooTest.php
+ // $file->filename doesn't give us a directory, so we use $file->uri
+ // Strip the drupal root directory and trailing slash off the URI
+ $filename = substr($file->uri, strlen(DRUPAL_ROOT)+1);
+ if (stripos($filename, '/Tests/')) {
+ $files[drupal_realpath($filename)] = 1;
+ } else if (stripos($filename, '.test')){
+ $files[drupal_realpath($filename)] = 1;
+ }
+ }
+
+ // Check for valid class names.
+ foreach ($all_tests as $class_name) {
+ $refclass = new ReflectionClass($class_name);
+ $classfile = $refclass->getFileName();
+ if (isset($files[$classfile])) {
+ $test_list[] = $class_name;
+ }
+ }
+ }
else {
// Check for valid group names and get all valid classes in group.
foreach ($args['test_names'] as $group_name) {
@@ -460,7 +526,7 @@ function simpletest_script_get_test_list() {
else {
simpletest_script_print_error('Test group not found: ' . $group_name);
simpletest_script_print_alternatives($group_name, array_keys($groups));
- exit(1);
+ exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
}
}
}
@@ -468,7 +534,7 @@ function simpletest_script_get_test_list() {
if (empty($test_list)) {
simpletest_script_print_error('No valid tests were specified.');
- exit;
+ exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
}
return $test_list;
}
diff --git a/themes/bartik/bartik.info b/themes/bartik/bartik.info
index 72ca64a..b2a0a40 100644
--- a/themes/bartik/bartik.info
+++ b/themes/bartik/bartik.info
@@ -34,8 +34,8 @@ regions[footer] = Footer
settings[shortcut_module_link] = 0
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/themes/garland/garland.info b/themes/garland/garland.info
index b46364a..24e5675 100644
--- a/themes/garland/garland.info
+++ b/themes/garland/garland.info
@@ -7,8 +7,8 @@ stylesheets[all][] = style.css
stylesheets[print][] = print.css
settings[garland_width] = fluid
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/themes/garland/template.php b/themes/garland/template.php
index 416a43a..ef7d477 100644
--- a/themes/garland/template.php
+++ b/themes/garland/template.php
@@ -19,22 +19,22 @@ function garland_breadcrumb($variables) {
/**
* Override or insert variables into the maintenance page template.
*/
-function garland_preprocess_maintenance_page(&$vars) {
+function garland_preprocess_maintenance_page(&$variables) {
// While markup for normal pages is split into page.tpl.php and html.tpl.php,
// the markup for the maintenance page is all in the single
// maintenance-page.tpl.php template. So, to have what's done in
// garland_preprocess_html() also happen on the maintenance page, it has to be
// called here.
- garland_preprocess_html($vars);
+ garland_preprocess_html($variables);
}
/**
* Override or insert variables into the html template.
*/
-function garland_preprocess_html(&$vars) {
+function garland_preprocess_html(&$variables) {
// Toggle fixed or fluid width.
if (theme_get_setting('garland_width') == 'fluid') {
- $vars['classes_array'][] = 'fluid-width';
+ $variables['classes_array'][] = 'fluid-width';
}
// Add conditional CSS for IE6.
drupal_add_css(path_to_theme() . '/fix-ie.css', array('group' => CSS_THEME, 'browsers' => array('IE' => 'lt IE 7', '!IE' => FALSE), 'preprocess' => FALSE));
@@ -43,27 +43,27 @@ function garland_preprocess_html(&$vars) {
/**
* Override or insert variables into the html template.
*/
-function garland_process_html(&$vars) {
+function garland_process_html(&$variables) {
// Hook into color.module
if (module_exists('color')) {
- _color_html_alter($vars);
+ _color_html_alter($variables);
}
}
/**
* Override or insert variables into the page template.
*/
-function garland_preprocess_page(&$vars) {
+function garland_preprocess_page(&$variables) {
// Move secondary tabs into a separate variable.
- $vars['tabs2'] = array(
+ $variables['tabs2'] = array(
'#theme' => 'menu_local_tasks',
- '#secondary' => $vars['tabs']['#secondary'],
+ '#secondary' => $variables['tabs']['#secondary'],
);
- unset($vars['tabs']['#secondary']);
+ unset($variables['tabs']['#secondary']);
- if (isset($vars['main_menu'])) {
- $vars['primary_nav'] = theme('links__system_main_menu', array(
- 'links' => $vars['main_menu'],
+ if (isset($variables['main_menu'])) {
+ $variables['primary_nav'] = theme('links__system_main_menu', array(
+ 'links' => $variables['main_menu'],
'attributes' => array(
'class' => array('links', 'inline', 'main-menu'),
),
@@ -75,11 +75,11 @@ function garland_preprocess_page(&$vars) {
));
}
else {
- $vars['primary_nav'] = FALSE;
+ $variables['primary_nav'] = FALSE;
}
- if (isset($vars['secondary_menu'])) {
- $vars['secondary_nav'] = theme('links__system_secondary_menu', array(
- 'links' => $vars['secondary_menu'],
+ if (isset($variables['secondary_menu'])) {
+ $variables['secondary_nav'] = theme('links__system_secondary_menu', array(
+ 'links' => $variables['secondary_menu'],
'attributes' => array(
'class' => array('links', 'inline', 'secondary-menu'),
),
@@ -91,66 +91,66 @@ function garland_preprocess_page(&$vars) {
));
}
else {
- $vars['secondary_nav'] = FALSE;
+ $variables['secondary_nav'] = FALSE;
}
// Prepare header.
$site_fields = array();
- if (!empty($vars['site_name'])) {
- $site_fields[] = $vars['site_name'];
+ if (!empty($variables['site_name'])) {
+ $site_fields[] = $variables['site_name'];
}
- if (!empty($vars['site_slogan'])) {
- $site_fields[] = $vars['site_slogan'];
+ if (!empty($variables['site_slogan'])) {
+ $site_fields[] = $variables['site_slogan'];
}
- $vars['site_title'] = implode(' ', $site_fields);
+ $variables['site_title'] = implode(' ', $site_fields);
if (!empty($site_fields)) {
$site_fields[0] = '' . $site_fields[0] . '';
}
- $vars['site_html'] = implode(' ', $site_fields);
+ $variables['site_html'] = implode(' ', $site_fields);
// Set a variable for the site name title and logo alt attributes text.
- $slogan_text = $vars['site_slogan'];
- $site_name_text = $vars['site_name'];
- $vars['site_name_and_slogan'] = $site_name_text . ' ' . $slogan_text;
+ $slogan_text = $variables['site_slogan'];
+ $site_name_text = $variables['site_name'];
+ $variables['site_name_and_slogan'] = $site_name_text . ' ' . $slogan_text;
}
/**
* Override or insert variables into the node template.
*/
-function garland_preprocess_node(&$vars) {
- $vars['submitted'] = $vars['date'] . ' — ' . $vars['name'];
+function garland_preprocess_node(&$variables) {
+ $variables['submitted'] = $variables['date'] . ' — ' . $variables['name'];
}
/**
* Override or insert variables into the comment template.
*/
-function garland_preprocess_comment(&$vars) {
- $vars['submitted'] = $vars['created'] . ' — ' . $vars['author'];
+function garland_preprocess_comment(&$variables) {
+ $variables['submitted'] = $variables['created'] . ' — ' . $variables['author'];
}
/**
* Override or insert variables into the block template.
*/
-function garland_preprocess_block(&$vars) {
- $vars['title_attributes_array']['class'][] = 'title';
- $vars['classes_array'][] = 'clearfix';
+function garland_preprocess_block(&$variables) {
+ $variables['title_attributes_array']['class'][] = 'title';
+ $variables['classes_array'][] = 'clearfix';
}
/**
* Override or insert variables into the page template.
*/
-function garland_process_page(&$vars) {
+function garland_process_page(&$variables) {
// Hook into color.module
if (module_exists('color')) {
- _color_page_alter($vars);
+ _color_page_alter($variables);
}
}
/**
* Override or insert variables into the region template.
*/
-function garland_preprocess_region(&$vars) {
- if ($vars['region'] == 'header') {
- $vars['classes_array'][] = 'clearfix';
+function garland_preprocess_region(&$variables) {
+ if ($variables['region'] == 'header') {
+ $variables['classes_array'][] = 'clearfix';
}
}
diff --git a/themes/seven/seven.info b/themes/seven/seven.info
index 1eff1ff..479d04a 100644
--- a/themes/seven/seven.info
+++ b/themes/seven/seven.info
@@ -13,8 +13,8 @@ regions[page_bottom] = Page bottom
regions[sidebar_first] = First sidebar
regions_hidden[] = sidebar_first
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/themes/stark/stark.info b/themes/stark/stark.info
index ac551a0..1a47c4c 100644
--- a/themes/stark/stark.info
+++ b/themes/stark/stark.info
@@ -5,8 +5,8 @@ version = VERSION
core = 7.x
stylesheets[all][] = layout.css
-; Information added by Drupal.org packaging script on 2015-04-02
-version = "7.36"
+; Information added by Drupal.org packaging script on 2016-10-05
+version = "7.51"
project = "drupal"
-datestamp = "1427943826"
+datestamp = "1475694174"
diff --git a/web.config b/web.config
index 09983d9..c6fb5c8 100644
--- a/web.config
+++ b/web.config
@@ -6,7 +6,7 @@
-
+