Skip to content
Snippets Groups Projects
Verified Commit 15cde9a5 authored by Lee Rowlands's avatar Lee Rowlands
Browse files

Issue #3091478 by lauriii, Tim Bozeman, malcomio, tim.plunkett, adeshsharma,...

Issue #3091478 by lauriii, Tim Bozeman, malcomio, tim.plunkett, adeshsharma, longwave, EclipseGc, bnjmnm, alexpott, larowlan, amateescu, dpi: Improve StringItem::generateSampleValue()
parent 442c0e41
No related branches found
No related tags found
26 merge requests!54479.5.x SF update,!5014Issue #3071143: Table Render Array Example Is Incorrect,!4868Issue #1428520: Improve menu parent link selection,!4289Issue #1344552 by marcingy, Niklas Fiekas, Ravi.J, aleevas, Eduardo Morales...,!4114Issue #2707291: Disable body-level scrolling when a dialog is open as a modal,!4100Issue #3249600: Add support for PHP 8.1 Enums as allowed values for list_* data types,!2378Issue #2875033: Optimize joins and table selection in SQL entity query implementation,!2334Issue #3228209: Add hasRole() method to AccountInterface,!2062Issue #3246454: Add weekly granularity to views date sort,!1591Issue #3199697: Add JSON:API Translation experimental module,!1484Exposed filters get values from URL when Ajax is on,!1255Issue #3238922: Refactor (if feasible) uses of the jQuery serialize function to use vanillaJS,!1162Issue #3100350: Unable to save '/' root path alias,!1105Issue #3025039: New non translatable field on translatable content throws error,!1073issue #3191727: Focus states on mobile second level navigation items fixed,!10223132456: Fix issue where views instances are emptied before an ajax request is complete,!925Issue #2339235: Remove taxonomy hard dependency on node module,!877Issue #2708101: Default value for link text is not saved,!872Draft: Issue #3221319: Race condition when creating menu links and editing content deletes menu links,!844Resolve #3036010 "Updaters",!617Issue #3043725: Provide a Entity Handler for user cancelation,!579Issue #2230909: Simple decimals fail to pass validation,!560Move callback classRemove outside of the loop,!555Issue #3202493,!485Sets the autocomplete attribute for username/password input field on login form.,!30Issue #3182188: Updates composer usage to point at ./vendor/bin/composer
......@@ -72,7 +72,47 @@ public function getConstraints() {
*/
public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
$random = new Random();
$values['value'] = $random->word(mt_rand(1, $field_definition->getSetting('max_length')));
$max_length = $field_definition->getSetting('max_length');
// When the maximum length is less than 15, or the field needs to be unique,
// generate a random word using the maximum length.
if ($max_length <= 15 || $field_definition->getConstraint('UniqueField')) {
$values['value'] = ucfirst($random->word($max_length));
return $values;
}
// The minimum length is either 10% of the maximum length, or 15 characters
// long, whichever is greater.
$min_length = max(ceil($max_length * 0.10), 15);
// Reduce the max length to allow us to add a period.
$max_length -= 1;
// The random value is generated multiple times to create a slight
// preference towards values that are closer to the minimum length of the
// string. For values larger than 255 (which is the default maximum value),
// the bias towards minimum length is increased. This is because the default
// maximum length of 255 is often used for fields that include shorter
// values (i.e. title).
$length = mt_rand($min_length, mt_rand($min_length, $max_length >= 255 ? mt_rand($min_length, $max_length) : $max_length));
$string = $random->sentences(1);
while (mb_strlen($string) < $length) {
$string .= " {$random->sentences(1)}";
}
if (mb_strlen($string) > $max_length) {
$string = substr($string, 0, $length);
$string = substr($string, 0, strrpos($string, ' '));
}
$string = rtrim($string, ' .');
// Ensure that the string ends with a full stop if there are multiple
// sentences.
$values['value'] = $string . (str_contains($string, '.') ? '.' : '');
return $values;
}
......
......@@ -2,6 +2,7 @@
namespace Drupal\Core\Field\Plugin\Field\FieldType;
use Drupal\Component\Utility\Random;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\TypedData\DataDefinition;
......@@ -77,8 +78,18 @@ public function isEmpty() {
* {@inheritdoc}
*/
public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
$values = parent::generateSampleValue($field_definition);
$suffix_length = $field_definition->getSetting('max_length') - 7;
$random = new Random();
$max_length = $field_definition->getSetting('max_length');
$min_length = min(10, $max_length);
// The random value is generated multiple times to create a slight
// preference towards values that are closer to the minimum length of the
// string.
$length = mt_rand($min_length, mt_rand($min_length, mt_rand($min_length, $max_length)));
$values['value'] = $random->word($length);
$suffix_length = $max_length - 7;
foreach ($values as $key => $value) {
$values[$key] = 'http://' . mb_substr($value, 0, $suffix_length);
}
......
......@@ -17,7 +17,9 @@ class UniqueFieldConstraint extends Constraint {
public $message = 'A @entity_type with @field_name %value already exists.';
/**
* {@inheritdoc}
* Returns the name of the class that validates this constraint.
*
* @return string
*/
public function validatedBy() {
return '\Drupal\Core\Validation\Plugin\Validation\Constraint\UniqueFieldValueValidator';
......
......@@ -2,6 +2,7 @@
namespace Drupal\user;
use Drupal\Component\Utility\Random;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\Plugin\Field\FieldType\StringItem;
......@@ -28,9 +29,36 @@ public function isEmpty() {
* {@inheritdoc}
*/
public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
$values = parent::generateSampleValue($field_definition);
// User names larger than 60 characters won't pass validation.
$values['value'] = substr($values['value'], 0, UserInterface::USERNAME_MAX_LENGTH);
$random = new Random();
$max_length = min(UserInterface::USERNAME_MAX_LENGTH, $field_definition->getSetting('max_length'));
// Generate a list of words, which can be used to generate a string.
$words = explode(' ', $random->sentences(8));
// Begin with a username that is either 2 or 3 words.
$count = mt_rand(2, 3);
// Capitalize the words used in usernames 50% of the time.
$words = mt_rand(0, 1) ? array_map('ucfirst', $words) : $words;
// Username is a single long word 50% of the time. In the case of a single
// long word, sometimes the generated username may also contain periods in
// the middle of the username.
$separator = ' ';
if (mt_rand(0, 1)) {
$separator = '';
$count = mt_rand(2, 8);
// The username will start with a capital letter 50% of the time.
$words = mt_rand(0, 1) ? array_map('strtolower', $words) : $words;
}
$string = implode($separator, array_splice($words, 0, $count));
// Normalize the string to not be longer than the maximum length, and to not
// end with a space or a period.
$values['value'] = rtrim(mb_substr($string, 0, $max_length), ' .');
return $values;
}
......
<?php
namespace Drupal\Tests\user\Unit;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Tests\UnitTestCase;
use Drupal\user\UserNameItem;
/**
* Defines a test for the UserNameItem field-type.
*
* @group Field
* @coversDefaultClass \Drupal\user\UserNameItem
*/
class UserNameItemTest extends UnitTestCase {
/**
* Tests generating sample values.
*
* @param int $max_length
* Maximum field length.
*
* @covers ::generateSampleValue
* @dataProvider providerMaxLength
*/
public function testGenerateSampleValue(int $max_length): void {
$definition = $this->prophesize(FieldDefinitionInterface::class);
$definition->getSetting('max_length')->willReturn($max_length);
for ($i = 0; $i < 1000; $i++) {
$sample_value = UserNameItem::generateSampleValue($definition->reveal());
$this->assertLessThanOrEqual($max_length, mb_strlen($sample_value['value']));
$this->assertEquals(trim($sample_value['value'], ' '), $sample_value['value']);
}
}
/**
* Data provider for maximum-lengths.
*
* @return array
* Test cases.
*/
public function providerMaxLength(): array {
return [
'32' => [32],
'255' => [255],
'500' => [500],
'15' => [15],
'64' => [64],
];
}
}
<?php
namespace Drupal\Tests\Core\Field;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\Plugin\Field\FieldType\StringItem;
use Drupal\Core\Validation\Plugin\Validation\Constraint\UniqueFieldConstraint;
use Drupal\Tests\UnitTestCase;
/**
* Defines a test for the StringItem field-type.
*
* @group Field
* @coversDefaultClass \Drupal\Core\Field\Plugin\Field\FieldType\StringItem
*/
class StringItemTest extends UnitTestCase {
/**
* Tests generating sample values.
*
* @param int $max_length
* Maximum field length.
*
* @covers ::generateSampleValue
* @dataProvider providerMaxLength
*/
public function testGenerateSampleValue(int $max_length): void {
foreach ([TRUE, FALSE] as $unique) {
$definition = $this->prophesize(FieldDefinitionInterface::class);
$constraints = $unique ? [$this->prophesize(UniqueFieldConstraint::class)] : [];
$definition->getConstraint('UniqueField')->willReturn($constraints);
$definition->getSetting('max_length')->willReturn($max_length);
for ($i = 0; $i < 1000; $i++) {
$sample_value = StringItem::generateSampleValue($definition->reveal());
// When the field value needs to be unique, the generated sample value
// should match the maximum length to ensure sufficient entropy.
if ($unique) {
$this->assertEquals($max_length, mb_strlen($sample_value['value']));
}
else {
$this->assertLessThanOrEqual($max_length, mb_strlen($sample_value['value']));
}
}
}
}
/**
* Data provider for maximum-lengths.
*
* @return array
* Test cases.
*/
public function providerMaxLength(): array {
return [
'32' => [32],
'255' => [255],
'500' => [500],
'15' => [15],
'4' => [4],
'64' => [64],
];
}
}
<?php
namespace Drupal\Tests\Core\Field;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\Plugin\Field\FieldType\UriItem;
use Drupal\Tests\UnitTestCase;
/**
* Defines a test for the UriItem field-type.
*
* @group Field
* @coversDefaultClass \Drupal\Core\Field\Plugin\Field\FieldType\UriItem
*/
class UriItemTest extends UnitTestCase {
/**
* Tests generating sample values.
*
* @param int $max_length
* Maximum field length.
*
* @covers ::generateSampleValue
* @dataProvider providerMaxLength
*/
public function testGenerateSampleValue(int $max_length): void {
$definition = $this->prophesize(FieldDefinitionInterface::class);
$definition->getSetting('max_length')->willReturn($max_length);
for ($i = 0; $i < 1000; $i++) {
$sample_value = UriItem::generateSampleValue($definition->reveal());
$this->assertLessThanOrEqual($max_length, mb_strlen($sample_value['value']));
$this->assertStringNotContainsString(' ', $sample_value['value']);
}
}
/**
* Data provider for maximum-lengths.
*
* @return array
* Test cases.
*/
public function providerMaxLength(): array {
return [
'32' => [32],
'255' => [255],
'500' => [500],
'15' => [15],
'64' => [64],
];
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment