Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add methods to normalize tag attributes in Html helper #19100

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions framework/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ Yii Framework 2 Change Log
- Bug #18993: Load defaults by `attributes()` in `yii\db\ActiveRecord::loadDefaultValues()` (WinterSilence)
- Bug #19021: Fix return type in PhpDoc `yii\db\Migration` functions `up()`, `down()`, `safeUp()` and `safeDown()` (WinterSilence, rhertogh)
- Bug #19030: Add DI container usage to `yii\base\Widget::end()` (papppeter)
- Enh #19100: Add methods `isCustomTagAttribute`, `normalizeTagAttributes` and `mergeTagAttributes` in `yii\helper\BaseHtml` (WinterSilence)
- Bug #19031: Fix displaying console help for parameters with declared types (WinterSilence)
- Bug #19096: Fix `Request::getIsConsoleRequest()` may return erroneously when testing a Web application in Codeception (WinterSilence)
- Enh #13105: Add yiiActiveForm `validate_only` property for skipping form auto-submission (ptolomaues)
Expand Down
94 changes: 94 additions & 0 deletions framework/helpers/BaseHtml.php
Original file line number Diff line number Diff line change
Expand Up @@ -2242,6 +2242,100 @@
return $result;
}

/**
* Checks if is custom attribute of tag by prefix [[$dataAttributes]].
*
* @param string $name the attribute name
* @param string|null $prefix the attribute prefix returns by reference
* @return bool
* @since 2.0.44
*/
public static function isCustomTagAttribute($name, &$prefix = null)

Check warning on line 2253 in framework/helpers/BaseHtml.php

View check run for this annotation

Codecov / codecov/patch

framework/helpers/BaseHtml.php#L2253

Added line #L2253 was not covered by tests
{
foreach (static::$dataAttributes as $prefix) {
if (StringHelper::startsWith($name, $prefix . '-')) {
return true;

Check warning on line 2257 in framework/helpers/BaseHtml.php

View check run for this annotation

Codecov / codecov/patch

framework/helpers/BaseHtml.php#L2255-L2257

Added lines #L2255 - L2257 were not covered by tests
}
}
$prefix = null;

Check warning on line 2260 in framework/helpers/BaseHtml.php

View check run for this annotation

Codecov / codecov/patch

framework/helpers/BaseHtml.php#L2260

Added line #L2260 was not covered by tests

return false;

Check warning on line 2262 in framework/helpers/BaseHtml.php

View check run for this annotation

Codecov / codecov/patch

framework/helpers/BaseHtml.php#L2262

Added line #L2262 was not covered by tests
}

/**
* Normalizes an array of tag attributes.
*
* ```php
* $attributes = Html::normalizeAttributes(['data-id' => 1, 'style' => 'height: 1px', 'class' => 'foo bar']);
* // $attributes: ['data' => ['id' => 1], 'style' => ['height' => '1px'], 'class' => ['foo', 'bar']]
* ```
*
* @param array $attributes the attributes to normalize
* @return array
* @since 2.0.44
*/
public static function normalizeTagAttributes(array $attributes)

Check warning on line 2277 in framework/helpers/BaseHtml.php

View check run for this annotation

Codecov / codecov/patch

framework/helpers/BaseHtml.php#L2277

