Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
3220329
feat(activefield): `labelOptions` are ignored when using custom label…
terabytesoftw Sep 27, 2025
6daf260
refactor(activefield): simplify label handling for radio and checkbox…
terabytesoftw Sep 27, 2025
13dcd79
feat(activefield): Enhance label method to support custom tag options…
terabytesoftw Sep 28, 2025
98d82c7
Update `CHANGELOG.md`.
terabytesoftw Sep 28, 2025
821e0cf
Add notes for `UPGRADE.md`.
terabytesoftw Sep 28, 2025
3ee13ad
Merge branch '22.0' into feat-20537
terabytesoftw Sep 30, 2025
3c0e03a
Enhance documentation for label options in `ActiveField::class`.
terabytesoftw Sep 30, 2025
9c6c8be
Refactor label method to use `ArrayHelper` for improved readability.
terabytesoftw Sep 30, 2025
137d399
Update `label` handling in `ActiveField::class`.
terabytesoftw Sep 30, 2025
6d9f1c6
Fix issue phpcs.
terabytesoftw Sep 30, 2025
aab7fd6
Apply fixed review Copilot.
terabytesoftw Sep 30, 2025
5bdb9ee
Apply fixed review Copilot.
terabytesoftw Sep 30, 2025
6aeab15
Apply fixed review Copilot.
terabytesoftw Sep 30, 2025
a3cd943
Apply fixed more review Copilot.
terabytesoftw Sep 30, 2025
e538593
Merge branch '22.0' into feat-20537
terabytesoftw Oct 1, 2025
845442c
Apply fixed Copilot review.
terabytesoftw Oct 1, 2025
1a15721
Merge branch '22.0' into feat-20537
terabytesoftw Oct 1, 2025
3a593c6
Merge branch '22.0' into feat-20537
terabytesoftw Oct 1, 2025
ceb388c
fix issue phpcs.
terabytesoftw Oct 1, 2025
82158c0
Merge branch '22.0' into feat-20537
terabytesoftw Oct 2, 2025
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 @@ -26,6 +26,7 @@ Yii Framework 2 Change Log
- Chg #20464: Remove `PHP_VERSION_ID` checks from web `Response` class (terabytesoftw)
- Enh #20511: Allow `jQuery` usage in `View` class while maintaining backward compatibility (terabytesoftw)
- Bug #20518: `PHP` 8.4 fixes for implicit nullability deprecation (terabytesoftw)
- Enh #20537: Enhance `label()` method in `ActiveField` with `tag` option and fix `labelOptions` for `checkbox`/`radio` (terabytesoftw)
- Bug #20547: `PHP` 8.4 fixes for implicit nullability deprecation (terabytesoftw)

2.0.54 under development
Expand Down
18 changes: 18 additions & 0 deletions framework/UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,24 @@ if you want to upgrade from version A to version C and there is
version B between A and C, you need to follow the instructions
for both A and B.

Upgrade from Yii 2.0.x
-----------------------

* Raised minimum supported PHP version to `8.1`.
* All methods that were previously deprecated have been removed. If your application still uses any deprecated methods,
you must update your code before upgrading.
* Support for CUBRID database has been removed.
* `yii\widgets\ActiveField::label()` method now supports a `tag` option to control the wrapper element. This provides
flexibility for custom label rendering while maintaining full backward compatibility:
- `tag => 'label'` default generates standard `<label>` element with `for` attribute.
- `tag => false` renders label content without any wrapper tag.
- `tag => 'span'/'div'/etc` uses specified HTML element as wrapper.

Example usage:

```php
$field->label('My Label', ['tag' => 'span', 'class' => 'custom-label']);
```
Upgrade from Yii 2.0.53
-----------------------

Expand Down
168 changes: 120 additions & 48 deletions framework/widgets/ActiveField.php
Original file line number Diff line number Diff line change
Expand Up @@ -264,8 +264,18 @@ public function end()
* If `false`, the generated field will not contain the label part.
* Note that this will NOT be [[Html::encode()|encoded]].
* @param array|null $options the tag options in terms of name-value pairs. It will be merged with [[labelOptions]].
* The options will be rendered as the attributes of the resulting tag. The values will be HTML-encoded
* using [[Html::encode()]]. If a value is `null`, the corresponding attribute will not be rendered.
* The options will be rendered as the attributes of the resulting tag. The values will be HTML-encoded using
* [[Html::encode()]]. If a value is `null`, the corresponding attribute will not be rendered.
* The following special options are recognized:
* - `label`: string|false|null, if specified in $options (or previously set in [[labelOptions]]), this value will
* be used when the $label parameter is `null`. If set to `false`, the label will not be rendered (same behavior
* as passing $label = `false`). The $label parameter always takes precedence over this option.
* - `tag`: string|false, specifies the tag name for the label element.
* - If not specified, defaults to `'label'`.
* - If set to `false`, the label content will be rendered as raw HTML without any wrapper tag.
* - If set to `'label'`, uses [[Html::activeLabel()]] to generate a standard label element.
* - If set to another tag name (for example, `'span'`, `'div'`), uses [[Html::tag()]] to generate that element.
*
* @return $this the field object itself.
*/
public function label($label = null, $options = [])
Expand All @@ -276,15 +286,35 @@ public function label($label = null, $options = [])
}

$options = array_merge($this->labelOptions, $options);
if ($label !== null) {
$options['label'] = $label;
}
$tag = ArrayHelper::remove($options, 'tag', 'label');

if ($this->_skipLabelFor) {
$options['for'] = null;
if ($label === null) {
$label = $options['label'] ?? null;

if ($label === false) {
$this->parts['{label}'] = '';

return $this;
}
}

$this->parts['{label}'] = Html::activeLabel($this->model, $this->attribute, $options);
$label ??= $this->model->getAttributeLabel(Html::getAttributeName($this->attribute));

$options['label'] = $label;

if ($tag === false) {
$this->parts['{label}'] = $label;
} elseif ($tag === 'label') {
if ($this->_skipLabelFor) {
$options['for'] = null;
}

$this->parts['{label}'] = Html::activeLabel($this->model, $this->attribute, $options);
} else {
unset($options['label'], $options['for']);

$this->parts['{label}'] = Html::tag($tag, $label, $options);
}

return $this;
}
Expand Down Expand Up @@ -523,11 +553,21 @@ public function textarea($options = [])
* it will take the default value `0`. This method will render a hidden input so that if the radio button
* is not checked and is submitted, the value of this attribute will still be submitted to the server
* via the hidden input. If you do not want any hidden input, you should explicitly set this option as `null`.
* - `label`: string, a label displayed next to the radio button. It will NOT be HTML-encoded. Therefore you can pass
* in HTML code such as an image tag. If this is coming from end users, you should [[Html::encode()|encode]] it to prevent XSS attacks.
* When this option is specified, the radio button will be enclosed by a label tag. If you do not want any label, you should
* explicitly set this option as `null`.
* - `labelOptions`: array, the HTML attributes for the label tag. This is only used when the `label` option is specified.
* - `label`: string|false|null, a label displayed next to the radio.
* - If a string, it will NOT be HTML-encoded. Therefore you can pass in HTML code such as an image tag.
* - If this is coming from end users, you should [[Html::encode()|encode]] it to prevent XSS attacks.
* - If `false`, no label will be rendered.
* - If `null`, no label will be rendered (same as `false`).
* When this option is specified as a string and `enclosedByLabel` is `true`, the radio will be enclosed by a
* label tag.
* - `labelOptions`: array, the HTML attributes for the label tag. This is only used when the `label` option is
* specified and `enclosedByLabel` is `false`. The following special option is recognized:
* - `tag`: string|false, specifies the tag name for the label element.
* - If `labelOptions` is not provided, the label content will be rendered as raw HTML without any wrapper tag.
* - If `labelOptions` is provided but `tag` is not specified, defaults to `'label'`.
* - If set to `'label'`, uses [[Html::activeLabel()]] to generate a standard label element.
* - If set to another tag name (for example, `'span'`, `'div'`), uses [[Html::tag()]] to generate that element.
* - If set to `false`, the label content will be rendered as raw HTML without any wrapper tag.
*
* The rest of the options will be rendered as the attributes of the resulting tag. The values will
* be HTML-encoded using [[Html::encode()]]. If a value is `null`, the corresponding attribute will not be rendered.
Expand All @@ -548,20 +588,11 @@ public function radio($options = [], $enclosedByLabel = true)
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);

if ($enclosedByLabel) {
$this->parts['{input}'] = Html::activeRadio($this->model, $this->attribute, $options);
$this->parts['{label}'] = '';
} else {
if (isset($options['label']) && !isset($this->parts['{label}'])) {
$this->parts['{label}'] = $options['label'];
if (!empty($options['labelOptions'])) {
$this->labelOptions = $options['labelOptions'];
}
}
unset($options['labelOptions']);
$options['label'] = null;
$this->parts['{input}'] = Html::activeRadio($this->model, $this->attribute, $options);
}
$this->parts['{label}'] = '';

$options = $enclosedByLabel ? $options : $this->generateLabel($options);

$this->parts['{input}'] = Html::activeRadio($this->model, $this->attribute, $options);

