Skip to content

Commit

Permalink
Merge pull request #14154 from craftcms/feature/cms-1236-propagate-el…
Browse files Browse the repository at this point in the history
…ement-site-deletions-to-nested-elements

Feature/cms 1236 propagate element site deletions to nested elements
  • Loading branch information
brandonkelly authored Jan 22, 2024
2 parents d46e113 + f8432c7 commit e9fc4f4
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 1 deletion.
8 changes: 8 additions & 0 deletions CHANGELOG-WIP.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@

### Administration
- Added “Save and continue editing” actions to all core settings pages with full-page forms. ([#14168](https://github.com/craftcms/cms/discussions/14168))
- Added the `utils/prune-orphaned-matrix-blocks` command. ([#14154](https://github.com/craftcms/cms/pull/14154))

### Extensibility
- Added `craft\base\ElementInterface::beforeDeleteForSite()`.
- Added `craft\base\ElementInterface::afterDeleteForSite()`.
- Added `craft\base\FieldInterface::beforeElementDeleteForSite()`.
- Added `craft\base\FieldInterface::afterElementDeleteForSite()`.

### System
- Reduced the system font file size, and prevented the flash of unstyled type for it. ([#13879](https://github.com/craftcms/cms/pull/13879))
- Log message timestamps are now set to the system time zone. ([#13341](https://github.com/craftcms/cms/issues/13341))
- Fixed a bug where deleting an entry for a site wasn’t propagating to Matrix blocks for that entry/site. ([#13948](https://github.com/craftcms/cms/issues/13948))
26 changes: 26 additions & 0 deletions src/base/Element.php
Original file line number Diff line number Diff line change
Expand Up @@ -5225,6 +5225,32 @@ public function afterDelete(): void
}
}

/**
* @inheritdoc
*/
public function beforeDeleteForSite(): bool
{
// Tell the fields about it
foreach ($this->fieldLayoutFields() as $field) {
if (!$field->beforeElementDeleteForSite($this)) {
return false;
}
}

return true;
}

/**
* @inheritdoc
*/
public function afterDeleteForSite(): void
{
// Tell the fields about it
foreach ($this->fieldLayoutFields() as $field) {
$field->afterElementDeleteForSite($this);
}
}

/**
* @inheritdoc
*/
Expand Down
15 changes: 15 additions & 0 deletions src/base/ElementInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -1715,6 +1715,21 @@ public function beforeDelete(): bool;
*/
public function afterDelete(): void;

/**
* Performs actions before an element is deleted for a site.
*
* @return bool Whether the element should be deleted
* @since 4.7.0
*/
public function beforeDeleteForSite(): bool;

/**
* Performs actions after an element is deleted for a site.
*
* @since 4.7.0
*/
public function afterDeleteForSite(): void;

/**
* Performs actions before an element is restored.
*
Expand Down
16 changes: 16 additions & 0 deletions src/base/Field.php
Original file line number Diff line number Diff line change
Expand Up @@ -850,6 +850,22 @@ public function afterElementDelete(ElementInterface $element): void
}
}

/**
* @inheritdoc
*/
public function beforeElementDeleteForSite(ElementInterface $element): bool
{
return true;
}

/**
* @inheritdoc
*/
public function afterElementDeleteForSite(ElementInterface $element): void
{
// carry on
}

/**
* @inheritdoc
*/
Expand Down
17 changes: 17 additions & 0 deletions src/base/FieldInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,23 @@ public function beforeElementDelete(ElementInterface $element): bool;
*/
public function afterElementDelete(ElementInterface $element): void;

/**
* Performs actions before an element is deleted for a site.
*
* @param ElementInterface $element The element that is about to be deleted
* @return bool Whether the element should be deleted for a site
* @since 4.7.0
*/
public function beforeElementDeleteForSite(ElementInterface $element): bool;

/**
* Performs actions after the element has been deleted.
*
* @param ElementInterface $element The element that was just deleted for a site
* @since 4.7.0
*/
public function afterElementDeleteForSite(ElementInterface $element): void;

/**
* Performs actions before an element is restored.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?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\db\Query;
use craft\db\Table;
use craft\elements\MatrixBlock;
use craft\helpers\Console;
use yii\console\ExitCode;

/**
* Prunes orphaned matrix blocks for each site.
*
* @author Pixel & Tonic, Inc. <[email protected]>
* @since 4.7.0
*/
class PruneOrphanedMatrixBlocksController extends Controller
{
/**
* Prunes orphaned matrix blocks for each site.
*
* @return int
*/
public function actionIndex(): int
{
if (!Craft::$app->getIsMultiSite()) {
$this->stdout("This command should only be run for multi-site installs.\n", Console::FG_YELLOW);
return ExitCode::OK;
}

$elementsService = Craft::$app->getElements();

// get all sites
$sites = Craft::$app->getSites()->getAllSites();

// for each site get all matrix blocks with owner that doesn't exist for this site
foreach ($sites as $site) {
$this->stdout(sprintf('Finding orphaned matrix blocks for site "%s" ... ', $site->getName()));

$esSubQuery = (new Query())
->from(['es' => Table::ELEMENTS_SITES])
->where([
'and',
'[[es.elementId]] = [[matrixblocks.primaryOwnerId]]',
['es.siteId' => $site->id],
]);

$matrixBlocks = MatrixBlock::find()
->status(null)
->siteId($site->id)
->where(['not exists', $esSubQuery])
->all();

if (empty($matrixBlocks)) {
$this->stdout("none found\n", Console::FG_GREEN);
continue;
}

$this->stdout(sprintf("%s found\n", count($matrixBlocks)), Console::FG_RED);

// delete the ones we found
foreach ($matrixBlocks as $block) {
$this->do(sprintf('Deleting block %s in %s', $block->id, $site->getName()), function() use ($block, $elementsService) {
$elementsService->deleteElementForSite($block);
});
}
}

$this->stdout("\nFinished pruning orphaned Matrix blocks.\n", Console::FG_GREEN);
return ExitCode::OK;
}
}
21 changes: 21 additions & 0 deletions src/fields/Matrix.php
Original file line number Diff line number Diff line change
Expand Up @@ -1145,6 +1145,27 @@ public function beforeElementDelete(ElementInterface $element): bool
return true;
}

/**
* @inheritdoc
*/
public function beforeElementDeleteForSite(ElementInterface $element): bool
{
$elementsService = Craft::$app->getElements();

/** @var MatrixBlock[] $matrixBlocks */
$matrixBlocks = MatrixBlock::find()
->primaryOwnerId($element->id)
->status(null)
->siteId($element->siteId)
->all();

foreach ($matrixBlocks as $matrixBlock) {
$elementsService->deleteElementForSite($matrixBlock);
}

return true;
}

/**
* @inheritdoc
*/
Expand Down
10 changes: 9 additions & 1 deletion src/services/Elements.php
Original file line number Diff line number Diff line change
Expand Up @@ -2220,6 +2220,10 @@ public function deleteElementsForSite(array $elements): void
}
}

foreach ($multiSiteElements as $element) {
$element->beforeDeleteForSite();
}

// Delete the rows in elements_sites
Db::delete(Table::ELEMENTS_SITES, [
'elementId' => $multiSiteElementIds,
Expand All @@ -2238,6 +2242,10 @@ public function deleteElementsForSite(array $elements): void
updateSearchIndex: false
);

foreach ($multiSiteElements as $element) {
$element->afterDeleteForSite();
}

// Fire 'afterDeleteForSite' events
if ($this->hasEventHandlers(self::EVENT_AFTER_DELETE_FOR_SITE)) {
foreach ($multiSiteElements as $element) {
Expand All @@ -2251,7 +2259,7 @@ public function deleteElementsForSite(array $elements): void
// Fully delete any single-site elements
if (!empty($singleSiteElements)) {
foreach ($singleSiteElements as $element) {
$this->deleteElement($element);
$this->deleteElement($element, true);
}
}
}
Expand Down

0 comments on commit e9fc4f4

Please sign in to comment.