Skip to content

Commit

Permalink
Merge branch 'release/4.8.0' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
brandonkelly committed Feb 26, 2024
2 parents 2f0a760 + 8f5c645 commit 6a7e5f3
Show file tree
Hide file tree
Showing 68 changed files with 1,072 additions and 448 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
branches:
- develop
- '4.8'
pull_request:
permissions:
contents: read
Expand Down
38 changes: 38 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,43 @@
# Release Notes for Craft CMS 4

## 4.8.0 - 2024-02-26

> [!NOTE]
> Trialing Craft and plugin updates with expired licenses is allowed now, on non-public domains.
> [!WARNING]
> When licensing issues occur on public domains, the control panel will now become temporarily inaccessible for logged-in users, alerting them to the problems and giving them an opportunity to resolve them. (The front end will not be impacted.)
### Content Management
- Assets fields’ selection modals now open to the last-viewed location by default, if their Default Upload Location doesn’t specify a subpath. ([#14382](https://github.com/craftcms/cms/pull/14382))
- Element sources no longer display `0` badges.

### Administration
- Color fields now have a “Presets” settings. ([#14463](https://github.com/craftcms/cms/discussions/14463))
- It’s now possible to update expired licenses from the Updates utility, on non-public domains.
- The `queue/run` command now supports a `--job-id` option.
- `update all` and `update <handle>` commands now support a `--with-expired` option.

### Development
- The GraphQL API is now available for Craft Solo installs.
- The `{% js %}` and `{% css %}` tags now support `.js.gz` and `.css.gz` URLs. ([#14243](https://github.com/craftcms/cms/issues/14243))
- Relation fields’ element query params now factor in the element query’s target site(s). ([#14258](https://github.com/craftcms/cms/issues/14258), [#14348](https://github.com/craftcms/cms/issues/14348), [#14304](https://github.com/craftcms/cms/pull/14304))
- Element queries’ `level` param now supports passing an array which includes `null`. ([#14419](https://github.com/craftcms/cms/issues/14419))

### Extensibility
- Added `craft\services\ProjectConfig::EVENT_AFTER_WRITE_YAML_FILES`. ([#14365](https://github.com/craftcms/cms/discussions/14365))
- Added `craft\services\Relations::deleteLeftoverRelations()`. ([#13956](https://github.com/craftcms/cms/issues/13956))
- Added `craft\services\Search::shouldCallSearchElements()`. ([#14293](https://github.com/craftcms/cms/issues/14293))

### System
- Relations for fields that are no longer included in an element’s field layout are now deleted after element save. ([#13956](https://github.com/craftcms/cms/issues/13956))
- The Sendmail email transport type now uses the `sendmail_path` PHP ini setting by default. ([#14433](https://github.com/craftcms/cms/pull/14433))
- Composer installation commands suggested by the Plugin Store now include a minimum version constraint.
- Fixed a bug where it wasn’t possible to eager-load Matrix block revisions, or load them via GraphQL. ([#14448](https://github.com/craftcms/cms/issues/14448))
- Fixed a PHP warning that could occur when publishing asset bundles on Dev Mode. ([#14455](https://github.com/craftcms/cms/pull/14455))
- Fixed a bug where the Updates utility and Updates widget weren’t handling update check failures.
- Updated Twig to 3.8.

## 4.7.4 - 2024-02-22

- The Plugin Store now shows “Tested on Cloud” and “Supports GraphQL” labels for plugins when appropriate.
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"symfony/var-dumper": "^5.0|^6.0",
"symfony/yaml": "^5.2.3",
"theiconic/name-parser": "^1.2",
"twig/twig": "~3.4.3",
"twig/twig": "~3.8.0",
"voku/stringy": "^6.4.0",
"webonyx/graphql-php": "~14.11.5",
"yiisoft/yii2": "~2.0.48.1",
Expand Down
26 changes: 11 additions & 15 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 9 additions & 2 deletions src/base/ApplicationTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -707,9 +707,11 @@ public function getCanUpgradeEdition(): bool
}

/**
* Returns whether Craft is running on a domain that is eligible to test out the editions.
* Returns whether Craft is running on a domain that is eligible to test
* unlicensed Craft and plugin editions/updates.
*
* @return bool
* @internal
*/
public function getCanTestEditions(): bool
{
Expand All @@ -723,7 +725,12 @@ public function getCanTestEditions(): bool

/** @var Cache $cache */
$cache = $this->getCache();
return $cache->get(sprintf('editionTestableDomain@%s', $this->getRequest()->getHostName()));
$cacheKey = sprintf('editionTestableDomain@%s', $this->getRequest()->getHostName());
if (!$cache->exists($cacheKey)) {
// err on the side of allowing it
return true;
}
return (bool)$cache->get($cacheKey);
}

/**
Expand Down
5 changes: 5 additions & 0 deletions src/base/Element.php
Original file line number Diff line number Diff line change
Expand Up @@ -5182,6 +5182,11 @@ public function afterPropagate(bool $isNew): void
$field->afterElementPropagate($this, $isNew);
}

// Delete relations that don’t belong to a relational field on the element's field layout
if (!ElementHelper::isDraftOrRevision($this)) {
Craft::$app->getRelations()->deleteLeftoverRelations($this);
}

// Trigger an 'afterPropagate' event
if ($this->hasEventHandlers(self::EVENT_AFTER_PROPAGATE)) {
$this->trigger(self::EVENT_AFTER_PROPAGATE, new ModelEvent([
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.7.4',
'version' => '4.8.0',
'schemaVersion' => '4.5.3.0',
'minVersionRequired' => '3.7.11',
'basePath' => dirname(__DIR__), // Defines the @app alias
Expand Down
9 changes: 9 additions & 0 deletions src/config/cproutes/common.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@
'entries/<section:{handle}>/<elementId:\d+><slug:(?:-[^\/]*)?>/revisions' => 'elements/revisions',
'globals' => 'globals',
'globals/<globalSetHandle:{handle}>' => 'globals/edit-content',
'graphiql' => 'graphql/graphiql',
'graphql' => 'graphql/cp-index',
'graphql/schemas' => 'graphql/view-schemas',
'graphql/schemas/new' => 'graphql/edit-schema',
'graphql/schemas/<schemaId:\d+>' => 'graphql/edit-schema',
'graphql/schemas/public' => 'graphql/edit-public-schema',
'graphql/tokens' => 'graphql/view-tokens',
'graphql/tokens/new' => 'graphql/edit-token',
'graphql/tokens/<tokenId:\d+>' => 'graphql/edit-token',
'myaccount' => [
'route' => 'users/edit-user',
'defaults' => ['userId' => 'current'],
Expand Down
9 changes: 0 additions & 9 deletions src/config/cproutes/pro.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,4 @@
'settings/users' => ['template' => 'settings/users/groups/_index'],
'settings/users/groups/new' => ['template' => 'settings/users/groups/_edit'],
'settings/users/groups/<groupId:\d+>' => ['template' => 'settings/users/groups/_edit'],
'graphiql' => 'graphql/graphiql',
'graphql' => 'graphql/cp-index',
'graphql/schemas' => 'graphql/view-schemas',
'graphql/schemas/new' => 'graphql/edit-schema',
'graphql/schemas/<schemaId:\d+>' => 'graphql/edit-schema',
'graphql/schemas/public' => 'graphql/edit-public-schema',
'graphql/tokens' => 'graphql/view-tokens',
'graphql/tokens/new' => 'graphql/edit-token',
'graphql/tokens/<tokenId:\d+>' => 'graphql/edit-token',
];
15 changes: 13 additions & 2 deletions src/console/controllers/UpdateController.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ class UpdateController extends Controller
*/
public $defaultAction = 'update';

/**
* @var bool Whether to update expired licenses.
*
* NOTE: This will result in “License purchase required” messages in the control panel on public domains,
* until the licenses have been renewed.
*
* @since 4.8.0
*/
public bool $withExpired = false;

/**
* @var bool Force the update if allowUpdates is disabled
*/
Expand All @@ -65,6 +75,7 @@ public function options($actionID): array
$options = parent::options($actionID);

if ($actionID === 'update') {
$options[] = 'withExpired';
$options[] = 'force';
$options[] = 'backup';
$options[] = 'migrate';
Expand Down Expand Up @@ -336,8 +347,8 @@ private function _getRequirements(string ...$handles): array
*/
private function _updateRequirements(array &$requirements, array &$info, string $handle, string $from, ?string $to, string $oldPackageName, Update $update): void
{
if ($update->status === Update::STATUS_EXPIRED) {
$this->stdout("Skipping $handle because its license has expired." . PHP_EOL, Console::FG_GREY);
if ($update->status === Update::STATUS_EXPIRED && !$this->withExpired) {
$this->stdout($this->markdownToAnsi("Skipping `$handle` because its license has expired. Run with `--with-expired` to update anyway.") . PHP_EOL);
return;
}

Expand Down
76 changes: 76 additions & 0 deletions src/controllers/AppController.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use craft\helpers\Cp;
use craft\helpers\DateTimeHelper;
use craft\helpers\Html;
use craft\helpers\Json;
use craft\helpers\Update as UpdateHelper;
use craft\helpers\UrlHelper;
use craft\models\Update;
Expand All @@ -30,6 +31,7 @@
use Throwable;
use yii\base\InvalidConfigException;
use yii\web\BadRequestHttpException;
use yii\web\Cookie;
use yii\web\ForbiddenHttpException;
use yii\web\Response;
use yii\web\ServerErrorHttpException;
Expand Down Expand Up @@ -392,6 +394,76 @@ public function actionShunCpAlert(): Response
return $this->asFailure(Craft::t('app', 'A server error occurred.'));
}

/**
* Displays a licensing issues takeover page.
*
* @param array $issues
* @param string $hash
* @return Response
* @internal
*/
public function actionLicensingIssues(array $issues, string $hash): Response
{
$this->requireCpRequest();

$consoleUrl = rtrim(Craft::$app->getPluginStore()->craftIdEndpoint, '/');
$cartUrl = UrlHelper::urlWithParams("$consoleUrl/cart/new", [
'items' => array_map(fn($issue) => $issue[2], $issues),
]);

$cookie = $this->request->getCookies()->get(App::licenseShunCookieName());
$data = $cookie ? Json::decode($cookie->value) : null;
if ($data['hash'] !== $hash) {
$data = null;
}

$duration = match ($data['count'] ?? 0) {
0 => 21,
1 => 34,
2 => 55,
3 => 89,
4 => 144,
5 => 233,
6 => 377,
7 => 610,
8 => 987,
default => 1597,
};

return $this->renderTemplate('_special/licensing-issues.twig', [
'issues' => $issues,
'hash' => $hash,
'cartUrl' => $cartUrl,
'duration' => $duration,
])->setStatusCode(402);
}

/**
* Sets the license shun cookie.
*
* @return Response
* @internal
*/
public function actionSetLicenseShunCookie(): Response
{
$cookieName = App::licenseShunCookieName();
$oldCookie = $this->request->getCookies()->get($cookieName);
$data = $oldCookie ? Json::decode($oldCookie->value) : [];

$newCookie = new Cookie(Craft::cookieConfig([
'name' => $cookieName,
'value' => Json::encode([
'hash' => $this->request->getRequiredBodyParam('hash'),
'timestamp' => DateTimeHelper::toIso8601(DateTimeHelper::now()),
'count' => ($data['count'] ?? 0) + 1,
]),
'expire' => DateTimeHelper::now()->modify('+1 year')->getTimestamp(),
], $this->request));

$this->response->getCookies()->add($newCookie);
return $this->asSuccess();
}

/**
* Tries a Craft edition on for size.
*
Expand Down Expand Up @@ -524,6 +596,10 @@ private function _transformUpdate(bool $allowUpdates, Update $update, string $ha
'price' => Craft::$app->getFormatter()->asCurrency($update->renewalPrice, $update->renewalCurrency),
]);
$arr['ctaUrl'] = UrlHelper::url($update->renewalUrl);

if ($allowUpdates && Craft::$app->getCanTestEditions()) {
$arr['altCtaText'] = Craft::t('app', 'Update anyway');
}
} else {
// Make sure that the platform & composer.json PHP version are compatible
$phpConstraintError = null;
Expand Down
2 changes: 0 additions & 2 deletions src/controllers/GraphqlController.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,6 @@ public function beforeAction($action): bool
return false;
}

Craft::$app->requireEdition(Craft::Pro);

return true;
}

Expand Down
20 changes: 16 additions & 4 deletions src/elements/db/ElementQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -2600,7 +2600,17 @@ private function _applyStructureParams(string $class): void
}

if ($this->level) {
$this->subQuery->andWhere(Db::parseNumericParam('structureelements.level', $this->level));
$allowNull = is_array($this->level) && in_array(null, $this->level, true);
if ($allowNull) {
$levelCondition = [
'or',
Db::parseNumericParam('structureelements.level', array_filter($this->level, fn($v) => $v !== null)),
['structureelements.level' => null],
];
} else {
$levelCondition = Db::parseNumericParam('structureelements.level', $this->level);
}
$this->subQuery->andWhere($levelCondition);
}

if ($this->leaves) {
Expand Down Expand Up @@ -2776,9 +2786,11 @@ private function _applySearchParam(): void
return;
}

if (isset($this->orderBy['score'])) {
$searchService = Craft::$app->getSearch();

if (isset($this->orderBy['score']) || $searchService->shouldCallSearchElements($this)) {
// Get the scored results up front
$searchResults = Craft::$app->getSearch()->searchElements($this);
$searchResults = $searchService->searchElements($this);

if ($this->orderBy['score'] === SORT_ASC) {
$searchResults = array_reverse($searchResults, true);
Expand All @@ -2804,7 +2816,7 @@ private function _applySearchParam(): void
$this->subQuery->andWhere(['elements.id' => array_keys($searchResults)]);
} else {
// Just filter the main query by the search query
$searchQuery = Craft::$app->getSearch()->createDbQuery($this->search, $this);
$searchQuery = $searchService->createDbQuery($this->search, $this);

if ($searchQuery === false) {
throw new QueryAbortedException();
Expand Down
Loading

0 comments on commit 6a7e5f3

Please sign in to comment.