return $this;
}
Expand All @@ -571,15 +602,25 @@ public function radio($options = [], $enclosedByLabel = true)
* This method will generate the `checked` tag attribute according to the model attribute value.
* @param array $options the tag options in terms of name-value pairs. The following options are specially handled:
*
* - `uncheck`: string, the value associated with the uncheck state of the radio button. If not set,
* it will take the default value `0`. This method will render a hidden input so that if the radio button
* - `uncheck`: string, the value associated with the uncheck state of the checkbox. If not set,
* it will take the default value `0`. This method will render a hidden input so that if the checkbox
* is not checked and is submitted, the value of this attribute will still be submitted to the server
* via the hidden input. If you do not want any hidden input, you should explicitly set this option as `null`.
* - `label`: string, a label displayed next to the checkbox. It will NOT be HTML-encoded. Therefore you can pass
* in HTML code such as an image tag. If this is coming from end users, you should [[Html::encode()|encode]] it to prevent XSS attacks.
* When this option is specified, the checkbox will be enclosed by a label tag. If you do not want any label, you should
* explicitly set this option as `null`.
* - `labelOptions`: array, the HTML attributes for the label tag. This is only used when the `label` option is specified.
* - `label`: string|false|null, a label displayed next to the checkbox.
* - If a string, it will NOT be HTML-encoded. Therefore you can pass in HTML code such as an image tag.
* - If this is coming from end users, you should [[Html::encode()|encode]] it to prevent XSS attacks.
* - If `false`, no label will be rendered.
* - If `null`, no label will be rendered (same as `false`).
* When this option is specified as a string and `enclosedByLabel` is `true`, the checkbox will be enclosed by a
* label tag.
* - `labelOptions`: array, the HTML attributes for the label tag. This is only used when the `label` option is
* specified and `enclosedByLabel` is `false`. The following special option is recognized:
* - `tag`: string|false, specifies the tag name for the label element.
* - If `labelOptions` is not provided, the label content will be rendered as raw HTML without any wrapper tag.
* - If `labelOptions` is provided but `tag` is not specified, defaults to `'label'`.
* - If set to `'label'`, uses [[Html::activeLabel()]] to generate a standard label element.
* - If set to another tag name (for example, `'span'`, `'div'`), uses [[Html::tag()]] to generate that element.
* - If set to `false`, the label content will be rendered as raw HTML without any wrapper tag.
*
* The rest of the options will be rendered as the attributes of the resulting tag. The values will
* be HTML-encoded using [[Html::encode()]]. If a value is `null`, the corresponding attribute will not be rendered.
Expand All @@ -600,20 +641,11 @@ public function checkbox($options = [], $enclosedByLabel = true)
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);

if ($enclosedByLabel) {
$this->parts['{input}'] = Html::activeCheckbox($this->model, $this->attribute, $options);
$this->parts['{label}'] = '';
} else {
if (isset($options['label']) && !isset($this->parts['{label}'])) {
$this->parts['{label}'] = $options['label'];
if (!empty($options['labelOptions'])) {
$this->labelOptions = $options['labelOptions'];
}
}
unset($options['labelOptions']);
$options['label'] = null;
$this->parts['{input}'] = Html::activeCheckbox($this->model, $this->attribute, $options);
}
$this->parts['{label}'] = '';

$options = $enclosedByLabel ? $options : $this->generateLabel($options);

$this->parts['{input}'] = Html::activeCheckbox($this->model, $this->attribute, $options);

return $this;
}
Expand Down Expand Up @@ -961,4 +993,44 @@ protected function addErrorClassIfNeeded(&$options)
Html::addCssClass($options, $this->form->errorCssClass);
}
}

/**
* Generates the label element based on `labelOptions` configuration when `enclosedByLabel` is `false`.
*
* @param array $options the input options array
*
* @return array the cleaned options array with `labelOptions` removed
*/
protected function generateLabel(array $options): array
{
if (isset($options['label']) && $options['label'] !== false && $this->parts['{label}'] === '') {
$tag = false;

if (!empty($options['labelOptions'])) {
$tag = $options['labelOptions']['tag'] ?? 'label';

unset($options['labelOptions']['tag']);

$this->labelOptions = array_merge($this->labelOptions, $options['labelOptions']);
}

if ($tag === false) {
$this->parts['{label}'] = $options['label'];
} elseif ($tag === 'label') {
$this->parts['{label}'] = Html::activeLabel(
$this->model,
$this->attribute,
array_merge(['label' => $options['label']], $this->labelOptions),
);
} else {
$this->parts['{label}'] = Html::tag($tag, $options['label'], $this->labelOptions);
}
}

unset($options['labelOptions']);

$options['label'] = null;

return $options;
}
}
Loading
Loading