Skip to content

Commit

Permalink
Merge branch 'release/4.2.2' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
brandonkelly committed Aug 23, 2022
2 parents 520908d + 5fec45b commit 42335c5
Show file tree
Hide file tree
Showing 73 changed files with 475 additions and 192 deletions.
30 changes: 29 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,33 @@
# Release Notes for Craft CMS 4

## 4.2.2 - 2022-08-23

### Added
- Added the `utils/fix-field-layout-uids` command. ([#11746](https://github.com/craftcms/cms/issues/11746))

### Changed
- Improved the styling of Categories fields.
- The first field group is now automatically selected by default when creating a new custom field.
- Improved console output for the `gc` command.
- The `gc` command now runs garbage collection for data caches.
- Exception JSON responses now include `name` and `code` keys. ([#11799](https://github.com/craftcms/cms/discussions/11799))
- `elements/*` actions no longer include custom field values in the failure response data, improving performance. ([#11807](https://github.com/craftcms/cms/discussions/11807))

### Fixed
- Fixed a bug where keyboard focus wasn’t being maintained when changing the element type within a “Related To” condition rule.
- Fixed a bug where keyboard focus wasn’t being maintained when changing the country within an address’s “Administrative Area” condition rule.
- Fixed a bug where Date fields’ Timezone menus could be clipped. ([#11780](https://github.com/craftcms/cms/pull/11780))
- Fixed an error that could occur when saving an unpublished draft, if any custom validation errors were added to it after its draft status had been removed. ([#11407](https://github.com/craftcms/cms/issues/11407))
- Fixed a bug where custom validation errors would be shown twice for unpublished drafts, if they were added to it after its draft status had been removed. ([#11407](https://github.com/craftcms/cms/issues/11407))
- Fixed PHP warnings that would occur when passing `0` into `craft\helpers\DateTimeHelper::humanDuration()`. ([#11787](https://github.com/craftcms/cms/issues/11787))
- Fixed a bug where selected assets weren’t getting automatically replaced when an image was edited and “Save as a new asset” was chosen. ([#11805](https://github.com/craftcms/cms/issues/11805))
- Fixed a JavaScript error that occurred when editing a user via a slideout, if the user had any addresses. ([#11810](https://github.com/craftcms/cms/issues/11810))
- Fixed a beg where some invalid slideout submissions weren’t being handled properly. ([#11812](https://github.com/craftcms/cms/issues/11812))
- Fixed a bug where `craft\helpers\DateTimeHelper::toDateInterval()` was returning negative interval durations when integers were passed in. ([#11814](https://github.com/craftcms/cms/pull/11814))
- Fixed a bug where `iframeResizer.contentWindow.js` was getting loaded for all preview requests, not just Live Preview, and even when `useIframeResizer` was disabled. ([#11778](https://github.com/craftcms/cms/issues/11778))
- Fixed a bug where deleted relations and Matrix blocks could persist if the edit form was submitted before they had been fully animated away. ([#11789](https://github.com/craftcms/cms/issues/11789))
- Fixed a PHP error that could occur if `craft\services\Assets::getUserTemporaryUploadFolder()` was called when there was no logged-in user account. ([#11751](https://github.com/craftcms/cms/issues/11751))

## 4.2.1.1 - 2022-08-10

### Fixed
Expand Down Expand Up @@ -38,7 +66,6 @@
- Fixed a bug where it wasn’t possible to preview or edit image assets if their filesystem and transform filesystem didn’t have public URLs. ([#11686](https://github.com/craftcms/cms/issues/11686), [#11687](https://github.com/craftcms/cms/issues/11687))
- Fixed a bug where not all project config changes would be applied if a site was deleted. ([#9567](https://github.com/craftcms/cms/issues/9567))
- Fixed a bug where `$` characters in database connection passwords weren’t being escaped property when backing up/restoring the database. ([#11750](https://github.com/craftcms/cms/issues/11750))
- Fixed a PHP error that could occur if `craft\services\Assets::getUserTemporaryUploadFolder()` was called when there was no logged-in user account. ([#11751](https://github.com/craftcms/cms/issues/11751))

### Security
- Fixed XSS vulnerabilities.
Expand Down Expand Up @@ -1282,6 +1309,7 @@
- Removed `craft\models\EntryDraft`.
- Removed `craft\models\EntryVersion`.
- Removed `craft\models\FieldLayout::setFields()`.
- Removed `craft\models\FieldLayoutTab::getFields()`.
- Removed `craft\models\Site::$originalBaseUrl`.
- Removed `craft\models\Site::$originalName`.
- Removed `craft\models\Site::overrideBaseUrl()`.
Expand Down
2 changes: 1 addition & 1 deletion src/base/Element.php
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ abstract class Element extends Component implements ElementInterface
* );
* ```
*
* @see canDelete()
* @see canDeleteForSite()
* @since 4.0.0
*/
public const EVENT_AUTHORIZE_DELETE_FOR_SITE = 'authorizeDeleteForSite';
Expand Down
2 changes: 1 addition & 1 deletion src/config/app.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
return [
'id' => 'CraftCMS',
'name' => 'Craft CMS',
'version' => '4.2.1.1',
'version' => '4.2.2',
'schemaVersion' => '4.0.0.9',
'minVersionRequired' => '3.7.11',
'basePath' => dirname(__DIR__), // Defines the @app alias
Expand Down
4 changes: 2 additions & 2 deletions src/console/controllers/GcController.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ public function actionRun(): int
$gc = Craft::$app->getGc();
$deleteAllTrashed = $gc->deleteAllTrashed;
$gc->deleteAllTrashed = $this->deleteAllTrashed || ($this->interactive && $this->confirm('Delete all trashed items?'));
$this->stdout('Running garbage collection ... ');
$this->stdout("Running garbage collection ...\n");
$gc->run(true);
$this->stdout('done' . PHP_EOL, Console::FG_GREEN);
$this->stdout('Finished running garbage collection.' . PHP_EOL, Console::FG_GREEN);
$gc->deleteAllTrashed = $deleteAllTrashed;
return ExitCode::OK;
}
Expand Down
101 changes: 101 additions & 0 deletions src/console/controllers/utils/FixFieldLayoutUidsController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php
/**
* @link https://craftcms.com/
* @copyright Copyright (c) Pixel & Tonic, Inc.
* @license https://craftcms.github.io/license/
*/

namespace craft\console\controllers\utils;

use Craft;
use craft\console\Controller;
use craft\helpers\Console;
use craft\helpers\StringHelper;
use yii\console\ExitCode;

/**
* Fixes any duplicate UUIDs found within field layout components in the project config.
*
* @author Pixel & Tonic, Inc. <[email protected]>
* @since 4.2.3
*/
class FixFieldLayoutUidsController extends Controller
{
/**
* Fixes any duplicate UUIDs found within field layout components in the project config.
*
* @return int
*/
public function actionIndex(): int
{
$this->stdout("Looking for duplicate UUIDs ...\n");
$count = 0;
$this->_fixUids(Craft::$app->getProjectConfig()->get(), $count);

if ($count) {
$summary = sprintf('Fixed %s duplicate %s.', $count, $count === 1 ? 'UUID' : 'UUIDs');
} else {
$summary = 'No duplicate UUIDs were found.';
}

$this->stdout('Done. ', Console::FG_GREEN);
$this->stdout("$summary\n");


return ExitCode::OK;
}

private function _fixUids(array $config, int &$count, string $path = '', array &$uids = []): void
{
if (isset($config['fieldLayouts']) && is_array($config['fieldLayouts'])) {
$modified = false;

foreach ($config['fieldLayouts'] as $fieldLayoutUid => &$fieldLayoutConfig) {
if (isset($fieldLayoutConfig['tabs']) && is_array($fieldLayoutConfig['tabs'])) {
foreach ($fieldLayoutConfig['tabs'] as $tabIndex => &$tabConfig) {
$tabPath = ($path ? "$path." : '') . "fieldLayouts.$fieldLayoutUid.tabs.$tabIndex";
$this->_checkUid($tabConfig, $count, $uids, $modified, $tabPath);

if (isset($tabConfig['elements']) && is_array($tabConfig['elements'])) {
foreach ($tabConfig['elements'] as $elementIndex => &$elementConfig) {
$elementPath = "$tabPath.elements.$elementIndex";
$this->_checkUid($elementConfig, $count, $uids, $modified, $elementPath);
}
}
}
}
}

if ($modified) {
Craft::$app->getProjectConfig()->set($path, $config);
}

return;
}

foreach ($config as $key => $value) {
if (is_array($value)) {
$this->_fixUids($value, $count, ($path ? "$path." : '') . $key, $uids);
}
}
}

private function _checkUid(array &$config, int &$count, array &$uids, bool &$modified, string $path): void
{
if (isset($config['uid'])) {
if (isset($uids[$config['uid']])) {
$config['uid'] = StringHelper::UUID();
$count++;
$modified = true;

$this->stdout(' > Duplicate found at ');
$this->stdout($path, Console::FG_CYAN);
$this->stdout(".\n Changing to ");
$this->stdout($config['uid'], Console::FG_CYAN);
$this->stdout(".\n");
} else {
$uids[$config['uid']] = true;
}
}
}
}
9 changes: 7 additions & 2 deletions src/controllers/ElementsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -1735,7 +1735,12 @@ private function _asSuccess(string $message, ElementInterface $element, array $d

private function _asFailure(ElementInterface $element, string $message): ?Response
{
/** @var Element $element */
return $this->asModelFailure($element, $message, 'element');
$data = [
'modelName' => 'element',
'element' => $element->toArray($element->attributes()),
'errors' => $element->getErrors(),
];

return $this->asFailure($message, $data, ['element' => $element]);
}
}
5 changes: 3 additions & 2 deletions src/controllers/FieldsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -198,17 +198,18 @@ public function actionEditField(?int $fieldId = null, ?FieldInterface $field = n

if ($groupId) {
$fieldGroup = $fieldsService->getGroupById($groupId);

if ($fieldGroup === null) {
throw new NotFoundHttpException('Field group not found');
}
} elseif (!$field->id && !$field->hasErrors()) {
$fieldGroup = reset($allGroups);
} else {
$fieldGroup = null;
}

$groupOptions = [];

if (!$groupId) {
if (!$fieldGroup) {
$groupOptions[] = ['value' => '', 'label' => ''];
}

Expand Down
4 changes: 2 additions & 2 deletions src/elements/actions/Delete.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
/**
* Delete represents a Delete element action.
*
* Element types that make this action available should implement [[ElementInterface::getIsDeletable()]] to explicitly state whether they can be
* Element types that make this action available should implement [[ElementInterface::canDelete()]] to explicitly state whether they can be
* deleted by the current user.
*
* @author Pixel & Tonic, Inc. <[email protected]>
Expand Down Expand Up @@ -70,7 +70,7 @@ public function setHardDelete(): void
*/
public function getTriggerHtml(): ?string
{
// Only enable for deletable elements, per getIsDeletable()
// Only enable for deletable elements, per canDelete()
Craft::$app->getView()->registerJsWithVars(fn($type) => <<<JS
(() => {
new Craft.ElementActionTrigger({
Expand Down
2 changes: 1 addition & 1 deletion src/elements/actions/DeleteAssets.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public function getConfirmationMessage(): ?string
*/
public function getTriggerHtml(): ?string
{
// Only enable for deletable elements, per getIsDeletable()
// Only enable for deletable elements, per canDelete()
Craft::$app->getView()->registerJsWithVars(fn($type) => <<<JS
(() => {
new Craft.ElementActionTrigger({
Expand Down
4 changes: 2 additions & 2 deletions src/elements/actions/DeleteForSite.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
/**
* Delete represents a “Delete for site” element action.
*
* Element types that make this action available should implement [[ElementInterface::getIsDeletable()]] to explicitly state whether they can be
* Element types that make this action available should implement [[ElementInterface::canDelete()]] to explicitly state whether they can be
* deleted by the current user.
*
* @author Pixel & Tonic, Inc. <[email protected]>
Expand All @@ -41,7 +41,7 @@ class DeleteForSite extends ElementAction
*/
public function getTriggerHtml(): ?string
{
// Only enable for deletable elements, per getIsDeletable()
// Only enable for deletable elements, per canDelete()
Craft::$app->getView()->registerJsWithVars(fn($type) => <<<JS
(() => {
new Craft.ElementActionTrigger({
Expand Down
1 change: 1 addition & 0 deletions src/elements/conditions/RelatedToConditionRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ protected function inputHtml(): string
{
return Html::tag('div',
Cp::selectHtml([
'id' => 'element-type',
'name' => 'elementType',
'options' => $this->_elementTypeOptions(),
'value' => $this->elementType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ public function matchElement(ElementInterface $element): bool
protected function inputHtml(): string
{
$countrySelect = Cp::selectFieldHtml([
'id' => 'country-code',
'name' => 'countryCode',
'options' => Craft::$app->getAddresses()->getCountryRepository()->getList(),
'value' => $this->countryCode,
Expand Down
6 changes: 3 additions & 3 deletions src/helpers/DateTimeHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,7 @@ public static function toDateInterval(mixed $value): DateInterval|false
// Use DateTime::diff() so the years/months/days/hours/minutes values are all populated correctly
$now = static::now();
$then = (clone $now)->modify("+$value seconds");
return $then->diff($now);
return $now->diff($then);
}

if (is_string($value)) {
Expand Down Expand Up @@ -555,7 +555,7 @@ public static function isValidIntervalString(string $intervalString): bool
*/
public static function humanDuration(mixed $dateInterval, ?bool $showSeconds = null): string
{
$dateInterval = static::toDateInterval($dateInterval);
$dateInterval = static::toDateInterval($dateInterval) ?: new DateInterval('PT0S');
$secondsOnly = !$dateInterval->y && !$dateInterval->m && !$dateInterval->d && !$dateInterval->h && !$dateInterval->i;

if ($showSeconds === null) {
Expand Down Expand Up @@ -600,7 +600,7 @@ public static function humanDuration(mixed $dateInterval, ?bool $showSeconds = n
$timeComponents[] = Craft::t('app', '{num, number} {num, plural, =1{minute} other{minutes}}', ['num' => $minutes]);
}

if ($showSeconds && $dateInterval->s) {
if ($showSeconds && ($dateInterval->s || empty($timeComponents))) {
$timeComponents[] = Craft::t('app', '{num, number} {num, plural, =1{second} other{seconds}}', ['num' => $dateInterval->s]);
}

Expand Down
10 changes: 6 additions & 4 deletions src/services/Assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -920,8 +920,10 @@ public function getUserTemporaryUploadFolder(?User $user = null): VolumeFolder
$user = Craft::$app->getUser()->getIdentity();
}

if ($user && isset($this->_userTempFolders[$user->id])) {
return $this->_userTempFolders[$user->id];
$cacheKey = $user->id ?? '__GUEST__';

if (isset($this->_userTempFolders[$cacheKey])) {
return $this->_userTempFolders[$cacheKey];
}

if ($user) {
Expand All @@ -945,7 +947,7 @@ public function getUserTemporaryUploadFolder(?User $user = null): VolumeFolder

if ($tempVolume) {
$path = ($tempSubpath ? "$tempSubpath/" : '') . $folderName;
return $this->_userTempFolders[$user->id] = $this->ensureFolderByFullPathAndVolume($path, $tempVolume);
return $this->_userTempFolders[$cacheKey] = $this->ensureFolderByFullPathAndVolume($path, $tempVolume);
}

$volumeTopFolder = $this->findFolder([
Expand Down Expand Up @@ -980,7 +982,7 @@ public function getUserTemporaryUploadFolder(?User $user = null): VolumeFolder
throw new VolumeException('Unable to create directory for temporary volume.');
}

return $this->_userTempFolders[$user->id] = $folder;
return $this->_userTempFolders[$cacheKey] = $folder;
}

/**
Expand Down
9 changes: 4 additions & 5 deletions src/services/Drafts.php
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ public function applyDraft(ElementInterface $draft): ElementInterface
} catch (Throwable $e) {
$transaction->rollBack();

if ($e instanceof InvalidElementException) {
if ($e instanceof InvalidElementException && $draft !== $e->element) {
// Add the errors from the duplicated element back onto the draft
$draft->addErrors($e->element->getErrors());
}
Expand Down Expand Up @@ -371,11 +371,10 @@ public function removeDraftData(ElementInterface $draft): void
$draft->validate();
}

if ($draft->hasErrors()) {
throw new InvalidElementException($draft, "Draft $draft->id could not be applied because it doesn't validate.");
}

try {
if ($draft->hasErrors()) {
throw new InvalidElementException($draft, "Draft $draft->id could not be applied because it doesn't validate.");
}
Craft::$app->getElements()->saveElement($draft, false);
Db::delete(Table::DRAFTS, [
'id' => $draftId,
Expand Down
Loading

0 comments on commit 42335c5

Please sign in to comment.