Skip to content
Merged
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
16 changes: 13 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@
- Added the “Inactive” user status, which can be used by users which can’t be signed into. ([#8963](https://github.com/craftcms/cms/discussions/8963))
- Added “Credentialed” and “Inactive” user sources.
- Added the “Deactivate…” user action for pending and active users.
- Craft now provides a native “Alternative Text” (`alt`) field for assets. ([#10302](https://github.com/craftcms/cms/discussions/10302))
- Asset thumbnails in the control panel now have `alt` attributes, for assets with a filled-in Alternative Text value.
- Added the `index-assets/cleanup` command.
- Added the “Deactivate users by default” user registration setting, which replaces “Suspend users by default”. ([#5830](https://github.com/craftcms/cms/issues/5830))
- Element source settings are now stored in the project config. ([#8616](https://github.com/craftcms/cms/discussions/8616))
- Added support for `JSON` columns. ([#9089](https://github.com/craftcms/cms/pull/9089))
- It’s now possible to edit images’ focal points from their preview modals. ([#8489](https://github.com/craftcms/cms/discussions/8489))
- Added the `assetUploaders` user query param.
- Added the `authors` user query param.
- Added the `hasAlt` asset query param.
- Added support for setting custom config settings from `config/custom.php`, which are accessible via `Craft::$app->config->custom`. ([#10012](https://github.com/craftcms/cms/issues/10012))
- Added `craft\base\ApplicationTrait::getConditions()`.
- Added `craft\base\ApplicationTrait::getElementSources()`, which replaces `getElementIndexes()`.
Expand All @@ -31,6 +34,7 @@
- Added `craft\base\conditions\ConditionInterface`.
- Added `craft\base\conditions\ConditionRuleInterface`.
- Added `craft\base\ElementInterface::createCondition()`.
- Added `craft\base\ElementInterface::getThumbAlt()`.
- Added `craft\base\FieldInterface::getElementConditionRuleType()`.
- Added `craft\base\FieldLayoutComponent`.
- Added `craft\base\Volume::CONFIG_MIMETYPE`.
Expand All @@ -48,12 +52,14 @@
- Added `craft\db\Migration::renameTable()`.
- Added `craft\db\Query::collect()`, which returns the query results as an `Illuminate\Support\Collection` object rather than an array. ([#8513](https://github.com/craftcms/cms/discussions/8513))
- Added `craft\db\Table::ASSETINDEXINGSESSIONS`.
- Added `craft\elements\Asset::$alt`.
- Added `craft\elements\Asset::setFilename()`.
- Added `craft\elements\conditions\assets\AssetCondition`.
- Added `craft\elements\conditions\assets\DateModifiedConditionRule`.
- Added `craft\elements\conditions\assets\FilenameConditionRule`.
- Added `craft\elements\conditions\assets\FileSizeConditionRule`.
- Added `craft\elements\conditions\assets\FileTypeConditionRule`.
- Added `craft\elements\conditions\assets\HasAltConditionRule`.
- Added `craft\elements\conditions\assets\HeightConditionRule`.
- Added `craft\elements\conditions\assets\UploaderConditionRule`.
- Added `craft\elements\conditions\assets\VolumeConditionRule`.
Expand Down Expand Up @@ -93,7 +99,9 @@
- Added `craft\elements\User::STATUS_INACTIVE`.
- Added `craft\errors\MissingVolumeFolderException`.
- Added `craft\events\RegisterConditionRuleTypesEvent`.
- Added `craft\fieldlayoutelements\AssetAltField`.
- Added `craft\fieldlayoutelements\BaseNativeField`, which replaces `craft\fieldlayoutelements\StandardField`.
- Added `craft\fieldlayoutelements\TextareaField`.
- Added `craft\fieldlayoutelements\TextField`, which replaces `craft\fieldlayoutelements\StandardTextField`.
- Added `craft\fields\Assets::$allowSubfolders`.
- Added `craft\fields\Assets::$restrictedDefaulUploadSubpath`.
Expand Down Expand Up @@ -221,6 +229,7 @@
- Filtering users by `active`, `pending`, and `locked` statuses no longer excludes suspended users.
- Assets fields that are restricted to a single location can now be configured to allow selection within subfolders of that location. ([#9070](https://github.com/craftcms/cms/discussions/9070))
- When an image is saved as a new asset from the Image Editor via an Assets field, the Assets field will now automatically replace the selected asset with the new one. ([#8974](https://github.com/craftcms/cms/discussions/8974))
- `alt` is now a reserved field handle for volume field layouts.
- Entry post dates are no longer set automatically until the entry is validated with the `live` scenario. ([#10093](https://github.com/craftcms/cms/pull/10093))
- Entry queries’ `authorGroup()` param method now accepts an array of `craft\models\UserGroup` objects.
- Relational fields now load elements in the current site rather than the primary site, if the source element isn’t localizable. ([#7048](https://github.com/craftcms/cms/issues/7048))
Expand Down Expand Up @@ -318,15 +327,16 @@
- Widgets’ `icon()` methods must now have a `?string` return type declaration.
- Widgets’ `maxColspan()` methods must now have an `?int` return type declaration.
- `craft\base\AssetPreviewHandlerInterface::getPreviewHtml()` now accepts an optional array of variable to pass on to the template.
- `craft\base\ElementInterface::getEagerLoadedElements()` now returns an `Illuminate\Support\Collection` object instead of an array. ([#8513](https://github.com/craftcms/cms/discussions/8513))
- `craft\base\Element::__get()` now clones custom field values before returning them. ([#8781](https://github.com/craftcms/cms/discussions/8781))
- `craft\base\Element::fieldLayoutFields()` now has a `visibleOnly` argument.
- `craft\base\Element::getFieldValue()` now returns eager-loaded element values for the field, when they exist. ([#10047](https://github.com/craftcms/cms/issues/10047))
- `craft\base\ElementInterface::getEagerLoadedElements()` now returns an `Illuminate\Support\Collection` object instead of an array. ([#8513](https://github.com/craftcms/cms/discussions/8513))
- `craft\base\MemoizableArray` no longer extends `ArrayObject`, and now implements `IteratorAggregate` and `Countable` directly.
- `craft\base\Model::datetimeAttributes()` is now called from the constructor, instead of the `init()` method.
- `craft\base\Model::setAttributes()` now normalizes date attributes into `DateTime` objects.
- `craft\db\Command` methods with `$includeAuditColumns` arguments now ensure the table actually has audit columns before modifying the query.
- `craft\db\Command::upsert()` no longer merges the `$updateColumns` array into `$insertColumns`. The full array of `INSERT` column values should be passed to `$insertColumns` now.
- `craft\db\Command` methods with `$includeAuditColumns` arguments now ensure the table actually has audit columns before modifying the query.
- `craft\elements\Asset::getImg()` now sets the `alt` attribute to the native Alternative Text field value, if set.
- `craft\elements\db\ElementQuery::ids()` no longer accepts an array of criteria params.
- `craft\events\DraftEvent::$source` has been renamed to `$canonical`.
- `craft\events\GetAssetThumbUrlEvent` has been renamed to `DefineAssetThumbUrlEvent`.
Expand All @@ -344,10 +354,10 @@
- `craft\services\Announcements::push()` no longer accepts callables to be passed to the `$heading` and `$body` arguments. `craft\i18n\Translation::prep()` should be used to prepare the messages to be lazy-translated instead.
- `craft\services\AssetIndexer::storeIndexList()` now expects the first argument to be a generator that returns `craft\models\VolumeListing` objects.
- `craft\services\Assets::ensureFolderByFullPathAndVolume()` now returns a `craft\models\VolumeFolder` object rather than a folder ID.
- `craft\services\Assets::ensureTopFolder()` now returns a `craft\models\VolumeFolder` object rather than a folder ID.
- `craft\services\Assets::EVENT_GET_ASSET_THUMB_URL` has been renamed to `EVENT_DEFINE_THUMB_URL`.
- `craft\services\Assets::EVENT_GET_ASSET_URL` has been renamed to `EVENT_DEFINE_ASSET_URL`.
- `craft\services\Assets::EVENT_GET_THUMB_PATH` has been renamed to `EVENT_DEFINE_THUMB_PATH`.
- `craft\services\Assets::ensureTopFolder()` now returns a `craft\models\VolumeFolder` object rather than a folder ID.
- `craft\services\Plugins::doesPluginRequireDatabaseUpdate()` has been renamed to `isPluginUpdatePending()`.
- `craft\services\Updates::getIsCraftDbMigrationNeeded()` has been renamed to `getIsCraftUpdatePending()`.
- `craft\services\Updates::getIsPluginDbUpdateNeeded()` has been renamed to `getIsPluginUpdatePending()`.
Expand Down
2 changes: 2 additions & 0 deletions src/base/ApplicationTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
use craft\events\DeleteSiteEvent;
use craft\events\EditionChangeEvent;
use craft\events\FieldEvent;
use craft\fieldlayoutelements\AssetAltField;
use craft\fieldlayoutelements\AssetTitleField;
use craft\fieldlayoutelements\EntryTitleField;
use craft\fieldlayoutelements\TitleField;
Expand Down Expand Up @@ -1508,6 +1509,7 @@ private function _registerFieldLayoutListener(): void
break;
case Asset::class:
$event->fields[] = AssetTitleField::class;
$event->fields[] = AssetAltField::class;
break;
case Entry::class:
$event->fields[] = EntryTitleField::class;
Expand Down
8 changes: 8 additions & 0 deletions src/base/Element.php
Original file line number Diff line number Diff line change
Expand Up @@ -2713,6 +2713,14 @@ public function getThumbUrl(int $size): ?string
return null;
}

/**
* @inheritdoc
*/
public function getThumbAlt(): ?string
{
return null;
}

/**
* @inheritdoc
*/
Expand Down
8 changes: 8 additions & 0 deletions src/base/ElementInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,14 @@ public function getPreviewTargets(): array;
*/
public function getThumbUrl(int $size): ?string;

/**
* Returns alt text for the element’s thumbnail.
*
* @return string|null
* @since 4.0.0
*/
public function getThumbAlt(): ?string;

/**
* Returns whether the element’s thumbnail should have a checkered background.
*
Expand Down
1 change: 1 addition & 0 deletions src/base/Volume.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ public function validateFieldLayout(): void
{
$fieldLayout = $this->getFieldLayout();
$fieldLayout->reservedFieldHandles = [
'alt',
'folder',
'volume',
];
Expand Down
22 changes: 21 additions & 1 deletion src/elements/Asset.php
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,12 @@ private static function _assembleSourceInfoForFolder(VolumeFolder $folder, bool
*/
public ?string $kind = null;

/**
* @var string|null Alternative text
* @since 4.0.0
*/
public ?string $alt = null;

/**
* @var int|null Size
*/
Expand Down Expand Up @@ -780,6 +786,11 @@ public function __get($name)
public function init(): void
{
parent::init();

if ($this->alt === '') {
$this->alt = null;
}

$this->_oldVolumeId = $this->_volumeId;
}

Expand Down Expand Up @@ -967,7 +978,7 @@ public function getImg($transform = null, ?array $sizes = null): ?Markup
'width' => $this->getWidth(),
'height' => $this->getHeight(),
'srcset' => $sizes ? $this->getSrcset($sizes) : false,
'alt' => $this->title,
'alt' => $this->alt ?? $this->title,
]);

if (isset($oldTransform)) {
Expand Down Expand Up @@ -1305,6 +1316,14 @@ public function getThumbUrl(int $size): ?string
return Craft::$app->getAssets()->getThumbUrl($this, $width, $height, false);
}

/**
* @inheritdoc
*/
public function getThumbAlt(): ?string
{
return $this->alt;
}

/**
* @inheritdoc
*/
Expand Down Expand Up @@ -1995,6 +2014,7 @@ public function afterSave(bool $isNew): void
$record->folderId = (int)$this->folderId;
$record->uploaderId = (int)$this->uploaderId ?: null;
$record->kind = $this->kind;
$record->alt = $this->alt;
$record->size = (int)$this->size ?: null;
$record->width = (int)$this->_width ?: null;
$record->height = (int)$this->_height ?: null;
Expand Down
1 change: 1 addition & 0 deletions src/elements/conditions/assets/AssetCondition.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ protected function conditionRuleTypes(): array
FileSizeConditionRule::class,
FileTypeConditionRule::class,
FilenameConditionRule::class,
HasAltConditionRule::class,
HeightConditionRule::class,
UploaderConditionRule::class,
VolumeConditionRule::class,
Expand Down
54 changes: 54 additions & 0 deletions src/elements/conditions/assets/HasAltConditionRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace craft\elements\conditions\assets;

use Craft;
use craft\base\conditions\BaseLightswitchConditionRule;
use craft\base\ElementInterface;
use craft\elements\Asset;
use craft\elements\conditions\ElementConditionRuleInterface;
use craft\elements\db\AssetQuery;
use craft\elements\db\ElementQueryInterface;

/**
* “Has alternative text” condition rule for assets.
*
* @author Pixel & Tonic, Inc. <support@pixelandtonic.com>
* @since 4.0.0
*/
class HasAltConditionRule extends BaseLightswitchConditionRule implements ElementConditionRuleInterface
{
/**
* @inheritdoc
*/
public function getLabel(): string
{
return Craft::t('app', 'Has alternative text');
}

/**
* @inheritdoc
*/
public function getExclusiveQueryParams(): array
{
return ['hasAlt'];
}

/**
* @inheritdoc
*/
public function modifyQuery(ElementQueryInterface $query): void
{
/** @var AssetQuery $query */
$query->hasAlt($this->value);
}

/**
* @inheritdoc
*/
public function matchElement(ElementInterface $element): bool
{
/** @var Asset $element */
return $this->matchValue($element->alt !== null);
}
}
25 changes: 25 additions & 0 deletions src/elements/db/AssetQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,13 @@ class AssetQuery extends ElementQuery
*/
public $kind;

/**
* @var bool|null Whether the query should filter assets depending on whether they have alternative text.
* @used-by hasAlt()
* @since 4.0.0
*/
public ?bool $hasAlt = null;

/**
* @var mixed The width (in pixels) that the resulting assets must have.
* ---
Expand Down Expand Up @@ -495,6 +502,19 @@ public function kind($value): self
return $this;
}

/**
* Narrows the query results based on whether the assets have alternative text.
*
* @param bool|null $value The property value
* @return self self reference
* @uses $hasAlt
*/
public function hasAlt(?bool $value = true): self
{
$this->hasAlt = $value;
return $this;
}

/**
* Narrows the query results based on the assets’ image widths.
*
Expand Down Expand Up @@ -783,6 +803,7 @@ protected function beforePrepare(): bool
'assets.uploaderId',
'assets.filename',
'assets.kind',
'assets.alt',
'assets.width',
'assets.height',
'assets.size',
Expand Down Expand Up @@ -831,6 +852,10 @@ protected function beforePrepare(): bool
$this->subQuery->andWhere($kindCondition);
}

if ($this->hasAlt !== null) {
$this->subQuery->andWhere($this->hasAlt ? ['not', ['assets.alt' => null]] : ['assets.alt' => null]);
}

if ($this->width) {
$this->subQuery->andWhere(Db::parseNumericParam('assets.width', $this->width));
}
Expand Down
83 changes: 83 additions & 0 deletions src/fieldlayoutelements/AssetAltField.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?php
/**
* @link https://craftcms.com/
* @copyright Copyright (c) Pixel & Tonic, Inc.
* @license https://craftcms.github.io/license/
*/

namespace craft\fieldlayoutelements;

use Craft;
use craft\base\ElementInterface;
use craft\base\Field;
use craft\elements\Asset;
use craft\helpers\ElementHelper;
use yii\base\InvalidArgumentException;

/**
* AssetAltField represents an Alternative Text field that can be included within a volume’s field layout designer.
*
* @author Pixel & Tonic, Inc. <support@pixelandtonic.com>
* @since 4.0.0
*/
class AssetAltField extends TextareaField
{
/**
* @inheritdoc
*/
public string $attribute = 'alt';

/**
* @inheritdoc
*/
public bool $translatable = true;

/**
* @inheritdoc
*/
public function __construct($config = [])
{
// We didn't start removing autofocus from fields() until 3.5.6
unset(
$config['mandatory'],
$config['attribute'],
$config['translatable'],
$config['maxlength'],
$config['autofocus']
);

parent::__construct($config);
}

/**
* @inheritdoc
*/
public function fields(): array
{
$fields = parent::fields();
unset(
$fields['mandatory'],
$fields['attribute'],
$fields['translatable'],
$fields['maxlength'],
$fields['autofocus']
);
return $fields;
}

/**
* @inheritdoc
*/
public function defaultLabel(?ElementInterface $element = null, bool $static = false): ?string
{
return Craft::t('app', 'Alternative Text');
}

/**
* @inheritdoc
*/
protected function translationDescription(?ElementInterface $element = null, bool $static = false): ?string
{
return Field::TRANSLATION_METHOD_SITE;
}
}
Loading