Skip to content

Commit

Permalink
Merge branch '5.x' into 5.5
Browse files Browse the repository at this point in the history
# Conflicts:
#	src/web/assets/cp/dist/cp.js
#	src/web/assets/cp/dist/cp.js.map
#	src/web/assets/cp/dist/css/cp.css
#	src/web/assets/cp/dist/css/cp.css.map
  • Loading branch information
brandonkelly committed Oct 15, 2024
2 parents eb42e97 + 2f939a0 commit cbf39be
Show file tree
Hide file tree
Showing 24 changed files with 208 additions and 61 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Release Notes for Craft CMS 5

## Unreleased

- Added `craft\helpers\App::isTty()`.
- Fixed a styling issue with Color field inputs. ([#15868](https://github.com/craftcms/cms/issues/15868))
- Fixed a deprecation error. ([#15873](https://github.com/craftcms/cms/issues/15873))
- Fixed a bug where element sources weren’t keyboard-selectable. ([#15876](https://github.com/craftcms/cms/issues/15876))
- Fixed a bug where Craft wasn’t auto-detecting interactive terminals on Windows.
- Fixed a bug where element actions were allowed on nested entries when viewing a revision. ([#15879](https://github.com/craftcms/cms/pull/15879))
- Fixed a bug where element error summaries weren’t linking to recursively-nested Matrix fields properly. ([#15797](https://github.com/craftcms/cms/issues/15797))
- Fixed a privilege escalation vulnerability.

## 5.4.7.1 - 2024-10-09

- Custom field condition rules are now ignored if they reference a field with an incompatible type. ([#15850](https://github.com/craftcms/cms/issues/15850))
Expand Down
3 changes: 1 addition & 2 deletions src/console/ControllerTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
use craft\helpers\App;
use craft\helpers\Console;
use craft\mutex\Mutex as CraftMutex;
use Symfony\Component\Process\Process;
use yii\base\Action;
use yii\base\InvalidRouteException;
use yii\console\Exception;
Expand Down Expand Up @@ -171,7 +170,7 @@ public function beforeAction($action): bool
protected function checkTty(): void
{
// Don't treat this as interactive if it doesn't appear to be a TTY shell
if ($this->interactive && !Process::isTtySupported()) {
if ($this->interactive && !App::isTty()) {
$this->interactive = false;
}
}
Expand Down
17 changes: 11 additions & 6 deletions src/controllers/AppController.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
use craft\helpers\UrlHelper;
use craft\models\Update;
use craft\models\Updates;
use craft\utilities\Updates as UpdatesUtility;
use craft\web\Controller;
use craft\web\ServiceUnavailableHttpException;
use DateInterval;
Expand Down Expand Up @@ -154,9 +155,11 @@ public function actionCheckForUpdates(): Response
{
$this->requireAcceptsJson();

// Require either the 'performUpdates' or 'utility:updates' permission
$userSession = Craft::$app->getUser();
if (!$userSession->checkPermission('performUpdates') && !$userSession->checkPermission('utility:updates')) {
// Require either the 'performUpdates' permission or access to the Updates utility
if (
!Craft::$app->getUser()->checkPermission('performUpdates') &&
!Craft::$app->getUtilities()->checkAuthorization(UpdatesUtility::class)
) {
throw new ForbiddenHttpException('User is not permitted to perform this action');
}

Expand Down Expand Up @@ -184,9 +187,11 @@ public function actionCacheUpdates(): Response
{
$this->requireAcceptsJson();

// Require either the 'performUpdates' or 'utility:updates' permission
$userSession = Craft::$app->getUser();
if (!$userSession->checkPermission('performUpdates') && !$userSession->checkPermission('utility:updates')) {
// Require either the 'performUpdates' permission or access to the Updates utility
if (
!Craft::$app->getUser()->checkPermission('performUpdates') &&
!Craft::$app->getUtilities()->checkAuthorization(UpdatesUtility::class)
) {
throw new ForbiddenHttpException('User is not permitted to perform this action');
}

Expand Down
6 changes: 5 additions & 1 deletion src/controllers/AssetIndexesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use craft\web\Controller;
use Throwable;
use yii\web\BadRequestHttpException;
use yii\web\ForbiddenHttpException;
use yii\web\Response;

/** @noinspection ClassOverridesFieldOfSuperClassInspection */
Expand All @@ -41,7 +42,10 @@ public function beforeAction($action): bool
}

// No permission no bueno
$this->requirePermission('utility:asset-indexes');
if (!Craft::$app->getUtilities()->checkAuthorization(AssetIndexes::class)) {
throw new ForbiddenHttpException('User is not authorized to perform this action.');
}

$this->requireAcceptsJson();

return true;
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/ElementIndexesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -838,7 +838,7 @@ protected function elementResponseData(bool $includeContainer, bool $includeActi

// Get the action head/foot HTML before any more is added to it from the element HTML
if ($includeActions) {
$responseData['actions'] = $this->actionData();
$responseData['actions'] = $this->viewState['static'] === true ? [] : $this->actionData();
$responseData['actionsHeadHtml'] = $view->getHeadHtml();
$responseData['actionsBodyHtml'] = $view->getBodyHtml();
$responseData['exporters'] = $this->exporterData();
Expand Down
13 changes: 12 additions & 1 deletion src/controllers/ElementsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -1054,11 +1054,22 @@ private function _errorSummary(ElementInterface $element): string
foreach ($tab->getElements() as $layoutElement) {
if ($layoutElement instanceof BaseField && $layoutElement->attribute() === $fieldKey) {
$tabUid = $tab->uid;
continue 2;
break 2;
}
}
}

// If the error is for a recursively-nested Matrix field,
// manipulate the key to only reference the nested Matrix field, entry and inner field
// Before: foo[<uuid>].bar[<uuid>].baz
// After: bar[<uuid>].baz
if (substr_count($key, '.') > 1) {
$keyParts = explode('.', $key);
if (preg_match(sprintf('/\[%s\]$/', StringHelper::UUID_PATTERN), $keyParts[count($keyParts) - 3])) {
$key = implode('.', array_slice($keyParts, -2));
}
}

$errorItem = null;
if ($error !== null) {
$error = Markdown::processParagraph(htmlspecialchars($error));
Expand Down
6 changes: 5 additions & 1 deletion src/controllers/ProjectConfigController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use craft\helpers\FileHelper;
use craft\helpers\ProjectConfig;
use craft\helpers\StringHelper;
use craft\utilities\ProjectConfig as ProjectConfigUtility;
use craft\web\Controller;
use Symfony\Component\Yaml\Yaml;
use yii\base\Exception;
Expand All @@ -35,7 +36,10 @@ public function beforeAction($action): bool
return false;
}

$this->requirePermission('utility:project-config');
if (!Craft::$app->getUtilities()->checkAuthorization(ProjectConfigUtility::class)) {
throw new ForbiddenHttpException('User is not authorized to perform this action.');
}

return true;
}

Expand Down
26 changes: 21 additions & 5 deletions src/controllers/QueueController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use craft\helpers\App;
use craft\helpers\Json;
use craft\queue\QueueInterface;
use craft\utilities\QueueManager;
use craft\web\Controller;
use yii\base\InvalidArgumentException;
use yii\db\Exception as YiiDbException;
Expand Down Expand Up @@ -95,7 +96,10 @@ public function actionRetry(): Response
{
$this->requireAcceptsJson();
$this->requirePostRequest();
$this->requirePermission('utility:queue-manager');

if (!Craft::$app->getUtilities()->checkAuthorization(QueueManager::class)) {
throw new ForbiddenHttpException('User is not authorized to perform this action.');
}

$id = $this->request->getRequiredBodyParam('id');
$this->queue->retry($id);
Expand All @@ -113,7 +117,10 @@ public function actionRelease(): Response
{
$this->requireAcceptsJson();
$this->requirePostRequest();
$this->requirePermission('utility:queue-manager');

if (!Craft::$app->getUtilities()->checkAuthorization(QueueManager::class)) {
throw new ForbiddenHttpException('User is not authorized to perform this action.');
}

$id = $this->request->getRequiredBodyParam('id');
$this->queue->release($id);
Expand All @@ -134,7 +141,10 @@ public function actionReleaseAll(): Response
{
$this->requireAcceptsJson();
$this->requirePostRequest();
$this->requirePermission('utility:queue-manager');

if (!Craft::$app->getUtilities()->checkAuthorization(QueueManager::class)) {
throw new ForbiddenHttpException('User is not authorized to perform this action.');
}

$this->queue->releaseAll();

Expand All @@ -153,7 +163,10 @@ public function actionRetryAll(): Response
{
$this->requireAcceptsJson();
$this->requirePostRequest();
$this->requirePermission('utility:queue-manager');

if (!Craft::$app->getUtilities()->checkAuthorization(QueueManager::class)) {
throw new ForbiddenHttpException('User is not authorized to perform this action.');
}

$this->queue->retryAll();

Expand Down Expand Up @@ -189,7 +202,10 @@ public function actionGetJobInfo(): Response
public function actionGetJobDetails(): Response
{
$this->requireAcceptsJson();
$this->requirePermission('utility:queue-manager');

if (!Craft::$app->getUtilities()->checkAuthorization(QueueManager::class)) {
throw new ForbiddenHttpException('User is not authorized to perform this action.');
}

$jobId = $this->request->getRequiredParam('id');
$details = [
Expand Down
6 changes: 5 additions & 1 deletion src/controllers/SystemMessagesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
use Craft;
use craft\enums\CmsEdition;
use craft\models\SystemMessage;
use craft\utilities\SystemMessages;
use craft\web\Controller;
use yii\web\ForbiddenHttpException;
use yii\web\Response;

/**
Expand All @@ -35,7 +37,9 @@ public function beforeAction($action): bool
Craft::$app->requireEdition(CmsEdition::Pro);

// Make sure they have access to the System Messages utility
$this->requirePermission('utility:system-messages');
if (!Craft::$app->getUtilities()->checkAuthorization(SystemMessages::class)) {
throw new ForbiddenHttpException('User is not authorized to perform this action.');
}

return true;
}
Expand Down
39 changes: 31 additions & 8 deletions src/controllers/UtilitiesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
use craft\helpers\Queue;
use craft\queue\jobs\FindAndReplace;
use craft\utilities\ClearCaches;
use craft\utilities\DbBackup;
use craft\utilities\DeprecationErrors;
use craft\utilities\FindAndReplace as FindAndReplaceUtility;
use craft\utilities\Migrations;
use craft\utilities\Updates;
use craft\utilities\Upgrade;
use craft\web\assets\utilities\UtilitiesAsset;
Expand Down Expand Up @@ -101,10 +105,13 @@ public function actionShowUtility(string $id): Response
*/
public function actionGetDeprecationErrorTracesModal(): Response
{
$this->requirePermission('utility:deprecation-errors');
$this->requirePostRequest();
$this->requireAcceptsJson();

if (!Craft::$app->getUtilities()->checkAuthorization(DeprecationErrors::class)) {
throw new ForbiddenHttpException('User is not authorized to perform this action.');
}

$logId = Craft::$app->request->getRequiredParam('logId');
$html = $this->getView()->renderTemplate('_components/utilities/DeprecationErrors/traces_modal.twig', [
'log' => Craft::$app->deprecator->getLogById($logId),
Expand All @@ -123,10 +130,13 @@ public function actionGetDeprecationErrorTracesModal(): Response
*/
public function actionDeleteAllDeprecationErrors(): Response
{
$this->requirePermission('utility:deprecation-errors');
$this->requirePostRequest();
$this->requireAcceptsJson();

if (!Craft::$app->getUtilities()->checkAuthorization(DeprecationErrors::class)) {
throw new ForbiddenHttpException('User is not authorized to perform this action.');
}

Craft::$app->deprecator->deleteAllLogs();

return $this->asSuccess();
Expand All @@ -140,10 +150,13 @@ public function actionDeleteAllDeprecationErrors(): Response
*/
public function actionDeleteDeprecationError(): Response
{
$this->requirePermission('utility:deprecation-errors');
$this->requirePostRequest();
$this->requireAcceptsJson();

if (!Craft::$app->getUtilities()->checkAuthorization(DeprecationErrors::class)) {
throw new ForbiddenHttpException('User is not authorized to perform this action.');
}

$logId = $this->request->getRequiredBodyParam('logId');
Craft::$app->deprecator->deleteLogById($logId);

Expand All @@ -159,7 +172,9 @@ public function actionDeleteDeprecationError(): Response
*/
public function actionClearCachesPerformAction(): Response
{
$this->requirePermission('utility:clear-caches');
if (!Craft::$app->getUtilities()->checkAuthorization(ClearCaches::class)) {
throw new ForbiddenHttpException('User is not authorized to perform this action.');
}

$caches = $this->request->getRequiredBodyParam('caches');

Expand Down Expand Up @@ -198,7 +213,9 @@ public function actionClearCachesPerformAction(): Response
*/
public function actionInvalidateTags(): Response
{
$this->requirePermission('utility:clear-caches');
if (!Craft::$app->getUtilities()->checkAuthorization(ClearCaches::class)) {
throw new ForbiddenHttpException('User is not authorized to perform this action.');
}

$tags = $this->request->getRequiredBodyParam('tags');
$cache = Craft::$app->getCache();
Expand All @@ -219,7 +236,9 @@ public function actionInvalidateTags(): Response
*/
public function actionDbBackupPerformAction(): ?Response
{
$this->requirePermission('utility:db-backup');
if (!Craft::$app->getUtilities()->checkAuthorization(DbBackup::class)) {
throw new ForbiddenHttpException('User is not authorized to perform this action.');
}

try {
$backupPath = Craft::$app->getDb()->backup();
Expand Down Expand Up @@ -252,7 +271,9 @@ public function actionDbBackupPerformAction(): ?Response
*/
public function actionFindAndReplacePerformAction(): Response
{
$this->requirePermission('utility:find-replace');
if (!Craft::$app->getUtilities()->checkAuthorization(FindAndReplaceUtility::class)) {
throw new ForbiddenHttpException('User is not authorized to perform this action.');
}

$params = $this->request->getRequiredBodyParam('params');

Expand All @@ -274,7 +295,9 @@ public function actionFindAndReplacePerformAction(): Response
*/
public function actionApplyNewMigrations(): Response
{
$this->requirePermission('utility:migrations');
if (!Craft::$app->getUtilities()->checkAuthorization(Migrations::class)) {
throw new ForbiddenHttpException('User is not authorized to perform this action.');
}

$migrator = Craft::$app->getContentMigrator();

Expand Down
2 changes: 2 additions & 0 deletions src/elements/NestedElementManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,7 @@ public function getIndexHtml(?ElementInterface $owner, array $config = []): stri
'prevalidate' => false,
'pageSize' => 50,
'storageKey' => null,
'static' => $owner->getIsRevision(),
];

if ($config['storageKey'] === null) {
Expand Down Expand Up @@ -514,6 +515,7 @@ function(string $id, array $config, string $attribute, array &$settings) use ($o
'actions' => [],
'canHaveDrafts' => $elementType::hasDrafts(),
'storageKey' => $config['storageKey'],
'static' => $config['static'],
];

if ($config['sortable']) {
Expand Down
Loading

0 comments on commit cbf39be

Please sign in to comment.