Added line #L2277 was not covered by tests
{
$result = [];

Check warning on line 2279 in framework/helpers/BaseHtml.php

View check run for this annotation

Codecov / codecov/patch

framework/helpers/BaseHtml.php#L2279

Added line #L2279 was not covered by tests

if (isset($attributes['style'])) {
if (is_string($attributes['style'])) {
$attributes['style'] = static::cssStyleToArray($attributes['style']);

Check warning on line 2283 in framework/helpers/BaseHtml.php

View check run for this annotation

Codecov / codecov/patch

framework/helpers/BaseHtml.php#L2281-L2283

Added lines #L2281 - L2283 were not covered by tests
}
$result['style'] = $attributes['style'];
unset($attributes['style']);

Check warning on line 2286 in framework/helpers/BaseHtml.php

View check run for this annotation

Codecov / codecov/patch

framework/helpers/BaseHtml.php#L2285-L2286

Added lines #L2285 - L2286 were not covered by tests
}
if (isset($attributes['class'])) {
if (is_string($attributes['class'])) {
$attributes['class'] = explode(' ', $attributes['class']);

Check warning on line 2290 in framework/helpers/BaseHtml.php

View check run for this annotation

Codecov / codecov/patch

framework/helpers/BaseHtml.php#L2288-L2290

Added lines #L2288 - L2290 were not covered by tests
}
$result['class'] = array_unique($attributes['class']);
unset($attributes['class']);

Check warning on line 2293 in framework/helpers/BaseHtml.php

View check run for this annotation

Codecov / codecov/patch

framework/helpers/BaseHtml.php#L2292-L2293

Added lines #L2292 - L2293 were not covered by tests
}
foreach ($attributes as $name => $value) {
if (is_array($value) && in_array($name, static::$dataAttributes, true)) {
$result[$name] = isset($result[$name]) ? array_merge($result[$name], $value) : $value;
} elseif (static::isCustomTagAttribute($name, $prefix)) {
if (!isset($result[$prefix])) {
$result[$prefix] = [];

Check warning on line 2300 in framework/helpers/BaseHtml.php

View check run for this annotation

Codecov / codecov/patch

framework/helpers/BaseHtml.php#L2295-L2300

Added lines #L2295 - L2300 were not covered by tests
}
list(, $name) = explode($prefix . '-', $name, 2);
$result[$prefix][$name] = $value;

Check warning on line 2303 in framework/helpers/BaseHtml.php

View check run for this annotation

Codecov / codecov/patch

framework/helpers/BaseHtml.php#L2302-L2303

Added lines #L2302 - L2303 were not covered by tests
} else {
$result[$name] = $value;

Check warning on line 2305 in framework/helpers/BaseHtml.php

View check run for this annotation

Codecov / codecov/patch

framework/helpers/BaseHtml.php#L2305

Added line #L2305 was not covered by tests
}
}

return $result;

Check warning on line 2309 in framework/helpers/BaseHtml.php

View check run for this annotation

Codecov / codecov/patch

framework/helpers/BaseHtml.php#L2309

Added line #L2309 was not covered by tests
}

/**
* Normalizes and merges two arrays of tag attributes.
*
* @param array $attributes The input attributes
* @param array $attributes2 The attributes to merge
* @return array
* @since 2.0.44
*/
public static function mergeTagAttributes(array $attributes, array $attributes2)

Check warning on line 2320 in framework/helpers/BaseHtml.php

View check run for this annotation

Codecov / codecov/patch

framework/helpers/BaseHtml.php#L2320

Added line #L2320 was not covered by tests
{
$attributes = static::normalizeTagAttributes($attributes);
$attributes2 = static::normalizeTagAttributes($attributes2);
if (isset($attributes2['class'])) {
static::addCssClass($attributes, $attributes2['class']);
unset($attributes2['class']);

Check warning on line 2326 in framework/helpers/BaseHtml.php

View check run for this annotation

Codecov / codecov/patch

framework/helpers/BaseHtml.php#L2322-L2326

Added lines #L2322 - L2326 were not covered by tests
}
foreach ($attributes2 as $key => $value) {
if (in_array($key, static::$dataAttributes, true) || $key === 'style') {
$attributes[$key] = isset($attributes[$key]) ? array_merge($attributes[$key], $value) : $value;

Check warning on line 2330 in framework/helpers/BaseHtml.php

View check run for this annotation

Codecov / codecov/patch

framework/helpers/BaseHtml.php#L2328-L2330

Added lines #L2328 - L2330 were not covered by tests
} else {
$attributes[$key] = $value;

Check warning on line 2332 in framework/helpers/BaseHtml.php

View check run for this annotation

Codecov / codecov/patch

framework/helpers/BaseHtml.php#L2332

Added line #L2332 was not covered by tests
}
}

return $attributes;

Check warning on line 2336 in framework/helpers/BaseHtml.php

View check run for this annotation

Codecov / codecov/patch

framework/helpers/BaseHtml.php#L2336

Added line #L2336 was not covered by tests
}

