Skip to content

Commit

Permalink
Element::resaveElements()
Browse files Browse the repository at this point in the history
resolves #3482

Signed-off-by: brandonkelly <[email protected]>
  • Loading branch information
brandonkelly committed Apr 8, 2019
1 parent 1e196c0 commit 5b0cd57
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 40 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG-v3.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

### Added
- Table fields can now have Dropdown columns. ([#811](https://github.com/craftcms/cms/issues/811))
- Added `craft\services\Elements::resaveElements()` along with `EVENT_BEFORE_RESAVE_ELEMENTS`, `EVENT_AFTER_RESAVE_ELEMENTS`, `EVENT_BEFORE_RESAVE_ELEMENT`, and `EVENT_AFTER_RELAVE_ELEMENT` events. ([#3482](https://github.com/craftcms/cms/issues/3482))

### Removed
- Removed the `--batch-size` option from `resave/*` actions.

### Fixed
- Fixed a bug where Control Panel pages that didn’t have a dedicated controller action weren’t ensuring that a user was logged in.
Expand Down
49 changes: 28 additions & 21 deletions src/console/controllers/ResaveController.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
use craft\elements\Entry;
use craft\elements\Tag;
use craft\elements\User;
use craft\events\ResaveElementEvent;
use craft\services\Elements;
use yii\console\Controller;
use yii\console\ExitCode;
use yii\helpers\Console;
Expand Down Expand Up @@ -59,11 +61,6 @@ class ResaveController extends Controller
*/
public $limit;

/**
* @var int The batch size to query elements in.
*/
public $batchSize = 100;

/**
* @var bool Whether to save the elements across all their enabled sites.
*/
Expand Down Expand Up @@ -101,7 +98,6 @@ public function options($actionID)
$options[] = 'status';
$options[] = 'offset';
$options[] = 'limit';
$options[] = 'batchSize';
$options[] = 'propagate';

switch ($actionID) {
Expand Down Expand Up @@ -250,26 +246,37 @@ private function _saveElements(ElementQueryInterface $query): int
$elementsService = Craft::$app->getElements();
$fail = false;

foreach ($query->each($this->batchSize) as $element) {
/** @var Element $element */
$this->stdout(" - Resaving {$element} ({$element->id}) ... ");
$element->setScenario(Element::SCENARIO_ESSENTIALS);
$element->resaving = true;
try {
if (!$elementsService->saveElement($element)) {
$beforeCallback = function(ResaveElementEvent $e) use ($query) {
if ($e->query === $query) {
/** @var Element $element */
$element = $e->element;
$this->stdout(" - Resaving {$element} ({$element->id}) ... ");
}
};

$afterCallback = function(ResaveElementEvent $e) use ($query, &$fail) {
if ($e->query === $query) {
/** @var Element $element */
$element = $e->element;
if ($e->exception) {
$this->stderr('error: ' . $e->exception->getMessage() . PHP_EOL, Console::FG_RED);
$fail = true;
} else if ($element->hasErrors()) {
$this->stderr('failed: ' . implode(', ', $element->getErrorSummary(true)) . PHP_EOL, Console::FG_RED);
$fail = true;
continue;
} else {
$this->stdout('done' . PHP_EOL, Console::FG_GREEN);
}
} catch (\Throwable $e) {
Craft::$app->getErrorHandler()->logException($e);
$this->stderr('error: ' . $e->getMessage() . PHP_EOL, Console::FG_RED);
$fail = true;
continue;
}
};

$this->stdout('done' . PHP_EOL, Console::FG_GREEN);
}
$elementsService->on(Elements::EVENT_BEFORE_RESAVE_ELEMENT, $beforeCallback);
$elementsService->on(Elements::EVENT_AFTER_RESAVE_ELEMENT, $afterCallback);

$elementsService->resaveElements($query, true);

$elementsService->off(Elements::EVENT_BEFORE_RESAVE_ELEMENT, $beforeCallback);
$elementsService->off(Elements::EVENT_AFTER_RESAVE_ELEMENT, $afterCallback);

$this->stdout("Done resaving {$elementsText}." . PHP_EOL . PHP_EOL, Console::FG_YELLOW);
return $fail ? ExitCode::UNSPECIFIED_ERROR : ExitCode::OK;
Expand Down
39 changes: 39 additions & 0 deletions src/events/ResaveElementEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php
/**
* @link https://craftcms.com/
* @copyright Copyright (c) Pixel & Tonic, Inc.
* @license https://craftcms.github.io/license/
*/

namespace craft\events;

use craft\base\ElementInterface;
use craft\elements\db\ElementQueryInterface;
use yii\base\Event;

/**
* Resave Element event class.
*
* @author Pixel & Tonic, Inc. <[email protected]>
* @since 3.2.0
*/
class ResaveElementEvent extends ResaveElementsEvent
{
// Properties
// =========================================================================

/**
* @var ElementInterface The element being resaved
*/
public $element;

/**
* @var int The element's position in the query (1-indexed)
*/
public $position;

/**
* @var \Throwable|null $exception The exception that was thrown if any
*/
public $exception;
}
29 changes: 29 additions & 0 deletions src/events/ResaveElementsEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php
/**
* @link https://craftcms.com/
* @copyright Copyright (c) Pixel & Tonic, Inc.
* @license https://craftcms.github.io/license/
*/

namespace craft\events;

use craft\base\ElementInterface;
use craft\elements\db\ElementQueryInterface;
use yii\base\Event;

/**
* Resave Elements event class.
*
* @author Pixel & Tonic, Inc. <[email protected]>
* @since 3.2.0
*/
class ResaveElementsEvent extends Event
{
// Properties
// =========================================================================

/**
* @var ElementQueryInterface The element query the elements will be pulled from.
*/
public $query;
}
31 changes: 12 additions & 19 deletions src/queue/jobs/ResaveElements.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@
namespace craft\queue\jobs;

use Craft;
use craft\base\Element;
use craft\base\ElementInterface;
use craft\db\QueryAbortedException;
use craft\elements\db\ElementQuery;
use craft\elements\db\ElementQueryInterface;
use craft\events\ResaveElementEvent;
use craft\queue\BaseJob;
use yii\base\Exception;
use craft\services\Elements;

/**
* ResaveElements job
Expand Down Expand Up @@ -50,24 +49,18 @@ public function execute($queue)

/** @var ElementQuery $query */
$query = $this->_query();
$totalElements = $query->count();
$count = $query->count();
$elementsService = Craft::$app->getElements();
$currentElement = 0;

try {
foreach ($query->each() as $element) {
$this->setProgress($queue, $currentElement++ / $totalElements);

/** @var Element $element */
$element->setScenario(Element::SCENARIO_ESSENTIALS);
$element->resaving = true;
if (!$elementsService->saveElement($element)) {
throw new Exception('Couldn’t save element ' . $element->id . ' (' . get_class($element) . ') due to validation errors.');
}

$callback = function(ResaveElementEvent $e) use ($queue, $query, $count) {
if ($e->query === $query) {
$this->setProgress($queue, $e->position / $count);
}
} catch (QueryAbortedException $e) {
// Fail silently
}
};

$elementsService->on(Elements::EVENT_AFTER_RESAVE_ELEMENT, $callback);
$elementsService->resaveElements($query);
$elementsService->off(Elements::EVENT_AFTER_RESAVE_ELEMENT, $callback);
}

// Protected Methods
Expand Down
93 changes: 93 additions & 0 deletions src/services/Elements.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@
use craft\base\ElementInterface;
use craft\base\Field;
use craft\db\Query;
use craft\db\QueryAbortedException;
use craft\db\Table;
use craft\elements\Asset;
use craft\elements\Category;
use craft\elements\db\ElementQuery;
use craft\elements\db\ElementQueryInterface;
use craft\elements\Entry;
use craft\elements\GlobalSet;
use craft\elements\MatrixBlock;
Expand All @@ -29,6 +31,8 @@
use craft\events\ElementEvent;
use craft\events\MergeElementsEvent;
use craft\events\RegisterComponentTypesEvent;
use craft\events\ResaveElementEvent;
use craft\events\ResaveElementsEvent;
use craft\helpers\App;
use craft\helpers\ArrayHelper;
use craft\helpers\Component as ComponentHelper;
Expand Down Expand Up @@ -114,6 +118,26 @@ class Elements extends Component
*/
const EVENT_AFTER_SAVE_ELEMENT = 'afterSaveElement';

/**
* @event ElementEvent The event that is triggered before resaving a batch of elements.
*/
const EVENT_BEFORE_RESAVE_ELEMENTS = 'beforeResaveElements';

/**
* @event ElementEvent The event that is triggered after resaving a batch of elements.
*/
const EVENT_AFTER_RESAVE_ELEMENTS = 'afterResaveElements';

/**
* @event ElementEvent The event that is triggered before an element is resaved.
*/
const EVENT_BEFORE_RESAVE_ELEMENT = 'beforeResaveElement';

/**
* @event ElementEvent The event that is triggered after an element is resaved.
*/
const EVENT_AFTER_RESAVE_ELEMENT = 'afterResaveElement';

/**
* @event ElementEvent The event that is triggered before an element’s slug and URI are updated, usually following a Structure move.
*/
Expand Down Expand Up @@ -582,6 +606,75 @@ public function saveElement(ElementInterface $element, bool $runValidation = tru
return true;
}

/**
* Resaves all elements that match a given element query.
*
* @param ElementQueryInterface $query The element query to fetch elements with
* @param bool $continueOnError Whether to continue going if an error occurs
* @throws \Throwable if reasons
* @since 3.2.0
*/
public function resaveElements(ElementQueryInterface $query, bool $continueOnError = false)
{
// Fire a 'beforeSaveElements' event
if ($this->hasEventHandlers(self::EVENT_BEFORE_RESAVE_ELEMENTS)) {
$this->trigger(self::EVENT_BEFORE_RESAVE_ELEMENTS, new ResaveElementsEvent([
'query' => $query,
]));
}

$position = 0;

try {
/** @var ElementQuery $query */
foreach ($query->each() as $element) {
$position++;

/** @var Element $element */
$element->setScenario(Element::SCENARIO_ESSENTIALS);
$element->resaving = true;

// Fire a 'beforeSaveElement' event
if ($this->hasEventHandlers(self::EVENT_BEFORE_RESAVE_ELEMENT)) {
$this->trigger(self::EVENT_BEFORE_RESAVE_ELEMENT, new ResaveElementEvent([
'query' => $query,
'element' => $element,
'position' => $position,
]));
}

$e = null;
try {
$this->saveElement($element);
} catch (\Throwable $e) {
if (!$continueOnError) {
throw $e;
}
Craft::$app->getErrorHandler()->logException($e);
}

// Fire an 'afterSaveElement' event
if ($this->hasEventHandlers(self::EVENT_AFTER_RESAVE_ELEMENT)) {
$this->trigger(self::EVENT_AFTER_RESAVE_ELEMENT, new ResaveElementEvent([
'query' => $query,
'element' => $element,
'position' => $position,
'exception' => $e,
]));
}
}
} catch (QueryAbortedException $e) {
// Fail silently
}

// Fire an 'afterSaveElements' event
if ($this->hasEventHandlers(self::EVENT_AFTER_RESAVE_ELEMENTS)) {
$this->trigger(self::EVENT_AFTER_RESAVE_ELEMENTS, new ResaveElementsEvent([
'query' => $query,
]));
}
}

/**
* Duplicates an element.
*
Expand Down

0 comments on commit 5b0cd57

Please sign in to comment.