Skip to content
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

Bugfix/15678 user addresses and fields #15685

Merged
merged 8 commits into from
Sep 6, 2024
Merged
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@
- Fixed a PHP error that occurred when running PHP 8.2 or 8.3.
- Fixed a bug where disabled entries became enabled when edited within Live Preview. ([#15670](https://github.com/craftcms/cms/issues/15670))
- Fixed a bug where new nested entries could get incremented slugs even if there were no elements with conflicting URIs. ([#15672](https://github.com/craftcms/cms/issues/15672))
- Fixed a bug where users’ Addresses screens were displaying addresses that belonged to the user via a custom Addresses field. ([#15678](https://github.com/craftcms/cms/issues/15678))
- Fixed a bug where Addresses fields weren’t always returning data in GraphQL.
- Fixed a bug where partial addresses weren’t getting garbage collected.
- Fixed a bug where orphaned nested addresses weren’t getting garbage collected. ([#15678](https://github.com/craftcms/cms/issues/15678))
- Fixed a bug where orphaned nested entries weren’t getting garbage collected after their field had been hard-deleted. ([#15678](https://github.com/craftcms/cms/issues/15678))
- Fixed an information disclosure vulnerability.

## 5.4.1 - 2024-09-04
Expand Down
4 changes: 3 additions & 1 deletion src/elements/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -1087,7 +1087,9 @@ public function getAddresses(): ElementCollection
return ElementCollection::make();
}

$this->_addresses = $this->createAddressQuery()->collect();
$this->_addresses = $this->createAddressQuery()
->andWhere(['fieldId' => null])
->collect();
}

return $this->_addresses;
Expand Down
52 changes: 38 additions & 14 deletions src/services/Gc.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use craft\db\Connection;
use craft\db\Query;
use craft\db\Table;
use craft\elements\Address;
use craft\elements\Asset;
use craft\elements\Category;
use craft\elements\Entry;
Expand Down Expand Up @@ -111,10 +112,12 @@ public function run(bool $force = false): void
$this->hardDelete([
Table::CATEGORYGROUPS,
Table::ENTRYTYPES,
Table::FIELDS,
Table::SECTIONS,
Table::TAGGROUPS,
]);

$this->deletePartialElements(Address::class, Table::ADDRESSES, 'id');
$this->deletePartialElements(Asset::class, Table::ASSETS, 'id');
$this->deletePartialElements(Category::class, Table::CATEGORIES, 'id');
$this->deletePartialElements(Entry::class, Table::ENTRIES, 'id');
Expand All @@ -123,7 +126,8 @@ public function run(bool $force = false): void
$this->deletePartialElements(User::class, Table::USERS, 'id');

$this->_deleteUnsupportedSiteEntries();
$this->_deleteOrphanedNestedEntries();
$this->deleteOrphanedNestedElements(Address::class, Table::ADDRESSES);
$this->deleteOrphanedNestedElements(Entry::class, Table::ENTRIES);

// Fire a 'run' event
// Note this should get fired *before* orphaned drafts & revisions are deleted
Expand All @@ -141,7 +145,6 @@ public function run(bool $force = false): void

$this->hardDelete([
Table::FIELDLAYOUTS,
Table::FIELDS,
Table::SITES,
]);

Expand Down Expand Up @@ -525,35 +528,56 @@ private function _deleteUnsupportedSiteEntries(): void
}

/**
* Deletes any orphaned nested entries.
* Deletes elements which have a `fieldId` value, but it’s set to an invalid field ID,
* or they're missing a row in the `elements_owners` table.
*
* @param string $elementType The element type
* @phpstan-param class-string<ElementInterface> $elementType
* @param string $table The extension table name
* @param string $fieldFk The column name that contains the foreign key to `fields.id`
* @since 5.4.2
*/
private function _deleteOrphanedNestedEntries(): void
public function deleteOrphanedNestedElements(string $elementType, string $table, string $fieldFk = 'fieldId'): void
{
$this->_stdout(' > deleting orphaned nested entries ... ');
/** @var string|ElementInterface $elementType */
$this->_stdout(sprintf(' > deleting orphaned nested %s ... ', $elementType::pluralLowerDisplayName()));

$now = Db::prepareDateForDb(new DateTime());
$elementsTable = Table::ELEMENTS;
$entriesTable = Table::ENTRIES;
$elementsOwnersTable = Table::ELEMENTS_OWNERS;
$fieldsTable = Table::FIELDS;

if ($this->db->getIsMysql()) {
$sql = <<<SQL
$sql1 = <<<SQL
DELETE [[el]].* FROM $elementsTable [[el]]
INNER JOIN $entriesTable [[en]] ON [[en.id]] = [[el.id]]
INNER JOIN $table [[t]] ON [[t.id]] = [[el.id]]
LEFT JOIN $elementsOwnersTable [[eo]] ON [[eo.elementId]] = [[el.id]]
WHERE [[en.fieldId]] IS NOT NULL AND [[eo.elementId]] IS NULL
WHERE [[t.$fieldFk]] IS NOT NULL AND [[eo.elementId]] IS NULL
SQL;
$sql2 = <<<SQL
DELETE [[el]].* FROM $elementsTable [[el]]
INNER JOIN $table [[t]] ON [[t.id]] = [[el.id]]
LEFT JOIN $fieldsTable [[f]] ON [[f.id]] = [[t.$fieldFk]]
WHERE [[t.$fieldFk]] IS NOT NULL AND [[f.id]] IS NULL
SQL;
} else {
$sql = <<<SQL
$sql1 = <<<SQL
DELETE FROM $elementsTable
USING $elementsTable [[el]]
INNER JOIN $entriesTable [[en]] ON [[en.id]] = [[el.id]]
INNER JOIN $table [[t]] ON [[t.id]] = [[el.id]]
LEFT JOIN $elementsOwnersTable [[eo]] ON [[eo.elementId]] = [[el.id]]
WHERE [[en.fieldId]] IS NOT NULL AND [[eo.elementId]] IS NULL
WHERE [[t.$fieldFk]] IS NOT NULL AND [[eo.elementId]] IS NULL
SQL;
$sql2 = <<<SQL
DELETE FROM $elementsTable
USING $elementsTable [[el]]
INNER JOIN $table [[t]] ON [[t.id]] = [[el.id]]
LEFT JOIN $fieldsTable [[f]] ON [[f.id]] = [[t.$fieldFk]]
WHERE [[t.$fieldFk]] IS NOT NULL AND [[f.id]] IS NULL
SQL;
}

$this->db->createCommand($sql)->execute();
$this->db->createCommand($sql1)->execute();
$this->db->createCommand($sql2)->execute();

$this->_stdout("done\n", Console::FG_GREEN);
}
Expand Down