/**
* Returns the real attribute name from the given attribute expression.
*
Expand Down
120 changes: 120 additions & 0 deletions tests/framework/helpers/HtmlTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1411,6 +1411,126 @@ public function testRemoveCssStyle()
$this->assertEquals('width: 100px;', $options['style']);
}

/**
* @return array[]
*/
public function dataProviderIsCustomTagAttribute()
{
return [
'valid "aria" prefix' => ['aria-expanded', true],
'valid "data" prefix' => ['data-bs-target', true],
'valid "data-ng" prefix' => ['data-ng-id', true],
'underscore suffix' => ['data-foo_bar', true],
'name in camelCase' => ['dataId', false],
'only "aria" prefix' => ['aria', false],
'invalid "js" prefix' => ['js-action', false],
];
}

/**
* @param string $name the attribute name
* @param bool $expected the expected result
* @dataProvider dataProviderIsCustomTagAttribute
*/
public function testIsCustomTagAttribute($name, $expected)
{
$this->assertEquals($expected, Html::isCustomTagAttribute($name));
}

/**
* @return array[]
*/
public function dataProviderNormalizeTagAttributes()
{
return [
'mixed "data"' => [
[
'data-id' => 2,
'data' => ['foo-bar' => 'baz'],
],
[
'data' => ['id' => 2, 'foo-bar' => 'baz'],
],
],
'inline "style"' => [
[
'style' => 'height:1px;width:100px',
],
[
'style' => ['height' => '1px', 'width' => '100px'],
],
],
'inline "class"' => [
[
'class' => 'form-control form-control-sm',
],
[
'class' => ['form-control', 'form-control-sm'],
],
],
'duplicate "class"' => [
[
'class' => 'form-control form-control',
],
[
'class' => ['form-control'],
],
],
'all cases' => [
[
'data-id' => 123,
'data-bs-target' => '#input-name',
'aria-hidden' => 'true',
'style' => 'display: none;',
'class' => 'navbar navbar-dark',
'method' => 'POST',
'id' => 'main-form',
],
[
'data' => ['id' => 123, 'bs-target' => '#input-name'],
'aria' => ['hidden' => 'true'],
'style' => ['display' => 'none'],
'class' => ['navbar', 'navbar-dark'],
'method' => 'POST',
'id' => 'main-form'
],
],
];
}

/**
* @param array $attributes the attributes to normalize
* @param array $expected the normalized attributes
* @dataProvider dataProviderNormalizeTagAttributes
*/
public function testNormalizeTagAttributes(array $attributes, array $expected)
{
$this->assertEquals($expected, Html::normalizeTagAttributes($attributes));
}

public function testMergeTagAttributes()
{
$attributes = [
'data-id' => 1,
'data-bs-target' => '#input-name',
'style' => 'display: none;',
'class' => ['widget' => 'navbar navbar-dark'],
'id' => 'form-1',
];
$attributes2 = [
'data' => ['id' => 2, 'foo' => 'bar'],
'class' => 'bg-primary',
'id' => 'form-2',
];
$expected = [
'data' => ['id' => 2, 'bs-target' => '#input-name', 'foo' => 'bar'],
'style' => ['display' => 'none'],
'class' => ['widget' => 'navbar navbar-dark', 'bg-primary'],
'id' => 'form-2',
];
$this->assertEquals($expected, Html::mergeTagAttributes($attributes, $attributes2));
}

public function testBooleanAttributes()
{
$this->assertEquals('<input type="email" name="mail">', Html::input('email', 'mail', null, ['required' => false]));
Expand Down
Loading