Skip to content

User cache invalidation remediation #16937

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
10 changes: 8 additions & 2 deletions src/base/Element.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use craft\behaviors\CustomFieldBehavior;
use craft\behaviors\DraftBehavior;
use craft\behaviors\RevisionBehavior;
use craft\cache\ElementQueryTagDependency;
use craft\db\Connection;
use craft\db\ExcludeDescendantIdsExpression;
use craft\db\Query;
Expand Down Expand Up @@ -1187,8 +1188,13 @@ public static function indexHtml(ElementQueryInterface $elementQuery, ?array $di
}

// Only cache if there's no search term
if (!$elementQuery->search) {
$elementQuery->cache();
if ($elementQuery instanceof ElementQuery && !$elementQuery->search) {
$elementQuery->cache(dependency: new ElementQueryTagDependency($elementQuery, [
'tags' => [
'element-index-query',
sprintf('element-index-query::%s', static::class),
],
]));
}

$variables['elements'] = static::indexElements($elementQuery, $sourceKey);
Expand Down
2 changes: 1 addition & 1 deletion src/cache/ElementQueryTagDependency.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public function __sleep(): array
protected function generateDependencyData($cache)
{
if ($this->elementQuery) {
$this->tags = $this->elementQuery->getCacheTags();
$this->tags = array_unique(array_merge($this->tags, $this->elementQuery->getCacheTags()));
}
return parent::generateDependencyData($cache);
}
Expand Down
25 changes: 25 additions & 0 deletions src/records/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,29 @@ public function getGroups(): ActiveQueryInterface
return $this->hasMany(UserGroup::class, ['id' => 'groupId'])
->viaTable(Table::USERGROUPS_USERS, ['userId' => 'id']);
}

/**
* Returns whether any properties that affect the user's status have changed.
*
* @retrun bool
* @since 4.15.0
*/
public function haveIndexAttributesChanged(): bool
{
if ($this->getIsNewRecord()) {
return false;
}

return !empty($this->getDirtyAttributes([
'active',
'email',
'firstName',
'fullName',
'lastLoginDate',
'lastName',
'pending',
'suspended',
'username',
]));
}
}
7 changes: 6 additions & 1 deletion src/services/Search.php
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,12 @@ public function searchElements(ElementQuery $elementQuery): array

$results = $query
->andWhere(['elementId' => $elementQuery])
->cache(true, new ElementQueryTagDependency($elementQuery))
->cache(true, new ElementQueryTagDependency($elementQuery, [
'tags' => [
'element-index-query',
sprintf('element-index-query::%s', $elementQuery->elementType),
],
]))
->all();

// Score the results
Expand Down
80 changes: 63 additions & 17 deletions src/services/Users.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
use yii\base\Exception;
use yii\base\InvalidArgumentException;
use yii\base\UserException;
use yii\caching\TagDependency;

/**
* The Users service provides APIs for managing users.
Expand Down Expand Up @@ -526,6 +527,8 @@ public function removeCredentials(User $user): bool
$userRecord->password = null;
$userRecord->verificationCode = null;

$indexAttributesChanged = $userRecord->haveIndexAttributesChanged();

if (!$userRecord->save()) {
return false;
}
Expand All @@ -534,6 +537,11 @@ public function removeCredentials(User $user): bool
$user->pending = false;
$user->password = null;
$user->verificationCode = null;

if ($indexAttributesChanged) {
$this->invalidateIndexCaches();
}

return true;
}

Expand Down Expand Up @@ -718,14 +726,16 @@ public function handleValidLogin(User $user): void
$userRecord->lastLoginAttemptIp = Craft::$app->getRequest()->getUserIP();
}

$indexAttributesChanged = $userRecord->haveIndexAttributesChanged();
$userRecord->save();

// Update the User model too
$user->lastLoginDate = $now;
$user->invalidLoginCount = null;

// Invalidate caches
Craft::$app->getElements()->invalidateCachesForElement($user);
if ($indexAttributesChanged) {
$this->invalidateIndexCaches();
}
}

/**
Expand Down Expand Up @@ -772,6 +782,7 @@ public function handleInvalidLogin(User $user): void
$user->invalidLoginCount = $userRecord->invalidLoginCount;
}

$indexAttributesChanged = $userRecord->haveIndexAttributesChanged();
$userRecord->save();

// Update the User model too
Expand All @@ -784,8 +795,9 @@ public function handleInvalidLogin(User $user): void
]));
}

// Invalidate caches
Craft::$app->getElements()->invalidateCachesForElement($user);
if ($indexAttributesChanged) {
$this->invalidateIndexCaches();
}
}

/**
Expand Down Expand Up @@ -846,6 +858,8 @@ public function activateUser(User $user): bool
$userRecord->invalidLoginCount = null;
$userRecord->lastInvalidLoginDate = null;
$userRecord->lockoutDate = null;

$indexAttributesChanged = $userRecord->haveIndexAttributesChanged();
$userRecord->save();

// If they have an unverified email address, now is the time to set it to their primary email address
Expand All @@ -864,8 +878,9 @@ public function activateUser(User $user): bool
]));
}

// Invalidate caches
Craft::$app->getElements()->invalidateCachesForElement($user);
if ($indexAttributesChanged) {
$this->invalidateIndexCaches();
}

return true;
}
Expand Down Expand Up @@ -903,6 +918,8 @@ public function deactivateUser(User $user): bool
$userRecord->invalidLoginCount = null;
$userRecord->lastInvalidLoginDate = null;
$userRecord->lockoutDate = null;

$indexAttributesChanged = $userRecord->haveIndexAttributesChanged();
$userRecord->save();

$user->active = false;
Expand All @@ -928,9 +945,9 @@ public function deactivateUser(User $user): bool
]));
}

// Invalidate caches
Craft::$app->getElements()->invalidateCachesForElement($user);

if ($indexAttributesChanged) {
$this->invalidateIndexCaches();
}
return true;
}

Expand All @@ -956,6 +973,8 @@ public function verifyEmailForUser(User $user): bool
$userRecord->username = $user->unverifiedEmail;
}

$indexAttributesChanged = $userRecord->haveIndexAttributesChanged();

if (!$userRecord->save()) {
$user->addErrors($userRecord->getErrors());
return false;
Expand All @@ -964,6 +983,8 @@ public function verifyEmailForUser(User $user): bool
// If the user status is pending, let's activate them.
if ($userRecord->pending) {
$this->activateUser($user);
} elseif ($indexAttributesChanged) {
$this->invalidateIndexCaches();
}

return true;
Expand Down Expand Up @@ -995,6 +1016,8 @@ public function unlockUser(User $user): bool
$userRecord->invalidLoginCount = null;
$userRecord->invalidLoginWindowStart = null;
$userRecord->lockoutDate = null;

$indexAttributesChanged = $userRecord->haveIndexAttributesChanged();
$userRecord->save();

$transaction->commit();
Expand All @@ -1015,8 +1038,9 @@ public function unlockUser(User $user): bool
]));
}

// Invalidate caches
Craft::$app->getElements()->invalidateCachesForElement($user);
if ($indexAttributesChanged) {
$this->invalidateIndexCaches();
}

return true;
}
Expand All @@ -1043,6 +1067,8 @@ public function suspendUser(User $user): bool
$userRecord = $this->_getUserRecordById($user->id);
$userRecord->suspended = true;
$user->suspended = true;

$indexAttributesChanged = $userRecord->haveIndexAttributesChanged();
$userRecord->save();

// Destroy all sessions for this user
Expand All @@ -1055,8 +1081,9 @@ public function suspendUser(User $user): bool
]));
}

// Invalidate caches
Craft::$app->getElements()->invalidateCachesForElement($user);
if ($indexAttributesChanged) {
$this->invalidateIndexCaches();
}

return true;
}
Expand Down Expand Up @@ -1085,6 +1112,8 @@ public function unsuspendUser(User $user): bool
try {
$userRecord = $this->_getUserRecordById($user->id);
$userRecord->suspended = false;

$indexAttributesChanged = $userRecord->haveIndexAttributesChanged();
$userRecord->save();

$transaction->commit();
Expand All @@ -1104,8 +1133,9 @@ public function unsuspendUser(User $user): bool
]));
}

// Invalidate caches
Craft::$app->getElements()->invalidateCachesForElement($user);
if ($indexAttributesChanged) {
$this->invalidateIndexCaches();
}

return true;
}
Expand Down Expand Up @@ -1196,6 +1226,7 @@ public function setVerificationCodeOnUser(User $user): string
}

$transaction = Craft::$app->getDb()->beginTransaction();
$indexAttributesChanged = $userRecord->haveIndexAttributesChanged();
$userRecord->save();

$originalUser = clone $user;
Expand All @@ -1214,8 +1245,9 @@ public function setVerificationCodeOnUser(User $user): string

$transaction->commit();

// Invalidate caches
Craft::$app->getElements()->invalidateCachesForElement($user);
if ($indexAttributesChanged) {
$this->invalidateIndexCaches();
}

return $unhashedCode;
}
Expand Down Expand Up @@ -1349,6 +1381,8 @@ public function assignUserToGroups(int $userId, array $groupIds): bool
]));
}

$this->invalidateIndexCaches();

return true;
}

Expand Down Expand Up @@ -1632,4 +1666,16 @@ private function _getUserUrl(User $user, string $fePath, string $cpPath): string

return $url;
}

/**
* Invalidates user index caches.
*
* @since 4.15.0
*/
public function invalidateIndexCaches(): void
{
TagDependency::invalidate(Craft::$app->getCache(), [
sprintf('element-index-query::%s', User::class),
]);
}
}
Loading